当一个请求由一个 servlet 处理时,是整个请求头/正文/等。已经加载了吗?
Posted
技术标签:
【中文标题】当一个请求由一个 servlet 处理时,是整个请求头/正文/等。已经加载了吗?【英文标题】:When a request is handled by a servlet, is the entire request header/body/etc. loaded already? 【发布时间】:2012-02-02 17:34:35 【问题描述】:当向网页发出请求并通过 servlet(通过 tomcat 处理)进行处理时,一旦您进入 servlet 级别(或 spring mvc 控制器)的处理,就会拥有整个请求标头/身体/等已经从客户端发送到服务器了吗?
假设客户端正在对网页执行 http POST,并且该帖子包含分配的表单元素。
所有这些数据是否会通过 tomcat 和您正在执行的 servlet,或者如果您实际上没有引用:
request.getParamater("abc")
那么你不会产生额外的负载,因为它不会被流式传输?
【问题讨论】:
我相当肯定整个请求都包含在发送到 servlet 的ServletRequest
中。我无法想象一个模型可以有条件地接收部分请求。
【参考方案1】:
我找不到参考,但我相信一旦整个标头可用(所有请求标头后跟两个换行符),servlet 就会开始处理。这就是为什么你有getInputStream()
和getReader()
而不是getBody()
返回String
或byte[]
。
这样,servlet 可以在客户端仍在发送请求数据时开始处理请求数据,从而允许 servlet 以较小的内存占用处理大量数据。比如upload servlet可以逐字节读取上传的文件并保存到磁盘,而不需要同时在内存中有完整的请求内容。
这是我用于测试的 servlet(在 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 时,servlet 会打印 name
参数,但会阻塞在 getLines()
(下面的 getInputStream()
)上。
我现在可以使用 nc
发送文本行(Tomcat 需要 10000000
字节),并且它们会逐渐打印在服务器端(input.getLines()
返回一个 Iterator[String]
阻塞,直到有新行可用)。
Servlet 总结
Tomcat 在开始处理请求(将其传递给匹配的 servlet)之前等待 整个 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
将在收到 HTTP 标头后立即调用 goodUpload
处理程序方法,但如果您尝试读取 body
InputStream
如果尚未收到任何正文,它将阻塞。
但是,/upload/bad
将等到整个 POST
正文可用,因为我们已明确请求整个正文作为字节数组(String
将具有相同的效果):@RequestBody body: Array[Byte]
。
因此,Spring MVC 如何处理大型请求体取决于您。
TCP/IP 级别
请记住,HTTP 在 TCP/IP 之上工作。仅仅因为您没有调用getInputStream()
/getReader()
并不意味着服务器没有从客户端接收数据。事实上,操作系统管理网络套接字并不断接收 TCP/IP 数据包,这些数据包没有被消费。这意味着来自客户端的数据被推送到服务器,但操作系统必须缓冲该数据。
也许更有经验的人可以回答在这种情况下发生的事情(对于本网站来说,这并不是一个真正的问题)。如果服务器不读取传入数据,O/S 可能会突然关闭套接字,或者如果缓冲区变大,它可能会简单地缓冲它并交换?另一种解决方案可能是停止确认客户端数据包,从而导致客户端减速/停止。真的取决于操作系统,而不是 HTTP/servlet。
【讨论】:
只是为了确保我理解,给定的请求由标头 + 正文组成,对吗? @codecompleting: 是的,请求参数存储在标头中(见我的编辑) 这样您就可以检查标头中的参数,同时仍然不会在发布的数据中进行流式传输。所以是说如果你不引用实际发布的数据,它就不会从客户端流式传输? @codecompleting: 查看我关于 Spring MVC 和 TCP/IP 内部的更新。以上是关于当一个请求由一个 servlet 处理时,是整个请求头/正文/等。已经加载了吗?的主要内容,如果未能解决你的问题,请参考以下文章