Reputation: 93
I have the following problem: I try to get body of a POST request before it is handled by a spring controller. For that I am using the HandlerInterceptorAdapter's preHandle() method.
As stated in this discussion Spring REST service: retrieving JSON from Request I also use the HttpServletRequestWrapper. With this wrapper I managed to print the body of the first POST request, but the second POST throws an IOException: StreamClosed.
Do you have any ideas on how I can get the body of all POST requests?
Here is the preHandle() method from the interceptor:
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getMethod());
MyRequestWrapper w = new MyRequestWrapper(request);
BufferedReader r = w.getReader();
System.out.println(r.readLine());
return super.preHandle(request, response, handler);
}
The HttpServletRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
private HttpServletRequest request;
public MyRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@Override
public ServletInputStream getInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
if (request.getMethod().equals("POST"))
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
*/
ServletInputStream inputStream = super.getInputStream();
if (inputStream == null) {
return;
}
IOUtils.copy(inputStream, cachedBytes);
}
/* 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();
}
}
}
The console output:
2014-10-15 12:13:00 INFO [http-nio-8080-exec-1] org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcherServlet': initialization completed in 9 ms
GET
null
GET
null
POST
{"long":null,"owner":{"__type":"Owner","id":20,"version":1,"md5Password":""},"string":"ws","tool":{"__type":"Tool","id":33,"version":1}}
POST
2014-10-15 12:13:00 ERROR [http-nio-8080-exec-3] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet dispatcherServlet threw exception
java.io.IOException: Stream closed
Upvotes: 2
Views: 10962
Reputation: 456
I've used filter that implements Filter & interceptor that extends HandlerInterceptorAdapter (because in the filter all fields are nullable and I can't save anything to DB. see Autowired Null Pointer Exception) to retreive request and response body and save them to DB. If your filter works fine then use only filter.
filter. Here I wrap a request and a response to read from them not only once. You can use ContentCachingRequestWrapper and ContentCachingResponseWrapper for that.
@Component
public class RequestLogFilter implements Filter {
private final Logger logger = LoggerFactory.getLogger(RequestLogFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("======================> FILTER <======================");
HttpServletRequest requestToCache = new ContentCachingRequestWrapper((HttpServletRequest) request);
HttpServletResponse responseToCache = new ContentCachingResponseWrapper((HttpServletResponse) response);
// before method
chain.doFilter(requestToCache, responseToCache);
// after method
// your logic(save to DB, logging...)
getRequestData(request);
getResponseData(response);
}
@Override
public void destroy() {
}
}
-
@Component
public class RequestLogInterceptor extends HandlerInterceptorAdapter {
private final Logger logger = LoggerFactory.getLogger(RequestLogInterceptor.class);
@Autowired
private InboundRequestLogStore inboundRequestLogStore;
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.info("====================> INTERCEPTOR <========================");
try {
if (request.getAttribute(InboundRequestAspect.INBOUND_LOG_MARKER) != null) {
InboundRequestLogRecord logRecord = new InboundRequestLogRecord();
logRecord.setIpAddress(request.getRemoteAddr());
// getting request and response body
logRecord.setRequestBody(getRequestData(request));
logRecord.setResponseBody(getResponseData(response));
logRecord.setResponseCode(((HttpServletResponse) response).getStatus());
String uri = request.getScheme() + "://" + request.getServerName()
+ ("http".equals(request.getScheme()) && request.getServerPort() == 80
|| "https".equals(request.getScheme()) && request.getServerPort() == 443 ? ""
: ":" + request.getServerPort())
+ request.getRequestURI()
+ (request.getQueryString() != null ? "?" + request.getQueryString() : "");
logRecord.setUrl(uri);
inboundRequestLogStore.add(logRecord); // save to DB
} else {
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
}
} catch (Exception e) {
logger.error("error ", e);
try {
((ContentCachingResponseWrapper) response).copyBodyToResponse(); // in other case you send null to the response
} catch (Exception e2) {
// TODO Auto-generated catch block
logger.error("error ", e2);
}
}
}
public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
String payload = null;
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
}
return payload;
}
public static String getResponseData(final HttpServletResponse response) throws UnsupportedEncodingException, IOException {
String payload = null;
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
wrapper.copyBodyToResponse(); // in other case you send null to the response
}
return payload;
}
}
add to servlet-context.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<beans:bean class="path.to.RequestLogInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
namespaces:
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
ContentCachingRequestWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingRequestWrapper.html
ContentCachingResponseWrapper - http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/util/ContentCachingResponseWrapper.html
Upvotes: 0
Reputation: 11
You're attempting to read from the original request in your Wrapper, but after this, the original request is still being read - hence the request input stream has been consumed and cannot be read from again.
Instead of using an Interceptor, consider using a javax.servlet.Filter. In the doFilter method, you can pass the wrapped request on down the chain.
Upvotes: 1