Spring - Changing Order of Global Error handler and Filter - spring

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.

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.

Accessing both Request and Response in a Jersey Filter

In Jersey, one can add a ContainerRequestFilter or a ContainerResponseFilter
public class RequestFilter implements ContainerRequestFilter {
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
// logic
}
}
public class ResponseFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// logic
}
}
Both are added using PackagesResourceConfig:
PackagesResourceConfig prc = new PackagesResourceConfig("com.company.xxx");
prc.getContainerRequestFilters().add(new RequestFilter());
prc.getContainerResponseFilters().add(new ResponseFilter());
Although the response filter also has access to the request, it is called AFTER the endpoint is called. What we require is the ability to access the request and the response in the same filter BEFORE the endpoint is called. The request filter is called before the endpoint, but does not have access to the response.
There seems to be no way to add a standard javax.servlet.Filter which has access to both the HttpServletRequest and the HttpServletResponse, and is called before the endpoint is invoked.
Thanks
You cannot filter anything without calling the endpoint, because that's how the request comes to your service. If you meant without hitting the controller, that's a different story. But even then, you want something like pre-matching response filter, which naturally does not exist. It defeats the purpose of your service. Why not just add whatever header you want on all responses after the controller and any other pieces in the handling chain finish their work?! You can access both the request context and the response context in the response filter, so as Paul mentioned in the comments you could store some information you will need in the requestContext while in the ContainerRequestFilter.
JAX-RS spec has some good examples of both filters and interceptors.

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/

Building a façade with spring which calls another server and returns its response

For an application I need to create a security façade in Spring 4.x.
This thiny layer must accepts any request from our mobile application and execute a security check for the provided token (with openId and Oauth).
Upon a successful validation, the request needs to be forwarded to the backend application, which does not need to be aware of the security token mechanism.
Thus, the flow will be something like this:
security_facade_url/path/of/the/request
With a header that indicates the backend to invoke upon successful validation of the token
Upon successful validation the security façade sends a request to the backend URL
backend_application_url/path/of/the/request
The façade must not have a controller which maps to any possible path of the request, but must call the request on the correct backend server, based on a value in the header of the request. Then return this response to the user.
What I have so far is an implementation of the HandlerInterceptor. This interceptor works, however, I am not really happy with the way I need to avoid the afterCompletion by throwing an exception in the postHandle method.
If I do not throw an error, the default error page is appended to the correct response in the afterCompletion step.
This is my code so far:
public class RequestProcessingInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(RequestProcessingInterceptor.class);
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("Doing some security stuff now ...");
log.warn("... security ok ... since I am not really checking stuff");
return true;
}
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("Forwarding request and sending that info back ...");
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(UriBuilder.fromUri("http://localhost:8080").build());
response.setContentType("application/json");
response.getWriter().write(service.path(modelAndView.getModel().get("path").toString()).accept("application/json").get(String.class));
response.setStatus(200);
throw new Exception("Need to avoid the execution of the afterCompletion. Only way to do so is by throwing an exception...");
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
Is there a more proper way to intervene with the Spring livecycle or obtain the behaviour as described above?
Found a better solution. For what I need, I do not need to manipulate the results in an interceptor.
A much cleaner way is to define a Controller which maps with the request methods.
#RequestMapping(method = {RequestMethod.GET, RequestMethod.PUT, RequestMethod.POST})
public void handleRequest(HttpServletRequest request, HttpServletResponse response) { // code omitted }
You should not try to avoid the call to afterCompletion. Just implement an empty method and let SpringFramework call it.
Provided your controller returns null indicating that no view has to be called, it should work with a smoother Spring integration.
But I cannot understand why you use Spring MVC here. As you only interact with low level HttpServletRequest and HttpServletResponse, you could as well use :
a dedicated servlet in charge to relay the request and response to the backend and write the returned value in the response
a filter that would do the security stuff before passing request to filter chain

Alter request header before controller method using #RequestHeader is called

I have a few controllers that use the #RequestHeader annotation to get the logged in user. I cannot seem to find the correct way to alter the header before the method is called.
I already tried to wrap the request and using an interceptor (in the prehandle method, if I'm not mistaken) and pass along the request,but it seems like the headers are not being queried. An exception is thrown by the servlet dispatcher that the username is missing in the header.
Therefore I'm wondering whether someone knows how and when spring handles this annotation, so I can write the proper interceptor.
Don't have the code at hand, but if needed, I will post fragments later on. But the question is simple: how to inject a param into the request header when #RequestHeader is used on a controller method?
Kind regards,
Tom
First edit:
#Sotirios
I tried using the Filter, which works. But this is less convenient for me than the handlerinterceptor. Since I only need the filter for debugging. So again the better question: why isn't this not working with an interceptor?
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(httpRequest) {
#Override
public Enumeration getHeaders(String name) {
Enumeration headers = super.getHeaders(name);
if( isUseFilter() && Constants.REMOTE_USER.equalsIgnoreCase(name) ){
String user = super.getHeader(name);
headers = enumeration(asList(isEmpty(user)? getDebuggingUserId() :user));
}
return headers;
}
};
chain.doFilter(wrapper, response);
}

Resources