简体   繁体   English

Spring Controllers:添加一个响应头参数,称为Elapsed-Time

[英]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. 我想在每个API REST请求上添加一个经过时间响应标头参数,即使那些以错误结束的参数也是如此。

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. 作为经过时间参数,计算为@RequestMapping方法完成的瞬间与@RequestMapping方法启动的瞬间之​​间的毫秒差。

I have been taking a look on Spring4 HandlerInterceptorAdapter. 我一直在研究Spring4 HandlerInterceptorAdapter。 preHandle and postHandle methods seem to fit perfectly for this case (despite the time overhead of executing every interceptor in the chain). preHandle和postHandle方法似乎非常适合这种情况(尽管执行链中每个拦截器的时间开销)。 But, unfortunatelly, changing response header in postHandle method has no effect because the response is already build. 但是,不幸的是,在postHandle方法中更改响应头没有任何作用,因为响应已经生成。

Spring documentation states: Spring文档指出:

Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with @ResponseBody and ResponseEntity methods. 请注意,HandlerInterceptor的postHandle方法并不总是非常适合与@ResponseBody和ResponseEntity方法一起使用。 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. 在这种情况下,HttpMessageConverter会在调用postHandle之前写入并提交响应,这使得无法更改响应,例如添加标头。 Instead an application can implement ResponseBodyAdvice and either declare it as an @ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter. 相反,应用程序可以实现ResponseBodyAdvice并将其声明为@ControllerAdvice bean或直接在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. 我不认为这种情况是在复制Spring-处理后(在postHandle中)为每个请求修改标头,因为我需要捕获一个值为开始时间的变量(当请求到达应用程序时,并且@RequestMapping方法开始之前) ),然后在@RequestMapping方法完成后使用此变量来计算经过的时间。

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 您需要使用Handle Interceptor,但不要实现postHandle方法,仅实现preHandle以便将startTime保存为请求中的参数。

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: 当控制器完成并返回响应时,控制器建议(实现ResponseBodyAdvice的类)将获取服务器请求的http servlet请求部分,恢复startTime并获取经过的时间,如下所示:

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

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM