Servlet: response.setContentLength() замедляет загрузку вниз

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() повторно замедляет загрузку вниз.
Если я не использую It или не помещаю его после out.close() все по-прежнему работает нормально, но загрузка быстрее muuuuch.
Может кто-нибудь объяснить мне, почему и если необходимо использовать response.setContentLength()?

Ответ 1

Возможно, из-за того, что вы указываете больший размер, чем тот, который фактически был отправлен в ответ, а веб-браузер в основном запутался и ждет новых данных? Вы знаете, ZIP сжимает файлы и уменьшает окончательный размер.

Просто не указывайте длину содержимого ответа, если вы не можете эффективно рассчитать его заранее. Контейнер сервлета все равно автоматически отправит его с чанкованным кодированием. Правда, это немного увеличивает издержки и оставляет веб-браузеру неизвестный ход загрузки, но для этого не требуется сначала буферизовать весь ответ в памяти сервера, чтобы вы могли получить правильную конечную длину содержимого ответа.

Если вы действительно хотите рассчитать конечную длину содержимого ответа, вам нужно вместо этого записать все это в ByteArrayOutputStream а затем получить byte[] его toByteArray(). Реальная длина содержимого ответа равна длине byte[].

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

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

Это только больше памяти, потому что все сначала будет храниться в памяти сервера. Если несколько пользователей делают это одновременно и выходные данные в формате zip относительно велики, то ваш сервер рано или поздно рискует исчерпать память. В качестве другой альтернативы вы можете записать его, используя FileOutputStream во временный файл, созданный File#createTempFile(), так что вы можете получить его размер с помощью File#length() и использовать FileInputStream для его потоковой передачи непосредственно в OutputStream ответа обычным способом. Это только медленнее, поскольку вы в основном передаете байты дважды.