Support HTTP range requests in Controllers
Prior to this commit, HTTP Range requests were only supported by the ResourceHttpRequestHandler when serving static resources. This commit improves the ResourceHttpMessageConverter that now supports partial writes of Resources. For this, the `HttpEntityMethodProcessor` and `RequestResponseBodyMethodProcessor` now wrap resources with HTTP range information in a `HttpRangeResource`, if necessary. The message converter handle those types and knows how to handle partial writes. Controller methods can now handle Range requests for return types that extend Resource or HttpEntity: @RequestMapping("/example/video.mp4") public Resource handler() { } @RequestMapping("/example/video.mp4") public HttpEntity<Resource> handler() { } Issue: SPR-13834
This commit is contained in:
parent
15fe8279e6
commit
c7bd3b8440
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.http;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder that combines a {@link Resource} descriptor with
|
||||||
|
* {@link HttpRange} information to be used for reading
|
||||||
|
* selected parts of the resource.
|
||||||
|
*
|
||||||
|
* <p>Used as an argument for partial conversion operations in
|
||||||
|
* {@link org.springframework.http.converter.ResourceHttpMessageConverter}.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
public class HttpRangeResource implements Resource {
|
||||||
|
|
||||||
|
private final List<HttpRange> httpRanges;
|
||||||
|
|
||||||
|
private final Resource resource;
|
||||||
|
|
||||||
|
public HttpRangeResource(List<HttpRange> httpRanges, Resource resource) {
|
||||||
|
Assert.notEmpty(httpRanges, "list of HTTP Ranges should not be empty");
|
||||||
|
this.httpRanges = httpRanges;
|
||||||
|
this.resource = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of HTTP (byte) ranges describing the requested
|
||||||
|
* parts of the Resource, as provided by the HTTP Range request.
|
||||||
|
*/
|
||||||
|
public final List<HttpRange> getHttpRanges() {
|
||||||
|
return httpRanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean exists() {
|
||||||
|
return resource.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadable() {
|
||||||
|
return resource.isReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return resource.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getURL() throws IOException {
|
||||||
|
return resource.getURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getURI() throws IOException {
|
||||||
|
return resource.getURI();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFile() throws IOException {
|
||||||
|
return resource.getFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long contentLength() throws IOException {
|
||||||
|
return resource.contentLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long lastModified() throws IOException {
|
||||||
|
return resource.lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Resource createRelative(String relativePath) throws IOException {
|
||||||
|
return resource.createRelative(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return resource.getFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return resource.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getInputStream() throws IOException {
|
||||||
|
return resource.getInputStream();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,8 +16,12 @@
|
||||||
|
|
||||||
package org.springframework.http.converter;
|
package org.springframework.http.converter;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.activation.FileTypeMap;
|
import javax.activation.FileTypeMap;
|
||||||
import javax.activation.MimetypesFileTypeMap;
|
import javax.activation.MimetypesFileTypeMap;
|
||||||
|
|
||||||
|
@ -25,23 +29,33 @@ import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpInputMessage;
|
import org.springframework.http.HttpInputMessage;
|
||||||
import org.springframework.http.HttpOutputMessage;
|
import org.springframework.http.HttpOutputMessage;
|
||||||
|
import org.springframework.http.HttpRange;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources}.
|
* Implementation of {@link HttpMessageConverter} that can read and write {@link Resource Resources}
|
||||||
|
* and supports byte range requests.
|
||||||
*
|
*
|
||||||
* <p>By default, this converter can read all media types. The Java Activation Framework (JAF) -
|
* <p>By default, this converter can read all media types. The Java Activation Framework (JAF) -
|
||||||
* if available - is used to determine the {@code Content-Type} of written resources.
|
* if available - is used to determine the {@code Content-Type} of written resources.
|
||||||
* If JAF is not available, {@code application/octet-stream} is used.
|
* If JAF is not available, {@code application/octet-stream} is used.
|
||||||
*
|
*
|
||||||
|
* <p>This converter supports HTTP byte range requests and can write partial content, when provided
|
||||||
|
* with an {@link HttpRangeResource} instance containing the required Range information.
|
||||||
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
* @author Kazuki Shimizu
|
* @author Kazuki Shimizu
|
||||||
|
* @author Brian Clozel
|
||||||
* @since 3.0.2
|
* @since 3.0.2
|
||||||
*/
|
*/
|
||||||
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
|
public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> {
|
||||||
|
@ -64,7 +78,7 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
|
||||||
protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
|
protected Resource readInternal(Class<? extends Resource> clazz, HttpInputMessage inputMessage)
|
||||||
throws IOException, HttpMessageNotReadableException {
|
throws IOException, HttpMessageNotReadableException {
|
||||||
|
|
||||||
if (InputStreamResource.class == clazz){
|
if (InputStreamResource.class == clazz) {
|
||||||
return new InputStreamResource(inputMessage.getBody());
|
return new InputStreamResource(inputMessage.getBody());
|
||||||
}
|
}
|
||||||
else if (clazz.isAssignableFrom(ByteArrayResource.class)) {
|
else if (clazz.isAssignableFrom(ByteArrayResource.class)) {
|
||||||
|
@ -94,6 +108,9 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
long contentLength = resource.contentLength();
|
long contentLength = resource.contentLength();
|
||||||
|
if (contentLength > Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
|
||||||
|
}
|
||||||
return (contentLength < 0 ? null : contentLength);
|
return (contentLength < 0 ? null : contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,18 +118,140 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
|
||||||
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
|
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
|
||||||
throws IOException, HttpMessageNotWritableException {
|
throws IOException, HttpMessageNotWritableException {
|
||||||
|
|
||||||
InputStream in = resource.getInputStream();
|
outputMessage.getHeaders().add(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||||
|
if (resource instanceof HttpRangeResource) {
|
||||||
|
writePartialContent((HttpRangeResource) resource, outputMessage);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeContent(resource, outputMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeContent(Resource resource, HttpOutputMessage outputMessage)
|
||||||
|
throws IOException, HttpMessageNotWritableException {
|
||||||
try {
|
try {
|
||||||
StreamUtils.copy(in, outputMessage.getBody());
|
InputStream in = resource.getInputStream();
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
try {
|
||||||
in.close();
|
StreamUtils.copy(in, outputMessage.getBody());
|
||||||
}
|
}
|
||||||
catch (IOException ex) {
|
catch (NullPointerException ex) {
|
||||||
|
// ignore, see SPR-13620
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
catch (Throwable ex) {
|
||||||
|
// ignore, see SPR-12999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex) {
|
||||||
|
// ignore, see SPR-12999
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write parts of the resource as indicated by the request {@code Range} header.
|
||||||
|
* @param resource the identified resource (never {@code null})
|
||||||
|
* @param outputMessage current servlet response
|
||||||
|
* @throws IOException in case of errors while writing the content
|
||||||
|
*/
|
||||||
|
protected void writePartialContent(HttpRangeResource resource, HttpOutputMessage outputMessage) throws IOException {
|
||||||
|
|
||||||
|
Assert.notNull(resource, "Resource should not be null");
|
||||||
|
|
||||||
|
List<HttpRange> ranges = resource.getHttpRanges();
|
||||||
|
HttpHeaders responseHeaders = outputMessage.getHeaders();
|
||||||
|
MediaType contentType = responseHeaders.getContentType();
|
||||||
|
Long length = getContentLength(resource, contentType);
|
||||||
|
|
||||||
|
if (ranges.size() == 1) {
|
||||||
|
HttpRange range = ranges.get(0);
|
||||||
|
|
||||||
|
long start = range.getRangeStart(length);
|
||||||
|
long end = range.getRangeEnd(length);
|
||||||
|
long rangeLength = end - start + 1;
|
||||||
|
|
||||||
|
responseHeaders.add("Content-Range", "bytes " + start + "-" + end + "/" + length);
|
||||||
|
responseHeaders.setContentLength((int) rangeLength);
|
||||||
|
|
||||||
|
InputStream in = resource.getInputStream();
|
||||||
|
try {
|
||||||
|
copyRange(in, outputMessage.getBody(), start, end);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String boundaryString = MimeTypeUtils.generateMultipartBoundaryString();
|
||||||
|
responseHeaders.set(HttpHeaders.CONTENT_TYPE, "multipart/byteranges; boundary=" + boundaryString);
|
||||||
|
|
||||||
|
OutputStream out = outputMessage.getBody();
|
||||||
|
|
||||||
|
for (HttpRange range : ranges) {
|
||||||
|
long start = range.getRangeStart(length);
|
||||||
|
long end = range.getRangeEnd(length);
|
||||||
|
|
||||||
|
InputStream in = resource.getInputStream();
|
||||||
|
|
||||||
|
// Writing MIME header.
|
||||||
|
println(out);
|
||||||
|
print(out, "--" + boundaryString);
|
||||||
|
println(out);
|
||||||
|
if (contentType != null) {
|
||||||
|
print(out, "Content-Type: " + contentType.toString());
|
||||||
|
println(out);
|
||||||
|
}
|
||||||
|
print(out, "Content-Range: bytes " + start + "-" + end + "/" + length);
|
||||||
|
println(out);
|
||||||
|
println(out);
|
||||||
|
|
||||||
|
// Printing content
|
||||||
|
copyRange(in, out, start, end);
|
||||||
|
}
|
||||||
|
println(out);
|
||||||
|
print(out, "--" + boundaryString + "--");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void println(OutputStream os) throws IOException {
|
||||||
|
os.write('\r');
|
||||||
|
os.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void print(OutputStream os, String buf) throws IOException {
|
||||||
|
os.write(buf.getBytes("US-ASCII"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
|
||||||
|
long skipped = in.skip(start);
|
||||||
|
if (skipped < start) {
|
||||||
|
throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
long bytesToCopy = end - start + 1;
|
||||||
|
byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
|
||||||
|
while (bytesToCopy > 0) {
|
||||||
|
int bytesRead = in.read(buffer);
|
||||||
|
if (bytesRead <= bytesToCopy) {
|
||||||
|
out.write(buffer, 0, bytesRead);
|
||||||
|
bytesToCopy -= bytesRead;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out.write(buffer, 0, (int) bytesToCopy);
|
||||||
|
bytesToCopy = 0;
|
||||||
|
}
|
||||||
|
if (bytesRead == -1) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outputMessage.getBody().flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,28 +16,41 @@
|
||||||
|
|
||||||
package org.springframework.http.converter;
|
package org.springframework.http.converter;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.Is.*;
|
||||||
|
import static org.hamcrest.core.IsInstanceOf.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.BDDMockito.*;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.core.io.ByteArrayResource;
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpRange;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.MockHttpInputMessage;
|
import org.springframework.http.MockHttpInputMessage;
|
||||||
import org.springframework.http.MockHttpOutputMessage;
|
import org.springframework.http.MockHttpOutputMessage;
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import static org.hamcrest.core.Is.*;
|
|
||||||
import static org.hamcrest.core.IsInstanceOf.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Kazuki Shimizu
|
* @author Kazuki Shimizu
|
||||||
|
* @author Brian Clozel
|
||||||
*/
|
*/
|
||||||
public class ResourceHttpMessageConverterTests {
|
public class ResourceHttpMessageConverterTests {
|
||||||
|
|
||||||
|
@ -45,18 +58,18 @@ public class ResourceHttpMessageConverterTests {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void canRead() {
|
public void canReadResource() {
|
||||||
assertTrue(converter.canRead(Resource.class, new MediaType("application", "octet-stream")));
|
assertTrue(converter.canRead(Resource.class, new MediaType("application", "octet-stream")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void canWrite() {
|
public void canWriteResource() {
|
||||||
assertTrue(converter.canWrite(Resource.class, new MediaType("application", "octet-stream")));
|
assertTrue(converter.canWrite(Resource.class, new MediaType("application", "octet-stream")));
|
||||||
assertTrue(converter.canWrite(Resource.class, MediaType.ALL));
|
assertTrue(converter.canWrite(Resource.class, MediaType.ALL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void read() throws IOException {
|
public void shouldReadImageResource() throws IOException {
|
||||||
byte[] body = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream("logo.jpg"));
|
byte[] body = FileCopyUtils.copyToByteArray(getClass().getResourceAsStream("logo.jpg"));
|
||||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
|
||||||
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
|
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
|
||||||
|
@ -65,7 +78,7 @@ public class ResourceHttpMessageConverterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-13443
|
@Test // SPR-13443
|
||||||
public void readWithInputStreamResource() throws IOException {
|
public void shouldReadInputStreamResource() throws IOException {
|
||||||
try (InputStream body = getClass().getResourceAsStream("logo.jpg") ) {
|
try (InputStream body = getClass().getResourceAsStream("logo.jpg") ) {
|
||||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
|
||||||
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
|
inputMessage.getHeaders().setContentType(MediaType.IMAGE_JPEG);
|
||||||
|
@ -76,7 +89,7 @@ public class ResourceHttpMessageConverterTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void write() throws IOException {
|
public void shouldWriteImageResource() throws IOException {
|
||||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
Resource body = new ClassPathResource("logo.jpg", getClass());
|
Resource body = new ClassPathResource("logo.jpg", getClass());
|
||||||
converter.write(body, null, outputMessage);
|
converter.write(body, null, outputMessage);
|
||||||
|
@ -85,6 +98,116 @@ public class ResourceHttpMessageConverterTests {
|
||||||
assertEquals("Invalid content-length", body.getFile().length(), outputMessage.getHeaders().getContentLength());
|
assertEquals("Invalid content-length", body.getFile().length(), outputMessage.getHeaders().getContentLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldWritePartialContentByteRange() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource body = new ClassPathResource("byterangeresource.txt", getClass());
|
||||||
|
List<HttpRange> httpRangeList = HttpRange.parseRanges("bytes=0-5");
|
||||||
|
|
||||||
|
converter.write(new HttpRangeResource(httpRangeList, body), MediaType.TEXT_PLAIN, outputMessage);
|
||||||
|
|
||||||
|
HttpHeaders headers = outputMessage.getHeaders();
|
||||||
|
assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
|
||||||
|
assertThat(headers.getContentLength(), is(6L));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 0-5/39"));
|
||||||
|
assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Spring"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldWritePartialContentByteRangeNoEnd() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource body = new ClassPathResource("byterangeresource.txt", getClass());
|
||||||
|
List<HttpRange> httpRangeList = HttpRange.parseRanges("bytes=7-");
|
||||||
|
|
||||||
|
converter.write(new HttpRangeResource(httpRangeList, body), MediaType.TEXT_PLAIN, outputMessage);
|
||||||
|
|
||||||
|
HttpHeaders headers = outputMessage.getHeaders();
|
||||||
|
assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
|
||||||
|
assertThat(headers.getContentLength(), is(32L));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 7-38/39"));
|
||||||
|
assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Framework test resource content."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldWritePartialContentByteRangeLargeEnd() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource body = new ClassPathResource("byterangeresource.txt", getClass());
|
||||||
|
List<HttpRange> httpRangeList = HttpRange.parseRanges("bytes=7-10000");
|
||||||
|
|
||||||
|
converter.write(new HttpRangeResource(httpRangeList, body), MediaType.TEXT_PLAIN, outputMessage);
|
||||||
|
|
||||||
|
HttpHeaders headers = outputMessage.getHeaders();
|
||||||
|
assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
|
||||||
|
assertThat(headers.getContentLength(), is(32L));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 7-38/39"));
|
||||||
|
assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Framework test resource content."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldWritePartialContentSuffixRange() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource body = new ClassPathResource("byterangeresource.txt", getClass());
|
||||||
|
List<HttpRange> httpRangeList = HttpRange.parseRanges("bytes=-8");
|
||||||
|
|
||||||
|
converter.write(new HttpRangeResource(httpRangeList, body), MediaType.TEXT_PLAIN, outputMessage);
|
||||||
|
|
||||||
|
HttpHeaders headers = outputMessage.getHeaders();
|
||||||
|
assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
|
||||||
|
assertThat(headers.getContentLength(), is(8L));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 31-38/39"));
|
||||||
|
assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("content."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldWritePartialContentSuffixRangeLargeSuffix() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource body = new ClassPathResource("byterangeresource.txt", getClass());
|
||||||
|
List<HttpRange> httpRangeList = HttpRange.parseRanges("bytes=-50");
|
||||||
|
|
||||||
|
converter.write(new HttpRangeResource(httpRangeList, body), MediaType.TEXT_PLAIN, outputMessage);
|
||||||
|
|
||||||
|
HttpHeaders headers = outputMessage.getHeaders();
|
||||||
|
assertThat(headers.getContentType(), is(MediaType.TEXT_PLAIN));
|
||||||
|
assertThat(headers.getContentLength(), is(39L));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).size(), is(1));
|
||||||
|
assertThat(headers.get(HttpHeaders.CONTENT_RANGE).get(0), is("bytes 0-38/39"));
|
||||||
|
assertThat(outputMessage.getBodyAsString(Charset.forName("UTF-8")), is("Spring Framework test resource content."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void partialContentMultipleByteRanges() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource body = new ClassPathResource("byterangeresource.txt", getClass());
|
||||||
|
List<HttpRange> httpRangeList = HttpRange.parseRanges("bytes=0-5, 7-15, 17-20");
|
||||||
|
|
||||||
|
converter.write(new HttpRangeResource(httpRangeList, body), MediaType.TEXT_PLAIN, outputMessage);
|
||||||
|
|
||||||
|
HttpHeaders headers = outputMessage.getHeaders();
|
||||||
|
assertThat(headers.getContentType().toString(), Matchers.startsWith("multipart/byteranges;boundary="));
|
||||||
|
String boundary = "--" + headers.getContentType().toString().substring(30);
|
||||||
|
String content = outputMessage.getBodyAsString(Charset.forName("UTF-8"));
|
||||||
|
String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n", false, true);
|
||||||
|
|
||||||
|
assertThat(ranges[0], is(boundary));
|
||||||
|
assertThat(ranges[1], is("Content-Type: text/plain"));
|
||||||
|
assertThat(ranges[2], is("Content-Range: bytes 0-5/39"));
|
||||||
|
assertThat(ranges[3], is("Spring"));
|
||||||
|
|
||||||
|
assertThat(ranges[4], is(boundary));
|
||||||
|
assertThat(ranges[5], is("Content-Type: text/plain"));
|
||||||
|
assertThat(ranges[6], is("Content-Range: bytes 7-15/39"));
|
||||||
|
assertThat(ranges[7], is("Framework"));
|
||||||
|
|
||||||
|
assertThat(ranges[8], is(boundary));
|
||||||
|
assertThat(ranges[9], is("Content-Type: text/plain"));
|
||||||
|
assertThat(ranges[10], is("Content-Range: bytes 17-20/39"));
|
||||||
|
assertThat(ranges[11], is("test"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test // SPR-10848
|
@Test // SPR-10848
|
||||||
public void writeByteArrayNullMediaType() throws IOException {
|
public void writeByteArrayNullMediaType() throws IOException {
|
||||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
@ -94,4 +217,45 @@ public class ResourceHttpMessageConverterTests {
|
||||||
assertTrue(Arrays.equals(byteArray, outputMessage.getBodyAsBytes()));
|
assertTrue(Arrays.equals(byteArray, outputMessage.getBodyAsBytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SPR-12999
|
||||||
|
@Test @SuppressWarnings("unchecked")
|
||||||
|
public void writeContentNotGettingInputStream() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource resource = mock(Resource.class);
|
||||||
|
given(resource.getInputStream()).willThrow(FileNotFoundException.class);
|
||||||
|
|
||||||
|
converter.write(resource, MediaType.APPLICATION_OCTET_STREAM, outputMessage);
|
||||||
|
|
||||||
|
assertEquals(0, outputMessage.getHeaders().getContentLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPR-12999
|
||||||
|
@Test
|
||||||
|
public void writeContentNotClosingInputStream() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource resource = mock(Resource.class);
|
||||||
|
InputStream inputStream = mock(InputStream.class);
|
||||||
|
given(resource.getInputStream()).willReturn(inputStream);
|
||||||
|
given(inputStream.read(any())).willReturn(-1);
|
||||||
|
doThrow(new NullPointerException()).when(inputStream).close();
|
||||||
|
|
||||||
|
converter.write(resource, MediaType.APPLICATION_OCTET_STREAM, outputMessage);
|
||||||
|
|
||||||
|
assertEquals(0, outputMessage.getHeaders().getContentLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPR-13620
|
||||||
|
@Test @SuppressWarnings("unchecked")
|
||||||
|
public void writeContentInputStreamThrowingNullPointerException() throws Exception {
|
||||||
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
Resource resource = mock(Resource.class);
|
||||||
|
InputStream in = mock(InputStream.class);
|
||||||
|
given(resource.getInputStream()).willReturn(in);
|
||||||
|
given(in.read(any())).willThrow(NullPointerException.class);
|
||||||
|
|
||||||
|
converter.write(resource, MediaType.APPLICATION_OCTET_STREAM, outputMessage);
|
||||||
|
|
||||||
|
assertEquals(0, outputMessage.getHeaders().getContentLength());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Spring Framework test resource content.
|
|
@ -26,9 +26,12 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpRange;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@ -194,6 +197,19 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(inputMessage.getHeaders().containsKey(HttpHeaders.RANGE) &&
|
||||||
|
Resource.class.isAssignableFrom(body.getClass())) {
|
||||||
|
try {
|
||||||
|
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
|
||||||
|
Resource bodyResource = (Resource) body;
|
||||||
|
body = new HttpRangeResource(httpRanges, bodyResource);
|
||||||
|
outputMessage.setStatusCode(HttpStatus.PARTIAL_CONTENT);
|
||||||
|
} catch (IllegalArgumentException exc) {
|
||||||
|
outputMessage.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
outputMessage.flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try even with null body. ResponseBodyAdvice could get involved.
|
// Try even with null body. ResponseBodyAdvice could get involved.
|
||||||
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
|
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
|
||||||
|
|
|
@ -24,10 +24,16 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import org.springframework.core.Conventions;
|
import org.springframework.core.Conventions;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpRange;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||||
|
@ -164,9 +170,25 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||||
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
|
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
|
||||||
|
|
||||||
mavContainer.setRequestHandled(true);
|
mavContainer.setRequestHandled(true);
|
||||||
|
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
|
||||||
|
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
|
||||||
|
|
||||||
|
if(inputMessage.getHeaders().containsKey(HttpHeaders.RANGE) &&
|
||||||
|
Resource.class.isAssignableFrom(returnValue.getClass())) {
|
||||||
|
try {
|
||||||
|
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
|
||||||
|
Resource bodyResource = (Resource) returnValue;
|
||||||
|
returnValue = new HttpRangeResource(httpRanges, bodyResource);
|
||||||
|
outputMessage.setStatusCode(HttpStatus.PARTIAL_CONTENT);
|
||||||
|
} catch (IllegalArgumentException exc) {
|
||||||
|
outputMessage.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
outputMessage.flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try even with null return value. ResponseBodyAdvice could get involved.
|
// Try even with null return value. ResponseBodyAdvice could get involved.
|
||||||
writeWithMessageConverters(returnValue, returnType, webRequest);
|
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,12 @@
|
||||||
|
|
||||||
package org.springframework.web.servlet.resource;
|
package org.springframework.web.servlet.resource;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@ -36,15 +33,15 @@ import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpRange;
|
import org.springframework.http.HttpRange;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||||
import org.springframework.http.server.ServletServerHttpRequest;
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.MimeTypeUtils;
|
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.util.ResourceUtils;
|
import org.springframework.util.ResourceUtils;
|
||||||
import org.springframework.util.StreamUtils;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||||
import org.springframework.web.HttpRequestHandler;
|
import org.springframework.web.HttpRequestHandler;
|
||||||
|
@ -94,16 +91,14 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
|
private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
|
||||||
|
|
||||||
private static final boolean jafPresent = ClassUtils.isPresent(
|
|
||||||
"javax.activation.FileTypeMap", ResourceHttpRequestHandler.class.getClassLoader());
|
|
||||||
|
|
||||||
|
|
||||||
private final List<Resource> locations = new ArrayList<Resource>(4);
|
private final List<Resource> locations = new ArrayList<Resource>(4);
|
||||||
|
|
||||||
private final List<ResourceResolver> resourceResolvers = new ArrayList<ResourceResolver>(4);
|
private final List<ResourceResolver> resourceResolvers = new ArrayList<ResourceResolver>(4);
|
||||||
|
|
||||||
private final List<ResourceTransformer> resourceTransformers = new ArrayList<ResourceTransformer>(4);
|
private final List<ResourceTransformer> resourceTransformers = new ArrayList<ResourceTransformer>(4);
|
||||||
|
|
||||||
|
private ResourceHttpMessageConverter resourceHttpMessageConverter;
|
||||||
|
|
||||||
private ContentNegotiationManager contentNegotiationManager;
|
private ContentNegotiationManager contentNegotiationManager;
|
||||||
|
|
||||||
private CorsConfiguration corsConfiguration;
|
private CorsConfiguration corsConfiguration;
|
||||||
|
@ -165,6 +160,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
return this.resourceTransformers;
|
return this.resourceTransformers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the {@link ResourceHttpMessageConverter} to use.
|
||||||
|
* <p>By default a {@link ResourceHttpMessageConverter} will be configured.
|
||||||
|
* @since 4.3.0
|
||||||
|
*/
|
||||||
|
public void setResourceHttpMessageConverter(ResourceHttpMessageConverter resourceHttpMessageConverter) {
|
||||||
|
this.resourceHttpMessageConverter = resourceHttpMessageConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceHttpMessageConverter getResourceHttpMessageConverter() {
|
||||||
|
return resourceHttpMessageConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a {@code ContentNegotiationManager} to determine the media types
|
* Configure a {@code ContentNegotiationManager} to determine the media types
|
||||||
* for resources being served. If the manager contains a path
|
* for resources being served. If the manager contains a path
|
||||||
|
@ -177,7 +186,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
* settings is used to create the manager. See the Javadoc of
|
* settings is used to create the manager. See the Javadoc of
|
||||||
* {@code ContentNegotiationManagerFactoryBean} for details
|
* {@code ContentNegotiationManagerFactoryBean} for details
|
||||||
* @param contentNegotiationManager the manager to use
|
* @param contentNegotiationManager the manager to use
|
||||||
* @since 4.3
|
* @since 4.3.0
|
||||||
*/
|
*/
|
||||||
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
||||||
this.contentNegotiationManager = contentNegotiationManager;
|
this.contentNegotiationManager = contentNegotiationManager;
|
||||||
|
@ -215,6 +224,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
if (this.contentNegotiationManager == null) {
|
if (this.contentNegotiationManager == null) {
|
||||||
this.contentNegotiationManager = initContentNegotiationManager();
|
this.contentNegotiationManager = initContentNegotiationManager();
|
||||||
}
|
}
|
||||||
|
if( this.resourceHttpMessageConverter == null) {
|
||||||
|
this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,7 +238,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
if (CollectionUtils.isEmpty(this.locations)) {
|
if (CollectionUtils.isEmpty(this.locations)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = getResourceResolvers().size()-1; i >= 0; i--) {
|
for (int i = getResourceResolvers().size() - 1; i >= 0; i--) {
|
||||||
if (getResourceResolvers().get(i) instanceof PathResourceResolver) {
|
if (getResourceResolvers().get(i) instanceof PathResourceResolver) {
|
||||||
PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i);
|
PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i);
|
||||||
if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
|
if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
|
||||||
|
@ -310,12 +322,26 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
|
||||||
if (request.getHeader(HttpHeaders.RANGE) == null) {
|
if (request.getHeader(HttpHeaders.RANGE) == null) {
|
||||||
setHeaders(response, resource, mediaType);
|
setHeaders(response, resource, mediaType);
|
||||||
writeContent(response, resource);
|
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
writePartialContent(request, response, resource, mediaType);
|
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
|
||||||
|
try {
|
||||||
|
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
|
||||||
|
HttpRangeResource rangeResource = new HttpRangeResource(httpRanges, resource);
|
||||||
|
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
||||||
|
this.resourceHttpMessageConverter.write(rangeResource, mediaType, outputMessage);
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ex) {
|
||||||
|
Long contentLength = resource.contentLength();
|
||||||
|
if (contentLength != null) {
|
||||||
|
response.addHeader("Content-Range", "bytes */" + resource.contentLength());
|
||||||
|
}
|
||||||
|
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,140 +533,6 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||||
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
|
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the actual content out to the given servlet response,
|
|
||||||
* streaming the resource's content.
|
|
||||||
* @param response current servlet response
|
|
||||||
* @param resource the identified resource (never {@code null})
|
|
||||||
* @throws IOException in case of errors while writing the content
|
|
||||||
*/
|
|
||||||
protected void writeContent(HttpServletResponse response, Resource resource) throws IOException {
|
|
||||||
try {
|
|
||||||
InputStream in = resource.getInputStream();
|
|
||||||
try {
|
|
||||||
StreamUtils.copy(in, response.getOutputStream());
|
|
||||||
}
|
|
||||||
catch (NullPointerException ex) {
|
|
||||||
// ignore, see SPR-13620
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
catch (Throwable ex) {
|
|
||||||
// ignore, see SPR-12999
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException ex) {
|
|
||||||
// ignore, see SPR-12999
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write parts of the resource as indicated by the request {@code Range} header.
|
|
||||||
* @param request current servlet request
|
|
||||||
* @param response current servlet response
|
|
||||||
* @param resource the identified resource (never {@code null})
|
|
||||||
* @param contentType the content type
|
|
||||||
* @throws IOException in case of errors while writing the content
|
|
||||||
*/
|
|
||||||
protected void writePartialContent(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
Resource resource, MediaType contentType) throws IOException {
|
|
||||||
|
|
||||||
long length = resource.contentLength();
|
|
||||||
|
|
||||||
List<HttpRange> ranges;
|
|
||||||
try {
|
|
||||||
HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
|
|
||||||
ranges = headers.getRange();
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException ex) {
|
|
||||||
response.addHeader("Content-Range", "bytes */" + length);
|
|
||||||
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
|
|
||||||
|
|
||||||
if (ranges.size() == 1) {
|
|
||||||
HttpRange range = ranges.get(0);
|
|
||||||
|
|
||||||
long start = range.getRangeStart(length);
|
|
||||||
long end = range.getRangeEnd(length);
|
|
||||||
long rangeLength = end - start + 1;
|
|
||||||
|
|
||||||
setHeaders(response, resource, contentType);
|
|
||||||
response.addHeader("Content-Range", "bytes " + start + "-" + end + "/" + length);
|
|
||||||
response.setContentLength((int) rangeLength);
|
|
||||||
|
|
||||||
InputStream in = resource.getInputStream();
|
|
||||||
try {
|
|
||||||
copyRange(in, response.getOutputStream(), start, end);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
in.close();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
String boundaryString = MimeTypeUtils.generateMultipartBoundaryString();
|
|
||||||
response.setContentType("multipart/byteranges; boundary=" + boundaryString);
|
|
||||||
|
|
||||||
ServletOutputStream out = response.getOutputStream();
|
|
||||||
|
|
||||||
for (HttpRange range : ranges) {
|
|
||||||
long start = range.getRangeStart(length);
|
|
||||||
long end = range.getRangeEnd(length);
|
|
||||||
|
|
||||||
InputStream in = resource.getInputStream();
|
|
||||||
|
|
||||||
// Writing MIME header.
|
|
||||||
out.println();
|
|
||||||
out.println("--" + boundaryString);
|
|
||||||
if (contentType != null) {
|
|
||||||
out.println("Content-Type: " + contentType);
|
|
||||||
}
|
|
||||||
out.println("Content-Range: bytes " + start + "-" + end + "/" + length);
|
|
||||||
out.println();
|
|
||||||
|
|
||||||
// Printing content
|
|
||||||
copyRange(in, out, start, end);
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
out.print("--" + boundaryString + "--");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
|
|
||||||
long skipped = in.skip(start);
|
|
||||||
if (skipped < start) {
|
|
||||||
throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
long bytesToCopy = end - start + 1;
|
|
||||||
byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
|
|
||||||
while (bytesToCopy > 0) {
|
|
||||||
int bytesRead = in.read(buffer);
|
|
||||||
if (bytesRead <= bytesToCopy) {
|
|
||||||
out.write(buffer, 0, bytesRead);
|
|
||||||
bytesToCopy -= bytesRead;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
out.write(buffer, 0, (int) bytesToCopy);
|
|
||||||
bytesToCopy = 0;
|
|
||||||
}
|
|
||||||
if (bytesRead == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]";
|
return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]";
|
||||||
|
|
|
@ -21,7 +21,6 @@ import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -33,11 +32,14 @@ import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpInputMessage;
|
import org.springframework.http.HttpInputMessage;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpOutputMessage;
|
import org.springframework.http.HttpOutputMessage;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
|
@ -55,13 +57,7 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.BDDMockito.any;
|
import static org.mockito.BDDMockito.*;
|
||||||
import static org.mockito.BDDMockito.eq;
|
|
||||||
import static org.mockito.BDDMockito.given;
|
|
||||||
import static org.mockito.BDDMockito.isA;
|
|
||||||
import static org.mockito.BDDMockito.mock;
|
|
||||||
import static org.mockito.BDDMockito.reset;
|
|
||||||
import static org.mockito.BDDMockito.verify;
|
|
||||||
import static org.springframework.web.servlet.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
|
import static org.springframework.web.servlet.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +68,7 @@ import static org.springframework.web.servlet.HandlerMapping.PRODUCIBLE_MEDIA_TY
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
* @author Brian Clozel
|
||||||
*/
|
*/
|
||||||
public class HttpEntityMethodProcessorMockTests {
|
public class HttpEntityMethodProcessorMockTests {
|
||||||
|
|
||||||
|
@ -79,7 +76,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
|
|
||||||
private HttpEntityMethodProcessor processor;
|
private HttpEntityMethodProcessor processor;
|
||||||
|
|
||||||
private HttpMessageConverter<String> messageConverter;
|
private HttpMessageConverter<String> stringHttpMessageConverter;
|
||||||
|
|
||||||
|
private HttpMessageConverter<Resource> resourceMessageConverter;
|
||||||
|
|
||||||
private MethodParameter paramHttpEntity;
|
private MethodParameter paramHttpEntity;
|
||||||
private MethodParameter paramRequestEntity;
|
private MethodParameter paramRequestEntity;
|
||||||
|
@ -87,6 +86,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
private MethodParameter paramInt;
|
private MethodParameter paramInt;
|
||||||
private MethodParameter returnTypeResponseEntity;
|
private MethodParameter returnTypeResponseEntity;
|
||||||
private MethodParameter returnTypeResponseEntityProduces;
|
private MethodParameter returnTypeResponseEntityProduces;
|
||||||
|
private MethodParameter returnTypeResponseEntityResource;
|
||||||
private MethodParameter returnTypeHttpEntity;
|
private MethodParameter returnTypeHttpEntity;
|
||||||
private MethodParameter returnTypeHttpEntitySubclass;
|
private MethodParameter returnTypeHttpEntitySubclass;
|
||||||
private MethodParameter returnTypeInt;
|
private MethodParameter returnTypeInt;
|
||||||
|
@ -106,12 +106,16 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
|
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
|
||||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||||
|
|
||||||
messageConverter = mock(HttpMessageConverter.class);
|
stringHttpMessageConverter = mock(HttpMessageConverter.class);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
|
resourceMessageConverter = mock(HttpMessageConverter.class);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
List<HttpMessageConverter<?>> converters = new ArrayList<>();
|
List<HttpMessageConverter<?>> converters = new ArrayList<>();
|
||||||
converters.add(messageConverter);
|
converters.add(stringHttpMessageConverter);
|
||||||
|
converters.add(resourceMessageConverter);
|
||||||
processor = new HttpEntityMethodProcessor(converters);
|
processor = new HttpEntityMethodProcessor(converters);
|
||||||
reset(messageConverter);
|
reset(stringHttpMessageConverter);
|
||||||
|
reset(resourceMessageConverter);
|
||||||
|
|
||||||
Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class,
|
Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class,
|
||||||
Integer.TYPE, RequestEntity.class);
|
Integer.TYPE, RequestEntity.class);
|
||||||
|
@ -125,6 +129,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
returnTypeHttpEntity = new MethodParameter(getClass().getMethod("handle2", HttpEntity.class), -1);
|
returnTypeHttpEntity = new MethodParameter(getClass().getMethod("handle2", HttpEntity.class), -1);
|
||||||
returnTypeHttpEntitySubclass = new MethodParameter(getClass().getMethod("handle2x", HttpEntity.class), -1);
|
returnTypeHttpEntitySubclass = new MethodParameter(getClass().getMethod("handle2x", HttpEntity.class), -1);
|
||||||
returnTypeInt = new MethodParameter(getClass().getMethod("handle3"), -1);
|
returnTypeInt = new MethodParameter(getClass().getMethod("handle3"), -1);
|
||||||
|
returnTypeResponseEntityResource = new MethodParameter(getClass().getMethod("handle5"), -1);
|
||||||
|
|
||||||
mavContainer = new ModelAndViewContainer();
|
mavContainer = new ModelAndViewContainer();
|
||||||
servletRequest = new MockHttpServletRequest("GET", "/foo");
|
servletRequest = new MockHttpServletRequest("GET", "/foo");
|
||||||
|
@ -159,8 +164,8 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||||
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
||||||
|
|
||||||
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
|
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
given(stringHttpMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||||
|
|
||||||
Object result = processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
Object result = processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
||||||
|
|
||||||
|
@ -181,8 +186,8 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
servletRequest.setRequestURI("/path");
|
servletRequest.setRequestURI("/path");
|
||||||
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
||||||
|
|
||||||
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
|
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
given(stringHttpMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||||
|
|
||||||
Object result = processor.resolveArgument(paramRequestEntity, mavContainer, webRequest, null);
|
Object result = processor.resolveArgument(paramRequestEntity, mavContainer, webRequest, null);
|
||||||
|
|
||||||
|
@ -201,8 +206,8 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
servletRequest.setMethod("POST");
|
servletRequest.setMethod("POST");
|
||||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||||
|
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
|
||||||
given(messageConverter.canRead(String.class, contentType)).willReturn(false);
|
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(false);
|
||||||
|
|
||||||
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
||||||
|
|
||||||
|
@ -230,7 +235,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
verify(stringHttpMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -241,12 +246,12 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
servletRequest.addHeader("Accept", "text/*");
|
servletRequest.addHeader("Accept", "text/*");
|
||||||
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
||||||
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
verify(messageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
verify(stringHttpMessageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -262,15 +267,15 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
|
given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
|
||||||
|
|
||||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||||
Collections.singletonList(messageConverter), null, Collections.singletonList(advice));
|
Collections.singletonList(stringHttpMessageConverter), null, Collections.singletonList(advice));
|
||||||
|
|
||||||
reset(messageConverter);
|
reset(stringHttpMessageConverter);
|
||||||
given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
||||||
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
verify(messageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
verify(stringHttpMessageConverter).write(eq("Foo"), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
@Test(expected = HttpMediaTypeNotAcceptableException.class)
|
||||||
|
@ -281,9 +286,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||||
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
|
@ -298,9 +303,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
MediaType accepted = MediaType.TEXT_PLAIN;
|
MediaType accepted = MediaType.TEXT_PLAIN;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||||
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
||||||
|
|
||||||
|
@ -340,7 +345,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||||
|
|
||||||
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
||||||
verify(messageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
verify(stringHttpMessageConverter).write(eq("body"), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
|
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
|
||||||
}
|
}
|
||||||
|
@ -519,50 +524,58 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void varyHeader() throws Exception {
|
public void handleReturnTypeResource() throws Exception {
|
||||||
String[] entityValues = {"Accept-Language", "User-Agent"};
|
ResponseEntity<Resource> returnValue = ResponseEntity
|
||||||
String[] existingValues = {};
|
.ok(new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8"))));
|
||||||
String[] expected = {"Accept-Language, User-Agent"};
|
|
||||||
testVaryHeader(entityValues, existingValues, expected);
|
given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
|
given(resourceMessageConverter.canWrite(ByteArrayResource.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(true);
|
||||||
|
|
||||||
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest);
|
||||||
|
|
||||||
|
then(resourceMessageConverter).should(times(1)).write(any(ByteArrayResource.class),
|
||||||
|
eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
|
||||||
|
assertEquals(200, servletResponse.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void varyHeaderWithExistingWildcard() throws Exception {
|
public void handleReturnTypeResourceByteRange() throws Exception {
|
||||||
String[] entityValues = {"Accept-Language"};
|
Resource resource = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||||
String[] existingValues = {"*"};
|
ResponseEntity<Resource> returnValue = ResponseEntity.ok(resource);
|
||||||
String[] expected = {"*"};
|
servletRequest.addHeader("Range", "bytes=0-5");
|
||||||
testVaryHeader(entityValues, existingValues, expected);
|
|
||||||
|
given(resourceMessageConverter.canWrite(HttpRangeResource.class, null)).willReturn(true);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
|
given(resourceMessageConverter.canWrite(HttpRangeResource.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(true);
|
||||||
|
|
||||||
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest);
|
||||||
|
|
||||||
|
then(resourceMessageConverter).should(times(1)).write(any(ByteArrayResource.class),
|
||||||
|
eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
|
||||||
|
assertEquals(206, servletResponse.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void varyHeaderWithExistingCommaValues() throws Exception {
|
public void handleReturnTypeResourceIllegalByteRange() throws Exception {
|
||||||
String[] entityValues = {"Accept-Language", "User-Agent"};
|
Resource resource = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||||
String[] existingValues = {"Accept-Encoding", "Accept-Language"};
|
ResponseEntity<Resource> returnValue = ResponseEntity.ok(resource);
|
||||||
String[] expected = {"Accept-Encoding", "Accept-Language", "User-Agent"};
|
servletRequest.addHeader("Range", "illegal");
|
||||||
testVaryHeader(entityValues, existingValues, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
|
||||||
public void varyHeaderWithExistingCommaSeparatedValues() throws Exception {
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
String[] entityValues = {"Accept-Language", "User-Agent"};
|
|
||||||
String[] existingValues = {"Accept-Encoding, Accept-Language"};
|
|
||||||
String[] expected = {"Accept-Encoding, Accept-Language", "User-Agent"};
|
|
||||||
testVaryHeader(entityValues, existingValues, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest);
|
||||||
public void handleReturnValueVaryHeader() throws Exception {
|
|
||||||
String[] entityValues = {"Accept-Language", "User-Agent"};
|
|
||||||
String[] existingValues = {"Accept-Encoding, Accept-Language"};
|
|
||||||
String[] expected = {"Accept-Encoding, Accept-Language", "User-Agent"};
|
|
||||||
testVaryHeader(entityValues, existingValues, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
then(resourceMessageConverter).should(never()).write(any(ByteArrayResource.class),
|
||||||
|
eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
|
||||||
|
assertEquals(416, servletResponse.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
private void initStringMessageConversion(MediaType accepted) {
|
private void initStringMessageConversion(MediaType accepted) {
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
|
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertResponseNotModified() {
|
private void assertResponseNotModified() {
|
||||||
|
@ -575,23 +588,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
|
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
|
||||||
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
ArgumentCaptor<HttpOutputMessage> outputMessage = ArgumentCaptor.forClass(HttpOutputMessage.class);
|
||||||
verify(messageConverter).write(eq(body), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
verify(stringHttpMessageConverter).write(eq(body), eq(MediaType.TEXT_PLAIN), outputMessage.capture());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testVaryHeader(String[] entityValues, String[] existingValues, String[] expected) throws Exception {
|
|
||||||
ResponseEntity<String> returnValue = ResponseEntity.ok().varyBy(entityValues).body("Foo");
|
|
||||||
for (String value : existingValues) {
|
|
||||||
servletResponse.addHeader("Vary", value);
|
|
||||||
}
|
|
||||||
initStringMessageConversion(MediaType.TEXT_PLAIN);
|
|
||||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
|
||||||
assertEquals(Arrays.asList(expected), servletResponse.getHeaders("Vary"));
|
|
||||||
verify(messageConverter).write(eq("Foo"), eq(MediaType.TEXT_PLAIN), isA(HttpOutputMessage.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> entity,
|
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> entity,
|
||||||
int i, RequestEntity<String> requestEntity) {
|
int i, RequestEntity<String> requestEntity) {
|
||||||
|
@ -620,8 +619,11 @@ public class HttpEntityMethodProcessorMockTests {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public ResponseEntity<Resource> handle5() {return null;}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class CustomHttpEntity extends HttpEntity<Object> {
|
public static class CustomHttpEntity extends HttpEntity<Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -32,8 +32,11 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpInputMessage;
|
import org.springframework.http.HttpInputMessage;
|
||||||
import org.springframework.http.HttpOutputMessage;
|
import org.springframework.http.HttpOutputMessage;
|
||||||
|
import org.springframework.http.HttpRangeResource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
@ -65,7 +68,9 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
|
|
||||||
private RequestResponseBodyMethodProcessor processor;
|
private RequestResponseBodyMethodProcessor processor;
|
||||||
|
|
||||||
private HttpMessageConverter<String> messageConverter;
|
private HttpMessageConverter<String> stringMessageConverter;
|
||||||
|
|
||||||
|
private HttpMessageConverter<Resource> resourceMessageConverter;
|
||||||
|
|
||||||
private MethodParameter paramRequestBodyString;
|
private MethodParameter paramRequestBodyString;
|
||||||
private MethodParameter paramInt;
|
private MethodParameter paramInt;
|
||||||
|
@ -74,6 +79,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
private MethodParameter returnTypeString;
|
private MethodParameter returnTypeString;
|
||||||
private MethodParameter returnTypeInt;
|
private MethodParameter returnTypeInt;
|
||||||
private MethodParameter returnTypeStringProduces;
|
private MethodParameter returnTypeStringProduces;
|
||||||
|
private MethodParameter returnTypeResource;
|
||||||
|
|
||||||
private ModelAndViewContainer mavContainer;
|
private ModelAndViewContainer mavContainer;
|
||||||
|
|
||||||
|
@ -81,14 +87,19 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
|
|
||||||
private MockHttpServletRequest servletRequest;
|
private MockHttpServletRequest servletRequest;
|
||||||
|
|
||||||
|
private MockHttpServletResponse servletResponse;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
messageConverter = mock(HttpMessageConverter.class);
|
stringMessageConverter = mock(HttpMessageConverter.class);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
|
|
||||||
processor = new RequestResponseBodyMethodProcessor(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
|
resourceMessageConverter = mock(HttpMessageConverter.class);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
|
|
||||||
|
processor = new RequestResponseBodyMethodProcessor(Arrays.asList(stringMessageConverter, resourceMessageConverter));
|
||||||
|
|
||||||
Method methodHandle1 = getClass().getMethod("handle1", String.class, Integer.TYPE);
|
Method methodHandle1 = getClass().getMethod("handle1", String.class, Integer.TYPE);
|
||||||
paramRequestBodyString = new MethodParameter(methodHandle1, 0);
|
paramRequestBodyString = new MethodParameter(methodHandle1, 0);
|
||||||
|
@ -96,6 +107,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
returnTypeString = new MethodParameter(methodHandle1, -1);
|
returnTypeString = new MethodParameter(methodHandle1, -1);
|
||||||
returnTypeInt = new MethodParameter(getClass().getMethod("handle2"), -1);
|
returnTypeInt = new MethodParameter(getClass().getMethod("handle2"), -1);
|
||||||
returnTypeStringProduces = new MethodParameter(getClass().getMethod("handle3"), -1);
|
returnTypeStringProduces = new MethodParameter(getClass().getMethod("handle3"), -1);
|
||||||
|
returnTypeResource = new MethodParameter(getClass().getMethod("handle6"), -1);
|
||||||
paramValidBean = new MethodParameter(getClass().getMethod("handle4", SimpleBean.class), 0);
|
paramValidBean = new MethodParameter(getClass().getMethod("handle4", SimpleBean.class), 0);
|
||||||
paramStringNotRequired = new MethodParameter(getClass().getMethod("handle5", String.class), 0);
|
paramStringNotRequired = new MethodParameter(getClass().getMethod("handle5", String.class), 0);
|
||||||
|
|
||||||
|
@ -103,7 +115,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
|
|
||||||
servletRequest = new MockHttpServletRequest();
|
servletRequest = new MockHttpServletRequest();
|
||||||
servletRequest.setMethod("POST");
|
servletRequest.setMethod("POST");
|
||||||
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
|
servletResponse = new MockHttpServletResponse();
|
||||||
|
webRequest = new ServletWebRequest(servletRequest, servletResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -126,8 +139,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
||||||
|
|
||||||
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
|
given(stringMessageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
given(stringMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||||
|
|
||||||
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory());
|
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory());
|
||||||
|
|
||||||
|
@ -174,7 +187,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||||
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
|
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
|
||||||
|
|
||||||
given(messageConverter.canRead(String.class, contentType)).willReturn(false);
|
given(stringMessageConverter.canRead(String.class, contentType)).willReturn(false);
|
||||||
|
|
||||||
processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
|
processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
|
||||||
}
|
}
|
||||||
|
@ -182,7 +195,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
@Test(expected = HttpMediaTypeNotSupportedException.class)
|
@Test(expected = HttpMediaTypeNotSupportedException.class)
|
||||||
public void resolveArgumentNoContentType() throws Exception {
|
public void resolveArgumentNoContentType() throws Exception {
|
||||||
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
|
servletRequest.setContent("payload".getBytes(Charset.forName("UTF-8")));
|
||||||
given(messageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
||||||
processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
|
processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,8 +212,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
public void resolveArgumentRequiredNoContent() throws Exception {
|
public void resolveArgumentRequiredNoContent() throws Exception {
|
||||||
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
|
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
|
||||||
servletRequest.setContent(new byte[0]);
|
servletRequest.setContent(new byte[0]);
|
||||||
given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
||||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(null);
|
given(stringMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(null);
|
||||||
assertNull(processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory()));
|
assertNull(processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +221,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
public void resolveArgumentNotRequiredNoContent() throws Exception {
|
public void resolveArgumentNotRequiredNoContent() throws Exception {
|
||||||
servletRequest.setContentType("text/plain");
|
servletRequest.setContentType("text/plain");
|
||||||
servletRequest.setContent(new byte[0]);
|
servletRequest.setContent(new byte[0]);
|
||||||
given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
||||||
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,8 +229,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
@Test
|
@Test
|
||||||
public void resolveArgumentNotRequiredNoContentNoContentType() throws Exception {
|
public void resolveArgumentNotRequiredNoContentNoContentType() throws Exception {
|
||||||
servletRequest.setContent(new byte[0]);
|
servletRequest.setContent(new byte[0]);
|
||||||
given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
||||||
given(messageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
||||||
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +238,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
public void resolveArgumentNotGetRequests() throws Exception {
|
public void resolveArgumentNotGetRequests() throws Exception {
|
||||||
servletRequest.setMethod("GET");
|
servletRequest.setMethod("GET");
|
||||||
servletRequest.setContent(new byte[0]);
|
servletRequest.setContent(new byte[0]);
|
||||||
given(messageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
||||||
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,14 +248,14 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
String body = "Foo";
|
String body = "Foo";
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||||
|
|
||||||
processor.handleReturnValue(body, returnTypeString, mavContainer, webRequest);
|
processor.handleReturnValue(body, returnTypeString, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue("The requestHandled flag wasn't set", mavContainer.isRequestHandled());
|
assertTrue("The requestHandled flag wasn't set", mavContainer.isRequestHandled());
|
||||||
verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
verify(stringMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -252,12 +265,12 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
servletRequest.addHeader("Accept", "text/*");
|
servletRequest.addHeader("Accept", "text/*");
|
||||||
servletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
servletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
||||||
|
|
||||||
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
|
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
verify(messageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
verify(stringMessageConverter).write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -266,9 +279,9 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
|
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||||
|
|
||||||
processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
|
processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
|
||||||
}
|
}
|
||||||
|
@ -278,13 +291,59 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
MediaType accepted = MediaType.TEXT_PLAIN;
|
MediaType accepted = MediaType.TEXT_PLAIN;
|
||||||
servletRequest.addHeader("Accept", accepted.toString());
|
servletRequest.addHeader("Accept", accepted.toString());
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||||
|
|
||||||
processor.handleReturnValue("Foo", returnTypeStringProduces, mavContainer, webRequest);
|
processor.handleReturnValue("Foo", returnTypeStringProduces, mavContainer, webRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleReturnTypeResource() throws Exception {
|
||||||
|
Resource returnValue = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||||
|
|
||||||
|
given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
|
given(resourceMessageConverter.canWrite(ByteArrayResource.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(true);
|
||||||
|
|
||||||
|
processor.handleReturnValue(returnValue, returnTypeResource, mavContainer, webRequest);
|
||||||
|
|
||||||
|
then(resourceMessageConverter).should(times(1)).write(any(ByteArrayResource.class),
|
||||||
|
eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
|
||||||
|
assertEquals(200, servletResponse.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleReturnTypeResourceByteRange() throws Exception {
|
||||||
|
Resource returnValue = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||||
|
servletRequest.addHeader("Range", "bytes=0-5");
|
||||||
|
|
||||||
|
given(resourceMessageConverter.canWrite(HttpRangeResource.class, null)).willReturn(true);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
|
given(resourceMessageConverter.canWrite(HttpRangeResource.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(true);
|
||||||
|
|
||||||
|
processor.handleReturnValue(returnValue, returnTypeResource, mavContainer, webRequest);
|
||||||
|
|
||||||
|
then(resourceMessageConverter).should(times(1)).write(any(ByteArrayResource.class),
|
||||||
|
eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
|
||||||
|
assertEquals(206, servletResponse.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleReturnTypeResourceIllegalByteRange() throws Exception {
|
||||||
|
Resource returnValue = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||||
|
servletRequest.addHeader("Range", "illegal");
|
||||||
|
|
||||||
|
given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
|
||||||
|
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||||
|
|
||||||
|
processor.handleReturnValue(returnValue, returnTypeResource, mavContainer, webRequest);
|
||||||
|
|
||||||
|
then(resourceMessageConverter).should(never()).write(any(ByteArrayResource.class),
|
||||||
|
eq(MediaType.APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class));
|
||||||
|
assertEquals(416, servletResponse.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
// SPR-9841
|
// SPR-9841
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -295,14 +354,14 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
|
|
||||||
servletRequest.addHeader("Accept", accepted);
|
servletRequest.addHeader("Accept", accepted);
|
||||||
|
|
||||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||||
given(messageConverter.getSupportedMediaTypes()).willReturn(supported);
|
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(supported);
|
||||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
|
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||||
|
|
||||||
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
|
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
|
||||||
|
|
||||||
assertTrue(mavContainer.isRequestHandled());
|
assertTrue(mavContainer.isRequestHandled());
|
||||||
verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
verify(stringMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,6 +390,10 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||||
public void handle5(@RequestBody(required=false) String s) {
|
public void handle5(@RequestBody(required=false) String s) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@ResponseBody
|
||||||
|
public Resource handle6() {return null;}
|
||||||
|
|
||||||
private final class ValidatingBinderFactory implements WebDataBinderFactory {
|
private final class ValidatingBinderFactory implements WebDataBinderFactory {
|
||||||
@Override
|
@Override
|
||||||
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
|
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
|
||||||
|
|
|
@ -17,12 +17,8 @@
|
||||||
package org.springframework.web.servlet.resource;
|
package org.springframework.web.servlet.resource;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.BDDMockito.given;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -531,47 +527,6 @@ public class ResourceHttpRequestHandlerTests {
|
||||||
assertEquals("t.", ranges[11]);
|
assertEquals("t.", ranges[11]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-12999
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
|
||||||
public void writeContentNotGettingInputStream() throws Exception {
|
|
||||||
Resource resource = mock(Resource.class);
|
|
||||||
given(resource.getInputStream()).willThrow(FileNotFoundException.class);
|
|
||||||
|
|
||||||
this.handler.writeContent(this.response, resource);
|
|
||||||
|
|
||||||
assertEquals(200, this.response.getStatus());
|
|
||||||
assertEquals(0, this.response.getContentLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPR-12999
|
|
||||||
@Test
|
|
||||||
public void writeContentNotClosingInputStream() throws Exception {
|
|
||||||
Resource resource = mock(Resource.class);
|
|
||||||
InputStream inputStream = mock(InputStream.class);
|
|
||||||
given(resource.getInputStream()).willReturn(inputStream);
|
|
||||||
given(inputStream.read(any())).willReturn(-1);
|
|
||||||
doThrow(new NullPointerException()).when(inputStream).close();
|
|
||||||
|
|
||||||
this.handler.writeContent(this.response, resource);
|
|
||||||
|
|
||||||
assertEquals(200, this.response.getStatus());
|
|
||||||
assertEquals(0, this.response.getContentLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPR-13620
|
|
||||||
@Test @SuppressWarnings("unchecked")
|
|
||||||
public void writeContentInputStreamThrowingNullPointerException() throws Exception {
|
|
||||||
Resource resource = mock(Resource.class);
|
|
||||||
InputStream in = mock(InputStream.class);
|
|
||||||
given(resource.getInputStream()).willReturn(in);
|
|
||||||
given(in.read(any())).willThrow(NullPointerException.class);
|
|
||||||
|
|
||||||
this.handler.writeContent(this.response, resource);
|
|
||||||
|
|
||||||
assertEquals(200, this.response.getStatus());
|
|
||||||
assertEquals(0, this.response.getContentLength());
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPR-14005
|
// SPR-14005
|
||||||
@Test
|
@Test
|
||||||
public void doOverwriteExistingCacheControlHeaders() throws Exception {
|
public void doOverwriteExistingCacheControlHeaders() throws Exception {
|
||||||
|
|
Loading…
Reference in New Issue