From 72cfe41f30bdc4a27cc32d6f41bce2fa2b3373af Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 23 Apr 2018 18:29:28 +0200 Subject: [PATCH] Disable HTTP Range support for InputStreamResource Prior to this commit, the `AbstractMessageConverterMethodProcessor` would fail to convert `InputStreamResource` to `ResourceRegion` as expected, since the content length cannot be read without consuming the stream. This is enforced by the `HttpRange` class. Now the method processor would still try to output HTTP range response headers to provide range support information. This step is using the resource content length and reads the input stream, leading to exceptions such as "IllegalStateException: InputStream has already been read". This commit improves the return type detection and excludes early `InputStreamResource` return types. With those types, HTTP range support is now completely disabled. Issue: SPR-16754 (cherry picked from commit e9a8a5065b) --- ...stractMessageConverterMethodProcessor.java | 4 +++- .../HttpEntityMethodProcessorMockTests.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 807affee657..a2b43202915 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ResolvableType; +import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.core.io.support.ResourceRegion; import org.springframework.http.HttpEntity; @@ -302,7 +303,8 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe * Return whether the returned value or the declared return type extend {@link Resource} */ protected boolean isResourceType(@Nullable Object value, MethodParameter returnType) { - return Resource.class.isAssignableFrom(value != null ? value.getClass() : returnType.getParameterType()); + Class clazz = getReturnValueType(value, returnType); + return clazz != InputStreamResource.class && Resource.class.isAssignableFrom(clazz); } /** diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java index 05f04c36699..1c3dbb66b77 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessorMockTests.java @@ -16,7 +16,9 @@ package org.springframework.web.servlet.mvc.method.annotation; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Method; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -27,6 +29,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -35,6 +38,7 @@ import org.mockito.ArgumentCaptor; import org.springframework.core.MethodParameter; import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -547,6 +551,23 @@ public class HttpEntityMethodProcessorMockTests { assertEquals(416, servletResponse.getStatus()); } + @Test //SPR-16754 + public void disableRangeSupportForStreamingResponses() throws Exception { + InputStream is = new ByteArrayInputStream("Content".getBytes(StandardCharsets.UTF_8)); + InputStreamResource resource = new InputStreamResource(is, "test"); + ResponseEntity returnValue = ResponseEntity.ok(resource); + servletRequest.addHeader("Range", "bytes=0-5"); + + given(resourceMessageConverter.canWrite(any(), eq(null))).willReturn(true); + given(resourceMessageConverter.canWrite(any(), eq(APPLICATION_OCTET_STREAM))).willReturn(true); + + processor.handleReturnValue(returnValue, returnTypeResponseEntityResource, mavContainer, webRequest); + then(resourceMessageConverter).should(times(1)).write( + any(InputStreamResource.class), eq(APPLICATION_OCTET_STREAM), any(HttpOutputMessage.class)); + assertEquals(200, servletResponse.getStatus()); + assertThat(servletResponse.getHeader(HttpHeaders.ACCEPT_RANGES), Matchers.isEmptyOrNullString()); + } + @Test //SPR-14767 public void shouldHandleValidatorHeadersInPutResponses() throws Exception { servletRequest.setMethod("PUT");