In my project i have multiple API's(implemented using Spring REST API).
Now i have this requirement that i have to manipulate the response in specific way before it is sent to client and changing it in every API method does not seems to be a good option.
Only solution i can think of is using servlet.Filter (by extending filter class)
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
> chain.doFilter(req, res);
and write my logic after
chain.doFilter(req, res);
but i am struggling to convert ServletResponse or HTTPServletResponse into HttpEntity.
Please help me how can i achieve this ? and is there any better approach available ?
Thanks
UPDATE
#jny solution has helped me.
little code snippet to show how it works.
#ControllerAdvice(basePackages = { "com.test.controller" }) // package where it will look for the controllers.
public class ResponseFilter implements ResponseBodyAdvice<Object> {
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
//here you can manipulate the body the way you want.
return body;
}
important is that your controller should be annotated with #ResponseBody
It depends on what exactly you need.
If you need to make changes of the body of the response,
if you use Spring 4.1 or higher, you can use ResponseBodyAdvice to manipulate the body of the response.
If you need to filter certain fields, there are other options available.
From documentation:
Implementations may be may be registered directly with
RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver or
more likely annotated with #ControllerAdvice in which case they will
be auto-detected by both.
This may point you in the right direction: Spring MVC: How to modify json response sent from controller
It involves wrapping the ServletResponse before invoking doFilter and using a custom ServletOutputStream that allows the response data to be manipulated after it would normally have been closed. HttpEntity is not involved.
Related
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/
I want to implement a before method for each controller in Spring Boot. So that the before method is invoked for any REST request.
You can use a Filter to intercept all incoming requests before handling them in the controller:
A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
You can do something like that in your code:
#Component
public MyLogFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
System.out.println("Requested received and handled before controller");
chain.doFilter(req, res);
System.out.println("Code executed after controller");
}
...
}
You can also add logic to bypass the call to the controller if needed. For example if you have an authentication filter and you see that the request is not authenticated you can directly reply with a 401 code without calling the controller.
For example, suppose I have this filter
#WebFilter("/api/*")
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
}
and an interceptor
#Interceptor
public class HeaderLoggerInterceptor {
#AroundInvoke
public Object validateHeaders(InvocationContext ctx) throws Exception {
// pre-action
Object result = ctx.proceed();
// post-action
return result;
}
}
which object will be invoked first when a service endpoint is hit?
if I want to call setAttribute to request so some additional values can be written to local_access log. It doesn't work in Interceptor, only seems to work with Filter.
is it a bad idea to have both in the service?
which one is better to validate header?
which one is better to write response log before sending back to the client?
I really can't find document on this topic. thanks
I will answer my own question after my experiment
However, I discover another strange problem.
I am using an ExceptionMapper, which allows me to centralize the exception handling in one place;
When there is a missing header, I throw out a custom MissingHeaderException;
The exception will be caught by the Mapper, and we format it and provide a nice XML response back to the client
This logic works only with Interceptor not Filter. Any exception thrown from a Filter is not handled by the Mapper.
We're currently working on adding headers to each response from our application. To add these headers we're using the Servlet APIs Filter-interface.
We've got the following filter in our application:
public class SecurityFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
chain.doFilter(request, response);
HttpServletResponse httpServletResponse = ((HttpServletResponse) response);
httpServletResponse.addHeader("X-Frame-Options", "DENY");
httpServletResponse.addHeader("X-Content-Type-Options", "nosniff");
}
#Override
public void destroy()
{
}
}
This (specifically the doFilter-method) is implemented correctly according to the documentation, which suggests the following order of work:
Examine the request
Optionally wrap the request object with a custom implementation to filter content or headers for input filtering
Optionally wrap the response object with a custom implementation to filter content or headers for output filtering
Either invoke the next entity in the chain using the FilterChain object (chain.doFilter()),
or not pass on the request/response pair to the next entity in the filter chain to block the request processing
Directly set headers on the response after invocation of the next entity in the filter chain.
As far as we can see, the order of our doFilter-method is correct according to the documentation (pass the request to the chain first as stated under point 4, add custom headers afterwards as stated under point 5). However, the headers we add aren't visible in the responses. If we change to order to the following, everything seems to work just fine:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
HttpServletResponse httpServletResponse = ((HttpServletResponse) response);
httpServletResponse.addHeader("X-Frame-Options", "DENY");
httpServletResponse.addHeader("X-Content-Type-Options", "nosniff");
chain.doFilter(request, response);
}
Can anyone explain this behavior?
Oracle tells you to wrap the response before passing it down the chain, if you want to add something after chain.doFilter(..)
Note that if you want to preprocess the request object or postprocess
the response object, you cannot directly manipulate the original
request or response object. You must use wrappers. When postprocessing
a response, for example, the target servlet has already completed and
the response could already be committed by the time a filter would
have a chance to do anything with the response. You must pass a
response wrapper instead of the original response in the chain
doFilter() call. See "Using a Filter to Wrap and Alter the Request or
Response".
http://docs.oracle.com/cd/B32110_01/web.1013/b28959/filters.htm#BCFCIHAH
I do not know what version of the Servlet spec are you referring to, but in 3.1, chapter 6.2.1 "Filter Lifecycle" I read (emphasis mine):
After invocation of the next filter in the chain, the filter may examine response headers.
"Examine" not "set"! Actually, the spec for the header methods says (Servlet 3.1, chapter 5.2 "Headers"):
To be successfully transmitted back to the client, headers must be set before the response is committed. Headers set after the response is committed will be ignored by the servlet container.
I guess this is happening to your request, some servlet or filter down the chain is "committing", so headers are ignored.
Bottom line: The spec (at least as far as I can see in 3.1) does not suggest to set the headers after calling chain.doFilter(). Your second version that works is correct (and how I've always implemented filters that add headers)!
HttpServletRequest has a method setAttribute(String, Object).
How can I extract this attribute from ContainterRequest?
I didn't find: getAttribute method!
Code
public class AuthenticationFilter implements Filter {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) servletRequest;
// .... ....
httpReq.setAttribute("businessId", businessId);
}
}
In Jersey Filter:
private class Filter implements ResourceFilter, ContainerRequestFilter {
public ContainerRequest filter(ContainerRequest request) {
// ..extract the attribute from the httpReq
}
}
You can't. They're not exposed through the Jersey API in any way. If you search the Jersey codebase, you'll find that there are no uses of HttpServletRequest.getAttributeNames(), which you'd expect to be used if they were being copied en masse. You'll also find that there are only a handful of uses of HttpServletRequest.getAttribute(), and it's strictly for internal bookkeeping.
Note, however, that when deployed in a Servlet Context, JAX-RS allows you to inject the original HttpServletRequest using the #Context annotation. I'm not certain whether you can do this in a Jersey filter, but it works in MessageBodyReaders/Writers and in resource classes.
Update: I've checked, and you can, in fact, inject the HttpServletRequest into a Jersey ContainerRequestFilter by simply including:
#Context private HttpServletRequest httpRequest;
If you're using Jersey 2, which implements JAX-RS 2.0, you can implement a ContainerRequestFilter which defines a filter method as follows:
public void filter(ContainerRequestContext requestContext) throws IOException;
ContainerRequestContext has getProperty(String) and setProperty(String, Object) methods, which in a Servlet environment (ServletPropertiesDelegate), map to the servlet request's getAttribute(String) and setAttribute(String, Object) methods.
See: Jersey on GitHub
I got the #Context working, but have the problem is that my ContainerRequestFilter is singleton.
I had to implement a custom javax.servlet.Filter and use a ThreadLocal to store the HttpServletRequest.
I wanted to add to previous answers my solution, in addition to adding context:
#Context
private HttpServletRequest httpRequest;
You should set and get attributes from the session.
Set:
httpRequest.getSession().setAttribute("businessId", "yourId");
Get:
Object attribute = httpRequest.getSession().getAttribute("businessId");