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 9914780626d0..f51a9bff6a9f 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. @@ -375,15 +375,9 @@ public Mono handle(ServerWebExchange exchange) { // 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), @@ -558,6 +552,7 @@ protected void setHeaders(ServerWebExchange exchange, Resource resource, @Nullab if (mediaType != null) { headers.setContentType(mediaType); } + if (resource instanceof HttpResource) { HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders(); exchange.getResponse().getHeaders().putAll(resourceHeaders); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java index 31052acbe9a7..b96a7d4a284f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java @@ -118,10 +118,6 @@ public void getResourceHttpHeader() throws Exception { assertThat(resourceLastModifiedDate("test/foo.css") / 1000).isEqualTo(headers.getLastModified() / 1000); assertThat(headers.getFirst("Accept-Ranges")).isEqualTo("bytes"); assertThat(headers.get("Accept-Ranges").size()).isEqualTo(1); - - StepVerifier.create(exchange.getResponse().getBody()) - .expectErrorMatches(ex -> ex.getMessage().startsWith("No content was written")) - .verify(); } @Test 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 21c8683d4501..0cd981b6dea1 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 @@ -531,22 +531,16 @@ public void handleRequest(HttpServletRequest request, HttpServletResponse respon // 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(); @@ -555,7 +549,7 @@ public void handleRequest(HttpServletRequest request, HttpServletResponse respon 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); } } @@ -773,6 +767,7 @@ protected void setHeaders(HttpServletResponse response, Resource resource, @Null if (mediaType != null) { response.setContentType(mediaType.toString()); } + if (resource instanceof HttpResource) { HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders(); resourceHeaders.forEach((headerName, headerValues) -> { @@ -788,6 +783,7 @@ protected void setHeaders(HttpServletResponse response, Resource resource, @Null } }); } + 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 6a17d8aa4188..38b39505f359 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 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 @@ * @author Rossen Stoyanchev * @author Brian Clozel */ +@ExtendWith(GzipSupport.class) public class ResourceHttpRequestHandlerTests { private ResourceHttpRequestHandler handler; @@ -116,7 +118,6 @@ public void getResourceHttpHeader() throws Exception { assertThat(this.response.getDateHeader("Last-Modified") / 1000).isEqualTo(resourceLastModified("test/foo.css") / 1000); assertThat(this.response.getHeader("Accept-Ranges")).isEqualTo("bytes"); assertThat(this.response.getHeaders("Accept-Ranges").size()).isEqualTo(1); - assertThat(this.response.getContentAsByteArray().length).isEqualTo(0); } @Test @@ -657,6 +658,49 @@ public void partialContentMultipleByteRanges() throws Exception { 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 // gh-25976 + public void partialContentWithHttpHead() throws Exception { + this.request.setMethod("HEAD"); + this.request.addHeader("Range", "bytes=0-1"); + this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); + this.handler.handleRequest(this.request, this.response); + + assertThat(this.response.getStatus()).isEqualTo(206); + assertThat(this.response.getContentType()).isEqualTo("text/plain"); + assertThat(this.response.getContentLength()).isEqualTo(2); + assertThat(this.response.getHeader("Content-Range")).isEqualTo("bytes 0-1/10"); + assertThat(this.response.getHeaderValues("Accept-Ranges")).containsExactly("bytes"); + } + @Test // SPR-14005 public void doOverwriteExistingCacheControlHeaders() throws Exception { this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");