Servlet: response.setContentLength() slows downloa

2019-04-04 08:15发布

问题:

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()?

回答1:

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);
// ...

byte[] bytes = baos.toByteArray();
response.setContentLength(bytes.length);
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.