nanohttpd: streaming big files over http with nanohttpd can cause java.net.SocketException - special handling needed

Hi All,

I am new to NanoHttpd. I am using this to stream a video in Android. For just normal playing this works like a charm without any issue. But when I try to seek to different time in the VideoPlayer, immediatly I get the following error and it creates a different socket and resumes to play after some time. But it shows the progress indicator for long time, which I think because of this issue and re-connecting with different socket. And Streaming is from local network server.

java.net.SocketException: sendto failed: EPIPE (Broken pipe)
        at libcore.io.IoBridge.maybeThrowAfterSendto(IoBridge.java:546)
        at libcore.io.IoBridge.sendto(IoBridge.java:515)
        at java.net.PlainSocketImpl.write(PlainSocketImpl.java:504)
        at java.net.PlainSocketImpl.access$100(PlainSocketImpl.java:37)
        at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:266)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.sendBody(NanoHTTPD.java:1423)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.sendBodyWithCorrectEncoding(NanoHTTPD.java:1396)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.sendBodyWithCorrectTransferAndEncoding(NanoHTTPD.java:1386)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.send(NanoHTTPD.java:1371)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$HTTPSession.execute(NanoHTTPD.java:794)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$ClientHandler.run(NanoHTTPD.java:195)
        at java.lang.Thread.run(Thread.java:818)

 Caused by: android.system.ErrnoException: sendto failed: EPIPE (Broken pipe)
        at libcore.io.Posix.sendtoBytes(Native Method)
        at libcore.io.Posix.sendto(Posix.java:206)
        at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:278)
        at libcore.io.IoBridge.sendto(IoBridge.java:513)
        at java.net.PlainSocketImpl.write(PlainSocketImpl.java:504)
        at java.net.PlainSocketImpl.access$100(PlainSocketImpl.java:37)
        at java.net.PlainSocketImpl$PlainSocketOutputStream.write(PlainSocketImpl.java:266)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.sendBody(NanoHTTPD.java:1423)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.sendBodyWithCorrectEncoding(NanoHTTPD.java:1396)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.sendBodyWithCorrectTransferAndEncoding(NanoHTTPD.java:1386)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$Response.send(NanoHTTPD.java:1371)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$HTTPSession.execute(NanoHTTPD.java:794)
        at me.etrmntonwheels.surfacetest.NanoHTTPD$ClientHandler.run(NanoHTTPD.java:195)
        at java.lang.Thread.run(Thread.java:818)

I am using serveFile function from SimpleWebServer sample to achieve partial content or streaming. I have read different articles and googled through stackoverflow but still could not figure out the reason.I am stuck at this issue from long time. Please help me to resolve this issue.

About this issue

  • Original URL
  • State: open
  • Created 9 years ago
  • Comments: 36 (8 by maintainers)

Most upvoted comments

One more workaround is to add try-catch block to the “sendBody” method, so it closes the connection to the client if the client can’t accept data

private void sendBody(OutputStream outputStream, long pending) throws IOException {
    try {
        long BUFFER_SIZE = 16 * 1024;
        byte[] buff = new byte[(int) BUFFER_SIZE];
        boolean sendEverything = pending == -1;
        while (pending > 0 || sendEverything) {
            long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);
            int read = this.data.read(buff, 0, (int) bytesToRead);
            if (read <= 0) {
                break;
            }
            outputStream.write(buff, 0, read);
            if (!sendEverything) {
                pending -= read;
            }
        }
    } catch (IOException e) {
        // TODO: REMOVE WORKAROUND
        outputStream.close();
    }
}
`

@OLEG4120 I used master version of NanoHTTPD (not 2.3.1), and it seems it doesn’t have this problem.

let me post some logs , with the code that i am using on Android 7.0 it might help you.

this code works fine , the file plays and i can seek to different location.but this error shows in console and nothing happens to playback.

now i want to transfer files in 1MB chunks , i used this endAt=startFrom+1000000;

and everything works fine with VLC (no broken pipe error), but with MXPlayer it shows the same error and stops the stream.

`

@Override
public Response serve(IHTTPSession session)
{
    Map<String, String> headers = session.getHeaders();
    Map<String, String> parms = session.getParms();
    Method method = session.getMethod();
    String uri = session.getUri();
    Map<String, String> files = new HashMap<>();

    if (Method.POST.equals(method) || Method.PUT.equals(method))
    {
        try
        {
            session.parseBody(files);
        }
        catch (IOException e)
        {
            return getResponse("Internal Error IO Exception: " + e.getMessage());
        }
        catch (ResponseException e)
        {
            return newFixedLengthResponse(e.getStatus(), MIME_PLAINTEXT, e.getMessage());
        }
    }

    File f = new File(Environment.getExternalStorageDirectory() + "/"+MainActivity.file);
    return serveFile(uri, headers, f);
}

private Response serveFile(String uri, Map<String, String> header, File file)
{


    System.out.println("--------------------------------------------------------" );
    for (Map.Entry<String, String> entry : header.entrySet())
    {
        String key = entry.getKey().toString();
        String value = entry.getValue();
        System.out.println("key, " + key + " value " + value);
    }
    System.out.println("--------------------------------------------------------" );


    Response res;
    String mime = getMimeTypeForFile(uri);
    try {
        String etag = Integer.toHexString((file.getAbsolutePath() +
                file.lastModified() + "" + file.length()).hashCode());
        long startFrom = 0;
        long endAt = -1;
        String range = header.get("range");
        if (range != null) {
            if (range.startsWith("bytes=")) {
                range = range.substring("bytes=".length());
                int minus = range.indexOf('-');
                try {
                    if (minus > 0) {
                        startFrom = Long.parseLong(range.substring(0, minus));
                        endAt = Long.parseLong(range.substring(minus + 1));
                    }
                } catch (NumberFormatException ignored) {
                }
            }
        }
        long fileLen = file.length();
        if (range != null && startFrom >= 0) {
            if (startFrom >= fileLen) {
                res = createResponse(Response.Status.RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "");
                res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
                res.addHeader("ETag", etag);
            } else {
                if (endAt < 0) {
                    endAt = fileLen - 1;
                }
                //endAt=startFrom+1000000;
                long newLen = endAt - startFrom + 1;
                if (newLen < 0) {
                    newLen = 0;
                }

                final long dataLen = newLen;
                FileInputStream fis = new FileInputStream(file) {
                    @Override
                    public int available() throws IOException {
                        return (int) dataLen;
                    }
                };
                fis.skip(startFrom);

                res = createResponse(Response.Status.PARTIAL_CONTENT, mime, fis,dataLen);
                res.addHeader("Content-Length", "" + dataLen);
                res.addHeader("Content-Range", "bytes " + startFrom + "-" +
                        endAt + "/" + fileLen);
                res.addHeader("ETag", etag);
                Log.d("Server", "serveFile --1--: Start:"+startFrom+" End:"+endAt);
            }
        } else {
            if (etag.equals(header.get("if-none-match"))) {
                res = createResponse(Response.Status.NOT_MODIFIED, mime, "");
                Log.d("Server", "serveFile --2--: Start:"+startFrom+" End:"+endAt);
            }
            else
            {
                FileInputStream fis=new FileInputStream(file);
                res = createResponse(Response.Status.OK, mime, fis,fis.available());
                res.addHeader("Content-Length", "" + fileLen);
                res.addHeader("ETag", etag);
                Log.d("Server", "serveFile --3--: Start:"+startFrom+" End:"+endAt);
            }
        }
    } catch (IOException ioe) {
        res = getResponse("Forbidden: Reading file failed");
    }

    return (res == null) ? getResponse("Error 404: File not found") : res;
}

private Response createResponse(Response.Status status, String mimeType, InputStream message,long totalBytes) {
    Response res = newFixedLengthResponse(status, mimeType, message,totalBytes);
    res.addHeader("Accept-Ranges", "bytes");
    return res;
}

private Response createResponse(Response.Status status, String mimeType, String message) {
    Response res = newFixedLengthResponse(status, mimeType, message);
    res.addHeader("Accept-Ranges", "bytes");
    return res;
}

private Response getResponse(String message) {
    return createResponse(Response.Status.OK, "text/plain", message);
}

`

----------------log--------------------

log.txt

Great! I will repeat the code here for the record. we should find a way to integrate this in the nano code.

@Override
public Response serve(IHTTPSession session) {
    Map<String, String> headers = session.getHeaders();
    Map<String, String> parms = session.getParms();
    Method method = session.getMethod();
    String uri = session.getUri();
    Map<String, String> files = new HashMap<>();

    if (Method.POST.equals(method) || Method.PUT.equals(method)) {
        try {
            session.parseBody(files);
        }
        catch (IOException e) {
            return getResponse("Internal Error IO Exception: " + e.getMessage());
        }
        catch (ResponseException e) {
            return new Response(e.getStatus(), MIME_PLAINTEXT, e.getMessage());
        }
    }

    uri = uri.trim().replace(File.separatorChar, '/');
    if (uri.indexOf('?') >= 0) {
        uri = uri.substring(0, uri.indexOf('?'));
    }

    File f = new File(uri);
    return serveFile(uri, header, f);
}

private Response serveFile(String uri, Map<String, String> header, File file) {
    Response res;
    String mime = getMimeTypeForFile(uri);
    try {
        // Calculate etag
        String etag = Integer.toHexString((file.getAbsolutePath() +
                file.lastModified() + "" + file.length()).hashCode());

        // Support (simple) skipping:
        long startFrom = 0;
        long endAt = -1;
        String range = header.get("range");
        if (range != null) {
            if (range.startsWith("bytes=")) {
                range = range.substring("bytes=".length());
                int minus = range.indexOf('-');
                try {
                    if (minus > 0) {
                        startFrom = Long.parseLong(range.substring(0, minus));
                        endAt = Long.parseLong(range.substring(minus + 1));
                    }
                } catch (NumberFormatException ignored) {
                }
            }
        }

        // Change return code and add Content-Range header when skipping is requested
        long fileLen = file.length();
        if (range != null && startFrom >= 0) {
            if (startFrom >= fileLen) {
                res = createResponse(Response.Status.RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "");
                res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
                res.addHeader("ETag", etag);
            } else {
                if (endAt < 0) {
                    endAt = fileLen - 1;
                }
                long newLen = endAt - startFrom + 1;
                if (newLen < 0) {
                    newLen = 0;
                }

                final long dataLen = newLen;
                FileInputStream fis = new FileInputStream(file) {
                    @Override
                    public int available() throws IOException {
                        return (int) dataLen;
                    }
                };
                fis.skip(startFrom);

                res = createResponse(Response.Status.PARTIAL_CONTENT, mime, fis);
                res.addHeader("Content-Length", "" + dataLen);
                res.addHeader("Content-Range", "bytes " + startFrom + "-" +
                        endAt + "/" + fileLen);
                res.addHeader("ETag", etag);
            }
        } else {
            if (etag.equals(header.get("if-none-match")))
                res = createResponse(Response.Status.NOT_MODIFIED, mime, "");
            else {
                res = createResponse(Response.Status.OK, mime, new FileInputStream(file));
                res.addHeader("Content-Length", "" + fileLen);
                res.addHeader("ETag", etag);
            }
        }
    } catch (IOException ioe) {
        res = getResponse("Forbidden: Reading file failed");
    }

    return (res == null) ? getResponse("Error 404: File not found") : res;
}

// Announce that the file server accepts partial content requests
private Response createResponse(Response.Status status, String mimeType, InputStream message) {
    Response res = new Response(status, mimeType, message);
    res.addHeader("Accept-Ranges", "bytes");
    return res;
}

// Announce that the file server accepts partial content requests
private Response createResponse(Response.Status status, String mimeType, String message) {
    Response res = new Response(status, mimeType, message);
    res.addHeader("Accept-Ranges", "bytes");
    return res;
}

private Response getResponse(String message) {
    return createResponse(Response.Status.OK, "text/plain", message);
}