I'm making a REST api for my Java database-like service using Vert.x.
It's not too dificult to write the JSON result as a String to the request's stream, as shown below:
...
routeMatcher.get("/myservice/api/v1/query/:query", req -> {
// get query
String querySring = req.params().get("query");
Query query = jsonMapper.readValue(querySring, Query.class);
// my service creates a list of resulting records...
List<Record> result = myservice.query(query);
String jsonResult = jsonMapper.writeValueAsString(result);
// write entire string to response
req.response().headers().set("Content-Type", "application/json; charset=UTF-8");
req.response().end(jsonResult);
});
...
However I'd like to stream the Java List to the request object by using Jackson's method:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(Outputstream, result);
But I don't know how to connect Jackson's Outputstream argument to Vert.x's re.response(), as they have their own Buffer system that seems incompatible with Jackson's java.io.Outputstream argument.
Can't I use Jackson in combination with Vert.x? Should I write a custom serializer by hand with Vert.x's own JSON library? Other suggestions?
I assume you are generating huge JSON documents as for the small ones string output is good enough: objectMapper.writeValue(<String>, result);
There's a problem with streams. ObjectMapper doesn't know the result size and you will end up with the exception:
java.lang.IllegalStateException: You must set the Content-Length header to be the total size of the message body BEFORE sending any data if you are not using HTTP chunked encoding.
at org.vertx.java.core.http.impl.DefaultHttpServerResponse.write(DefaultHttpServerResponse.java:474)
So in your example I would use temporary files for JSON output and then flush them into response (I haven't tested the code)
File tmpFile = File.createTempFile("tmp", ".json");
mapper.writeValue(tmpFile, result);
req.response().sendFile(tmpFile.getAbsolutePath(), (result) -> tmpFile.delete());
In case you know content length initially you can use the following code to map OutputStream with WriteStream
import org.vertx.java.core.buffer.Buffer;
import org.vertx.java.core.streams.WriteStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputWriterStream extends OutputStream {
public WriteStream writeStream;
public Runnable closeHandler;
#Override
public void write(int b) throws IOException {
throw new UnsupportedOperationException();
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
if (off == 0 && len == b.length) {
writeStream.write(new Buffer(b));
return;
}
byte[] bytes = new byte[len];
System.arraycopy(b, off, bytes, 0, len);
writeStream.write(new Buffer(bytes));
}
#Override
public void write(byte[] b) throws IOException {
writeStream.write(new Buffer(b));
}
#Override
public void close() throws IOException {
closeHandler.run();
}
}
This might be a bit better (and updated for Vertx3) answer:
import io.vertx.core.file.AsyncFile;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.streams.WriteStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputWriterStream extends OutputStream {
public OutputWriterStream(final WriteStream response) {
this.response = response;
this.buffer = new byte[8192];
}
#Override
public synchronized void write(final int b) throws IOException {
buffer[counter++] = (byte) b;
if (counter >= buffer.length) {
flush();
}
}
#Override
public void flush() throws IOException {
super.flush();
if (counter > 0) {
byte[] remaining = buffer;
if (counter < buffer.length) {
remaining = new byte[counter];
System.arraycopy(buffer, 0, remaining, 0, counter);
}
response.write(Buffer.buffer(remaining));
counter = 0;
}
}
#Override
public void close() throws IOException {
flush();
super.close();
if (response instanceof HttpServerResponse) {
try {
response.end();
}
catch (final IllegalStateException ignore) {
}
}
else if (response instanceof AsyncFile) {
((AsyncFile) response).close();
}
}
private final WriteStream<Buffer> response;
private final byte[] buffer;
private int counter = 0;
}
I have a web application. I am using java and spring. The application can create a file and send it to the browser, this is working fine. The way I do it is:
I create the file in a Services class, and the method returns the address to the controller. The controller then sends the file, and it is downloaded correctly. The code for the controller method is this.
#RequestMapping("/getFile")
public #ResponseBody
FileSystemResource getFile() {
String address = Services.createFile();
response.setContentType("application/vnd.ms-excel");
return new FileSystemResource(new File (address));
}
The problem is that the file is saved in the server, and after many requests it will have a lot of files. I have to delete them manually. The question is: How can I delete this file after sending it? or Is there a way to send the file without saving it in the server?
Don't use #ResponseBody. Have Spring inject the HttpServletResponse and write directly to its OutputStream.
#RequestMapping("/getFile")
public void getFile(HttpServletResponse response) {
String address = Services.createFile();
File file = new File(address);
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-disposition", "attachment; filename=" + file.getName());
OutputStream out = response.getOutputStream();
FileInputStream in = new FileInputStream(file);
// copy from in to out
IOUtils.copy(in,out);
out.close();
in.close();
file.delete();
}
I haven't added any exception handling. I leave that to you.
FileSystemResource is really just is a wrapper for FileInputStream that's used by Spring.
Or, if you want to be hardcore, you could make your own FileSystemResource implementation with its own getOutputStream() method that returns your own implementation of FileOutputStream that deletes the underlying file when you call close() on it.
So I decided to take Sotirious's suggestion for a "hardcore" way. It is pretty simple, but has one problem. If user of that class opens input stream once to check something and closes it, it will not be able to open it again since file is deleted on close. Spring does not seem to do that, but you will need to check after every version upgrade.
public class DeleteAfterReadeFileSystemResource extends FileSystemResource {
public DeleteAfterReadeFileSystemResource(File file) {
super(file);
}
#Override
public InputStream getInputStream() throws IOException {
return new DeleteOnCloseFileInputStream(super.getFile());
}
private static final class DeleteOnCloseFileInputStream extends FileInputStream {
private File file;
DeleteOnCloseFileInputStream(File file) throws FileNotFoundException {
super(file);
this.file = file;
}
#Override
public void close() throws IOException {
super.close();
file.delete();
}
}
}
A minor adaption to this answer.
Using a InputStreamResource instead of FileSystemResource makes this a little shorter.
public class CleanupInputStreamResource extends InputStreamResource {
public CleanupInputStreamResource(File file) throws FileNotFoundException {
super(new FileInputStream(file) {
#Override
public void close() throws IOException {
super.close();
Files.delete(file.toPath());
}
});
}
}
You can write Mag's solution with anonymous classes like this:
new FileSystemResource(file) {
#Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(file) {
#Override
public void close() throws IOException {
super.close();
Files.delete(file.toPath());
}
};
}
}
Used this answer and added some modification. Working so far. Due to my limited knowledge I couldn't create dynamic proxy for my custom inputStream.
import static org.apache.commons.io.FileUtils.deleteQuietly;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import org.springframework.core.io.FileSystemResource;
import lombok.RequiredArgsConstructor;
public final class AutoDeleteFileSystemResource extends FileSystemResource {
public AutoDeleteFileSystemResource(Path filePath) {
super(filePath);
}
#RequiredArgsConstructor
private static final class AutoDeleteStream extends InputStream {
private final File file;
private final InputStream original;
#Override
public int read() throws IOException {
return original.read();
}
#Override
public void close() throws IOException {
original.close();
deleteQuietly(file);
}
#Override
public int available() throws IOException {
return original.available();
}
#Override
public int read(byte[] b) throws IOException {
return original.read(b);
}
#Override
public int read(byte[] b, int off, int len) throws IOException {
return original.read(b, off, len);
}
#Override
public long skip(long n) throws IOException {
return original.skip(n);
}
#Override
public boolean equals(Object obj) {
return original.equals(obj);
}
#Override
public int hashCode() {
return original.hashCode();
}
#Override
public synchronized void mark(int readlimit) {
original.mark(readlimit);
}
#Override
public boolean markSupported() {
return original.markSupported();
}
#Override
public synchronized void reset() throws IOException {
original.reset();
}
#Override
public String toString() {
return original.toString();
}
}
/**
* #see org.springframework.core.io.FileSystemResource#getInputStream()
*/
#Override
public InputStream getInputStream() throws IOException {
return new AutoDeleteStream(getFile(), super.getInputStream());
}
}
I have a Jersey Web-Service that I need to parse some json data that was send along with a request.
#POST
#Path ("/authenticate")
#Produces (MediaType.APPLICATION_JSON)
public Response authenticate (#Context HttpServletRequest request)
{
try {
StringBuffer json = new StringBuffer ();
BufferedReader reader = request.getReader();
int line;
while ((line = reader.readLine()) != null)
{
json.append(line);
}
System.out.prinln (json);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return Response.ok().entity(json).build();
}//end authenticate method
This service generates the following Exception:
java.lang.IllegalStateException: getInputStream() has already been called for this request
I did some research that suggests a getReader and getInputStream cannot be called on the same request. Therefore, it seemed like a getInputStream instance is already called. How is this possible if I haven't made a explicit call to it? To solve this problem, I used the getInputStream method instead
try {
ServletInputStream reader = request.getInputStream();
int line;
while ((line = reader.read()) != -1)
{
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return Response.ok().entity().build();
With this approach, how can I use the int of bytes to get the json?
Seems like you're missing a #Consumes annotation. You realise you can just have a method;
#POST
#Path ("/authenticate")
#Consumes (MediaType.APPLICATION_JSON)
#Produces (MediaType.APPLICATION_JSON)
public Response authenticate (String entity) {
//entity contains the posted content
}
Without having to read the stream yourself? If you have a bean representing your consumed JSON , then you can just add it as a method param and jersey will automatically parse it for you;
#POST
#Path ("/authenticate")
#Consumes (MediaType.APPLICATION_JSON)
#Produces (MediaType.APPLICATION_JSON)
public Response authenticate (AuthBean auth) {
//auth bean contains the parsed JSON
}
class AuthBean {
private String username;
private String password;
// getters/setters
}
Example post;
{
"username" : "joe#example.com",
"password" : "super s3cret"
}
we can read from the HttpServletRequest only once, so we have to be using HttpServletRequestWrapper inside a filter before reading the request and use the wrapper for subsequent calls for multiple times..
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import com.dbs.bds.ingestion.util.MultiReadRequestWrapper;
import lombok.extern.slf4j.Slf4j;
#Slf4j
public class SessionValidationFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
log.debug("inside filter");
HttpServletRequest httpServletRequest = new MultiReadRequestWrapper((HttpServletRequest) servletRequest);
filterChain.doFilter(httpServletRequest, servletResponse);
} catch (Exception ex) {
log.error("error inside filter:{}", ex.getMessage());
}
}
}
The below is the code for Wrapper which we used in above Filter.
import org.apache.commons.io.input.BoundedInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class MultiReadRequestWrapper extends HttpServletRequestWrapper {
private static final Logger LOG = LoggerFactory.getLogger(MultiReadRequestWrapper.class);
// We include a max byte size to protect against malicious requests,
//since this all has to be read into memory
public static final Integer MAX_BYTE_SIZE = 1_048_576; // 1 MB
private StringBuilder body;
public MultiReadRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = new StringBuilder("");
try (
InputStream bounded = new BoundedInputStream(request.getInputStream(), MAX_BYTE_SIZE);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bounded));){
String line;
while ((line = bufferedReader.readLine()) != null) {
body.append(line);
}
} catch(Exception e) {
LOG.error(e.getMessage());
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.toString().getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
//do nothing
}
};
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
Now expose the above filter as bean
#Bean(name = "sessionValidationFilter")
public Filter getSessionValidationFilter() {
return new SessionValidationFilter();
}
#SuppressWarnings({"rawtypes", "unchecked"})
#Bean
public FilterRegistrationBean sessionValidationFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(getSessionValidationFilter());
registration.addUrlPatterns("/v1/create/record");
registration.setName("sessionValidationFilter");
return registration;
}
I'm trying to access two http request parameters in a Java Servlet filter, nothing new here, but was surprised to find that the parameters have already been consumed! Because of this, they are not available in the filter chain anymore.
It seems that this only occurs when parameters come in a POST request body (a form submit, for example).
Is there a way to read the parameters and NOT consume them?
So far I've found only this reference: Servlet Filter using request.getParameter loses Form data.
Thanks!
As an aside, an alternative way to solve this problem is to not use the filter chain and instead build your own interceptor component, perhaps using aspects, which can operate on the parsed request body. It will also likely be more efficient as you are only converting the request InputStream into your own model object once.
However, I still think it's reasonable to want to read the request body more than once particularly as the request moves through the filter chain. I would typically use filter chains for certain operations that I want to keep at the HTTP layer, decoupled from the service components.
As suggested by Will Hartung I achieved this by extending HttpServletRequestWrapper, consuming the request InputStream and essentially caching the bytes.
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An input stream which reads the cached request body */
private static class CachedServletInputStream extends ServletInputStream {
private final ByteArrayInputStream buffer;
public CachedServletInputStream(byte[] contents) {
this.buffer = new ByteArrayInputStream(contents);
}
#Override
public int read() {
return buffer.read();
}
#Override
public boolean isFinished() {
return buffer.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
}
}
Now the request body can be read more than once by wrapping the original request before passing it through the filter chain:
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
/* here I read the inputstream and do my thing with it; when I pass the
* wrapped request through the filter chain, the rest of the filters, and
* request handlers may read the cached inputstream
*/
doMyThing(multiReadRequest.getInputStream());
//OR
anotherUsage(multiReadRequest.getReader());
chain.doFilter(multiReadRequest, response);
}
}
This solution will also allow you to read the request body multiple times via the getParameterXXX methods because the underlying call is getInputStream(), which will of course read the cached request InputStream.
Edit
For newer version of ServletInputStream interface. You need to provide implementation of few more methods like isReady, setReadListener etc. Refer this question as provided in comment below.
I know I'm late, but this question was still relevant for me and this SO post was one of the top hits in Google. I'm going ahead and post my solution in the hopes that someone else might save couple of hours.
In my case I needed to log all requests and responses with their bodies. Using Spring Framework the answer is actually quite simple, just use ContentCachingRequestWrapper and ContentCachingResponseWrapper.
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoggingFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
try {
chain.doFilter(requestWrapper, responseWrapper);
} finally {
String requestBody = new String(requestWrapper.getContentAsByteArray());
String responseBody = new String(responseWrapper.getContentAsByteArray());
// Do not forget this line after reading response content or actual response will be empty!
responseWrapper.copyBodyToResponse();
// Write request and response body, headers, timestamps etc. to log files
}
}
}
The above answers were very helpful, but still had some problems in my experience. On tomcat 7 servlet 3.0, the getParamter and getParamterValues also had to be overwritten. The solution here includes both get-query parameters and the post-body. It allows for getting raw-string easily.
Like the other solutions it uses Apache commons-io and Googles Guava.
In this solution the getParameter* methods do not throw IOException but they use super.getInputStream() (to get the body) which may throw IOException. I catch it and throw runtimeException. It is not so nice.
import com.google.common.collect.Iterables;
import com.google.common.collect.ObjectArrays;
import org.apache.commons.io.IOUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* Purpose of this class is to make getParameter() return post data AND also be able to get entire
* body-string. In native implementation any of those two works, but not both together.
*/
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
public static final String UTF8 = "UTF-8";
public static final Charset UTF8_CHARSET = Charset.forName(UTF8);
private ByteArrayOutputStream cachedBytes;
private Map<String, String[]> parameterMap;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}
public static void toMap(Iterable<NameValuePair> inputParams, Map<String, String[]> toMap) {
for (NameValuePair e : inputParams) {
String key = e.getName();
String value = e.getValue();
if (toMap.containsKey(key)) {
String[] newValue = ObjectArrays.concat(toMap.get(key), value);
toMap.remove(key);
toMap.put(key, newValue);
} else {
toMap.put(key, new String[]{value});
}
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null) cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputStream in order to read it multiple times. For
* convenience, I use apache.commons IOUtils
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
#Override
public String getParameter(String key) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(key);
return values != null && values.length > 0 ? values[0] : null;
}
#Override
public String[] getParameterValues(String key) {
Map<String, String[]> parameterMap = getParameterMap();
return parameterMap.get(key);
}
#Override
public Map<String, String[]> getParameterMap() {
if (parameterMap == null) {
Map<String, String[]> result = new LinkedHashMap<String, String[]>();
decode(getQueryString(), result);
decode(getPostBodyAsString(), result);
parameterMap = Collections.unmodifiableMap(result);
}
return parameterMap;
}
private void decode(String queryString, Map<String, String[]> result) {
if (queryString != null) toMap(decodeParams(queryString), result);
}
private Iterable<NameValuePair> decodeParams(String body) {
Iterable<NameValuePair> params = URLEncodedUtils.parse(body, UTF8_CHARSET);
try {
String cts = getContentType();
if (cts != null) {
ContentType ct = ContentType.parse(cts);
if (ct.getMimeType().equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType())) {
List<NameValuePair> postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), UTF8_CHARSET);
params = Iterables.concat(params, postParams);
}
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
return params;
}
public String getPostBodyAsString() {
try {
if (cachedBytes == null) cacheInputStream();
return cachedBytes.toString(UTF8);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/* An inputStream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
#Override
public String toString() {
String query = dk.bnr.util.StringUtil.nullToEmpty(getQueryString());
StringBuilder sb = new StringBuilder();
sb.append("URL='").append(getRequestURI()).append(query.isEmpty() ? "" : "?" + query).append("', body='");
sb.append(getPostBodyAsString());
sb.append("'");
return sb.toString();
}
}
So this is basically Lathy's answer BUT updated for newer requirements for ServletInputStream.
Namely (for ServletInputStream), one has to implement:
public abstract boolean isFinished();
public abstract boolean isReady();
public abstract void setReadListener(ReadListener var1);
This is the edited Lathy's object
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class RequestWrapper extends HttpServletRequestWrapper {
private String _body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
_body = "";
BufferedReader bufferedReader = request.getReader();
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
CustomServletInputStream kid = new CustomServletInputStream(_body.getBytes());
return kid;
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
and somewhere (??) I found this (which is a first-class class that deals with the "extra" methods.
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class CustomServletInputStream extends ServletInputStream {
private byte[] myBytes;
private int lastIndexRetrieved = -1;
private ReadListener readListener = null;
public CustomServletInputStream(String s) {
try {
this.myBytes = s.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new IllegalStateException("JVM did not support UTF-8", ex);
}
}
public CustomServletInputStream(byte[] inputBytes) {
this.myBytes = inputBytes;
}
#Override
public boolean isFinished() {
return (lastIndexRetrieved == myBytes.length - 1);
}
#Override
public boolean isReady() {
// This implementation will never block
// We also never need to call the readListener from this method, as this method will never return false
return isFinished();
}
#Override
public void setReadListener(ReadListener readListener) {
this.readListener = readListener;
if (!isFinished()) {
try {
readListener.onDataAvailable();
} catch (IOException e) {
readListener.onError(e);
}
} else {
try {
readListener.onAllDataRead();
} catch (IOException e) {
readListener.onError(e);
}
}
}
#Override
public int read() throws IOException {
int i;
if (!isFinished()) {
i = myBytes[lastIndexRetrieved + 1];
lastIndexRetrieved++;
if (isFinished() && (readListener != null)) {
try {
readListener.onAllDataRead();
} catch (IOException ex) {
readListener.onError(ex);
throw ex;
}
}
return i;
} else {
return -1;
}
}
};
Ultimately, I was just trying to log the requests. And the above frankensteined together pieces helped me create the below.
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
//one or the other based on spring version
//import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* A filter which logs web requests that lead to an error in the system.
*/
#Component
public class LogRequestFilter extends OncePerRequestFilter implements Ordered {
// I tried apache.commons and slf4g loggers. (one or the other in these next 2 lines of declaration */
//private final static org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory.getLog(LogRequestFilter.class);
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(LogRequestFilter.class);
// put filter at the end of all other filters to make sure we are processing after all others
private int order = Ordered.LOWEST_PRECEDENCE - 8;
private ErrorAttributes errorAttributes;
#Override
public int getOrder() {
return order;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String temp = ""; /* for a breakpoint, remove for production/real code */
/* change to true for easy way to comment out this code, remove this if-check for production/real code */
if (false) {
filterChain.doFilter(request, response);
return;
}
/* make a "copy" to avoid issues with body-can-only-read-once issues */
RequestWrapper reqWrapper = new RequestWrapper(request);
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
// pass through filter chain to do the actual request handling
filterChain.doFilter(reqWrapper, response);
status = response.getStatus();
try {
Map<String, Object> traceMap = getTrace(reqWrapper, status);
// body can only be read after the actual request handling was done!
this.getBodyFromTheRequestCopy(reqWrapper, traceMap);
/* now do something with all the pieces of information gatherered */
this.logTrace(reqWrapper, traceMap);
} catch (Exception ex) {
logger.error("LogRequestFilter FAILED: " + ex.getMessage(), ex);
}
}
private void getBodyFromTheRequestCopy(RequestWrapper rw, Map<String, Object> trace) {
try {
if (rw != null) {
byte[] buf = IOUtils.toByteArray(rw.getInputStream());
//byte[] buf = rw.getInputStream();
if (buf.length > 0) {
String payloadSlimmed;
try {
String payload = new String(buf, 0, buf.length, rw.getCharacterEncoding());
payloadSlimmed = payload.trim().replaceAll(" +", " ");
} catch (UnsupportedEncodingException ex) {
payloadSlimmed = "[unknown]";
}
trace.put("body", payloadSlimmed);
}
}
} catch (IOException ioex) {
trace.put("body", "EXCEPTION: " + ioex.getMessage());
}
}
private void logTrace(HttpServletRequest request, Map<String, Object> trace) {
Object method = trace.get("method");
Object path = trace.get("path");
Object statusCode = trace.get("statusCode");
logger.info(String.format("%s %s produced an status code '%s'. Trace: '%s'", method, path, statusCode,
trace));
}
protected Map<String, Object> getTrace(HttpServletRequest request, int status) {
Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception");
Principal principal = request.getUserPrincipal();
Map<String, Object> trace = new LinkedHashMap<String, Object>();
trace.put("method", request.getMethod());
trace.put("path", request.getRequestURI());
if (null != principal) {
trace.put("principal", principal.getName());
}
trace.put("query", request.getQueryString());
trace.put("statusCode", status);
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
trace.put("header:" + key, value);
}
if (exception != null && this.errorAttributes != null) {
trace.put("error", this.errorAttributes
.getErrorAttributes((WebRequest) new ServletRequestAttributes(request), true));
}
return trace;
}
}
Please take this code with a grain of salt.
The MOST important "test" is if a POST works with a payload. This is what will expose "double read" issues.
pseudo example code
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("myroute")
public class MyController {
#RequestMapping(method = RequestMethod.POST, produces = "application/json")
#ResponseBody
public String getSomethingExample(#RequestBody MyCustomObject input) {
String returnValue = "";
return returnValue;
}
}
You can replace "MyCustomObject" with plain ole "Object" if you just want to test.
This answer is frankensteined from several different SOF posts and examples..but it took a while to pull it all together so I hope it helps a future reader.
Please upvote Lathy's answer before mine. I could have not gotten this far without it.
Below is one/some of the exceptions I got while working this out.
getReader() has already been called for this request
Looks like some of the places I "borrowed" from are here:
http://slackspace.de/articles/log-request-body-with-spring-boot/
https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java
https://howtodoinjava.com/servlets/httpservletrequestwrapper-example-read-request-body/
https://www.oodlestechnologies.com/blogs/How-to-create-duplicate-object-of-httpServletRequest-object
https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java
January 2021 APPEND.
I have learned the hard way that the above code does NOT work for
x-www-form-urlencoded
Consider the example below:
#CrossOrigin
#ResponseBody
#PostMapping(path = "/mypath", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseEntity myMethodName(#RequestParam Map<String, String> parameters
) {
/* DO YOU GET ANY PARAMETERS HERE? Or are they empty because of logging/auditing filter ?*/
return new ResponseEntity(HttpStatus.OK);
}
I had to go through several of the other examples here.
I came up with a "wrapper" that works explicitly for APPLICATION_FORM_URLENCODED_VALUE
import org.apache.commons.io.IOUtils;
import org.springframework.http.MediaType;
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Makes a "copy" of the HttpRequest so the body can be accessed more than 1 time.
* WORKS WITH APPLICATION_FORM_URLENCODED_VALUE
* See : https://stackoverflow.com/questions/44182370/why-do-we-wrap-httpservletrequest-the-api-provides-an-httpservletrequestwrappe/44187955#44187955
*/
public final class AppFormUrlEncodedSpecificContentCachingRequestWrapper extends ContentCachingRequestWrapper {
public static final String ERROR_MSG_CONTENT_TYPE_NOT_SUPPORTED = "ContentType not supported. (Input ContentType(s)=\"%1$s\", Supported ContentType(s)=\"%2$s\")";
public static final String ERROR_MSG_PERSISTED_CONTENT_CACHING_REQUEST_WRAPPER_CONSTRUCTOR_FAILED = "AppFormUrlEncodedSpecificContentCachingRequestWrapper constructor failed";
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(AppFormUrlEncodedSpecificContentCachingRequestWrapper.class);
private byte[] body;
private ServletInputStream inputStream;
public AppFormUrlEncodedSpecificContentCachingRequestWrapper(HttpServletRequest request) {
super(request);
super.getParameterMap(); // init cache in ContentCachingRequestWrapper. THIS IS THE VITAL CALL so that "#RequestParam Map<String, String> parameters" are populated on the REST Controller. See https://stackoverflow.com/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once/64924380#64924380
String contentType = request.getContentType();
/* EXPLICTLY check for APPLICATION_FORM_URLENCODED_VALUE and allow nothing else */
if (null == contentType || !contentType.equalsIgnoreCase(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
IllegalArgumentException ioex = new IllegalArgumentException(String.format(ERROR_MSG_CONTENT_TYPE_NOT_SUPPORTED, contentType, MediaType.APPLICATION_FORM_URLENCODED_VALUE));
LOGGER.error(ERROR_MSG_PERSISTED_CONTENT_CACHING_REQUEST_WRAPPER_CONSTRUCTOR_FAILED, ioex);
throw ioex;
}
try {
loadBody(request);
} catch (IOException ioex) {
throw new RuntimeException(ioex);
}
}
private void loadBody(HttpServletRequest request) throws IOException {
body = IOUtils.toByteArray(request.getInputStream());
inputStream = new CustomServletInputStream(this.getBody());
}
private byte[] getBody() {
return body;
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
}
return super.getInputStream();
}
}
Note Andrew Sneck's answer on this same page.
It is pretty much this : https://programmersought.com/article/23981013626/
I have not had time to harmonize the two above implementations (my two that is).
So I created a Factory to "choose" from the two:
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
/**
* Factory to return different concretes of HttpServletRequestWrapper. APPLICATION_FORM_URLENCODED_VALUE needs a different concrete.
*/
public class HttpServletRequestWrapperFactory {
public static final String ERROR_MSG_HTTP_SERVLET_REQUEST_WRAPPER_FACTORY_CREATE_HTTP_SERVLET_REQUEST_WRAPPER_FAILED = "HttpServletRequestWrapperFactory createHttpServletRequestWrapper FAILED";
public static HttpServletRequestWrapper createHttpServletRequestWrapper(final HttpServletRequest request) {
HttpServletRequestWrapper returnItem = null;
if (null != request) {
String contentType = request.getContentType();
if (null != contentType && contentType.equalsIgnoreCase(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
returnItem = new AppFormUrlEncodedSpecificContentCachingRequestWrapper(request);
} else {
try {
returnItem = new PersistedBodyRequestWrapper(request);
} catch (IOException ioex) {
throw new RuntimeException(ERROR_MSG_HTTP_SERVLET_REQUEST_WRAPPER_FACTORY_CREATE_HTTP_SERVLET_REQUEST_WRAPPER_FAILED, ioex);
}
}
}
return returnItem;
}
}
Below is the "other" one that works with JSON, etc. It is the other concrete that the Factory can output. I put it here so that my Jan 2021 APPEND is consistent..I don't know if the code below is perfect consistent with my original answer:
import org.springframework.http.MediaType;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* Makes a "copy" of the HttpRequest so the body can be accessed more than 1 time.
* See : https://stackoverflow.com/questions/44182370/why-do-we-wrap-httpservletrequest-the-api-provides-an-httpservletrequestwrappe/44187955#44187955
* DOES NOT WORK WITH APPLICATION_FORM_URLENCODED_VALUE
*/
public final class PersistedBodyRequestWrapper extends HttpServletRequestWrapper {
public static final String ERROR_MSG_CONTENT_TYPE_NOT_SUPPORTED = "ContentType not supported. (ContentType=\"%1$s\")";
public static final String ERROR_MSG_PERSISTED_BODY_REQUEST_WRAPPER_CONSTRUCTOR_FAILED = "PersistedBodyRequestWrapper constructor FAILED";
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(PersistedBodyRequestWrapper.class);
private String persistedBody;
private final Map<String, String[]> parameterMap;
public PersistedBodyRequestWrapper(final HttpServletRequest request) throws IOException {
super(request);
String contentType = request.getContentType();
/* Allow everything EXCEPT APPLICATION_FORM_URLENCODED_VALUE */
if (null != contentType && contentType.equalsIgnoreCase(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
IllegalArgumentException ioex = new IllegalArgumentException(String.format(ERROR_MSG_CONTENT_TYPE_NOT_SUPPORTED, MediaType.APPLICATION_FORM_URLENCODED_VALUE));
LOGGER.error(ERROR_MSG_PERSISTED_BODY_REQUEST_WRAPPER_CONSTRUCTOR_FAILED, ioex);
throw ioex;
}
parameterMap = request.getParameterMap();
this.persistedBody = "";
BufferedReader bufferedReader = request.getReader();
String line;
while ((line = bufferedReader.readLine()) != null) {
this.persistedBody += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
CustomServletInputStream csis = new CustomServletInputStream(this.persistedBody.getBytes(StandardCharsets.UTF_8));
return csis;
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
#Override
public Map<String, String[]> getParameterMap() {
return this.parameterMap;
}
}
I found good solution for any format of request body.
I tested for application/x-www-form-urlencoded and application/json both worked very well. Problem of ContentCachingRequestWrapper that is designed only for x-www-form-urlencoded request body, but not work with e.g. json. I found solution for json link. It had trouble that it didn't support x-www-form-urlencoded.
I joined both in my code:
import org.apache.commons.io.IOUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class MyContentCachingRequestWrapper extends ContentCachingRequestWrapper {
private byte[] body;
public MyContentCachingRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
super.getParameterMap(); // init cache in ContentCachingRequestWrapper
body = super.getContentAsByteArray(); // first option for application/x-www-form-urlencoded
if (body.length == 0) {
try {
body = IOUtils.toByteArray(super.getInputStream()); // second option for other body formats
} catch (IOException ex) {
body = new byte[0];
}
}
}
public byte[] getBody() {
return body;
}
#Override
public ServletInputStream getInputStream() {
return new RequestCachingInputStream(body);
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
}
#Override
public int read() throws IOException {
return inputStream.read();
}
#Override
public boolean isFinished() {
return inputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readlistener) {
}
}
}
The only way would be for you to consume the entire input stream yourself in the filter, take what you want from it, and then create a new InputStream for the content you read, and put that InputStream in to a ServletRequestWrapper (or HttpServletRequestWrapper).
The downside is you'll have to parse the payload yourself, the standard doesn't make that capability available to you.
Addenda --
As I said, you need to look at HttpServletRequestWrapper.
In a filter, you continue along by calling FilterChain.doFilter(request, response).
For trivial filters, the request and response are the same as the ones passed in to the filter. That doesn't have to be the case. You can replace those with your own requests and/or responses.
HttpServletRequestWrapper is specifically designed to facilitate this. You pass it the original request, and then you can intercept all of the calls. You create your own subclass of this, and replace the getInputStream method with one of your own. You can't change the input stream of the original request, so instead you have this wrapper and return your own input stream.
The simplest case is to consume the original requests input stream in to a byte buffer, do whatever magic you want on it, then create a new ByteArrayInputStream from that buffer. This is what is returned in your wrapper, which is passed to the FilterChain.doFilter method.
You'll need to subclass ServletInputStream and make another wrapper for your ByteArrayInputStream, but that's not a big deal either.
I too had the same issue and I believe the code below is more simple and it is working for me,
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private String _body;
public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
_body = "";
BufferedReader bufferedReader = request.getReader();
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
in the filter java class,
HttpServletRequest properRequest = ((HttpServletRequest) req);
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest);
req = wrappedRequest;
inputJson = IOUtils.toString(req.getReader());
System.out.println("body"+inputJson);
Please let me know if you have any queries
Spring has built-in support for this with an AbstractRequestLoggingFilter:
#Bean
public Filter loggingFilter(){
final AbstractRequestLoggingFilter filter = new AbstractRequestLoggingFilter() {
#Override
protected void beforeRequest(final HttpServletRequest request, final String message) {
}
#Override
protected void afterRequest(final HttpServletRequest request, final String message) {
}
};
filter.setIncludePayload(true);
filter.setIncludeQueryString(false);
filter.setMaxPayloadLength(1000000);
return filter;
}
Unfortunately you still won't be able to read the payload directly off the request, but the String message parameter will include the payload so you can grab it from there as follows:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
Just overwriting of getInputStream() did not work in my case. My server implementation seems to parse parameters without calling this method. I did not find any other way, but re-implement the all four getParameter* methods as well. Here is the code of getParameterMap (Apache Http Client and Google Guava library used):
#Override
public Map<String, String[]> getParameterMap() {
Iterable<NameValuePair> params = URLEncodedUtils.parse(getQueryString(), NullUtils.UTF8);
try {
String cts = getContentType();
if (cts != null) {
ContentType ct = ContentType.parse(cts);
if (ct.getMimeType().equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType())) {
List<NameValuePair> postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), NullUtils.UTF8);
params = Iterables.concat(params, postParams);
}
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
Map<String, String[]> result = toMap(params);
return result;
}
public static Map<String, String[]> toMap(Iterable<NameValuePair> body) {
Map<String, String[]> result = new LinkedHashMap<>();
for (NameValuePair e : body) {
String key = e.getName();
String value = e.getValue();
if (result.containsKey(key)) {
String[] newValue = ObjectArrays.concat(result.get(key), value);
result.remove(key);
result.put(key, newValue);
} else {
result.put(key, new String[] {value});
}
}
return result;
}
The method getContentAsByteArray() of the Spring class ContentCachingRequestWrapper reads the body multiple times, but the methods getInputStream() and getReader() of the same class do not read the body multiple times:
"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 class is not suitable in all situations."
In my case more general solution that solved this problem was to add following three classes to my Spring boot project (and the required dependencies to the pom file):
CachedBodyHttpServletRequest.java:
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}
#Override
public BufferedReader getReader() throws IOException {
// Create a reader from cachedContent
// and return it
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
}
CachedBodyServletInputStream.java:
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
#Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
#Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
}
ContentCachingFilter.java:
#Order(value = Ordered.HIGHEST_PRECEDENCE)
#Component
#WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*")
public class ContentCachingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
System.out.println("IN ContentCachingFilter ");
CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest);
filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
}
}
I also added the following dependencies to pom:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
A tuturial and full source code is located here:
https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
If you have control over the request, you could set the content type to binary/octet-stream. This allows to query for parameters without consuming the input stream.
However, this might be specific to some application servers. I only tested tomcat, jetty seems to behave the same way according to https://stackoverflow.com/a/11434646/957103.
you can use servlet filter chain, but instead use the original one, you can create your own request yourownrequests extends HttpServletRequestWrapper.
First of all we should not read parameters within the filter. Usually the headers are read in the filter to do few authentication tasks. Having said that one can read the HttpRequest body completely in the Filter or Interceptor by using the CharStreams:
String body = com.google.common.io.CharStreams.toString(request.getReader());
This does not affect the subsequent reads at all.