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:
Brian Clozel
2016-03-22 19:06:36 +01:00
parent 15fe8279e6
commit c7bd3b8440
10 changed files with 700 additions and 327 deletions

View File

@@ -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> {
}
}
}

View File

@@ -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 {

View File

@@ -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 {