Reading a Servlet/Spring Request Body Multiple Times
I've recently been writing a javax.servlet.Filter
to perform validation on a request coming from Netlify's Deploy Notifications and have needed to read the request body to validate that the request is correct.
However, I've found this a little bit painful, as the Java Servlets provide a ServletInputStream
that can only be read once, and if you don't, the web server you're using i.e. Spring may reject the incoming request.
This means that your requests will fail with the following/a similar error:
HTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 25 May 2020 15:45:29 GMT
Connection: close
{
"timestamp": "2020-05-25T15:45:33.739+0000",
"status": 400,
"error": "Bad Request",
"message": "Required request body is missing: public org.springframework.http.ResponseEntity<java.lang.String> me.jvt.hacking.controller.Controller.echo(java.lang.String)",
"path": "/"
}
To avoid this, we need to cache the ServletInputStream
, so the web server can read the input, as well as the Filter
(s) themselves.
For the sake of this example, I'll create an endpoint that echoes the request body, i.e.
$ curl localhost:8080/ -d 'hi' -H 'content-type: application/json' -i
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 2
Date: Mon, 25 May 2020 15:42:16 GMT
hi
And will have a Filter
that logs the request body, too.
Complete code can be found at jamietanna/multiple-read-servlet.
Using ContentCachingRequestWrapper
On paper, using Spring's ContentCachingRequestWrapper
should work. It caches content read from the ServletInputStream
, and allows easy retrieval.
However, it doesn't take into account the need for the ServletInputStream
to be re-read, which means we still receive the HttpMessageNotReadableException
exceptions about the request body being missing.
Creating our own class
Following arberg's response on Http Servlet request lose params from POST body after read it once and Marco's response on HttpServletRequestWrapper, example implementation for setReadListener / isFinished / isReady?, I've created the MultiReadHttpServletRequest
class which is available in Maven Central:
<groupId>me.jvt.multireadservlet</groupId>
<artifactId>multiple-read-servlet</artifactId>
<version>4.0.1</version> <!-- version mirrors the javax.servlet:javax.servlet-api version -->
And can be found in MultiReadHttpServletRequest.java
, with example usage in BodyReadFilter.java
.