Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Servlet: response.setContentLength() slows download down

private void downloadAllRelease(HttpServletRequest request,
        HttpServletResponse response) {
    LoginToken tok=getToken(request, response);
    int size = 0;
    try {
        ArrayList<Release> releases = manager.getReleases(tok.getUsername);
        ZipOutputStream out = new ZipOutputStream(response.getOutputStream());
        for (int i=0; i<releases.size(); i++) {
            size += releases.get(i).getFile().length;
            out.putNextEntry(new ZipEntry(releases.get(i).getFilename()));
            out.write(releases.get(i).getFile());
            out.closeEntry();
        }
        response.setContentLength(size);
        response.setContentType("application/force-download");
        response.setHeader("Content-Disposition","attachment;filename=release.zip");
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

response.setContentLength() reeeeally slows downloads down.
If I don't use It or put it after out.close() everything still works fine but downloads are muuuuch faster.
Can someone explain me why and if it is necessary to use response.setContentLength()?

like image 552
Simon Avatar asked Jan 10 '12 00:01

Simon


People also ask

What is setContentLength?

According to the documentation, the setContentLength(int len) method of the ServletResponse interface sets the length of the content body in the response In HTTP servlets, this method sets the HTTP Content-Length header.

What is Httpservlet response?

public abstract interface HttpServletResponse extends ServletResponse. Defines an HTTP servlet response that a servlet running on a Web server sends to a client using HTTP. This interface allows the servlet's service method to access HTTP headers and return data to its client.


1 Answers

Perhaps because you're specifying a larger size than what is actually been sent to the response and the webbrowser basically get confused and is waiting for more data? You know, ZIP compresses files and reduces the final size.

Just don't specify the response's content length if you cannot efficiently calculate it beforehand. The servlet container will automatically send it with chunked encoding anyway. True, this has a little more overhead and leaves the webbrowser with an unknown download progress, but this doesn't require you to buffer the entire response in server's memory first so that you can get the proper final response content length.

If you really want to calculate the final response content length, you'd need to write it all to a ByteArrayOutputStream instead and then get the byte[] by its toByteArray() method. The real response content length is then the length of the byte[].

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream out = new ZipOutputStream(baos);
// ...

response.setContentLength(baos.size());
response.getOutputStream().write(bytes);

This is only more memory hogging because everything will be stored in server's memory first. If multiple users do this concurrently and the zip output is relatively large, then your server might risk to run out of memory sooner or later. As another alternative, you could write it using FileOutputStream to a temp file as created by File#createTempFile(), so that you can obtain its size by File#length() and use FileInputStream to stream it directly into the OutputStream of the response the usual way. This is only slower as you're basically transferring the bytes around twice.

like image 80
BalusC Avatar answered Jan 05 '23 05:01

BalusC