From d91b66a04ca9760219f1c6ee3a55761621bc3b03 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 27 Oct 2020 10:15:56 +0000 Subject: [PATCH] Allow Resource to add headers for range requests Closes gh-25976 --- .../reactive/resource/ResourceWebHandler.java | 6 ++-- .../resource/ResourceHttpRequestHandler.java | 8 ++--- .../ResourceHttpRequestHandlerTests.java | 31 +++++++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index 87d59e7cd7..5d33a3d449 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -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"); * 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 MediaType mediaType = MediaTypeFactory.getMediaType(resource).orElse(null); + setHeaders(exchange, resource, mediaType); // Content phase if (HttpMethod.HEAD.matches(exchange.getRequest().getMethodValue())) { - setHeaders(exchange, resource, mediaType); exchange.getResponse().getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes"); return Mono.empty(); } - setHeaders(exchange, resource, mediaType); ResourceHttpMessageWriter writer = getResourceHttpMessageWriter(); Assert.state(writer != null, "No ResourceHttpMessageWriter"); return writer.write(Mono.just(resource), @@ -535,6 +534,7 @@ public class ResourceWebHandler implements WebHandler, InitializingBean { if (mediaType != null) { headers.setContentType(mediaType); } + if (resource instanceof HttpResource) { HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders(); exchange.getResponse().getHeaders().putAll(resourceHeaders); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 186c79d7bb..cbf5659fb4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -508,22 +508,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator // Check the media type for the resource MediaType mediaType = getMediaType(request, resource); + setHeaders(response, resource, mediaType); // Content phase if (METHOD_HEAD.equals(request.getMethod())) { - setHeaders(response, resource, mediaType); return; } ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response); if (request.getHeader(HttpHeaders.RANGE) == null) { Assert.state(this.resourceHttpMessageConverter != null, "Not initialized"); - setHeaders(response, resource, mediaType); this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage); } else { Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized"); - response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request); try { List httpRanges = inputMessage.getHeaders().getRange(); @@ -532,7 +530,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage); } 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); } } @@ -750,6 +748,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator if (mediaType != null) { response.setContentType(mediaType.toString()); } + if (resource instanceof HttpResource) { HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders(); resourceHeaders.forEach((headerName, headerValues) -> { @@ -765,6 +764,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator } }); } + response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java index 9af18827f9..d6a9f2b7ff 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandlerTests.java @@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -58,6 +59,7 @@ import static org.mockito.Mockito.mock; * @author Rossen Stoyanchev * @author Brian Clozel */ +@ExtendWith(GzipSupport.class) public class ResourceHttpRequestHandlerTests { private ResourceHttpRequestHandler handler; @@ -655,6 +657,35 @@ public class ResourceHttpRequestHandlerTests { assertThat(ranges[11]).isEqualTo("t."); } + @Test // gh-25976 + public void partialContentByteRangeWithEncodedResource(GzipSupport.GzippedFiles gzippedFiles) throws Exception { + String path = "js/foo.js"; + gzippedFiles.create(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); + + assertThat(this.response.getStatus()).isEqualTo(206); + assertThat(this.response.getHeaderNames()).containsExactlyInAnyOrder( + "Content-Type", "Content-Length", "Content-Range", "Accept-Ranges", + "Last-Modified", "Content-Encoding", "Vary"); + + assertThat(this.response.getContentType()).isEqualTo("text/javascript"); + assertThat(this.response.getContentLength()).isEqualTo(2); + assertThat(this.response.getHeader("Content-Range")).isEqualTo("bytes 0-1/66"); + assertThat(this.response.getHeaderValues("Accept-Ranges")).containsExactly("bytes"); + assertThat(this.response.getHeaderValues("Content-Encoding")).containsExactly("gzip"); + assertThat(this.response.getHeaderValues("Vary")).containsExactly("Accept-Encoding"); + } + @Test // SPR-14005 public void doOverwriteExistingCacheControlHeaders() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");