Spring MVC InterceptorHandler called twice with DeferredResult - java

When I am using custom HandlerInterceptor and my controller returns DeferredResult, the preHandle method of my custom interceptor called twice on each request. Consider a toy example.
My custom interceptor:
public class MyInterceptor implements HandlerInterceptor {
static int i = 0;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(i++);
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
My Spring Java configuration:
#Configuration
#EnableWebMvc
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
My Controller:
#Controller
public class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public DeferredResult<String> test() {
DeferredResult<String> df = new DeferredResult<String>();
df.setResult("blank");
return df;
}
}
So, on each page load I see two outputs from preHandle method. However, if I modify MyController in order to return just "blank" template (instead of DeferredResult with "blank" template), I see just one output from preHandle on each page load.
So, my question is why preHandle called twice when I use DeferredResult and is it possible to avoid this?

You need to use org.springframework.web.servlet.AsyncHandlerInterceptor:
public interface AsyncHandlerInterceptor extends HandlerInterceptor {
void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
}
Spring MVC execute sequence:
preHandle
afterConcurrentHandlingStarted
preHandle
postHandle
afterCompletion

The difference between the two invocations can be seen by examining the value of request.getDispatcherType().

I followed what #thunder mentioned by checking the value of request.getDispatcherType() and this worked for me.
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// NOTE: For all dispatchers that are not "REQUEST" like "ERROR", do
// an early return which prevents the preHandle function from
// running multiple times
if (request.getDispatcherType() != DispatcherType.REQUEST) {
return true;
}
// ... do other stuff and then do a final return
return true;
}
}

As I'm exploring adding filter and Interceptor, I'm prettry sure that's caused by async calling. You use a DeferredResult here, which spring will do the filter in origin thread and filtering it again in a new thread. If you set log level to Debug, you will notice log like this.
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#efe6068
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
15:14:06.948 [http-nio-8199-exec-5] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#163cab73
15:14:07.148 [http-nio-8199-exec-6] DEBUG o.s.b.w.f.OrderedRequestContextFilter - Bound request context to thread: SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.core.ApplicationHttpRequest#42f1262]]
In a word, it's executed once in one thread, but here're two threads.
As I digged the google, I found there's no good solution.
If you have something like auth in request, a walk around way is add
security.filter-dispatcher-types=REQUEST, ERROR
Then the new thread(async one) will not get the security context. You need to check it and stop the filter chain inside it.
Or you just use the tranditional sync call like:
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return "blank";
}
I found another helpful answer. https://jira.spring.io/browse/SPR-12608
Hope it helps!

Inside preHandle method, If you have response.sendError(<>, <>), then this preHandle method will execute twice for each API request. So remove sendError() method to execute this only once.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.sendError(401, "Token is invalid");
return false;
}

The prehandle method of the AsyncHandlerInterceptor will always be executed twice in async processing.

Related

Spring Boot Request Header Validation

I am using Spring Boot 2.3.8 for creating rest services. I have a requirement to validate the request headers such that it should have certain headers otherwise throw error. This should be common for all the methods or services. I tried below,
public ResponseEntity<Object> addEmployee(
#RequestHeader(name = "header1", required = true) String header1,
#RequestHeader(name = "header2", required = true) String header2,
#RequestBody Employee employee)
throws Exception
{
But I need to add this for all the methods in all the controllers. If this is the case how can I throw an error like "Header1 missing in request headers" / "header2 missing in request headers" for all the services globally ?
For global use you can register an interceptor.
#Component
public class MyHandlerInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3) throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//here check headers contained in request object
if (request.getHeader("header1") == null || request.getHeader("header2") == null) {
response.getWriter().write("something");
response.setStatus(someErrorCode);
return false;
}
return true;
}
And then register it
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
private MyHandlerInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
This is what filters are for. You want to filter out requests based on a header being there or not and return an error if its missing.
Extend OncePerRequestFilter optionally override shouldNotFilter if you do not want it to be used for all request OR you could implement a normal Filter and use FilterRegistrationBean to register it only for a specific path.
then in the filter you could throw an exception and let Spring figure out how to display that, or actually set the response to something meaningful.
If you have Zuul in place then you can read the request header attributes there in pre route and on validation failure reply back to the request with error populated.
This is a cross-cutting concern and should be done using AOP. Since you're using Spring, you can do the following:
Create an annotation called ValidateHeaders:
public #interface ValidateHeaders {}
Create a #Before advice that intercepts methods annotated with #ValidateHeaders:
#Before("#annotation(com.somepackage.ValidateHeaders)")
public void controllerProxy(JoinPoint jp) {
Object[] args = jp.getArgs();
//validation logic, throw exception if validation fails
}
Note that you'll have to extract the fields using reflection as:
Annotation[][] pa = ms.getMethod().getParameterAnnotations();
You can iterate through all the annotations and search for request headers as:
if(annotations[i][j].annotationType().getName().contains("RequestHeader")) {
RequestHeader requestHeader = (RequestHeader) m.getParameterAnnotations()[i][j];
//now access fields in requestHeader to do the validation
}
Here's an article that can get you started with Advice types:
https://www.baeldung.com/spring-aop-advice-tutorial

Spring Controllers: Adding a response header parameter called Elapsed-Time

I would like to add an Elapsed-Time response header parameter on every API REST request, even those that finish with error.
For instance:
===>
GET /api/customer/123 HTTP/1.1
Accept: application/vnd.company.app.customer-v1+json
<===
HTTP/1.1 200 OK
Content-Type: application/vnd.company.app.customer-v1+json
Elapsed-Time: 12
{ id: 123, name: Jordi, age: 28 }
Being Elapsed-Time parameter calculated as the difference in milliseconds between the instant that the #RequestMapping method finishes, and the instant that the #RequestMapping method starts.
I have been taking a look on Spring4 HandlerInterceptorAdapter. preHandle and postHandle methods seem to fit perfectly for this case (despite the time overhead of executing every interceptor in the chain). But, unfortunatelly, changing response header in postHandle method has no effect because the response is already build.
Spring documentation states:
Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with #ResponseBody and ResponseEntity methods. In such cases an HttpMessageConverter writes to and commits the response before postHandle is called which makes it impossible to change the response, for example to add a header. Instead an application can implement ResponseBodyAdvice and either declare it as an #ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter.
Do you know of any working elegant solution to deal with this case?
I don't think think this case is duplicating Spring - Modifying headers for every request after processing (in postHandle) because I need to capture a variable whose value is the start time (when petition gets to the application and before the #RequestMapping method starts), and then use this variable once the #RequestMapping method finishes, to compute the elapsed time.
You need to stay with Handle Interceptor, but do not implement the postHandle method, only preHandle in order to save the startTime as a parameter in the request
public class ExecuterTimeInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
}
When the controller finishes and returns a response, a Controller Advice (class that implements ResponseBodyAdvice), will get the http servlet request part of Server Request, recover the startTime and obtain the time elapsed as follows:
#ControllerAdvice
public class GeneralControllerAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
ServletServerHttpRequest servletServerRequest = (ServletServerHttpRequest) request;
long startTime = (long) servletServerRequest.getServletRequest().getAttribute("startTime");
long timeElapsed = System.currentTimeMillis() - startTime;
response.getHeaders().add("Elapsed-Time", String.valueOf(timeElapsed));
return body;
}
}
Finally you add the interceptor to the main application (/** path as you wanted for every Resource)
#SpringBootApplication
#ComponentScan(basePackages = "com.your.package")
#Configuration
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ExecuterTimeInterceptor()).addPathPatterns("/**");
}
}

Spring Boot - determining a sub-domain (with wildcard)?

New to Spring Boot here. Spring MVC provides the #SubdomainMapping annotation, which does not seem to be available from what I can see in Spring Boot. I've seen a few people discuss using a filter to handle this. Or other approaches that seem overly convoluted.
Would there not be a (simple/cleaner) way to handle all sub-domains within a standard controller such as:
#SubdomainMapping(value = {"**"}
public String data(ModelMap modelMap, HttpServletRequest request) {
//Code to handles subdomain logic here ....
}
This would be a simple approach where all values are treated equally with minor differences.
Any suggestions would be helpful!
I have worked on this myself and I have an answer that isn't as simple as you wanted, but I don't think there is one that simple.
So, you can create a handler interceptor adapter that will grab every request before it gets to your controller and grabs and processes the subdomain. This would require something like this:
#Component
public class SubDomainInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView model)
throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
String mysub = request.getRequestURL().toString();
//...
// Do whatever you need to do with the subdomain
//
if (isGoodSubdomain){
session.sendAttribute("subdomain", mysub);
} else {
response.sendRedirect("http://www.basesite.com"):
}
return true;
}
You then use that session variable in your controllers to filter values or whatever you need to use them for. I know this isn't the simple answer you wanted, but it was the best one that I have found so far.

Spring Boot runs filter twice for requests returning a CompletionStage

I was having an issue where my filter was running twice when methods were returning a CompletionStage. From the documentation on RequestMapping (here), it is a supported return value.
A CompletionStage (implemented by CompletableFuture for example) which the application uses to produce a return value in a separate thread of its own choosing, as an alternative to returning a Callable.
Since the project was pretty complex with a lot of concurrent code, I created a new simple spring-boot project. This is the (only) controller in it:
#Controller
public class BaseController {
#RequestMapping("/hello")
#ResponseBody
public CompletionStage<String> world() {
return CompletableFuture.supplyAsync(() -> "Hello World");
}
}
And a filter:
#WebFilter
#Component
public class GenericLoggingFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
System.out.println(httpServletRequest.getMethod() + " " +
httpServletRequest.getRequestURI());
chain.doFilter(request, response);
}
}
When I make a call curl http://localhost:8080/hello, it prints GET /hello twice on the console. When I change the controller method to return a String:
#RequestMapping("/hello")
#ResponseBody
public String world() {
return "Hello World";
}
It prints it only once. This behavior is exhibited even if I change it to a Callable, which has no real concurrency significance (of course, spring itself might be treating this now as an Async request).
So, if spring is running the entire web-stack again to have a request context available, even that doesn't really make sense because the following:
#RequestMapping("/hello")
#ResponseBody
public CompletionStage<String> world() {
return CompletableFuture.supplyAsync(() -> {
System.out.println(RequestContextHolder.currentRequestAttributes());
return "Hello World";
});
}
throws an exception: IllegalStateException: No thread-bound request found...
What is surprising is, that the following works:
#RequestMapping("/hello")
#ResponseBody
public Callable<String> world() {
return () -> {
System.out.println(RequestContextHolder.currentRequestAttributes());
return "Hello World";
};
}
So, I am unsure about quite a few things.
It appears that Callable and CompletionStage is being treated differently in context of which thread it is executed in.
If that is the case, then why is my filter running twice in each case? If the job of a filter is to setup a certain request-specific context, it doesn't make sense to run it again for a CompletionStage if it anyways is not accessible.
Why exactly is the filter running twice either which ways?
Please replace GenericFilterBean with OncePerRequestFilter.

Why does Spring's HandlerInterceptorAdapter is called twice only when my controller has #RequestMapping (produces = ...)?

After a long time debugging my app, I realized something which makes no sense to me. Whenever I call a controller annotated as exemplified below, my Interceptor is executed twice.
#RequestMapping(method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> getMedia(String url) throws IOException {
...
}
For testing purposes I removed the "produces" part and the same interceptor is only called once.
Can someone please enlighten me why is this happening? The fact it is being called twice is generating errors, because on the second call all request headers are empty and thus my validations fail.
My interceptor is simply an implementation as follow:
public class AuthenticatorInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//validations
return super.preHandle(request, response, handler);
}
Check if your class also has a #RequestMapping annotation producing a different mimetype. Maybe this is the source of your problem, since both "produces" declaration might be the reason your interceptor is being called twice.

Categories

Resources