Когда запрос обрабатывается сервлетом, это весь заголовок запроса/тело/etc. уже загружен?

Когда запрос отправляется на веб-страницу и обрабатывается через сервлет (который обрабатывается через tomcat), как только вы вводите обработку на уровне сервлета (или spring mvc-контроллер), имеет весь запрос заголовок/тело/и т.д.. уже отправлено с клиента на сервер?

Предположим, что клиент выполняет http POST на веб-странице, и сообщение содержит выделение элементов формы.

Будут ли все эти данные проходить через tomcat и ваш исполняемый сервлет или если вы действительно не ссылаетесь:

request.getParamater("abc")

Тогда вы не будете подвергать эту дополнительную нагрузку, так как она не будет транслироваться?

Ответ 1

Я не могу найти ссылку, но я верю, что сервлет начинает обрабатываться после того, как весь заголовок доступен (все заголовки запросов следуют двумя новыми строками). Поэтому у вас есть getInputStream() и getReader(), а не getBody(), возвращающие String или byte[].

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

Вот сервлет, который я использовал для тестирования (в Scala, извините за это):

@WebServlet(Array("/upload"))
class UploadServlet extends HttpServlet {

    @Override
    override def doPost(request: HttpServletRequest, response: HttpServletResponse) {
        println(request.getParameter("name"));
        val input = Source.fromInputStream(request.getInputStream)
        input.getLines() foreach println
        println("Done")
    }

}

Теперь я использую nc для имитации медленного клиента:

$ nc localhost 8080

Ничего не происходит на стороне сервера. Теперь я вручную отправляю HTTP-заголовки:

POST /upload?name=foo HTTP/1.1
Host: localhost:8080
Content-Length: 10000000

По-прежнему ничего не происходит на стороне сервера. Tomcat принял соединение, но еще не вызвал UploadServlet.doPost. Но в тот момент, когда я ударил Enter два раза, сервлет печатает параметр name, но блокирует на getLines() (getInputStream() снизу).

Теперь я могу отправлять строки текста (Tomcat ожидает 10000000 bytes) с помощью nc, и они инкрементно печатаются на стороне сервера (input.getLines() возвращает блокировку Iterator[String] до тех пор, пока не появится новая строка).

Обзор сервлетов

  • Tomcat ожидает заголовок HTTP целиком, прежде чем он начнет обработку запроса (передав его в соответствующий сервлет)

  • Тело запроса не должно быть полностью доступно до вызова doPost(). Это прекрасно, иначе у нас скоро закончится память.

  • То же самое относится к отправке ответа - мы можем сделать это постепенно.

Spring MVC

С Spring MVC вы должны быть осторожны. Рассмотрим следующие два метода (обратите внимание на разные типы аргументов):

@Controller
@RequestMapping(value = Array("/upload"))
class UploadController  {

    @RequestMapping(value = Array("/good"), method = Array(POST))
    @ResponseStatus(HttpStatus.NO_CONTENT)
    def goodUpload(body: InputStream) {
        //...
    }

    @RequestMapping(value = Array("/bad"), method = Array(POST))
    @ResponseStatus(HttpStatus.NO_CONTENT)
    def badUpload(@RequestBody body: Array[Byte]) {
        //...
    }

}

Ввод /upload/good будет вызывать метод обработчика goodUpload, как только будет получен HTTP-заголовок, но он будет заблокирован, если вы попытаетесь прочитать body InputStream, если тело еще не получено.

Однако /upload/bad будет ожидать, пока тело POST будет доступно, поскольку мы явно запросили весь объект как массив байтов (String будет иметь тот же эффект): @RequestBody body: Array[Byte].

Итак, вам решать, как Spring MVC обрабатывает большие тела запросов.

Уровень TCP/IP

Помните, что HTTP работает поверх TCP/IP. Просто потому, что вы не вызывали getInputStream()/getReader(), не означает, что сервер не получает данные от клиента. Фактически, операционная система управляет сетевым сокетом и продолжает получать пакеты TCP/IP, которые не потребляются. Это означает, что данные от клиента переносятся на сервер, но операционная система должна буферизовать эти данные.

Возможно, кто-то более опытный может ответить на то, что происходит в этих ситуациях (на самом деле это не вопрос для этого сайта). O/S может внезапно закрыть розетку, если сервер не будет считывать входящие данные, или он может просто буферизовать его и обменять, если буфер увеличивается до большого? Другим решением может быть прекращение подтверждения клиентских пакетов, что приводит к замедлению/остановке клиента. Действительно зависит от O/S, а не от HTTP/сервлетов.