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.