Handle Resources beyond int length through Servlet 3.1's setContentLengthLong

Issue: SPR-14135
This commit is contained in:
Juergen Hoeller 2016-04-08 15:54:24 +02:00
parent 463c8f149d
commit 37bd51cf6d
3 changed files with 65 additions and 64 deletions

View File

@ -744,52 +744,6 @@ project("spring-web") {
}
}
project("spring-websocket") {
description = "Spring WebSocket"
dependencies {
compile(project(":spring-core"))
compile(project(":spring-context"))
compile(project(":spring-web"))
optional(project(":spring-messaging"))
optional(project(":spring-webmvc"))
optional("javax.servlet:javax.servlet-api:3.1.0")
optional("javax.websocket:javax.websocket-api:1.0")
optional("org.apache.tomcat:tomcat-websocket:${tomcatVersion}") {
exclude group: "org.apache.tomcat", module: "tomcat-websocket-api"
exclude group: "org.apache.tomcat", module: "tomcat-servlet-api"
}
optional("org.glassfish.tyrus:tyrus-spi:${tyrusVersion}")
optional("org.glassfish.tyrus:tyrus-core:${tyrusVersion}")
optional("org.glassfish.tyrus:tyrus-server:${tyrusVersion}")
optional("org.glassfish.tyrus:tyrus-container-servlet:${tyrusVersion}")
optional("org.eclipse.jetty:jetty-webapp:${jettyVersion}") {
exclude group: "javax.servlet", module: "javax.servlet"
}
optional("org.eclipse.jetty.websocket:websocket-server:${jettyVersion}") {
exclude group: "javax.servlet", module: "javax.servlet"
}
optional("org.eclipse.jetty.websocket:websocket-client:${jettyVersion}")
optional("org.eclipse.jetty:jetty-client:${jettyVersion}")
optional("io.undertow:undertow-core:${undertowVersion}")
optional("io.undertow:undertow-servlet:${undertowVersion}") {
exclude group: "org.jboss.spec.javax.servlet", module: "jboss-servlet-api_3.1_spec"
exclude group: "org.jboss.spec.javax.annotation", module: "jboss-annotations-api_1.2_spec"
}
optional("io.undertow:undertow-websockets-jsr:${undertowVersion}") {
exclude group: "org.jboss.spec.javax.websocket", module: "jboss-websocket-api_1.1_spec"
}
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-websocket:${tomcatVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}")
testCompile("io.projectreactor:reactor-net:${reactorVersion}")
testCompile("io.netty:netty-all:${nettyVersion}")
testCompile("log4j:log4j:1.2.17")
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
}
}
project("spring-orm") {
description = "Spring Object/Relational Mapping"
@ -874,7 +828,7 @@ project("spring-webmvc") {
compile(files(project(":spring-core").objenesisRepackJar))
compile(project(":spring-expression"))
compile(project(":spring-web"))
provided("javax.servlet:javax.servlet-api:3.0.1")
provided("javax.servlet:javax.servlet-api:3.1.0")
optional(project(":spring-context-support")) // for Velocity support
optional(project(":spring-oxm")) // for MarshallingView
optional("javax.servlet.jsp:javax.servlet.jsp-api:2.2.1")
@ -993,6 +947,52 @@ project("spring-webmvc-portlet") {
}
}
project("spring-websocket") {
description = "Spring WebSocket"
dependencies {
compile(project(":spring-core"))
compile(project(":spring-context"))
compile(project(":spring-web"))
optional(project(":spring-messaging"))
optional(project(":spring-webmvc"))
optional("javax.servlet:javax.servlet-api:3.1.0")
optional("javax.websocket:javax.websocket-api:1.0")
optional("org.apache.tomcat:tomcat-websocket:${tomcatVersion}") {
exclude group: "org.apache.tomcat", module: "tomcat-websocket-api"
exclude group: "org.apache.tomcat", module: "tomcat-servlet-api"
}
optional("org.glassfish.tyrus:tyrus-spi:${tyrusVersion}")
optional("org.glassfish.tyrus:tyrus-core:${tyrusVersion}")
optional("org.glassfish.tyrus:tyrus-server:${tyrusVersion}")
optional("org.glassfish.tyrus:tyrus-container-servlet:${tyrusVersion}")
optional("org.eclipse.jetty:jetty-webapp:${jettyVersion}") {
exclude group: "javax.servlet", module: "javax.servlet"
}
optional("org.eclipse.jetty.websocket:websocket-server:${jettyVersion}") {
exclude group: "javax.servlet", module: "javax.servlet"
}
optional("org.eclipse.jetty.websocket:websocket-client:${jettyVersion}")
optional("org.eclipse.jetty:jetty-client:${jettyVersion}")
optional("io.undertow:undertow-core:${undertowVersion}")
optional("io.undertow:undertow-servlet:${undertowVersion}") {
exclude group: "org.jboss.spec.javax.servlet", module: "jboss-servlet-api_3.1_spec"
exclude group: "org.jboss.spec.javax.annotation", module: "jboss-annotations-api_1.2_spec"
}
optional("io.undertow:undertow-websockets-jsr:${undertowVersion}") {
exclude group: "org.jboss.spec.javax.websocket", module: "jboss-websocket-api_1.1_spec"
}
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-websocket:${tomcatVersion}")
testCompile("org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}")
testCompile("io.projectreactor:reactor-net:${reactorVersion}")
testCompile("io.netty:netty-all:${nettyVersion}")
testCompile("log4j:log4j:1.2.17")
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
}
}
project("spring-test") {
description = "Spring TestContext Framework"

View File

@ -21,7 +21,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
@ -108,9 +107,6 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
return null;
}
long contentLength = resource.contentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
}
return (contentLength < 0 ? null : contentLength);
}
@ -158,9 +154,7 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
* @throws IOException in case of errors while writing the content
*/
protected void writePartialContent(HttpRangeResource resource, HttpOutputMessage outputMessage) throws IOException {
Assert.notNull(resource, "Resource should not be null");
List<HttpRange> ranges = resource.getHttpRanges();
HttpHeaders responseHeaders = outputMessage.getHeaders();
MediaType contentType = responseHeaders.getContentType();
@ -168,14 +162,11 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
if (ranges.size() == 1) {
HttpRange range = ranges.get(0);
long start = range.getRangeStart(length);
long end = range.getRangeEnd(length);
long rangeLength = end - start + 1;
responseHeaders.add("Content-Range", "bytes " + start + "-" + end + "/" + length);
responseHeaders.setContentLength((int) rangeLength);
responseHeaders.setContentLength(rangeLength);
InputStream in = resource.getInputStream();
try {
copyRange(in, outputMessage.getBody(), start, end);
@ -192,15 +183,11 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
else {
String boundaryString = MimeTypeUtils.generateMultipartBoundaryString();
responseHeaders.set(HttpHeaders.CONTENT_TYPE, "multipart/byteranges; boundary=" + boundaryString);
OutputStream out = outputMessage.getBody();
for (HttpRange range : ranges) {
long start = range.getRangeStart(length);
long end = range.getRangeEnd(length);
InputStream in = resource.getInputStream();
// Writing MIME header.
println(out);
print(out, "--" + boundaryString);
@ -212,7 +199,6 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
print(out, "Content-Range: bytes " + start + "-" + end + "/" + length);
println(out);
println(out);
// Printing content
copyRange(in, out, start, end);
}

View File

@ -21,6 +21,7 @@ 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;
import javax.servlet.http.HttpServletResponse;
@ -38,6 +39,7 @@ 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.ObjectUtils;
import org.springframework.util.ResourceUtils;
@ -88,8 +90,13 @@ import org.springframework.web.servlet.support.WebContentGenerator;
public class ResourceHttpRequestHandler extends WebContentGenerator
implements HttpRequestHandler, InitializingBean, CorsConfigurationSource {
// Servlet 3.1 setContentLengthLong(long) available?
private static final boolean contentLengthLongAvailable =
ClassUtils.hasMethod(ServletResponse.class, "setContentLengthLong", long.class);
private static final Log logger = LogFactory.getLog(ResourceHttpRequestHandler.class);
private final List<Resource> locations = new ArrayList<Resource>(4);
private final List<ResourceResolver> resourceResolvers = new ArrayList<ResourceResolver>(4);
@ -515,9 +522,17 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
protected void setHeaders(HttpServletResponse response, Resource resource, MediaType mediaType) throws IOException {
long length = resource.contentLength();
if (length > Integer.MAX_VALUE) {
throw new IOException("Resource content too long (beyond Integer.MAX_VALUE): " + resource);
if (contentLengthLongAvailable) {
response.setContentLengthLong(length);
}
else {
response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(length));
}
}
response.setContentLength((int) length);
else {
response.setContentLength((int) length);
}
if (mediaType != null) {
response.setContentType(mediaType.toString());
}