To read the POST request body, you can use the getReader
method of ServletRequest
interface.
ServletRequest request;
StringBuilder sb = new StringBuilder();
BufferedReader reader = request.getReader();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String requestBody = sb.toString(); // request body as string
The catch here is you cannot read the request body multiple times. As the HTTP request wasn't sent multiple times and the network is a serial medium it requires servlet stack to buffer the request body in case you decide to re-read it. Unless you don't care about memory it makes sense to not buffer multiple requests.
Spring provides a ContentCachingRequestWrapper#getContentAsByteArray()
method to read the body multiple times. This has limitation we can't read the body multiple times using the getInputStream()
and getReader()
methods. This class caches the request body by consuming the InputStream
. If we read the InputStream
in one of the filters, then other subsequent filters in the filter chain can't read it anymore. Because of this limitation, this approach is not suitable in all situations.
Let's create a class viz CacheBodyServletInputStream
extending ServletInputStream
which provides an input stream for reading binary data from a client request. We will provide custom implementation for handling cached InputStream
.
public class CacheBodyServletInputStream extends ServletInputStream {
private final InputStream cacheBodyInputStream;
public CachBodyServletInputStream(byte[] cacheBody) {
this.cacheBodyInputStream = new ByteArrayInputStream(cacheBody);
}
/**
* Indicates whether InputStream has more data to read or not.
*
* @return true when zero bytes available to read
*/
@Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* Indicates whether InputStream is ready for reading or not.
* Since we've already copied InputStream in a byte array, we'll return true to indicate that it's always available.
*
* @return true
*/
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
}
Let's create a class viz CacheBodyHttpServletRequest
that will cache the request body by reading the body from actual InputStream
and storing it in byte[]
object. CacheBodyHttpServletRequest
overrides the HttpServletRequestWrapper#getInputStream()
to read the raw body and convert it into CacheBodyServletInputStream
. It also, overrides the HttpServletRequestWrapper#getReader()
which returns the cached byte[]
object as BufferedReader
.
public class CacheBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cacheBody;
public CacheBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cacheBody = StreamUtils.copyToByteArray(requestInputStream);
}
@Override
public ServletInputStream getInputStream() {
return new CacheBodyServletInputStream(this.cacheBody);
}
@Override
public BufferedReader getReader() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cacheBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
}
Finally, let's create a filter that will create an object of the CacheBodyHttpServletRequest
from the actual request body.
@Component
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class CacheBodyHttpServletFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CacheBodyHttpServletRequest cacheBodyHttpServletRequest = new CacheBodyHttpServletRequest(request);
filterChain.doFilter(cacheBodyHttpServletRequest, response);
}
}
Happy Coding :)
Top comments (0)