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
This commit is contained in:
parent
a7a9e36ca0
commit
3596e72050
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Some text.
|
||||
Loading…
Reference in New Issue