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:
@@ -26,9 +26,12 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.HttpRangeResource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -194,6 +197,19 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
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.
|
||||
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
|
||||
|
||||
@@ -24,10 +24,16 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.MethodParameter;
|
||||
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.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
@@ -164,9 +170,25 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
|
||||
|
||||
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.
|
||||
writeWithMessageConverters(returnValue, returnType, webRequest);
|
||||
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,15 +16,12 @@
|
||||
|
||||
package org.springframework.web.servlet.resource;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@@ -36,15 +33,15 @@ import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRange;
|
||||
import org.springframework.http.HttpRangeResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
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 boolean jafPresent = ClassUtils.isPresent(
|
||||
"javax.activation.FileTypeMap", ResourceHttpRequestHandler.class.getClassLoader());
|
||||
|
||||
|
||||
private final List<Resource> locations = new ArrayList<Resource>(4);
|
||||
|
||||
private final List<ResourceResolver> resourceResolvers = new ArrayList<ResourceResolver>(4);
|
||||
|
||||
private final List<ResourceTransformer> resourceTransformers = new ArrayList<ResourceTransformer>(4);
|
||||
|
||||
private ResourceHttpMessageConverter resourceHttpMessageConverter;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
private CorsConfiguration corsConfiguration;
|
||||
@@ -165,6 +160,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
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
|
||||
* 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
|
||||
* {@code ContentNegotiationManagerFactoryBean} for details
|
||||
* @param contentNegotiationManager the manager to use
|
||||
* @since 4.3
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
|
||||
this.contentNegotiationManager = contentNegotiationManager;
|
||||
@@ -215,6 +224,9 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
if (this.contentNegotiationManager == null) {
|
||||
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)) {
|
||||
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) {
|
||||
PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i);
|
||||
if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
|
||||
@@ -310,12 +322,26 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
return;
|
||||
}
|
||||
|
||||
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
|
||||
if (request.getHeader(HttpHeaders.RANGE) == null) {
|
||||
setHeaders(response, resource, mediaType);
|
||||
writeContent(response, resource);
|
||||
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public String toString() {
|
||||
return "ResourceHttpRequestHandler [locations=" + getLocations() + ", resolvers=" + getResourceResolvers() + "]";
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -33,11 +32,14 @@ import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
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.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.HttpRangeResource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
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.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.BDDMockito.any;
|
||||
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.mockito.BDDMockito.*;
|
||||
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 Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class HttpEntityMethodProcessorMockTests {
|
||||
|
||||
@@ -79,7 +76,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
|
||||
private HttpEntityMethodProcessor processor;
|
||||
|
||||
private HttpMessageConverter<String> messageConverter;
|
||||
private HttpMessageConverter<String> stringHttpMessageConverter;
|
||||
|
||||
private HttpMessageConverter<Resource> resourceMessageConverter;
|
||||
|
||||
private MethodParameter paramHttpEntity;
|
||||
private MethodParameter paramRequestEntity;
|
||||
@@ -87,6 +86,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
private MethodParameter paramInt;
|
||||
private MethodParameter returnTypeResponseEntity;
|
||||
private MethodParameter returnTypeResponseEntityProduces;
|
||||
private MethodParameter returnTypeResponseEntityResource;
|
||||
private MethodParameter returnTypeHttpEntity;
|
||||
private MethodParameter returnTypeHttpEntitySubclass;
|
||||
private MethodParameter returnTypeInt;
|
||||
@@ -106,12 +106,16 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
messageConverter = mock(HttpMessageConverter.class);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
stringHttpMessageConverter = mock(HttpMessageConverter.class);
|
||||
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<>();
|
||||
converters.add(messageConverter);
|
||||
converters.add(stringHttpMessageConverter);
|
||||
converters.add(resourceMessageConverter);
|
||||
processor = new HttpEntityMethodProcessor(converters);
|
||||
reset(messageConverter);
|
||||
reset(stringHttpMessageConverter);
|
||||
reset(resourceMessageConverter);
|
||||
|
||||
Method handle1 = getClass().getMethod("handle1", HttpEntity.class, ResponseEntity.class,
|
||||
Integer.TYPE, RequestEntity.class);
|
||||
@@ -125,6 +129,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
returnTypeHttpEntity = new MethodParameter(getClass().getMethod("handle2", HttpEntity.class), -1);
|
||||
returnTypeHttpEntitySubclass = new MethodParameter(getClass().getMethod("handle2x", HttpEntity.class), -1);
|
||||
returnTypeInt = new MethodParameter(getClass().getMethod("handle3"), -1);
|
||||
returnTypeResponseEntityResource = new MethodParameter(getClass().getMethod("handle5"), -1);
|
||||
|
||||
mavContainer = new ModelAndViewContainer();
|
||||
servletRequest = new MockHttpServletRequest("GET", "/foo");
|
||||
@@ -159,8 +164,8 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||
given(stringHttpMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||
|
||||
Object result = processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
||||
|
||||
@@ -181,8 +186,8 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
servletRequest.setRequestURI("/path");
|
||||
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||
given(stringHttpMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||
|
||||
Object result = processor.resolveArgument(paramRequestEntity, mavContainer, webRequest, null);
|
||||
|
||||
@@ -201,8 +206,8 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
servletRequest.setMethod("POST");
|
||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
|
||||
given(messageConverter.canRead(String.class, contentType)).willReturn(false);
|
||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(contentType));
|
||||
given(stringHttpMessageConverter.canRead(String.class, contentType)).willReturn(false);
|
||||
|
||||
processor.resolveArgument(paramHttpEntity, mavContainer, webRequest, null);
|
||||
|
||||
@@ -230,7 +235,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||
|
||||
assertTrue(mavContainer.isRequestHandled());
|
||||
verify(messageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
||||
verify(stringHttpMessageConverter).write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -241,12 +246,12 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
servletRequest.addHeader("Accept", "text/*");
|
||||
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);
|
||||
|
||||
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")
|
||||
@@ -262,15 +267,15 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
given(advice.beforeBodyWrite(any(), any(), any(), any(), any(), any())).willReturn("Foo");
|
||||
|
||||
HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(
|
||||
Collections.singletonList(messageConverter), null, Collections.singletonList(advice));
|
||||
Collections.singletonList(stringHttpMessageConverter), null, Collections.singletonList(advice));
|
||||
|
||||
reset(messageConverter);
|
||||
given(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
||||
reset(stringHttpMessageConverter);
|
||||
given(stringHttpMessageConverter.canWrite(String.class, MediaType.TEXT_HTML)).willReturn(true);
|
||||
|
||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||
|
||||
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)
|
||||
@@ -281,9 +286,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
MediaType accepted = MediaType.APPLICATION_ATOM_XML;
|
||||
servletRequest.addHeader("Accept", accepted.toString());
|
||||
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
|
||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||
|
||||
@@ -298,9 +303,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
MediaType accepted = MediaType.TEXT_PLAIN;
|
||||
servletRequest.addHeader("Accept", accepted.toString());
|
||||
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
|
||||
processor.handleReturnValue(returnValue, returnTypeResponseEntityProduces, mavContainer, webRequest);
|
||||
|
||||
@@ -340,7 +345,7 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
processor.handleReturnValue(returnValue, returnTypeResponseEntity, mavContainer, webRequest);
|
||||
|
||||
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());
|
||||
assertEquals("headerValue", outputMessage.getValue().getHeaders().get("header").get(0));
|
||||
}
|
||||
@@ -519,50 +524,58 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void varyHeader() throws Exception {
|
||||
String[] entityValues = {"Accept-Language", "User-Agent"};
|
||||
String[] existingValues = {};
|
||||
String[] expected = {"Accept-Language, User-Agent"};
|
||||
testVaryHeader(entityValues, existingValues, expected);
|
||||
public void handleReturnTypeResource() throws Exception {
|
||||
ResponseEntity<Resource> returnValue = ResponseEntity
|
||||
.ok(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, 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
|
||||
public void varyHeaderWithExistingWildcard() throws Exception {
|
||||
String[] entityValues = {"Accept-Language"};
|
||||
String[] existingValues = {"*"};
|
||||
String[] expected = {"*"};
|
||||
testVaryHeader(entityValues, existingValues, expected);
|
||||
public void handleReturnTypeResourceByteRange() throws Exception {
|
||||
Resource resource = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||
ResponseEntity<Resource> returnValue = ResponseEntity.ok(resource);
|
||||
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, 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
|
||||
public void varyHeaderWithExistingCommaValues() 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);
|
||||
}
|
||||
public void handleReturnTypeResourceIllegalByteRange() throws Exception {
|
||||
Resource resource = new ByteArrayResource("Content".getBytes(Charset.forName("UTF-8")));
|
||||
ResponseEntity<Resource> returnValue = ResponseEntity.ok(resource);
|
||||
servletRequest.addHeader("Range", "illegal");
|
||||
|
||||
@Test
|
||||
public void varyHeaderWithExistingCommaSeparatedValues() 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);
|
||||
}
|
||||
given(resourceMessageConverter.canWrite(ByteArrayResource.class, null)).willReturn(true);
|
||||
given(resourceMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.ALL));
|
||||
|
||||
@Test
|
||||
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);
|
||||
}
|
||||
processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest);
|
||||
|
||||
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) {
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||
given(stringHttpMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringHttpMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(stringHttpMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||
}
|
||||
|
||||
private void assertResponseNotModified() {
|
||||
@@ -575,23 +588,9 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
assertTrue(mavContainer.isRequestHandled());
|
||||
assertEquals(HttpStatus.OK.value(), servletResponse.getStatus());
|
||||
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")
|
||||
public ResponseEntity<String> handle1(HttpEntity<String> httpEntity, ResponseEntity<String> entity,
|
||||
int i, RequestEntity<String> requestEntity) {
|
||||
@@ -620,8 +619,11 @@ public class HttpEntityMethodProcessorMockTests {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public ResponseEntity<Resource> handle5() {return null;}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class CustomHttpEntity extends HttpEntity<Object> {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,11 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
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.HttpOutputMessage;
|
||||
import org.springframework.http.HttpRangeResource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@@ -65,7 +68,9 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
|
||||
private RequestResponseBodyMethodProcessor processor;
|
||||
|
||||
private HttpMessageConverter<String> messageConverter;
|
||||
private HttpMessageConverter<String> stringMessageConverter;
|
||||
|
||||
private HttpMessageConverter<Resource> resourceMessageConverter;
|
||||
|
||||
private MethodParameter paramRequestBodyString;
|
||||
private MethodParameter paramInt;
|
||||
@@ -74,6 +79,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
private MethodParameter returnTypeString;
|
||||
private MethodParameter returnTypeInt;
|
||||
private MethodParameter returnTypeStringProduces;
|
||||
private MethodParameter returnTypeResource;
|
||||
|
||||
private ModelAndViewContainer mavContainer;
|
||||
|
||||
@@ -81,14 +87,19 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
private MockHttpServletResponse servletResponse;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
messageConverter = mock(HttpMessageConverter.class);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
stringMessageConverter = mock(HttpMessageConverter.class);
|
||||
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);
|
||||
paramRequestBodyString = new MethodParameter(methodHandle1, 0);
|
||||
@@ -96,6 +107,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
returnTypeString = new MethodParameter(methodHandle1, -1);
|
||||
returnTypeInt = new MethodParameter(getClass().getMethod("handle2"), -1);
|
||||
returnTypeStringProduces = new MethodParameter(getClass().getMethod("handle3"), -1);
|
||||
returnTypeResource = new MethodParameter(getClass().getMethod("handle6"), -1);
|
||||
paramValidBean = new MethodParameter(getClass().getMethod("handle4", SimpleBean.class), 0);
|
||||
paramStringNotRequired = new MethodParameter(getClass().getMethod("handle5", String.class), 0);
|
||||
|
||||
@@ -103,7 +115,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
|
||||
servletRequest = new MockHttpServletRequest();
|
||||
servletRequest.setMethod("POST");
|
||||
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
|
||||
servletResponse = new MockHttpServletResponse();
|
||||
webRequest = new ServletWebRequest(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,8 +139,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
String body = "Foo";
|
||||
servletRequest.setContent(body.getBytes(Charset.forName("UTF-8")));
|
||||
|
||||
given(messageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||
given(stringMessageConverter.canRead(String.class, contentType)).willReturn(true);
|
||||
given(stringMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(body);
|
||||
|
||||
Object result = processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory());
|
||||
|
||||
@@ -174,7 +187,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
servletRequest.addHeader("Content-Type", contentType.toString());
|
||||
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);
|
||||
}
|
||||
@@ -182,7 +195,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
@Test(expected = HttpMediaTypeNotSupportedException.class)
|
||||
public void resolveArgumentNoContentType() throws Exception {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -199,8 +212,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
public void resolveArgumentRequiredNoContent() throws Exception {
|
||||
servletRequest.setContentType(MediaType.TEXT_PLAIN_VALUE);
|
||||
servletRequest.setContent(new byte[0]);
|
||||
given(messageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
||||
given(messageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(null);
|
||||
given(stringMessageConverter.canRead(String.class, MediaType.TEXT_PLAIN)).willReturn(true);
|
||||
given(stringMessageConverter.read(eq(String.class), isA(HttpInputMessage.class))).willReturn(null);
|
||||
assertNull(processor.resolveArgument(paramRequestBodyString, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||
}
|
||||
|
||||
@@ -208,7 +221,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
public void resolveArgumentNotRequiredNoContent() throws Exception {
|
||||
servletRequest.setContentType("text/plain");
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -216,8 +229,8 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
@Test
|
||||
public void resolveArgumentNotRequiredNoContentNoContentType() throws Exception {
|
||||
servletRequest.setContent(new byte[0]);
|
||||
given(messageConverter.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.TEXT_PLAIN)).willReturn(true);
|
||||
given(stringMessageConverter.canRead(String.class, MediaType.APPLICATION_OCTET_STREAM)).willReturn(false);
|
||||
assertNull(processor.resolveArgument(paramStringNotRequired, mavContainer, webRequest, new ValidatingBinderFactory()));
|
||||
}
|
||||
|
||||
@@ -225,7 +238,7 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
public void resolveArgumentNotGetRequests() throws Exception {
|
||||
servletRequest.setMethod("GET");
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -235,14 +248,14 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
servletRequest.addHeader("Accept", accepted.toString());
|
||||
|
||||
String body = "Foo";
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||
|
||||
processor.handleReturnValue(body, returnTypeString, mavContainer, webRequest);
|
||||
|
||||
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
|
||||
@@ -252,12 +265,12 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
servletRequest.addHeader("Accept", "text/*");
|
||||
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);
|
||||
|
||||
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;
|
||||
servletRequest.addHeader("Accept", accepted.toString());
|
||||
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Arrays.asList(MediaType.TEXT_PLAIN));
|
||||
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
|
||||
processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
|
||||
}
|
||||
@@ -278,13 +291,59 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
MediaType accepted = MediaType.TEXT_PLAIN;
|
||||
servletRequest.addHeader("Accept", accepted.toString());
|
||||
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(false);
|
||||
|
||||
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
|
||||
|
||||
@Test
|
||||
@@ -295,14 +354,14 @@ public class RequestResponseBodyMethodProcessorMockTests {
|
||||
|
||||
servletRequest.addHeader("Accept", accepted);
|
||||
|
||||
given(messageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(messageConverter.getSupportedMediaTypes()).willReturn(supported);
|
||||
given(messageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||
given(stringMessageConverter.canWrite(String.class, null)).willReturn(true);
|
||||
given(stringMessageConverter.getSupportedMediaTypes()).willReturn(supported);
|
||||
given(stringMessageConverter.canWrite(String.class, accepted)).willReturn(true);
|
||||
|
||||
processor.handleReturnValue(body, returnTypeStringProduces, mavContainer, webRequest);
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ResponseBody
|
||||
public Resource handle6() {return null;}
|
||||
|
||||
private final class ValidatingBinderFactory implements WebDataBinderFactory {
|
||||
@Override
|
||||
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
|
||||
|
||||
@@ -17,12 +17,8 @@
|
||||
package org.springframework.web.servlet.resource;
|
||||
|
||||
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.InputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -531,47 +527,6 @@ public class ResourceHttpRequestHandlerTests {
|
||||
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
|
||||
@Test
|
||||
public void doOverwriteExistingCacheControlHeaders() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user