4

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.

  • Possible duplicate of [Spring - Modifying headers for every request after processing (in postHandle)](https://stackoverflow.com/questions/30702970/spring-modifying-headers-for-every-request-after-processing-in-posthandle) – jhyot Sep 14 '17 at 21:03

1 Answers1

8

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("/**");
  }

}
avilbol
  • 145
  • 1
  • 2
  • 7
  • 1
    Thank you, it works! I would like to point that maybe the pathPattern should be "/**" in order to add the interceptor on every resource. – Jordi Sabater Picañol Sep 15 '17 at 07:08
  • You're right Jordi. I proceed to edit the answer. For future readers, this link can help to understand the difference between /* and /** in URL patterns: https://stackoverflow.com/questions/12569308/spring-difference-of-and-with-regards-to-paths – avilbol Sep 15 '17 at 14:05
  • Implementing a filter with the highest precedence will be called before the interceptor. – falmp Oct 21 '20 at 15:54