Я не могу найти ссылку, но я верю, что сервлет начинает обрабатывать, как только весь заголовок становится доступным (все заголовки запроса сопровождаются двумя символами новой строки).Вот почему у вас есть 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
байт), используя 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 / сервлетов.