Spring - Modifying headers for every request after processing (in postHandle)
Well, Java shows you the HTTP response as an Object for which you can alter the different fields independently.
But what is actually exchanged between the server and the client is a byte stream, and headers and sent before the body. That is the reason why the HttpResponse has the isCommitted()
method : the response is committed when headers have been sent. And of course once it is committed, you can no longer add of modify headers. And the servlet container may commit and flush the response once enough characters have been written to the body.
So trying to change headers is unsafe after the request have been processed. It could work only if request has not been committed. The only case where it is safe is when the controller does not write the response itself and just forwards to a view. Then in the postHandle
interceptor method, the response has not been committed, and you can change headers. Otherwise, you must test isCommitted()
, and if it returns true ... then it is too late to change headers !
Of course in that case, neither an interceptor nor a filter could do anything ...
It sounds like you're on the right track with a servlet filter, what you probably need to do is wrap the servlet response object with one that detects when a 401 status code has been set and adds your custom header at that time:
HttpServletResponse wrappedResponse = new HttpServletResponseWrapper(response) {
public void setStatus(int code) {
super.setStatus(code);
if(code == 401) handle401();
}
// three similar methods for the other setStatus and the two
// versions of sendError
private void handle401() {
this.addHeader(...);
}
};
filterChain.doFilter(request, wrappedResponse);
Well, I implemented the ResponseBodyAdvice. Yes, it allows body modification, but I couldn't manage to modify the headers, event couldn't find the status code returned from the controller.
Well, actually you can if you cast that ServerHttpResponse
to ServletServerHttpResponse
.
(It must be ServletServerHttpResponse
based on the how the ResponseBodyAdvice
is called , you can see that ServerHttpResponse
passed to ResponseBodyAdvice
is actually an ServletServerHttpResponse
in this method).
So simply implement a ResponseBodyAdvice
and no need to wrap the HttpServletResponse
anymore :
@ControllerAdvice
public class FooBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(response instanceof ServletServerHttpResponse) {
ServletServerHttpResponse res= (ServletServerHttpResponse)(response);
res.getServletResponse().getStatus(); //get the status code
res.getHeaders().set("fooHeader", "fooValue"); //modify headers
res.getHeaders().setETag("33a64df551425fcc55e4d42a148795d9f25f89d4") //use "type safe" methods to modify header
}
return body;
}
}
If checking status code is not required then you can just add those headers on preHandle method (as Spring commits response before postHandle fires, so adding them in postHandle will not work for response returned from @ResponseBody marked controller method):
public class ControllerHandleInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
}
return true;
}
// other code...
}