codecompleting
codecompleting

Reputation: 9621

When a request is handled by a servlet, is the entire request header/body/etc. loaded already?

When a request is made to a web page, and it is handled via a servlet (which is processed via tomcat), once you enter processing at the servlet level (or a spring mvc controller), has the entire request header/body/etc. been sent from the client to the server already?

Say the client is performing a http POST to a web page, and the post contains allot of form elements.

Will all this data be going through tomcat and your executing servlet or if you don't actually reference:

request.getParamater("abc")

Then you won't incurr that extra load since it won't be streamed?

Upvotes: 12

Views: 3065

Answers (1)

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340883

I cannot find a reference but I belive that the servlet starts processing once the whole header is available (all request headers followed by two newlines). That's why you have getInputStream() and getReader() rather than getBody() returning String or byte[].

This way the servlet can start handling the request data while the client is still sending it, allowing servlets to process very large amounts of data with small memory footprint. For instance upload servlet can read uploaded file byte by byte and save it to disk without the need to have full request contents in memory at the same time.

Here is a servlet I used for testing (in Scala, sorry for that):

@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")
    }

}

Now I use nc to simulate slow client:

$ nc localhost 8080

Nothing happens on the server side. I now manually send some HTTP headers:

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

Still nothing happens on the server side. Tomcat accepted the connection but haven't yet invoked UploadServlet.doPost. But the moment I hit Enter two times, the servlet prints the name parameter but blocks on getLines() (getInputStream() underneath).

I can now send lines of text (Tomcat expects 10000000 bytes) using nc and they are incrementally printed on the server side (input.getLines() returns an Iterator[String] blocking until new line is available).

Servlets summary

  1. Tomcat waits for the whole HTTP header before it starts processing the request (passing it to matching servlet)

  2. Request body does not have to be fully available prior to doPost() invocation. This is fine, otherwise we would soon run out of memory.

  3. The same applies to sending response - we can do this incrementally.

Spring MVC

With Spring MVC you have to be careful. Consider the following two methods (note different argument types):

@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]) {
        //...
    }

}

Entering /upload/good will invoke goodUpload handler method as soon as HTTP header is received but it will block if you try to read body InputStream if no body has yet been received.

However /upload/bad will wait until the whole POST body is available since we have explicitly requested the whole body as a byte array (String would have the same effect): @RequestBody body: Array[Byte].

So it is up to you how Spring MVC handles large request bodies.

TCP/IP level

Remember that HTTP works on top of TCP/IP. Just because you haven't called getInputStream()/getReader() doesn't mean that the server is not receiving the data from the client. In fact, the operating system manages the network socket and keeps receiving TCP/IP packets, which aren't consumed. It means that the data from the client is pushed to the server, but the operating system has to buffer that data.

Maybe somebody more experienced can answer what is happening in this situations (not really a question for this site). O/S may close the socket abruptly if the server does not read incoming data or it may simply buffer it and swap if the buffer grows to large? Another solution might be to stop acknowledging client packets, causing the client to slow down/stop. Really depends on the O/S, not on HTTP/servlets.

Upvotes: 14

Related Questions