Allow Resource to add headers for range requests

Closes gh-25976
This commit is contained in:
Rossen Stoyanchev
2020-10-27 10:15:56 +00:00
parent 20fe1bcd99
commit f02a2bef8f
3 changed files with 37 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -352,15 +352,14 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
// Check the media type for the resource // Check the media type for the resource
MediaType mediaType = MediaTypeFactory.getMediaType(resource).orElse(null); MediaType mediaType = MediaTypeFactory.getMediaType(resource).orElse(null);
setHeaders(exchange, resource, mediaType);
// Content phase // Content phase
if (HttpMethod.HEAD.matches(exchange.getRequest().getMethodValue())) { if (HttpMethod.HEAD.matches(exchange.getRequest().getMethodValue())) {
setHeaders(exchange, resource, mediaType);
exchange.getResponse().getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes"); exchange.getResponse().getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
return Mono.empty(); return Mono.empty();
} }
setHeaders(exchange, resource, mediaType);
ResourceHttpMessageWriter writer = getResourceHttpMessageWriter(); ResourceHttpMessageWriter writer = getResourceHttpMessageWriter();
Assert.state(writer != null, "No ResourceHttpMessageWriter"); Assert.state(writer != null, "No ResourceHttpMessageWriter");
return writer.write(Mono.just(resource), return writer.write(Mono.just(resource),
@@ -535,6 +534,7 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
if (mediaType != null) { if (mediaType != null) {
headers.setContentType(mediaType); headers.setContentType(mediaType);
} }
if (resource instanceof HttpResource) { if (resource instanceof HttpResource) {
HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders(); HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders();
exchange.getResponse().getHeaders().putAll(resourceHeaders); exchange.getResponse().getHeaders().putAll(resourceHeaders);

View File

@@ -475,22 +475,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
// Check the media type for the resource // Check the media type for the resource
MediaType mediaType = getMediaType(request, resource); MediaType mediaType = getMediaType(request, resource);
setHeaders(response, resource, mediaType);
// Content phase // Content phase
if (METHOD_HEAD.equals(request.getMethod())) { if (METHOD_HEAD.equals(request.getMethod())) {
setHeaders(response, resource, mediaType);
return; return;
} }
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response); ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
if (request.getHeader(HttpHeaders.RANGE) == null) { if (request.getHeader(HttpHeaders.RANGE) == null) {
Assert.state(this.resourceHttpMessageConverter != null, "Not initialized"); Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
setHeaders(response, resource, mediaType);
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage); this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
} }
else { else {
Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized"); Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
try { try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
@@ -499,7 +497,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage); HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
response.setHeader("Content-Range", "bytes */" + resource.contentLength()); response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
} }
} }
@@ -694,6 +692,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
if (mediaType != null) { if (mediaType != null) {
response.setContentType(mediaType.toString()); response.setContentType(mediaType.toString());
} }
if (resource instanceof HttpResource) { if (resource instanceof HttpResource) {
HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders(); HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders();
resourceHeaders.forEach((headerName, headerValues) -> { resourceHeaders.forEach((headerName, headerValues) -> {
@@ -709,6 +708,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
} }
}); });
} }
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
} }

View File

@@ -24,6 +24,7 @@ import java.util.List;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -650,6 +651,35 @@ public class ResourceHttpRequestHandlerTests {
assertEquals("t.", ranges[11]); assertEquals("t.", ranges[11]);
} }
@Test // gh-25976
public void partialContentByteRangeWithEncodedResource() throws Exception {
String path = "js/foo.js";
EncodedResourceResolverTests.createGzippedFile(path);
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setResourceResolvers(Arrays.asList(new EncodedResourceResolver(), new PathResourceResolver()));
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
handler.setServletContext(new MockServletContext());
handler.afterPropertiesSet();
this.request.addHeader("Accept-Encoding", "gzip");
this.request.addHeader("Range", "bytes=0-1");
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, path);
handler.handleRequest(this.request, this.response);
assertEquals(206, this.response.getStatus());
assertThat(response.getHeaderNames(), CoreMatchers.hasItems(
"Last-Modified", "Content-Length", "Content-Type", "Content-Encoding",
"Vary", "Accept-Ranges", "Content-Range"));
assertEquals("application/javascript", this.response.getContentType());
assertEquals(2, this.response.getContentLength());
assertEquals("bytes 0-1/66", this.response.getHeader("Content-Range"));
assertEquals("bytes", this.response.getHeaderValue("Accept-Ranges"));
assertEquals("gzip", this.response.getHeaderValue("Content-Encoding"));
assertEquals("Accept-Encoding", this.response.getHeaderValue("Vary"));
}
@Test // SPR-14005 @Test // SPR-14005
public void doOverwriteExistingCacheControlHeaders() throws Exception { public void doOverwriteExistingCacheControlHeaders() throws Exception {
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css"); this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");