Browse Source

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
pull/1175/head
Brian Clozel 9 years ago
parent
commit
3596e72050
  1. 15
      spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java
  2. 48
      spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java
  3. 1
      spring-web-reactive/src/test/resources/org/springframework/web/reactive/resource/test/foo.txt

15
spring-web-reactive/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java

@ -54,6 +54,7 @@ import org.springframework.web.reactive.HandlerMapping; @@ -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; @@ -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 @@ -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 @@ -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);
}
});

48
spring-web-reactive/src/test/java/org/springframework/web/reactive/resource/ResourceWebHandlerTests.java

@ -16,6 +16,10 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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<DataBuffer> 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);

1
spring-web-reactive/src/test/resources/org/springframework/web/reactive/resource/test/foo.txt

@ -0,0 +1 @@ @@ -0,0 +1 @@
Some text.
Loading…
Cancel
Save