TL:DR
How can we override the current behavior of Spring 4.3+ that forces the use of the RequestMethod.GET or @GetMapping for HEAD requests so we can return the Content-Length header without having to write all the data to the responses OutputStream?
Longer Version:
It has just come to my attention that Spring has changed the way that GET/HEAD requests are handled by default:
HTTP HEAD, OPTIONS
@GetMapping — and also @RequestMapping(method=HttpMethod.GET), support HTTP HEAD transparently for request mapping purposes. Controller methods don’t need to change. A response wrapper, applied in javax.servlet.http.HttpServlet, ensures a "Content-Length" header is set to the number of bytes written and without actually writing to the response.
@GetMapping — and also @RequestMapping(method=HttpMethod.GET), are implicitly mapped to and also support HTTP HEAD. An HTTP HEAD request is processed as if it were HTTP GET except but instead of writing the body, the number of bytes are counted and the "Content-Length" header set.
By default HTTP OPTIONS is handled by setting the "Allow" response header to the list of HTTP methods listed in all @RequestMapping methods with matching URL patterns.
For a @RequestMapping without HTTP method declarations, the "Allow" header is set to "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS". Controller methods should always declare the supported HTTP methods for example by using the HTTP method specific variants — @GetMapping, @PostMapping, etc.
@RequestMapping method can be explicitly mapped to HTTP HEAD and HTTP OPTIONS, but that is not necessary in the common case.
Sources:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-head-options
https://stackoverflow.com/a/45412434/42962
How can we override this default behavior so we can handle the HEAD response and set the Content-Length header ourselves?
We want to do this because we sever large files (think over 10 gigs in size) through our web application we would like to not have to read all the bytes into the Response's OutputStream if possible.
Here is an example of our current code. Only the second method (handleRequest with the RequestMethod.GET) gets called.
@RequestMapping(value = "/file/{fileName:.+}", method = RequestMethod.HEAD)
public void handleHeadRequest(@RequestParam(value = "fileName") String fileName, HttpServletRequest request, HttpServletResponse response) {
File file = fileRepository.getFileByName(fileName)
response.addHeader("Accept-Ranges", "bytes");
response.addDateHeader("Last-Modified", file.lastModified());
Long fileSize = file.length();
response.addHeader(HttpHeaderConstants.CONTENT_LENGTH, fileSize.toString());
}
@RequestMapping(value = "/file/{fileName:.+}", headers = "!Range", method = RequestMethod.GET)
public void handleRequest(@PathVariable(value = "fileName") String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {
File file = fileRepository.getFileByName(fileName)
response.addHeader("Accept-Ranges", "bytes");
response.addDateHeader("Last-Modified", file.lastModified());
Long fileSize = file.length();
response.addHeader(HttpHeaderConstants.CONTENT_LENGTH, fileSize.toString());
// Stream file to end user client.
fileDownloadHandler.handle(request, response, file);
}
HttpServletResponse response
as a parameter to your method annotated with@GetMapping
and do something likeresponse.setHeader("Content-Length", "12345");
– Impulse The FoxhandleHeadRequest
method is no longer called at all. :( – hooknchandleHeadRequest
method above instead of thehandleRequest
method for HTTP HEAD method requests. – hooknc