Issue
I have a controller annotated @RestController
in Spring Boot (v2.4). This should return a HTTP header "Cache-Control" with a quite static configuration by default for all endpoints it handles.
Until now I achieved this with a @ModelAttribute
annotated method inside the controller class that simply sets the headers like this:
@ModelAttribute
public void setResponseHeader(HttpServletResponse response) {
response.setHeader(HttpHeaders.CACHE_CONTROL,
CacheControl.maxAge(1, TimeUnit.DAYS).cachePrivate().getHeaderValue());
}
This works fine, but I found out that it collides a bit with my exception handler - I configured a global one by putting @RestControllerAdvice
onto another class and then having multiple methods annotated with @ExceptionHandler
for the various possible exceptions inside.
The exception handlers most of the time set a specific HTTP status of 400 and above and of course I don't want that to be cached.
Even if I make the HttpServletResponse
available inside the exception handling methods, the HTTP headers are already set in there by my setResponseHeader(HttpServletResponse)
method and the interface HttpServletResponse
doesn't allow me to remove them again.
So is there any strategy to have the default caching headers on one side, but exceptions for the Exception handlers on the other side?
I really would appreciate if I don't have to set the caching headers individually inside my controller methods per endpoint.
Additional challenge is, that I need to stream big data to the client, so I really would like to avoid server-side caching because I expect lots of requests and want to prevent that the server fills its heap with the contents that should be delivered to the clients. Unfortunately I cannot use the plain resource handler mechanism since complex queries regarding permissions (not just OAuth2, but licensing) are required that I don't want to put into a filter.
Solution
After several tries, I found this solution for my special situation:
- I stick to the
@ModelAttribute
to fill in default caching header. - In the
@ExceptionHandler
annotated methods I overwrite the caching header with a "no-cache" value.
package test;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletResponse;
@RestControllerAdvice
public class ExHandlers {
@ExceptionHandler({NullPointerException.class, IllegalStateException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String nullPointer(RuntimeException e, HttpServletResponse response) {
response.setHeader(HttpHeaders.CACHE_CONTROL, CacheControl.noCache().getHeaderValue())
return e.getMessage();
}
}
Important note: Don't use org.springframework.http.ResponseEntity
as this will add a second Cache-Control header to the response, while HttpServletResponse.setHeader(...)
replaces the default one.
I checked the implementations and it is not possible to actively remove the header once it is set.
Downside of my solution: when the body has started to be streamed and an exception occurs, I cannot "revert" the headers - probably they have gone over the wire yet. But with the wish for no server-side caching I think that's the only way to go.
Answered By - cyberbrain
Answer Checked By - Mary Flores (JavaFixing Volunteer)