Getting post data from HttpServletRequest multiple times [duplicate] - java

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.

Related

Use log4j logging jersey 2.25

I am using Log4j logging.
As Jersey 2.25 uses java util logging, Requests and responses are not getting logged
In Older jersey, I had custom filter for this.
Does any one have idea to fix it.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.protocol.HttpContext;
import org.glassfish.jersey.message.internal.ReaderWriter;
import org.glassfish.jersey.server.ContainerException;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ContainerResponse;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
/**
* If true the request and response entities (if present) will not be logged. If false the request
* and response entities will be logged.
* <p>
* The default value is false.
*/
public static final String FEATURE_LOGGING_DISABLE_ENTITY =
"com.sun.jersey.config.feature.logging.DisableEntitylogging";
private static final Log LOGGER = LogFactory.getLog(LoggingFilter.class.getName());
private static final String NOTIFICATION_PREFIX = "* ";
private static final String REQUEST_PREFIX = "> ";
private static final String RESPONSE_PREFIX = "< ";
private final Log logger;
private #Context HttpContext hc;
private #Context ResourceConfig rc;
private long id = 0;
/**
* Create a logging filter logging the request and response to a default JDK logger, named as the
* fully qualified class name of this class.
*/
public LoggingFilter() {
this(LOGGER);
}
/**
* Create a logging filter logging the request and response to a JDK logger.
*
* #param logger the logger to log requests and responses.
*/
public LoggingFilter(Log logger) {
this.logger = logger;
}
private synchronized void setId() {
if (hc.getAttribute("request-id") == null) {
hc.setAttribute("request-id", Long.toString(++id));
}
}
private StringBuilder prefixId(StringBuilder b) {
b.append(hc.getAttribute("request-id").toString()).append(" ");
return b;
}
public ContainerRequest filter(ContainerRequest request) {
setId();
final StringBuilder b = new StringBuilder();
printRequestLine(b, request);
printRequestHeaders(b, request.getRequestHeaders());
if (rc.getProperties().get(FEATURE_LOGGING_DISABLE_ENTITY)) {
logger.debug(b.toString());
return request;
} else {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = request.getEntityInputStream();
try {
if (in.available() > 0) {
ReaderWriter.writeTo(in, out);
byte[] requestEntity = out.toByteArray();
try {
String contentType = request.getRequestHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (contentType != null && (contentType.startsWith("image")
|| contentType.equals("application/octet-stream"))) {
b.append("Binary content: " + contentType).append("\n");
} else {
printEntity(b, requestEntity);
}
} catch (Exception e) {
logger.warn("Failed to log request", e);
}
request.setEntityInputStream(new ByteArrayInputStream(requestEntity));
}
return request;
} catch (IOException ex) {
throw new ContainerException(ex);
} finally {
logger.debug(b.toString());
}
}
}
private void printRequestLine(StringBuilder b, ContainerRequest request) {
prefixId(b).append(NOTIFICATION_PREFIX).append("Server in-bound request").append('\n');
prefixId(b).append(REQUEST_PREFIX).append(request.getMethod()).append(" ")
.append(request.getRequestUri().toASCIIString()).append('\n');
}
private void printRequestHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {
for (Map.Entry<String, List<String>> e : headers.entrySet()) {
String header = e.getKey();
for (String value : e.getValue()) {
prefixId(b).append(REQUEST_PREFIX).append(header).append(": ").append(value).append('\n');
}
}
prefixId(b).append(REQUEST_PREFIX).append('\n');
}
private void printEntity(StringBuilder b, byte[] entity) throws IOException {
if (entity.length == 0) {
return;
}
b.append(new String(entity)).append("\n");
}
private final class Adapter implements ContainerResponseWriter {
private final ContainerResponseWriter crw;
private final boolean disableEntity;
private long contentLength;
private ContainerResponse response;
private ByteArrayOutputStream baos;
private StringBuilder b = new StringBuilder();
Adapter(ContainerResponseWriter crw) {
this.crw = crw;
this.disableEntity = rc.getFeature(FEATURE_LOGGING_DISABLE_ENTITY);
}
public OutputStream writeStatusAndHeaders(long contentLength, ContainerResponse response)
throws IOException {
printResponseLine(b, response);
printResponseHeaders(b, response.getHttpHeaders());
if (disableEntity) {
logger.debug(b.toString());
return crw.writeStatusAndHeaders(contentLength, response);
} else {
this.contentLength = contentLength;
this.response = response;
return this.baos = new ByteArrayOutputStream();
}
}
public void finish() throws IOException {
if (!disableEntity) {
byte[] entity = baos.toByteArray();
try {
MediaType mediaType =
(MediaType) response.getHttpHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
if (mediaType != null && "application".equals(mediaType.getType())
&& "pdf".equals(mediaType.getSubtype()) == false) {
printEntity(b, entity);
} else {
b.append("Binary content: ");
if (mediaType != null && mediaType.getType() != null
&& mediaType.getSubtype() != null) {
b.append(mediaType.getType());
b.append("/");
b.append(mediaType.getSubtype());
}
b.append("\n");
}
} catch (Exception e) {
logger.error("Failed to log response", e);
}
//Output to log
logger.debug(b.toString());
// Write out the headers and buffered entity
OutputStream out = crw.writeStatusAndHeaders(contentLength, response);
out.write(entity);
}
crw.finish();
}
}
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
setId();
response.setContainerResponseWriter(new Adapter(response.getContainerResponseWriter()));
return response;
}
private void printResponseLine(StringBuilder b, ContainerResponse response) {
prefixId(b).append(NOTIFICATION_PREFIX).append("Server out-bound response").append('\n');
prefixId(b).append(RESPONSE_PREFIX).append(Integer.toString(response.getStatus())).append('\n');
}
private void printResponseHeaders(StringBuilder b, MultivaluedMap<String, Object> headers) {
for (Map.Entry<String, List<Object>> e : headers.entrySet()) {
String header = e.getKey();
for (Object value : e.getValue()) {
prefixId(b).append(RESPONSE_PREFIX).append(header).append(": ")
.append(ContainerResponse.getHeaderValue(value)).append('\n');
}
}
prefixId(b).append(RESPONSE_PREFIX).append('\n');
}
}

How to stream JSON result with Jackson in Vert.x (java)

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;
}

How can I set an auto numeric name generator for image uploads, using a singleton pattern counter

So my Problem is that I have to create a Singleton pattern counter for numeric name giving. For example "1", "2", "3" etc. The idea is that every time i start the application and the Server(tomcat), it gets the last number and when I upload another image it should continue from there. Lets say the last one was "43", so the the next time I start the application it should know it and put "44" for the next image upload.
I'm not that good in Java so please give me some patience :)
This is my FileUploadServlet. It handles the request from the fileUploadForm.jsp by taking the file from the submit.
package upload;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import utils.FormatChecker;
import utils.UnzipFile;
//Servlet for handling the Upload request from the Index.jsp
#MultipartConfig
public class FileUploadServlet extends HttpServlet {
// Instace of the FileUpload object
private FileUploader uploader = new FileUploader();
// Instance of the FormatChecker object
private FormatChecker checker = new FormatChecker();
// Instance of the UnzipFile object
private UnzipFile unzip = new UnzipFile();
private static final long serialVersionUID = 1L;
private static final String SAVE_FOLDER = "C:\\Users\\cuche\\Desktop\\tomcat\\apache-tomcat-7.0.47\\webapps\\files";
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect("error.jsp");
}
/**
* #see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String contentType;
boolean isFormatValid;
Part filePart = request.getPart("file");
contentType = filePart.getContentType();
ServletContext context = getServletContext();
String appPath = context.getRealPath("/");
String fileNameOld = getFileName(filePart);
String fileNameNew = appPath + fileNameOld;
isFormatValid = checker.check(contentType);
pleas ignore the part with the FileUnziper
if (isFormatValid == true) {
if (contentType == ("application/x-zip-compressed")) {
unzip.FileUnziper(fileNameNew, SAVE_FOLDER);
} else {
//gets the content and saves in form of a stream
InputStream fileContent = filePart.getInputStream();
//using the uploadImage method of uploader class
uploader.uploadImage(fileNameNew, fileContent);
}
try {
response.sendRedirect("result.jsp");
} catch (IOException ex) {
response.getWriter().append(ex.getLocalizedMessage());
}
} else {
response.getWriter().append("Format is wrong");
}
}
// method for removing header for proper file upload
private String getFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String filename = cd.substring(cd.indexOf('=') + 1).trim()
.replace("\"", "");
return filename.substring(filename.lastIndexOf('/') + 1)
.substring(filename.lastIndexOf('\\') + 1); // MSIE fix.
}
}
return null;
}
}
This is my FileUploader class
package upload;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* #author Iwan Cuche
* #date:
*/
public class FileUploader {
/**
* This method reads a File
*
* #param fileName
* #param stream
*/
public void uploadImage(String fileName, InputStream stream)
throws IOException {
try {
File file = new File(fileName);
OutputStream os = new FileOutputStream(file);
int data;
while ((data = stream.read()) != -1) {
os.write(data);
}
os.flush();
os.close();
System.out.println("Uploaded file successfully saved in "
+ file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
This is my Singleton class
package utils;
public class ServerCounter {
private static ServerCounter INSTANCE = new ServerCounter();
private ServerCounter() {};
public static ServerCounter getInstance() {
return INSTANCE;
}
}
I hope someone can help me because I'm not sure how to go at it.
In ServerCounter, add
private final AtomicLong counter = new AtomicLong();
public String nextval() { return String.valueOf(counter.incrementAndGet()); }
Each time you call INSTANCE.nextval() you'll get a fresh numeric string.
Clearly, each time you restart your application, the counter will restart.
ok, first you have to persist your counter if you want to get it after tomcat shutdown. we need listener for tomcat:
package utils;
public class ContextListener implements ServletContextListener{
void contextInitialized(ServletContextEvent sce){ // we could call loadFromFile here as well
}
//will be executed at tomcat shutdown
void contextDestroyed(ServletContextEvent sce){
ServerCounter .getInstance().writeToFile();
}
}
now the singleton(like in Marko's answer:)):
package utils;
public class ServerCounter {
private static ServerCounter INSTANCE = new ServerCounter();
private final AtomicLong counter;
private ServerCounter() {
//load value from file, do you need help by it?
long value = this.loadCounterFromFile();
counter = new AtomicLong(value);
};
private long loadCounterFromFile(){
BufferedReader br = null;
try {
//no problem if there is no file, we will return 0 in this case
br = new BufferedReader(new FileReader("C:\\Test\\counter.txt"));
String line = br.readLine();
if(line != null && line.length() > 0)
return Long.parseLong(line);
return 0;
//catch all exceptionse, because we could get NumberFormatException or FileNotFound from parseLong
} catch (Exception e) {
return 0;
} finally {
try {
if (br != null)br.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static ServerCounter getInstance() {
return INSTANCE;
}
public String nextval() { return String.valueOf(counter.incrementAndGet()); }
//will be executed by listener
public void writeToFile(){
//write the counter to file
writeToFile(counter.get());
}
private void writeToFile(long value){
try{
//you need folder c:\Test, file will be created automatically if there is no file, it will override the old file
BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\Test\\counter.txt"));
//need "" to create String
bw.write("" + value);
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
now you can use ServerCounter.getInstance.nextval() to increment the counter and get the value
last thing is, you need to put the listener to your webApplication:
<web-app>
...
<listener>
<listener-class>utils.ContextListener </listener-class>
</listener>
</web-app>
EDIT: ServerCounter was implementing ServletContextListener by mistake
EDIT2: added read/write file

java.lang.IllegalStateException: getInputStream() has already been called for this request

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;
}

Content-MD5 in Jersey's Client API

I am trying to set the Content-MD5 header on a request created with the Jersey Client API. I have created ClientFilter which implements another adapter (similar to how the GZipFilter works). Like this:
public class ContentMD5Filter extends ClientFilter {
private static final class ContentMD5Adapter extends AbstractClientRequestAdapter {
ContentMD5Adapter(final ClientRequestAdapter cra) {
super(cra);
}
#Override
public OutputStream adapt(final ClientRequest request, final OutputStream out) throws IOException {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
request.getHeaders().add("Content-MD5", instance);
return new DigestOutputStream(out, instance);
} catch (NoSuchAlgorithmException e) {
throw new WebApplicationException();
}
}
}
#Override
public ClientResponse handle(final ClientRequest cr) throws ClientHandlerException {
cr.setAdapter(new ContentMD5Adapter(cr.getAdapter()));
return getNext().handle(cr);
}
}
This doesn't work, as the digest are read to early (ie. before the whole content is written.
Any ideas on how to do this? As well as setting the header, I need to access it in another filter afterwards (to implement a security mechanism)
I found the solution. It's a quite complex way to do it, since I have to temporarily store the outputstream, but I guess you can figure it out if you need this yourself :)
The code for the filter:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import com.sun.jersey.api.client.AbstractClientRequestAdapter;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientRequestAdapter;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.filter.ClientFilter;
public class ContentMD5Filter extends ClientFilter {
#Override
public ClientResponse handle(final ClientRequest cr) throws ClientHandlerException {
cr.setAdapter(new ContentMD5Adapter(cr.getAdapter()));
return getNext().handle(cr);
}
private static final class ContentMD5Adapter extends AbstractClientRequestAdapter {
ContentMD5Adapter(final ClientRequestAdapter cra) {
super(cra);
}
#Override
public OutputStream adapt(final ClientRequest request, final OutputStream out) throws IOException {
return new ContentMD5OutputStream(request, getAdapter().adapt(request, out));
}
}
private static final class ContentMD5OutputStream extends OutputStream {
private final ByteArrayOutputStream byteArrayOutputStream;
private final OutputStream jerseyStream;
private final ClientRequest request;
public ContentMD5OutputStream(final ClientRequest request, final OutputStream jerseyStream) {
this.jerseyStream = jerseyStream;
this.request = request;
byteArrayOutputStream = new ByteArrayOutputStream();
}
#Override
public void write(final int b) throws IOException {
byteArrayOutputStream.write(b);
}
#Override
public void close() throws IOException {
byteArrayOutputStream.close();
byte[] byteArray = byteArrayOutputStream.toByteArray();
setContentMD5Header(byteArray);
IOUtils.write(byteArray, jerseyStream);
jerseyStream.close();
}
private void setContentMD5Header(final byte[] data) {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
String contentMD5 = new String(Base64.encodeBase64(instance.digest(data)));
request.getHeaders().add("Content-MD5", contentMD5);
} catch (NoSuchAlgorithmException e) {
// Errorhandling. Should not happen though!
}
}
}
}
To use this filter:
Client client = Client.create();
webResource = client.resource("http://localhost:8080/pathToYourResource");
contentMD5filter = new ContentMD5Filter();
webResource.addFilter(contentMD5filter);

Categories