Spring Controller:添加一个名为Elapsed-Time

时间:2017-09-14 20:29:20

标签: java spring rest spring-mvc

我想在每个API REST请求中添加一个Elapsed-Time响应头参数,即使是那些因错误而完成的请求。

例如:

===>
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 }

Elapsed-Time参数计算为@RequestMapping方法完成的瞬间与@RequestMapping方法启动的瞬间之​​间的毫秒差异。

我一直在看Spring4 HandlerInterceptorAdapter。 preHandle和postHandle方法似乎完全适合这种情况(尽管执行链中每个拦截器的时间开销)。但是,不幸的是,在postHandle方法中更改响应头没有效果,因为响应已经构建。

Spring documentation州:

  

请注意,HandlerInterceptor的postHandle方法并不总是非常适合与@ResponseBody和ResponseEntity方法一起使用。在这种情况下,HttpMessageConverter在调用postHandle之前写入并提交响应,这使得无法更改响应,例如添加标头。相反,应用程序可以实现ResponseBodyAdvice并将其声明为@ControllerAdvice bean或直接在RequestMappingHandlerAdapter上配置它。

你知道处理这种情况的任何优雅的解决方案吗?

我不认为这种情况会重复Spring - Modifying headers for every request after processing (in postHandle),因为我需要捕获一个值为开始时间的变量(当请求到达应用程序时和@RequestMapping方法开始之前),然后使用@RequestMapping方法完成后,此变量用于计算已用时间。

1 个答案:

答案 0 :(得分:3)

你需要继续使用Handle Interceptor,但是不要实现postHandle方法,只有preHandle才能将startTime保存为请求中的参数

let qs = {
    requests: 5,
    timeout: 1000
};
let prices = [];
let highest = 0;
function xhr(qs){
    return $.ajax({
        url: url,
        dataType: 'json',
        timeout: qs.timeout
    }).then(data => {
        let json = JSON.stringify(data['price']);
        prices.push(json);
        getHighestPrice(prices);
        console.log(highest);
    });
}

function makeRequest(qs) {
    let p = $.when([]);
    for(let i = 0; i < qs.requests; i++) {
        p = p.then(() => xhr(qs));
        // or p.then(xhr); if you don't need to pass qs on to xhr function (remove qs argument in xhr as well)
    }
    p.then(() => {
        // this is run once all have completed
    }).fail(reason => {
        // this is run if there's a failure anywhere
    });
}

当控制器完成并返回响应时,Controller Advice(实现ResponseBodyAdvice的类)将获取Server Request的http servlet请求部分,恢复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;
  }
}

最后,您将拦截器添加到主应用程序(每个资源都需要/ **路径)

@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;
  }
}