From f02a2bef8f20a646c3de6bb72974e2d74b008d0b 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 | 30 +++++++++++++++++++ 3 files changed, 37 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 6dcf31c1d5..ba40aa96d0 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 @@ -475,22 +475,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(); @@ -499,7 +497,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); } } @@ -694,6 +692,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) -> { @@ -709,6 +708,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 0e594a086c..7ed815f74b 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 @@ -24,6 +24,7 @@ import java.util.List; import javax.servlet.http.HttpServletResponse; +import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; @@ -650,6 +651,35 @@ public class ResourceHttpRequestHandlerTests { 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 public void doOverwriteExistingCacheControlHeaders() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");