Дополнительные 2000 строк ([32 30 30 30] байт) в начале файла

У меня действительно странная проблема, и я не могу найти решение.

У меня есть простой тестовый сервлет, который передает в ответ небольшой файл PDF:

public class TestPdf extends HttpServlet implements Servlet {

    private static final long serialVersionUID = 1L;

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {

        File file = new File(getServletContext().getRealPath("/lorem.pdf"));

        response.setContentType("application/pdf");

        ServletOutputStream out = response.getOutputStream();

        InputStream in = new FileInputStream(file);

        byte[] bytes = new byte[10000];

        int count = -1;

        while ((count = in.read(bytes)) != -1) {
            out.write(bytes, 0, count);
        }

        in.close();

        out.flush();
        out.close();

    }

}

Если я вызываю URL-адрес сервлета с помощью браузера, curl, wget, все в порядке, но когда я вызываю его с помощью простого сценария TCL, например:

#!/usr/bin/tclsh8.5

package require http;

set testUrl "http://localhost:8080/test/pdf"
set httpResponse [http::geturl "$testUrl" -channel stdout]

в начале файла есть строка «2000», которая искажает pdf.

Проблема не связана с версией Tomcat или JDK, поскольку я могу воспроизвести ее в своей среде разработки (Ubuntu 16.04) как с JDK 1.5.0_22 Tomcat 5.5.36, так и с JDK 1.8.0_74 и Tomcat 8.5.15.


person Luca Dominici    schedule 10.08.2018    source источник
comment
Никогда не использовал TCL, но разве это не просто http-код 200 плюс дополнительный 0, а затем ваш файл?   -  person jhamon    schedule 10.08.2018
comment
Спасибо за комментарий. я только что попытался изменить код ответа HTTP на 201, но 2000 все тот же.   -  person Luca Dominici    schedule 10.08.2018
comment
Пробовали ли вы получить доступ к заведомо хорошему URL-адресу (например, stackoverflow.com/robots.txt) с помощью сценария TCL? Таким образом, вы можете выяснить, связана ли проблема с кодом Java или с TCL.   -  person Joachim Sauer    schedule 10.08.2018
comment
Хорошо, я попробовал с образцом PDF (unec.edu.az /application/uploads/2014/12/pdf-sample.pdf) и загруженный файл правильный, поэтому проблема связана с Java/Tomcat...   -  person Luca Dominici    schedule 10.08.2018
comment
Может ли это быть длина файла?   -  person Paul Karam    schedule 10.08.2018
comment
Возможно ли, что сервлет использует фрагментированное кодирование передачи, и это не поддерживается вашим сценарием? При групповом кодировании передачи данные передаются порциями, перед которыми указывается длина порции (в кодировке ASCII она закодирована как шестнадцатеричная, поэтому 2000 означает, что порция составляет 8192 байта), за которой следует последовательность CRLF, затем 8192 байта данных, затем CRLF, за которым следует следующий фрагмент и т. д.   -  person Mark Rotteveel    schedule 10.08.2018
comment
@Paul Karam: Нет, я только что сделал простой тестовый сервлет с файлом PDF, но в исходном коде проблема присутствует с каждым файлом, который я пытаюсь загрузить.   -  person Luca Dominici    schedule 10.08.2018
comment
Спасибо @Mark, вы указываете мне правильное направление. Я попытался добавить в ответ заголовок Content-Length, и проблема исчезла. Теперь мне просто нужно понять, что вызвало это в первую очередь, поскольку проблема возникла после обновления версии JDK и Tomcat в тестовой среде, в результате чего код сервлета и TCL нашего приложения не изменился.   -  person Luca Dominici    schedule 10.08.2018
comment
Рад слышать. Установка явной длины содержимого действительно отключит кодирование передачи по частям; не уверен, что есть много других вариантов (кроме изменения вашего сценария TCL для использования/поддержки кодирования передачи по частям). Возможная причина заключается в том, что Tomcat что-то изменил в способе буферизации ответов, или, возможно, ваше обновление удалило/переписало параметр конфигурации для этого (например, может быть, раньше он буферизировал больше байтов, чтобы затем вычислить длину содержимого, и теперь буфер меньше, поэтому он переключается на фрагментацию ранее).   -  person Mark Rotteveel    schedule 10.08.2018
comment
Еще раз спасибо, я должен иметь возможность изменить код приложения и отправить заголовок длины содержимого, чтобы решить проблему. Если вы превратите комментарий в ответ, я с радостью приму его :)   -  person Luca Dominici    schedule 10.08.2018


Ответы (2)


То, что вы видите, — это начало фрагмента, количество октетов, содержащихся в фрагменте, как указано другими. Чтобы справиться с этим со стороны клиента Tcl (а не путем отключения фрагментированного кодирования передачи из Tomcat POV), вам нужно опустить параметр -channel для http::geturl:

package require http;

set testUrl "http://localhost:8080/test/pdf"
set httpResponse [http::geturl "$testUrl"]
fconfigure stdout -translation binary; # turn off auto-encoding on the way out
puts -nonewline stdout [http::data $httpResponse]

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

person mrcalvin    schedule 10.08.2018
comment
Отличие от использования -channel заключается в том, что ваши данные ответа окажутся дополнительными в памяти, как значение Tcl. Вы также можете опустить stdout до puts (по умолчанию), это просто для того, чтобы сделать связь с вашим скриптом более заметной. - person mrcalvin; 10.08.2018
comment
Теперь, когда я освежил свою память: начиная с Tcl 8.6, http 2.8.0, -channel обрабатывает фрагментированное кодирование передачи. Вы должны получить новую установку Tcl. - person mrcalvin; 10.08.2018
comment
Это пришло с версией 8.6.0, выпущенной в 2009 году. Так что вам ДЕЙСТВИТЕЛЬНО следует обновиться! - person mrcalvin; 10.08.2018

Я никогда не использовал TCL, но именно так вы можете написать общий сервлет для загрузки файлов:

public class DownloadServlet extends HttpServlet {
    private final int BUFFER_SIZE = 10000;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {

        String filename = "test.pdf";
        String pathToFile = "..../" + filename;

        resp.setContentType("application/pdf");
        resp.setHeader("Content-disposition", "attachment; filename=" + filename);

        try(InputStream in = req.getServletContext().getResourceAsStream(pathToFile);
          OutputStream out = resp.getOutputStream()) {

            byte[] buffer = new byte[BUFFER_SIZE];
            int numBytesRead;

            while ((numBytesRead = in.read(buffer)) > 0) {
                out.write(buffer, 0, numBytesRead);
            }
        }
    }
}

Надеюсь, что этот фрагмент кода поможет вам.

person zappee    schedule 10.08.2018