Подтвердить что ты не робот

Почему Tomcat возвращает разные заголовки для запросов HEAD и GET в мой RESTful API?

Моя первоначальная цель состояла в том, чтобы проверить передачу chunked HTTP. Но случайно обнаружил эту несогласованность.

API предназначен для возврата файла клиенту. Я использую методы HEAD и GET. Возвращаются разные заголовки.

Для GET, я получаю эти заголовки: (Это то, что я ожидал.)

Transfer-Encoding: chunked, no Content-Length.

Для HEAD, я получаю следующие заголовки:

Content-Length: 1017118720, no Transfer-Encoding.

В соответствии с этот поток, HEAD и GET СЛЕДУЕТ возвращать одинаковые заголовки, но не обязательно.

Мой вопрос:

Если используется Transfer-Encoding: chunked, потому что файл динамически передается клиенту, а сервер Tomcat не может заранее знать его размер, как Tomcat знает Content-Length, когда используется метод HEAD? Выполняет ли Tomcat только dry-run обработчик и подсчитывает все байты файлов? Почему он просто не возвращает тот же заголовок Transfer-Encoding: chunked?

Ниже мой API RESTful реализован с Spring веб-MVC:

@RestController
public class ChunkedTransferAPI {

    @Autowired
    ServletContext servletContext;

    @RequestMapping(value = "bootfile.efi", method = { RequestMethod.GET, RequestMethod.HEAD })
    public void doHttpBoot(HttpServletResponse response) {

        String filename = "/bootfile.efi";
        try {
            ServletOutputStream output = response.getOutputStream();
            InputStream input = servletContext.getResourceAsStream(filename);
            BufferedInputStream bufferedInput = new BufferedInputStream(input);
            int datum = bufferedInput.read();
            while (datum != -1) {
                output.write(datum);
                datum = bufferedInput.read();
            }
            output.flush();
            output.close();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

ДОБАВИТЬ 1

В моем коде я явно не добавлял заголовки, тогда должен быть Tomcat, который добавляет заголовки Content-Length и Transfer-Encoding по своему усмотрению.

Итак, какие правила для Tomcat определяют, какие заголовки отправлять?

ADD 2

Возможно, это связано с тем, как работает Tomcat. Надеюсь, кто-то может пролить свет здесь. В противном случае я буду отлаживать источник Tomcat 8 и делиться результатами. Но это может занять некоторое время.

Связанный:

4b9b3361

Ответ 1

Помогает ли Tomcat просто запустить обработчик и подсчитать все байты файлов?

Да, стандартная реализация javax.servlet.http.HttpServlet.doHead() делает это.

Вы можете посмотреть вспомогательные классы NoBodyResponse, NoBodyOutputStream в HttpServlet.java

Класс DefaultServlet (сервлет Tomcat, который используется для обслуживания статических файлов) более разумен. Он способен отправлять правильное значение Content-Length, а также обслуживать запросы GET для подмножества файла (заголовок Range). Вы можете перенаправить свой запрос на этот сервлет,

  ServletContext.getNamedDispatcher("default").forward(request, response);

Ответ 2

Хотя кажется странным, может возникнуть смысл отправить размер только в ответ на запрос HEAD и передать его в ответ на запрос GET в зависимости от типа данных, которые должны быть возвращены сервером.

Хотя ваш API, как представляется, предоставляет статический файл, вы также говорите о динамически создаваемых файлах или данных, поэтому я буду говорить вообще здесь (также для веб-серверов вообще).

Сначала рассмотрим различные способы использования для GET и HEAD:

  • С GET клиент запрашивает весь файл или данные (или диапазон данных) и хочет как можно быстрее. Поэтому нет особой причины, по которой сервер сначала отправляет размер данных, особенно когда он может начать отправлять быстрее/раньше в режиме chunked. Таким образом, самый быстрый способ здесь предпочтительнее (клиент будет иметь размер после загрузки в любом случае).

  • С HEAD, с другой стороны, клиент обычно хочет получить определенную информацию. Это может быть просто проверка на существование или "последнее изменение", но его также можно использовать, если клиент хочет определенную часть данных (с запросом диапазона, включая проверку, чтобы проверить, поддерживаются ли запросы диапазона для этого запроса), или просто нужно знать размер данных вверх по какой-то причине.

Не смотрите на возможные сценарии:

Статический файл:

HEAD: нет причин не включать размер в заголовок ответа, потому что эта информация доступна.

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

Live logfile:

Хорошо, несколько странно, но возможно: загрузка файла, размер которого может измениться при загрузке.

HEAD: снова клиент, вероятно, хочет размер, и сервер может легко предоставить размер файла в указанное время в заголовке.

GET: поскольку логические строки могут быть добавлены при загрузке, размер неизвестен спереди. Единственный вариант - отправить chunked.

Таблица с фиксированными размерами:

Предположим, что серверу необходимо отправить обратно таблицу с фиксированными записями из нескольких источников/баз данных:

HEAD: размер, вероятно, нужен клиенту. Сервер может быстро выполнить запрос для подсчета в каждой базе данных и отправить вычисленный размер обратно клиенту.

GET: вместо того, чтобы сначала выполнить запрос для подсчета в каждой базе данных, сервер лучше начнет отправлять результирующие записи из каждой базы данных в куски.

Динамически сгенерированные zip файлы:

Может быть, не общий, а интересный пример.

Представьте, что вы хотите предоставить динамически генерируемые zip файлы пользователю на основе некоторых параметров.

Сначала рассмотрим структуру zip файла:

Есть две части: сначала есть блок для каждого файла: маленький заголовок, за которым следуют сжатые данные для этого файла. Затем список всех файлов внутри zip файла (включая размеры/позиции).

Таким образом, подготовленные блоки для каждого файла могут быть предварительно сгенерированы на диске (и имена/размеры, хранящиеся в некоторой структуре данных.

HEAD: клиент, вероятно, хочет знать размер здесь. Сервер может легко вычислить размер всех необходимых блоков + размер второй части со списком файлов внутри.

Если клиент хочет извлечь один файл, он может запросить последнюю часть файла (с запросом диапазона) для списка, а затем со вторым запросом запросить этот единственный файл. Хотя размер не обязательно необходим для получения последних n байтов, это может быть удобно, если, например, если вы хотите хранить разные части в разреженном файле с одинаковым размером полного zip файла.

GET: сначала не нужно делать вычисления (включая создание второй части, чтобы узнать ее размер). Было бы лучше и быстрее просто начать отправлять каждый блок в куски.

Полностью динамически сгенерированный файл:

В этом случае было бы не очень эффективно возвращать размер в запрос HEAD, конечно, так как весь файл нужно было бы генерировать, чтобы знать его размер.