当一个请求由一个 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() 返回Stringbyte[]

这样,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 处理时,是整个请求头/正文/等。已经加载了吗?的主要内容,如果未能解决你的问题,请参考以下文章

Servlet快速入门:第一个Servlet程序

处理servlet中的多个请求,并为每个请求提供正确的响应

一个Servlet中可以有多个处理请求的方法

Servlet:一个Servlet处理多个请求

Servlet处理GET和POST请求

一个servlet中可以有多个处理请求的方法