From 3596e720505745ef48678bfce17c8d036943852d Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 12 Sep 2016 15:25:47 +0200 Subject: [PATCH] Add range requests support in ResourceWebHandler This commit handles range requests in `ResourceWebHandler`, using the `ResourceHttpMessageWriter` configured within the handler. This `WebHandler` will add a `HTTP_RANGE_REQUEST_HINT` writer hint containing all the HTTP Range information of the request. Issue: SPR-14664 --- .../reactive/resource/ResourceWebHandler.java | 15 +++--- .../resource/ResourceWebHandlerTests.java | 48 +++++++++++-------- .../web/reactive/resource/test/foo.txt | 1 + 3 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 spring-web-reactive/src/test/resources/org/springframework/web/reactive/resource/test/foo.txt diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index a0549ad316..bda5181981 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -54,6 +54,7 @@ import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.accept.CompositeContentTypeResolver; import org.springframework.web.reactive.accept.PathExtensionContentTypeResolver; import org.springframework.web.server.MethodNotAllowedException; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; @@ -82,6 +83,7 @@ import org.springframework.web.server.WebHandler; * client. * * @author Rossen Stoyanchev + * @author Brian Clozel * @since 5.0 */ public class ResourceWebHandler @@ -188,7 +190,7 @@ public class ResourceWebHandler } /** - * Return the list of configured resource converters. + * Return the configured resource message writer. */ public ResourceHttpMessageWriter getResourceHttpMessageWriter() { return this.resourceHttpMessageWriter; @@ -333,15 +335,12 @@ public class ResourceWebHandler return Mono.empty(); } - // TODO: range requests - setHeaders(exchange, resource, mediaType); - - return this.resourceHttpMessageWriter.write( - Mono.just(resource), ResolvableType.forClass(Resource.class), - mediaType, exchange.getResponse(), Collections.emptyMap()); + return this.resourceHttpMessageWriter.write(Mono.just(resource), + null, ResolvableType.forClass(Resource.class), mediaType, + exchange.getRequest(), exchange.getResponse(), Collections.emptyMap()); } - catch (IOException ex) { + catch (IOException|ResponseStatusException ex) { return Mono.error(ex); } }); diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java index bc6b1eb03c..be59b5b709 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java @@ -16,6 +16,10 @@ package org.springframework.web.reactive.resource; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.springframework.web.reactive.HandlerMapping.*; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -25,12 +29,17 @@ import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.buffer.support.DataBufferTestUtils; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; @@ -44,14 +53,12 @@ import org.springframework.util.StringUtils; import org.springframework.web.reactive.accept.CompositeContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.server.MethodNotAllowedException; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.DefaultWebSessionManager; import org.springframework.web.server.session.WebSessionManager; -import static org.junit.Assert.*; -import static org.springframework.web.reactive.HandlerMapping.*; - /** * Unit tests for {@link ResourceWebHandler}. * @@ -69,6 +76,8 @@ public class ResourceWebHandlerTests { private WebSessionManager sessionManager = new DefaultWebSessionManager(); + private DataBufferFactory bufferFactory = new DefaultDataBufferFactory(); + @Before public void setUp() throws Exception { @@ -437,7 +446,6 @@ public class ResourceWebHandlerTests { } @Test - @Ignore public void partialContentByteRange() throws Exception { this.request.addHeader("Range", "bytes=0-1"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); @@ -453,7 +461,6 @@ public class ResourceWebHandlerTests { } @Test - @Ignore public void partialContentByteRangeNoEnd() throws Exception { this.request.addHeader("Range", "bytes=9-"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); @@ -469,7 +476,6 @@ public class ResourceWebHandlerTests { } @Test - @Ignore public void partialContentByteRangeLargeEnd() throws Exception { this.request.addHeader("Range", "bytes=9-10000"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); @@ -485,7 +491,6 @@ public class ResourceWebHandlerTests { } @Test - @Ignore public void partialContentSuffixRange() throws Exception { this.request.addHeader("Range", "bytes=-1"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); @@ -501,7 +506,6 @@ public class ResourceWebHandlerTests { } @Test - @Ignore public void partialContentSuffixRangeLargeSuffix() throws Exception { this.request.addHeader("Range", "bytes=-11"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); @@ -517,20 +521,19 @@ public class ResourceWebHandlerTests { } @Test - @Ignore public void partialContentInvalidRangeHeader() throws Exception { this.request.addHeader("Range", "bytes= foo bar"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); - this.handler.handle(this.exchange).blockMillis(5000); - assertEquals(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE, this.response.getStatusCode()); - assertEquals("bytes */10", this.response.getHeaders().getFirst("Content-Range")); - assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges")); - assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size()); + TestSubscriber.subscribe(this.handler.handle(this.exchange)) + .assertErrorWith(throwable -> { + assertThat(throwable, instanceOf(ResponseStatusException.class)); + ResponseStatusException exc = (ResponseStatusException) throwable; + assertThat(exc.getStatus(), is(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)); + }); } @Test - @Ignore public void partialContentMultipleByteRanges() throws Exception { this.request.addHeader("Range", "bytes=0-1, 4-5, 8-9"); this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt"); @@ -538,11 +541,18 @@ public class ResourceWebHandlerTests { assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode()); assertTrue(this.response.getHeaders().getContentType().toString() - .startsWith("multipart/byteranges; boundary=")); + .startsWith("multipart/byteranges;boundary=")); - String boundary = "--" + this.response.getHeaders().getContentType().toString().substring(31); + String boundary = "--" + this.response.getHeaders().getContentType().toString().substring(30); - TestSubscriber.subscribe(this.response.getBody()) + Mono reduced = Flux.from(this.response.getBody()) + .reduce(this.bufferFactory.allocateBuffer(), (previous, current) -> { + previous.write(current); + DataBufferUtils.release(current); + return previous; + }); + + TestSubscriber.subscribe(reduced) .assertValuesWith(buf -> { String content = DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8); String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n", false, true); diff --git a/spring-web-reactive/src/test/resources/org/springframework/web/reactive/resource/test/foo.txt b/spring-web-reactive/src/test/resources/org/springframework/web/reactive/resource/test/foo.txt new file mode 100644 index 0000000000..a9ed77c8dd --- /dev/null +++ b/spring-web-reactive/src/test/resources/org/springframework/web/reactive/resource/test/foo.txt @@ -0,0 +1 @@ +Some text. \ No newline at end of file