Avoid package dependency cycles
This commit is contained in:
parent
65e01eabf0
commit
d94ce0a1b2
|
@ -28,6 +28,8 @@ import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
|
@ -134,7 +136,7 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||||
private byte[] getContentRangeHeader(ResourceRegion region) {
|
private byte[] getContentRangeHeader(ResourceRegion region) {
|
||||||
long start = region.getPosition();
|
long start = region.getPosition();
|
||||||
long end = start + region.getCount() - 1;
|
long end = start + region.getCount() - 1;
|
||||||
OptionalLong contentLength = ResourceUtils.contentLength(region.getResource());
|
OptionalLong contentLength = contentLength(region.getResource());
|
||||||
if (contentLength.isPresent()) {
|
if (contentLength.isPresent()) {
|
||||||
return getAsciiBytes("Content-Range: bytes " + start + "-" + end + "/" + contentLength.getAsLong() + "\r\n\r\n");
|
return getAsciiBytes("Content-Range: bytes " + start + "-" + end + "/" + contentLength.getAsLong() + "\r\n\r\n");
|
||||||
}
|
}
|
||||||
|
@ -143,4 +145,22 @@ public class ResourceRegionEncoder extends AbstractEncoder<ResourceRegion> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine, if possible, the contentLength of the given resource without reading it.
|
||||||
|
* @param resource the resource instance
|
||||||
|
* @return the contentLength of the resource
|
||||||
|
*/
|
||||||
|
private OptionalLong contentLength(Resource resource) {
|
||||||
|
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
|
||||||
|
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
|
||||||
|
if (InputStreamResource.class != resource.getClass()) {
|
||||||
|
try {
|
||||||
|
return OptionalLong.of(resource.contentLength());
|
||||||
|
}
|
||||||
|
catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OptionalLong.empty();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,16 +18,11 @@ package org.springframework.util;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.util.OptionalLong;
|
|
||||||
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility methods for resolving resource locations to files in the
|
* Utility methods for resolving resource locations to files in the
|
||||||
|
@ -389,23 +384,4 @@ public abstract class ResourceUtils {
|
||||||
con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
|
con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine, if possible, the contentLength of the given resource
|
|
||||||
* without reading it.
|
|
||||||
* @param resource the resource instance
|
|
||||||
* @return the contentLength of the resource
|
|
||||||
*/
|
|
||||||
public static OptionalLong contentLength(Resource resource) {
|
|
||||||
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
|
|
||||||
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
|
|
||||||
if (InputStreamResource.class != resource.getClass()) {
|
|
||||||
try {
|
|
||||||
return OptionalLong.of(resource.contentLength());
|
|
||||||
}
|
|
||||||
catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return OptionalLong.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -527,9 +527,7 @@ public class ResourceWebHandlerTests {
|
||||||
|
|
||||||
TestSubscriber.subscribe(this.handler.handle(this.exchange))
|
TestSubscriber.subscribe(this.handler.handle(this.exchange))
|
||||||
.assertErrorWith(throwable -> {
|
.assertErrorWith(throwable -> {
|
||||||
assertThat(throwable, instanceOf(ResponseStatusException.class));
|
assertThat(throwable, instanceOf(IllegalArgumentException.class));
|
||||||
ResponseStatusException exc = (ResponseStatusException) throwable;
|
|
||||||
assertThat(exc.getStatus(), is(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -31,6 +32,7 @@ import reactor.core.publisher.Mono;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.core.codec.ResourceDecoder;
|
import org.springframework.core.codec.ResourceDecoder;
|
||||||
import org.springframework.core.codec.ResourceEncoder;
|
import org.springframework.core.codec.ResourceEncoder;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.support.ResourceRegion;
|
import org.springframework.core.io.support.ResourceRegion;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -43,8 +45,6 @@ import org.springframework.http.ZeroCopyHttpOutputMessage;
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.util.ResourceUtils;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link HttpMessageWriter} that can write
|
* Implementation of {@link HttpMessageWriter} that can write
|
||||||
|
@ -74,19 +74,15 @@ public class ResourceHttpMessageWriter extends AbstractServerHttpMessageWriter<R
|
||||||
this.resourceRegionHttpMessageWriter = new ResourceRegionHttpMessageWriter(bufferSize);
|
this.resourceRegionHttpMessageWriter = new ResourceRegionHttpMessageWriter(bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
|
protected Map<String, Object> resolveWriteHints(ResolvableType streamType, ResolvableType elementType,
|
||||||
MediaType mediaType, ServerHttpRequest request) {
|
MediaType mediaType, ServerHttpRequest request) {
|
||||||
try {
|
|
||||||
List<HttpRange> httpRanges = request.getHeaders().getRange();
|
List<HttpRange> httpRanges = request.getHeaders().getRange();
|
||||||
if (!httpRanges.isEmpty()) {
|
if (!httpRanges.isEmpty()) {
|
||||||
return Collections.singletonMap(ResourceHttpMessageWriter.HTTP_RANGE_REQUEST_HINT, httpRanges);
|
return Collections.singletonMap(ResourceHttpMessageWriter.HTTP_RANGE_REQUEST_HINT, httpRanges);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
|
|
||||||
"Could not parse Range request header", ex);
|
|
||||||
}
|
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,12 +135,31 @@ public class ResourceHttpMessageWriter extends AbstractServerHttpMessageWriter<R
|
||||||
headers.setContentType(mediaType);
|
headers.setContentType(mediaType);
|
||||||
}
|
}
|
||||||
if (headers.getContentLength() < 0) {
|
if (headers.getContentLength() < 0) {
|
||||||
ResourceUtils.contentLength(resource).ifPresent(headers::setContentLength);
|
contentLength(resource).ifPresent(headers::setContentLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine, if possible, the contentLength of the given resource without reading it.
|
||||||
|
* @param resource the resource instance
|
||||||
|
* @return the contentLength of the resource
|
||||||
|
*/
|
||||||
|
private OptionalLong contentLength(Resource resource) {
|
||||||
|
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
|
||||||
|
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
|
||||||
|
if (InputStreamResource.class != resource.getClass()) {
|
||||||
|
try {
|
||||||
|
return OptionalLong.of(resource.contentLength());
|
||||||
|
}
|
||||||
|
catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OptionalLong.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<Void> writeContent(Resource resource, ResolvableType type,
|
private Mono<Void> writeContent(Resource resource, ResolvableType type,
|
||||||
ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
|
ReactiveHttpOutputMessage outputMessage, Map<String, Object> hints) {
|
||||||
|
|
||||||
if (outputMessage instanceof ZeroCopyHttpOutputMessage) {
|
if (outputMessage instanceof ZeroCopyHttpOutputMessage) {
|
||||||
Optional<File> file = getFile(resource);
|
Optional<File> file = getFile(resource);
|
||||||
if (file.isPresent()) {
|
if (file.isPresent()) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.core.codec.ResourceRegionEncoder;
|
import org.springframework.core.codec.ResourceRegionEncoder;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.support.ResourceRegion;
|
import org.springframework.core.io.support.ResourceRegion;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
@ -80,7 +81,7 @@ class ResourceRegionHttpMessageWriter extends EncoderHttpMessageWriter<ResourceR
|
||||||
private void writeSingleResourceRegionHeaders(ResourceRegion region, MediaType contentType,
|
private void writeSingleResourceRegionHeaders(ResourceRegion region, MediaType contentType,
|
||||||
ReactiveHttpOutputMessage outputMessage) {
|
ReactiveHttpOutputMessage outputMessage) {
|
||||||
|
|
||||||
OptionalLong resourceLength = ResourceUtils.contentLength(region.getResource());
|
OptionalLong resourceLength = contentLength(region.getResource());
|
||||||
resourceLength.ifPresent(length -> {
|
resourceLength.ifPresent(length -> {
|
||||||
long start = region.getPosition();
|
long start = region.getPosition();
|
||||||
long end = start + region.getCount() - 1;
|
long end = start + region.getCount() - 1;
|
||||||
|
@ -91,6 +92,24 @@ class ResourceRegionHttpMessageWriter extends EncoderHttpMessageWriter<ResourceR
|
||||||
outputMessage.getHeaders().setContentType(contentType);
|
outputMessage.getHeaders().setContentType(contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine, if possible, the contentLength of the given resource without reading it.
|
||||||
|
* @param resource the resource instance
|
||||||
|
* @return the contentLength of the resource
|
||||||
|
*/
|
||||||
|
private OptionalLong contentLength(Resource resource) {
|
||||||
|
// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
|
||||||
|
// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
|
||||||
|
if (InputStreamResource.class != resource.getClass()) {
|
||||||
|
try {
|
||||||
|
return OptionalLong.of(resource.contentLength());
|
||||||
|
}
|
||||||
|
catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OptionalLong.empty();
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<Void> writeResourceRegion(ResourceRegion region,
|
private Mono<Void> writeResourceRegion(ResourceRegion region,
|
||||||
ResolvableType type, ReactiveHttpOutputMessage outputMessage) {
|
ResolvableType type, ReactiveHttpOutputMessage outputMessage) {
|
||||||
if (outputMessage instanceof ZeroCopyHttpOutputMessage) {
|
if (outputMessage instanceof ZeroCopyHttpOutputMessage) {
|
||||||
|
|
|
@ -16,13 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.http.codec;
|
package org.springframework.http.codec;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -40,16 +36,18 @@ import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpRange;
|
import org.springframework.http.HttpRange;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
import org.springframework.tests.TestSubscriber;
|
import org.springframework.tests.TestSubscriber;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link ResourceHttpMessageWriter}.
|
* Unit tests for {@link ResourceHttpMessageWriter}.
|
||||||
|
*
|
||||||
* @author Brian Clozel
|
* @author Brian Clozel
|
||||||
*/
|
*/
|
||||||
public class ResourceHttpMessageWriterTests {
|
public class ResourceHttpMessageWriterTests {
|
||||||
|
@ -72,6 +70,7 @@ public class ResourceHttpMessageWriterTests {
|
||||||
this.resource = new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8));
|
this.resource = new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void writableMediaTypes() throws Exception {
|
public void writableMediaTypes() throws Exception {
|
||||||
assertThat(this.writer.getWritableMediaTypes(),
|
assertThat(this.writer.getWritableMediaTypes(),
|
||||||
|
@ -80,7 +79,6 @@ public class ResourceHttpMessageWriterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldWriteResource() throws Exception {
|
public void shouldWriteResource() throws Exception {
|
||||||
|
|
||||||
TestSubscriber.subscribe(this.writer.write(Mono.just(resource), null, ResolvableType.forClass(Resource.class),
|
TestSubscriber.subscribe(this.writer.write(Mono.just(resource), null, ResolvableType.forClass(Resource.class),
|
||||||
MediaType.TEXT_PLAIN, this.request, this.response, Collections.emptyMap())).assertComplete();
|
MediaType.TEXT_PLAIN, this.request, this.response, Collections.emptyMap())).assertComplete();
|
||||||
|
|
||||||
|
@ -93,7 +91,6 @@ public class ResourceHttpMessageWriterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldWriteResourceRange() throws Exception {
|
public void shouldWriteResourceRange() throws Exception {
|
||||||
|
|
||||||
this.request.getHeaders().setRange(Collections.singletonList(HttpRange.createByteRange(0, 5)));
|
this.request.getHeaders().setRange(Collections.singletonList(HttpRange.createByteRange(0, 5)));
|
||||||
|
|
||||||
TestSubscriber.subscribe(this.writer.write(Mono.just(resource), null, ResolvableType.forClass(Resource.class),
|
TestSubscriber.subscribe(this.writer.write(Mono.just(resource), null, ResolvableType.forClass(Resource.class),
|
||||||
|
@ -111,10 +108,9 @@ public class ResourceHttpMessageWriterTests {
|
||||||
public void shouldThrowErrorInvalidRange() throws Exception {
|
public void shouldThrowErrorInvalidRange() throws Exception {
|
||||||
this.request.getHeaders().set(HttpHeaders.RANGE, "invalid");
|
this.request.getHeaders().set(HttpHeaders.RANGE, "invalid");
|
||||||
|
|
||||||
this.expectedException.expect(ResponseStatusException.class);
|
this.expectedException.expect(IllegalArgumentException.class);
|
||||||
TestSubscriber.subscribe(this.writer.write(Mono.just(resource), null, ResolvableType.forClass(Resource.class),
|
TestSubscriber.subscribe(this.writer.write(Mono.just(resource), null, ResolvableType.forClass(Resource.class),
|
||||||
MediaType.TEXT_PLAIN, this.request, this.response, Collections.emptyMap()));
|
MediaType.TEXT_PLAIN, this.request, this.response, Collections.emptyMap()));
|
||||||
this.expectedException.expect(Matchers.hasProperty("status", is(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<String> reduceToString(Publisher<DataBuffer> buffers, DataBufferFactory bufferFactory) {
|
private Mono<String> reduceToString(Publisher<DataBuffer> buffers, DataBufferFactory bufferFactory) {
|
||||||
|
@ -127,4 +123,5 @@ public class ResourceHttpMessageWriterTests {
|
||||||
})
|
})
|
||||||
.map(buffer -> DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8));
|
.map(buffer -> DataBufferTestUtils.dumpString(buffer, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue