12

What I want to do is, adding a new header to the response after the request is processed. I need to check the processed HttpStatus code (401 unauthorized in my case) and add a new header. I know Spring has interceptors, but the response cannot be modified as stated in the document:

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.

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.

The other option, using servlet filters is not also successful. I need to add the header after filterChain.doFilter(servletRequest, servletResponse); call. But it again doesn't modify the header value. Is there a way to accomplish this easy task?

mtyurt
  • 3,369
  • 6
  • 38
  • 57
  • You said servlet filters. Where did you add your filter in the chain? Can you quickly check if http://stackoverflow.com/a/16191770/2231632 works for you? – Praba Jun 08 '15 at 07:05
  • I tried it. I'm adding the header after `filterChain.doChain()` because I need the status code information after the chain is completed. It doesn't work, Spring commits response and does not allow modification in the filter. – mtyurt Jun 08 '15 at 07:22
  • That isn't anything spring related, but plain java as soon as the response is already (partially) send to the client you cannot modify the header information anymore. You can only change it before that. But I assume you are returning the 401 yourself then why not simply do it at that location? – M. Deinum Jun 08 '15 at 07:31
  • It's returned in various places. The document states **modifying headers** is possible not in the postHandle but some place else, I'm looking for that place and how to do that. – mtyurt Jun 08 '15 at 07:43
  • in the `preHandle`. As soon as the code is send nothing can be done about it anymore. Also isn't it a bit of a smell to have that all around the code base? Shouldn't security be done in a single location... – M. Deinum Jun 08 '15 at 07:44
  • I'm aware of `preHandle` and I use it effectively. Yet it doesn't solve my problem, doesn't change my intention and my question. A filter like this solves many problems I have right now. – mtyurt Jun 08 '15 at 07:49
  • Well you cannot and never will be able to do it in the `postHandle` or after the execution of the `filterChain` because the response is already (partially) send. Your only hope would be to wrap the response and override the methods (as mentioned in the answers), but again it feels like you are reinventing the a security wheel (but imho that is). – M. Deinum Jun 08 '15 at 07:58

5 Answers5

14

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);
Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
  • You would also have to override `sendError` methods and the additional (deprecated) `setStatus` message. Just to be sure that alla cases are captured. – M. Deinum Jun 08 '15 at 07:56
  • @M.Denium yes indeed, as I indicated in my comment in the code. The implementation of these methods is left as an exercise for the reader... – Ian Roberts Jun 08 '15 at 08:23
  • Thank you for your answer, this solved my problem with the alfresco stack – Dark Star1 Nov 11 '15 at 13:51
  • What can one do if they dont call setStatus() since it is 200 be default when you respond with an object? – Nick H Jan 16 '17 at 22:45
3

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 ...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
3

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;
    }
}
Ken Chan
  • 84,777
  • 26
  • 143
  • 172
2

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...
}
Zaur Guliyev
  • 4,254
  • 7
  • 28
  • 44
0

You can implement a ServletFilter and just wrap the original response object.

This will allow you to defer the actual writing of the response and add your custom headers.

On the other hand: This looks a bit like the Spring Security Processing chain.