Refactor HTTP Range support with ResourceRegion
Prior to this commit, the `ResourceHttpMessageConverter` would support all HTTP Range requests and `MethodProcessors` would "wrap" controller handler return values with a `HttpRangeResource` to support that use case in Controllers. This commit refactors that support in several ways: * a new ResourceRegion class has been introduced * a new, separate, ResourceRegionHttpMessageConverter handles the HTTP range use cases when serving static resources with the ResourceHttpRequestHandler * the support of HTTP range requests on Controller handlers has been removed until a better solution is found Issue: SPR-14221, SPR-13834
This commit is contained in:
@@ -26,12 +26,9 @@ 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;
|
||||
@@ -197,24 +194,8 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
|
||||
}
|
||||
}
|
||||
|
||||
Object body = responseEntity.getBody();
|
||||
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 ex) {
|
||||
outputMessage.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
outputMessage.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Try even with null body. ResponseBodyAdvice could get involved.
|
||||
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
|
||||
writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);
|
||||
|
||||
// Ensure headers are flushed even if no body was written.
|
||||
outputMessage.flush();
|
||||
|
||||
@@ -19,16 +19,12 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
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;
|
||||
@@ -173,21 +169,6 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
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 ex) {
|
||||
outputMessage.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
||||
outputMessage.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Try even with null return value. ResponseBodyAdvice could get involved.
|
||||
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -30,12 +31,13 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceRegion;
|
||||
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.converter.ResourceRegionHttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -105,6 +107,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
|
||||
private ResourceHttpMessageConverter resourceHttpMessageConverter;
|
||||
|
||||
private ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter;
|
||||
|
||||
private ContentNegotiationManager contentNegotiationManager;
|
||||
|
||||
private CorsConfiguration corsConfiguration;
|
||||
@@ -169,7 +173,7 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
/**
|
||||
* Configure the {@link ResourceHttpMessageConverter} to use.
|
||||
* <p>By default a {@link ResourceHttpMessageConverter} will be configured.
|
||||
* @since 4.3
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public void setResourceHttpMessageConverter(ResourceHttpMessageConverter resourceHttpMessageConverter) {
|
||||
this.resourceHttpMessageConverter = resourceHttpMessageConverter;
|
||||
@@ -179,6 +183,20 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
return this.resourceHttpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the {@link ResourceRegionHttpMessageConverter} to use.
|
||||
* <p>By default a {@link ResourceRegionHttpMessageConverter} will be configured.
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public ResourceRegionHttpMessageConverter getResourceRegionHttpMessageConverter() {
|
||||
return resourceRegionHttpMessageConverter;
|
||||
}
|
||||
|
||||
public void setResourceRegionHttpMessageConverter(
|
||||
ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter) {
|
||||
this.resourceRegionHttpMessageConverter = resourceRegionHttpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a {@code ContentNegotiationManager} to determine the media types
|
||||
* for resources being served. If the manager contains a path
|
||||
@@ -225,13 +243,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
this.resourceResolvers.add(new PathResourceResolver());
|
||||
}
|
||||
initAllowedLocations();
|
||||
|
||||
if (this.contentNegotiationManager == null) {
|
||||
this.contentNegotiationManager = initContentNegotiationManager();
|
||||
}
|
||||
if (this.resourceHttpMessageConverter == null) {
|
||||
this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
|
||||
}
|
||||
if(this.resourceRegionHttpMessageConverter == null) {
|
||||
this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,6 +348,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
}
|
||||
|
||||
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
|
||||
outputMessage.getHeaders().add(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||
|
||||
if (request.getHeader(HttpHeaders.RANGE) == null) {
|
||||
setHeaders(response, resource, mediaType);
|
||||
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
|
||||
@@ -336,9 +358,15 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
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);
|
||||
if(httpRanges.size() == 1) {
|
||||
ResourceRegion resourceRegion = httpRanges.get(0).toResourceRegion(resource);
|
||||
this.resourceRegionHttpMessageConverter.write(resourceRegion, mediaType, outputMessage);
|
||||
}
|
||||
else {
|
||||
this.resourceRegionHttpMessageConverter
|
||||
.write(HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
response.addHeader("Content-Range", "bytes */" + resource.contentLength());
|
||||
|
||||
Reference in New Issue
Block a user