ContentCachingRequestWrapper getContentAsByteArray() method return only 8000 bytes - spring

I`m using ContentCachingRequestWrapper to cache my request in Spring Boot filter. Unfortunatelly, when I use method getContentAsByteArray() to get content of my request - I get only array with size 8000 bytes.
I haven`t got any post limit in Tomcat. What is more, when I check size of request earlier - it is correct.
Do you know why ContentCachingRequestWrapper.getContentAsByteArray() return only 8000 bytes?
Code:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
chain.doFilter(requestWrapper, response);
//here --> requestWrapper.getContentAsByteArray().length = 8000 (for larger request)
savemyRequest(new String(requestWrapper.getContentAsByteArray()), response.getStatus());
}

I encountered a similar issue and decided to keep what I got here.
People in the comments already mentioned a bit.
ContentCachingRequestWrapper itself does not read the request body. It wraps the request body with (as class name implies) PushBackInputStream. As soon as the request body is read (somewhere after filters), then a copy of the data is cached in ContentCachingRequestWrapper.
In my case issue was the next: Request body was started to be converted by
MappingJackson2HttpMessageConverter. Reading of request body and converting to the Java Object happened in a gradual way: first part (8000bytes) read - first part converted, ... . Then conversion exception happened and then I got only 8000bytes in a controller exception handler (#ExceptionHandler) - part of the request body that was read by a converter.

Related

POST request not getting forwarded from Filter to Controller class

From postman I am hitting a post request like http://localhost:8084/abc/api/v1/xyz having payload and header. we have configured a Filter class extending GenericFilterBean before it hits the Controller. Its executing all the codes of Filter class fine but while executing 'chain.doFilter(request, response);' in the end to forward request to controller method its throwing below exception.In Filter class we are reading the payload and saving in audit table. In Controller class method we have parameters like #RequestBody annotaion, #Context HttpServletRequest, BindingResult.
18:59:25,779 INFO [stdout] (default task-1) 18:59:25,778||WARN |AbstractHandlerExceptionResolver:197|Resolved [org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: UT010029: Stream is closed]
Kindly suggest on this.
Is your filter reading the contents of the request? If so then you'll need to look at alternatives to this, as the input stream is not likely to be reusable without some assistance.
Can you post some code from the filter method?
Spring provides at least one mechanism to work around this, which might be helpful here, specifically the ContentCachingRequestWrapper. You can either create a new Filter which wraps the actual request in this wrapper, or you can simply make use of it yourself in your filter.
You might update your filter with something like this
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(currentRequest);
// todo: your magic code, using wrappedRequest to access the body of the request
// note: passing through the wrapped request,
// which allows later handlers to also access the request
chain.doFilter(wrappedRequest, servletResponse);
}
Note the documentation for ContentCachingRequestWrapper notes
HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.
The error that you're receiving indicates you're reading the InputStream of the request, and you should rather simply access the getContentAsByteArray method of the wrapper.

Spring - Changing Order of Global Error handler and Filter

I have a filter like:
#Component
#Order(8)
public class LogReqFilter extends OncePerRequestFilter
{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
{...}
}
it basically logs all requests and responses. When there is a 400 error however the response is blank. There is a global exception handler that replaces the body with the custom error:
#RestControllerAdvice
public class GlobalExceptHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleArgNotValid(MethodArgumentNotValidException ex
, HttpHeaders headers, HttpStatus status, WebRequest request) {...}
}
I noticed that the Global Exception handler is called after the filter and I think that this is why the response is blank in the filter. Is there a way to have the filter called after the Global Exception Handler?
Edited:
TLDR
No You can't change the order as per the OP Title because your GlobalError Handler is always in the layer that holds the ControllerAdvices and that is always between the RequestFilter layer and the Controller.
Long Version
You are somewhat right.
i refer you to this answer: https://stackoverflow.com/a/17716298/405749
expanding on this:
be aware that before and after depends from which you are looking from, ie.
inbound (before it reaches your controller code) OR
outbound (after the controller is done)
now you can plug in different components on this path, e.g. here RequestFilter and Controller Advice
depending on the method you implement, it gets called only on the inbound or outbound track or both, here:
doFilterInternal() is called inbound. Only if you call filterChain.doFilter(request, response); the request continues on the inbound track otherwise it stops here and the response is returned with whatever is already in its output stream or will be added by this filter or filters still be encountered on the way out
this is the call sequence assuming we only have this filter and advice
'inbound'-methods from request filters
'inbound'-methods from controller advice(s)
'outbound'-methods from controller advice(s)
'outbound'-methods from request filters
Now, the handleArgNotValid() only gets called in case such an exception is thrown and potentially adds content to the Response-Output stream. Your example doesnt show if you return an object here or not, but i guess from your OP that you dont. and as a consequence the output stream is empty.
Plz also note, that there is no easy way to dump/look into the output stream unless you wrap it, but that's another topic.

request.getServletPath() returned null from Spring MVC

I made a filter to capture HttpServletRequest sevlet path from all requests
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
// debug to see the output
String path = request.getServletPath();
filterChain.doFilter(request, response);
}
There is a URL in jsp that has no controller or view mapped to it
<div>
<spring:url value="/app" var="app_url" htmlEscape="true"/>
<spring:message code="label_3rd_app" />
</div>
However, when click on the url while debugging on the filter, I see request.getServletPath() value from two request:
/null
/null/app
My question is why request.getServletPath() never returns /app instead?
You're getting null because
request.getServletPath();
is for Servlets, and you're doing it inside a Filter. To get it in a Filter you have to manually build it like this:
HttpServletRequest request = (HttpServletRequest) req;
String path = request.getRequestURI().substring(request.getContextPath().length());
more info why:
How to get request URI without context path?
Mr. Lalibertes solution is not working correctly as the result of getRequestURI is uri-encoded and getServletPath is not.
Better use Springs UrlPathHelper for getting the servlet path in such situations.
new org.springframework.web.util.UrlPathHelper().getPathWithinApplication(servletRequest);
This method delivers the uri-decoded servlet path as getServletPath would have - if it would'nt be null.
For people coming to this question just because your context path was null (like me): I was calling request.getContextPath() in a background thread.
The request then became 'out of scope', if I understand this bug report discussion correctly: https://bugs.eclipse.org/bugs/show_bug.cgi?id=401768
Also, I know I'm not supposed to use normal threads in servlet environments, but use Executor instead. But the problem would probably remain: https://developer.jboss.org/thread/232135.
Solution was to extract all needed info from the request before handing off to background thread.

Spring reading request body twice

In spring I have a controller with an endpoint like so:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public OutputStuff createStuff(#RequestBody Stuff stuff) {
//my logic here
}
This way if doing a POST on this endpoint, the JSON in request body will be automatically deserialized to my model (Stuff). The problem is, I just got a requirement to log the raw JSON as it is coming in! I tried different approaches.
Inject HttpServletRequest into createStuff, read the body there and log:
Code:
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public OutputStuff createStuff(#RequestBody Stuff stuff, HttpServletRequest req) {
StringBuilder sb = new StringBuilder();
req.getReader().getLines().forEach(line -> {
sb.append(line);
});
//log sb.toString();
//my logic here
}
The problem with this is that by the time I execute this, the reader's InputStream would have already been executed to deserialize JSON into Stuff. So I will get an error because I can't read the same input stream twice.
Use custom HandlerInterceptorAdapter that would log raw JSON before the actual handler is called.
Code (part of it):
public class RawRequestLoggerInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
StringBuilder sb = new StringBuilder();
req.getReader().getLines().forEach(line -> {
sb.append(line);
});
//log sb.toString();
return true;
}
}
The problem with this tho is, that by the time the deserialization to stuff happens, the InputStream from the request would have been read already! So I would get an exception again.
Another option I considered, but not implemented yet, would be somehow forcing Spring to use my custom implementation of HttpServletRequest that would cache the input stream and allow multiple read of it. I have no idea if this is doable tho and I can't find any documentation or examples of that!
Yet another option would be not to read Stuff on my endpoint, but rather read the request body as String, log it and then deserialize it to Stuff using ObjectMapper or something like that. I do not like this idea either tho.
Are there better solutions, that I did not mention and/or am not aware of? I would appreciate help. I am using the latest release of SpringBoot.
To read the request body multiple times, we must cache the initial payload. Because once the original InputStream is consumed we can't read it again.
Firstly, Spring MVC provides the ContentCachingRequestWrapper class which stores the original content. So we can retrieve the body multiple times calling the getContentAsByteArray() method.
So in your case, you can make use of this class in a Filter:
#Component
public class CachingRequestBodyFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(currentRequest);
// Other details
chain.doFilter(wrappedRequest, servletResponse);
}
}
Alternatively, you can register CommonsRequestLoggingFilter in your application. This filter uses ContentCachingRequestWrapper behind the scenes and is designed for logging the requests.
As referenced in this post: How to Log HttpRequest and HttpResponse in a file?, spring provides the AbstractRequestLoggingFilter you can use to log the request.
AbstractRequestLoggingFilter API Docs, found here
I also tried to do that in Spring but i could not find way to pass my custom http request to chain so what did was,i have written traditional j2ee filter in that i have passed my custom http request to chain that is it then onward i can read http request more than once
Check this example http://www.myjavarecipes.com/how-to-read-post-request-data-twice-in-spring/

EHCache and DropWizard: Returning no response

I have successfully integrated SimplePageCachingFilter with DropWizard.
However, there is one thing standing in my way. Whenever an exception in my application is thrown, instead of being routed to my ExceptionMapper I get "Response contains no data" in my API Browser. I also happen to see this pass by in the log.
WARN [2015-02-12 04:06:21,768] net.sf.ehcache.constructs.web.GenericResponseWrapper: Discarding message because this method is deprecated.
Traditionally my ExceptionMapper returns the appropriate Json Responses.
Has anyone else seen anything similar?
I would put a breakpoint in http://grepcode.com/file/repo1.maven.org/maven2/com.sun.jersey/jersey-server/1.18.1/com/sun/jersey/server/impl/application/WebApplicationImpl.java#WebApplicationImpl on line 1472 then trigger your error.
That's throwing an exception, and the mapping of the exception to the exception mapper happens in the catch a little deeper in the stack. Probably in ContainerResponse.mapException
Hopefully this will give you enough insight into why it's not being called.
FYI. I got to that code by putting a breakpoint in a exception mapper that was being fired, and looking at the call stack.
After attaching a debugger to DropWizard (per #CAB's advice) I discovered that the CachingFilter does not write out a response unless the status code is 200.
I extended CachingFilter and overrode the doFilter method and ignored the status check. This appears to have solved my problem.
#Override
protected void doFilter(final HttpServletRequest request,
final HttpServletResponse response, final FilterChain chain) throws Exception {
if (response.isCommitted()) {
throw new AlreadyCommittedException("Response already committed before doing buildPage.");
}
logRequestHeaders(request);
PageInfo pageInfo = buildPageInfo(request, response, chain);
writeResponse(request, response, pageInfo);
}

Resources