Support byte ranges in ResourceHttpRequestHandler
This commit introduces support for HTTP byte ranges in the ResourceHttpRequestHandler. This support consists of a number of changes: - Parsing of HTTP Range headers in HttpHeaders, using a new HttpRange class and inner ByteRange/SuffixByteRange subclasses. - MIME boundary generation moved from FormHttpMessageConverter to MimeTypeUtils. - writePartialContent() method introduced in ResourceHttpRequestHandler, handling the byte range logic - Additional partial content tests added to ResourceHttpRequestHandlerTests. Issue: SPR-10805
This commit is contained in:
committed by
Rossen Stoyanchev
parent
0e7eecfe34
commit
da48739628
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -31,11 +32,10 @@ import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for ResourceHttpRequestHandler.
|
||||
*
|
||||
@@ -277,6 +277,121 @@ public class ResourceHttpRequestHandlerTests {
|
||||
assertEquals(404, this.response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentByteRange() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=0-1");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(206, this.response.getStatus());
|
||||
assertEquals("text/plain", this.response.getContentType());
|
||||
assertEquals(2, this.response.getContentLength());
|
||||
assertEquals("bytes 0-1/10",
|
||||
this.response.getHeader("Content-Range"));
|
||||
assertEquals("So", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentByteRangeNoEnd() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=9-");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(206, this.response.getStatus());
|
||||
assertEquals("text/plain", this.response.getContentType());
|
||||
assertEquals(1, this.response.getContentLength());
|
||||
assertEquals("bytes 9-9/10",
|
||||
this.response.getHeader("Content-Range"));
|
||||
assertEquals(".", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentByteRangeLargeEnd() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=9-10000");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(206, this.response.getStatus());
|
||||
assertEquals("text/plain", this.response.getContentType());
|
||||
assertEquals(1, this.response.getContentLength());
|
||||
assertEquals("bytes 9-9/10",
|
||||
this.response.getHeader("Content-Range"));
|
||||
assertEquals(".", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentSuffixRange() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=-1");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(206, this.response.getStatus());
|
||||
assertEquals("text/plain", this.response.getContentType());
|
||||
assertEquals(1, this.response.getContentLength());
|
||||
assertEquals("bytes 9-9/10",
|
||||
this.response.getHeader("Content-Range"));
|
||||
assertEquals(".", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentSuffixRangeLargeSuffix() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=-11");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(206, this.response.getStatus());
|
||||
assertEquals("text/plain", this.response.getContentType());
|
||||
assertEquals(10, this.response.getContentLength());
|
||||
assertEquals("bytes 0-9/10",
|
||||
this.response.getHeader("Content-Range"));
|
||||
assertEquals("Some text.", this.response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentInvalidRangeHeader() throws Exception {
|
||||
this.request.addHeader("Range", "bytes= foo bar");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
|
||||
"foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(416, this.response.getStatus());
|
||||
assertEquals("bytes */10",
|
||||
this.response.getHeader("Content-Range"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialContentMultipleByteRanges() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=0-1, 4-5, 8-9");
|
||||
this.request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handleRequest(this.request, this.response);
|
||||
|
||||
assertEquals(206, this.response.getStatus());
|
||||
assertTrue(this.response.getContentType()
|
||||
.startsWith("multipart/byteranges; boundary="));
|
||||
|
||||
String boundary = "--" + this.response.getContentType().substring(31);
|
||||
|
||||
String[] ranges = StringUtils.tokenizeToStringArray(this.response.getContentAsString(),
|
||||
"\r\n", false, true);
|
||||
|
||||
assertEquals(boundary, ranges[0]);
|
||||
assertEquals("Content-Type: text/plain", ranges[1]);
|
||||
assertEquals("Content-Range: bytes 0-1/10", ranges[2]);
|
||||
assertEquals("So", ranges[3]);
|
||||
|
||||
assertEquals(boundary, ranges[4]);
|
||||
assertEquals("Content-Type: text/plain", ranges[5]);
|
||||
assertEquals("Content-Range: bytes 4-5/10", ranges[6]);
|
||||
assertEquals(" t", ranges[7]);
|
||||
|
||||
assertEquals(boundary, ranges[8]);
|
||||
assertEquals("Content-Type: text/plain", ranges[9]);
|
||||
assertEquals("Content-Range: bytes 8-9/10", ranges[10]);
|
||||
assertEquals("t.", ranges[11]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private long headerAsLong(String responseHeaderName) {
|
||||
return Long.valueOf(this.response.getHeader(responseHeaderName));
|
||||
|
||||
Reference in New Issue
Block a user