Jetty 9.4.x 2550 coalesce ranges (#2551)

Issue #2550 coalesce ranges

Author: Lachlan Roberts <lachlan@webtide.com>
Signed-off-by: olivier lamy <oliver.lamy@gmail.com>
Signed-off-by: Greg Wilkins <gregw@webtide.com>
Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Greg Wilkins 2018-05-18 08:45:54 +10:00 committed by GitHub
parent 07b3367519
commit 252ab9facf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 431 additions and 214 deletions

View File

@ -18,11 +18,12 @@
package org.eclipse.jetty.server;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -54,8 +55,8 @@ public class InclusiveByteRange
{
private static final Logger LOG = Log.getLogger(InclusiveByteRange.class);
long first = 0;
long last = 0;
private long first;
private long last;
public InclusiveByteRange(long first, long last)
{
@ -74,19 +75,87 @@ public class InclusiveByteRange
}
/* ------------------------------------------------------------ */
private void coalesce(InclusiveByteRange r)
{
first = Math.min(first,r.first);
last = Math.max(last,r.last);
}
/* ------------------------------------------------------------ */
private boolean overlaps(InclusiveByteRange range)
{
return (range.first>=this.first && range.first<=this.last)
||
(range.last>=this.first && range.last<=this.last)
||
(range.first<this.first && range.last>this.last);
}
/* ------------------------------------------------------------ */
public long getSize()
{
return last-first+1;
}
/* ------------------------------------------------------------ */
public String toHeaderRangeString(long size)
{
StringBuilder sb = new StringBuilder(40);
sb.append("bytes ");
sb.append(first);
sb.append('-');
sb.append(last);
sb.append("/");
sb.append(size);
return sb.toString();
}
/* ------------------------------------------------------------ */
@Override
public int hashCode()
{
return (int)(first ^ last);
}
/* ------------------------------------------------------------ */
@Override
public boolean equals( Object obj )
{
if(obj == null)
return false;
if(!(obj instanceof InclusiveByteRange))
return false;
return ((InclusiveByteRange)obj).first == this.first &&
((InclusiveByteRange)obj).last == this.last;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(60);
sb.append(Long.toString(first));
sb.append(":");
sb.append(Long.toString(last));
return sb.toString();
}
/* ------------------------------------------------------------ */
/**
* @param headers Enumeration of Range header fields.
* @param size Size of the resource.
* @return LazyList of satisfiable ranges
* @return List of satisfiable ranges
*/
public static List<InclusiveByteRange> satisfiableRanges(Enumeration<String> headers, long size)
{
Object satRanges=null;
List<InclusiveByteRange> ranges = null;
final long end = size-1;
// walk through all Range headers
headers:
while (headers.hasMoreElements())
{
String header = headers.nextElement();
@ -100,52 +169,89 @@ public class InclusiveByteRange
try
{
t = tok.nextToken().trim();
if ("bytes".equals(t))
continue;
long first = -1;
long last = -1;
int d = t.indexOf('-');
if (d < 0 || t.indexOf("-",d + 1) >= 0)
int dash = t.indexOf('-');
if (dash < 0 || t.indexOf("-",dash + 1) >= 0)
{
if ("bytes".equals(t))
continue;
LOG.warn("Bad range format: {}",t);
continue headers;
break;
}
else if (d == 0)
if (dash>0)
first = Long.parseLong(t.substring(0,dash).trim());
if (dash<(t.length()-1))
last = Long.parseLong(t.substring(dash + 1).trim());
if (first==-1)
{
if (d + 1 < t.length())
last = Long.parseLong(t.substring(d + 1).trim());
else
if (last==-1)
{
LOG.warn("Bad range format: {}",t);
break;
}
if (last==0)
continue;
// This is a suffix range
first = Math.max(0,size-last);
last = end;
}
else
{
// Range starts after end
if (first>=size)
continue;
if (last==-1)
last = end;
else if (last>=end)
last = end;
}
if (last<first)
{
LOG.warn("Bad range format: {}",t);
break;
}
InclusiveByteRange range = new InclusiveByteRange(first,last);
if (ranges==null)
ranges = new ArrayList<>();
boolean coalesced = false;
for (Iterator<InclusiveByteRange> i = ranges.listIterator(); i.hasNext();)
{
InclusiveByteRange r = i.next();
if (range.overlaps(r))
{
coalesced = true;
r.coalesce(range);
while(i.hasNext())
{
InclusiveByteRange r2 = i.next();
if (r2.overlaps(r))
{
r.coalesce(r2);
i.remove();
}
}
}
}
else if (d + 1 < t.length())
{
first = Long.parseLong(t.substring(0,d).trim());
last = Long.parseLong(t.substring(d + 1).trim());
}
else
first = Long.parseLong(t.substring(0,d).trim());
if (!coalesced)
ranges.add(range);
if (first == -1 && last == -1)
continue headers;
if (first != -1 && last != -1 && (first > last))
continue headers;
if (first < size)
{
InclusiveByteRange range = new InclusiveByteRange(first,last);
satRanges = LazyList.add(satRanges,range);
}
}
catch (NumberFormatException e)
{
LOG.warn("Bad range format: {}",t);
LOG.ignore(e);
continue;
}
}
}
@ -155,51 +261,8 @@ public class InclusiveByteRange
LOG.ignore(e);
}
}
return LazyList.getList(satRanges,true);
}
/* ------------------------------------------------------------ */
public long getFirst(long size)
{
if (first<0)
{
long tf=size-last;
if (tf<0)
tf=0;
return tf;
}
return first;
}
/* ------------------------------------------------------------ */
public long getLast(long size)
{
if (first<0)
return size-1;
if (last<0 ||last>=size)
return size-1;
return last;
}
/* ------------------------------------------------------------ */
public long getSize(long size)
{
return getLast(size)-getFirst(size)+1;
}
/* ------------------------------------------------------------ */
public String toHeaderRangeString(long size)
{
StringBuilder sb = new StringBuilder(40);
sb.append("bytes ");
sb.append(getFirst(size));
sb.append('-');
sb.append(getLast(size));
sb.append("/");
sb.append(size);
return sb.toString();
return ranges;
}
/* ------------------------------------------------------------ */
@ -212,18 +275,6 @@ public class InclusiveByteRange
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
StringBuilder sb = new StringBuilder(60);
sb.append(Long.toString(first));
sb.append(":");
sb.append(Long.toString(last));
return sb.toString();
}
}

View File

@ -735,7 +735,7 @@ public class ResourceService
else
{
// Parse the satisfiable ranges
List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges( reqRanges, content_length);
// if there are no satisfiable ranges, send 416 response
if (ranges==null || ranges.size()==0)
@ -752,15 +752,15 @@ public class ResourceService
// since were here now), send that range with a 216 response
if ( ranges.size()== 1)
{
InclusiveByteRange singleSatisfiableRange = ranges.get(0);
long singleLength = singleSatisfiableRange.getSize(content_length);
InclusiveByteRange singleSatisfiableRange = ranges.iterator().next();
long singleLength = singleSatisfiableRange.getSize();
putHeaders(response,content,singleLength);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
if (!response.containsHeader(HttpHeader.DATE.asString()))
response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
singleSatisfiableRange.toHeaderRangeString(content_length));
content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
content.getResource().writeTo(out,singleSatisfiableRange.getFirst(),singleLength);
return true;
}
@ -793,9 +793,9 @@ public class ResourceService
// calculate the content-length
int length=0;
String[] header = new String[ranges.size()];
for (int i=0;i<ranges.size();i++)
int i = 0;
for (InclusiveByteRange ibr:ranges)
{
InclusiveByteRange ibr = ranges.get(i);
header[i]=ibr.toHeaderRangeString(content_length);
length+=
((i>0)?2:0)+
@ -803,18 +803,19 @@ public class ResourceService
(mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
2+
(ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
(ibr.getLast()-ibr.getFirst())+1;
i++;
}
length+=2+2+multi.getBoundary().length()+2+2;
response.setContentLength(length);
for (int i=0;i<ranges.size();i++)
i=0;
for (InclusiveByteRange ibr:ranges)
{
InclusiveByteRange ibr = ranges.get(i);
multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
long start=ibr.getFirst(content_length);
long size=ibr.getSize(content_length);
long start=ibr.getFirst();
long size=ibr.getSize();
if (in!=null)
{
// Handle non cached resource
@ -836,6 +837,8 @@ public class ResourceService
else
// Handle cached resource
content.getResource().writeTo(multi,start,size);
i++;
}
if (in!=null)
in.close();

View File

@ -18,34 +18,35 @@
package org.eclipse.jetty.server;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AdvancedRunner.class)
public class InclusiveByteRangeTest
{
@SuppressWarnings("unchecked")
private void assertInvalidRange(String rangeString)
{
Vector strings = new Vector();
Vector<String> strings = new Vector<>();
strings.add(rangeString);
List ranges = InclusiveByteRange.satisfiableRanges(strings.elements(),200);
List<InclusiveByteRange> ranges = InclusiveByteRange.satisfiableRanges( strings.elements(), 200);
assertNull("Invalid Range [" + rangeString + "] should result in no satisfiable ranges",ranges);
}
private void assertRange(String msg, int expectedFirst, int expectedLast, int size, InclusiveByteRange actualRange)
{
assertEquals(msg + " - first",expectedFirst,actualRange.getFirst(size));
assertEquals(msg + " - last",expectedLast,actualRange.getLast(size));
assertEquals(msg + " - first",expectedFirst,actualRange.getFirst());
assertEquals(msg + " - last",expectedLast,actualRange.getLast());
String expectedHeader = String.format("bytes %d-%d/%d",expectedFirst,expectedLast,size);
assertEquals(msg + " - header range string",expectedHeader,actualRange.toHeaderRangeString(size));
}
@ -54,32 +55,30 @@ public class InclusiveByteRangeTest
{
InclusiveByteRange range = parseRange(rangeId,size);
assertEquals("Range [" + rangeId + "] - first",expectedFirst,range.getFirst(size));
assertEquals("Range [" + rangeId + "] - last",expectedLast,range.getLast(size));
assertEquals("Range [" + rangeId + "] - first",expectedFirst,range.getFirst());
assertEquals("Range [" + rangeId + "] - last",expectedLast,range.getLast());
String expectedHeader = String.format("bytes %d-%d/%d",expectedFirst,expectedLast,size);
assertEquals("Range [" + rangeId + "] - header range string",expectedHeader,range.toHeaderRangeString(size));
}
@SuppressWarnings("unchecked")
private InclusiveByteRange parseRange(String rangeString, int size)
{
Vector strings = new Vector();
Vector<String> strings = new Vector<>();
strings.add(rangeString);
List ranges = InclusiveByteRange.satisfiableRanges(strings.elements(),size);
List<InclusiveByteRange> ranges = InclusiveByteRange.satisfiableRanges(strings.elements(),size);
assertNotNull("Satisfiable Ranges should not be null",ranges);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",1,ranges.size());
return (InclusiveByteRange)ranges.get(0);
return (InclusiveByteRange)ranges.iterator().next();
}
@SuppressWarnings("unchecked")
private List<InclusiveByteRange> parseRanges(String rangeString, int size)
private List<InclusiveByteRange> parseRanges(int size, String... rangeString)
{
Vector strings = new Vector();
strings.add(rangeString);
Vector<String> strings = new Vector<>();
for (String range : rangeString)
strings.add(range);
List<InclusiveByteRange> ranges;
ranges = InclusiveByteRange.satisfiableRanges(strings.elements(),size);
List<InclusiveByteRange> ranges = InclusiveByteRange.satisfiableRanges(strings.elements(),size);
assertNotNull("Satisfiable Ranges should not be null",ranges);
return ranges;
}
@ -112,10 +111,26 @@ public class InclusiveByteRangeTest
rangeString = "bytes=5-20,35-65";
List<InclusiveByteRange> ranges = parseRanges(rangeString,size);
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
assertRange("Range [" + rangeString + "]",5,20,size,ranges.get(0));
assertRange("Range [" + rangeString + "]",35,49,size,ranges.get(1));
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",5,20,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",35,49,size,inclusiveByteRangeIterator.next());
}
/**
* Ranges have a multiple ranges, all absolutely defined.
*/
@Test
public void testMultipleAbsoluteRangesSplit()
{
int size = 50;
List<InclusiveByteRange> ranges = parseRanges(size,"bytes=5-20","bytes=35-65");
assertEquals(2,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("testMultipleAbsoluteRangesSplit[0]",5,20,size,inclusiveByteRangeIterator.next());
assertRange("testMultipleAbsoluteRangesSplit[1]",35,49,size,inclusiveByteRangeIterator.next());
}
/**
@ -124,42 +139,122 @@ public class InclusiveByteRangeTest
@Test
public void testMultipleRangesClipped()
{
int size = 50;
String rangeString;
rangeString = "bytes=5-20,35-65,-5";
List<InclusiveByteRange> ranges = parseRanges(rangeString,50);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",3,ranges.size());
assertRange("Range [" + rangeString + "]",5,20,50,ranges.get(0));
assertRange("Range [" + rangeString + "]",35,49,50,ranges.get(1));
assertRange("Range [" + rangeString + "]",45,49,50,ranges.get(2));
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",5,20,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",35,49,size,inclusiveByteRangeIterator.next());
}
@Test
public void testMultipleRangesOverlapping()
{
int size = 200;
String rangeString;
rangeString = "bytes=5-20,15-25";
List<InclusiveByteRange> ranges = parseRanges(rangeString,200);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
assertRange("Range [" + rangeString + "]",5,20,200,ranges.get(0));
assertRange("Range [" + rangeString + "]",15,25,200,ranges.get(1));
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",1,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",5,25,size,inclusiveByteRangeIterator.next());
}
@Test
public void testMultipleRangesSplit()
{
int size = 200;
String rangeString;
rangeString = "bytes=5-10,15-20";
List<InclusiveByteRange> ranges = parseRanges(rangeString,200);
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
assertRange("Range [" + rangeString + "]",5,10,200,ranges.get(0));
assertRange("Range [" + rangeString + "]",15,20,200,ranges.get(1));
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",5,10,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",15,20,size,inclusiveByteRangeIterator.next());
}
@Test
public void testMultipleSameRangesSplit()
{
int size = 200;
String rangeString;
rangeString = "bytes=5-10,15-20,5-10,15-20,5-10,5-10,5-10,5-10,5-10,5-10";
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",5,10,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",15,20,size,inclusiveByteRangeIterator.next());
}
@Test
public void testMultipleOverlappingRanges()
{
int size = 200;
String rangeString;
rangeString = "bytes=5-15,20-30,10-25";
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",1,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",5,30,size,inclusiveByteRangeIterator.next());
}
@Test
public void testMultipleOverlappingRangesOrdered()
{
int size = 200;
String rangeString;
rangeString = "bytes=20-30,5-15,0-5,25-35";
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",20,35,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",0,15,size,inclusiveByteRangeIterator.next());
}
@Test
public void testMultipleOverlappingRangesOrderedSplit()
{
int size = 200;
String rangeString;
rangeString = "bytes=20-30,5-15,0-5,25-35";
List<InclusiveByteRange> ranges = parseRanges(size,"bytes=20-30","bytes=5-15","bytes=0-5,25-35");
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",20,35,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",0,15,size,inclusiveByteRangeIterator.next());
}
@Test
public void testNasty()
{
int size = 200;
String rangeString;
rangeString = "bytes=90-100, 10-20, 30-40, -161";
List<InclusiveByteRange> ranges = parseRanges(size,rangeString);
assertEquals("Satisfiable Ranges of [" + rangeString + "] count",2,ranges.size());
Iterator<InclusiveByteRange> inclusiveByteRangeIterator = ranges.iterator();
assertRange("Range [" + rangeString + "]",30,199,size,inclusiveByteRangeIterator.next());
assertRange("Range [" + rangeString + "]",10,20,size,inclusiveByteRangeIterator.next());
}
@Test
public void testRange_OpenEnded()
{
assertSimpleRange(50, 499, "bytes=50-", 500);
}
@Test
public void testSimpleRange()
{
@ -167,5 +262,93 @@ public class InclusiveByteRangeTest
assertSimpleRange(195,199,"bytes=-5",200);
assertSimpleRange(50,119,"bytes=50-150",120);
assertSimpleRange(50,119,"bytes=50-",120);
assertSimpleRange(1,50,"bytes= 1 - 50",120);
}
// TODO: evaluate this vs assertInvalidRange() above, which behavior is correct? null? or empty list?
private void assertBadRangeList(int size, String badRange)
{
Vector<String> strings = new Vector<>();
strings.add(badRange);
List<InclusiveByteRange> ranges = InclusiveByteRange.satisfiableRanges(strings.elements(),size);
// if one part is bad, the entire set of ranges should be treated as bad, per RFC7233
assertThat("Should have no ranges", ranges, is(nullValue()));
}
@Test
@Ignore
public void testBadRange_SetPartiallyBad()
{
assertBadRangeList(500, "bytes=1-50,1-b,a-50");
}
@Test
public void testBadRange_NoNumbers()
{
assertBadRangeList(500, "bytes=a-b");
}
@Test
public void testBadRange_Empty()
{
assertBadRangeList(500, "bytes=");
}
@Test
@Ignore
public void testBadRange_ZeroPrefixed()
{
assertBadRangeList(500, "bytes=01-050");
}
@Test
public void testBadRange_Hex()
{
assertBadRangeList(500, "bytes=0F-FF");
}
@Test
@Ignore
public void testBadRange_TabWhitespace()
{
assertBadRangeList(500, "bytes=\t1\t-\t50");
}
@Test
public void testBadRange_TabDelim()
{
assertBadRangeList(500, "bytes=1-50\t90-101\t200-250");
}
@Test
public void testBadRange_SemiColonDelim()
{
assertBadRangeList(500, "bytes=1-50;90-101;200-250");
}
@Test
public void testBadRange_NegativeSize()
{
assertBadRangeList(500, "bytes=50-1");
}
@Test
public void testBadRange_DoubleDash()
{
assertBadRangeList(500, "bytes=1--20");
}
@Test
public void testBadRange_TrippleDash()
{
assertBadRangeList(500, "bytes=1---");
}
@Test
public void testBadRange_ZeroedNegativeSize()
{
assertBadRangeList(500, "bytes=050-001");
}
}

View File

@ -18,12 +18,15 @@
package org.eclipse.jetty.servlet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.LocalConnector;
@ -32,7 +35,6 @@ import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
@ -161,6 +163,59 @@ public class DefaultServletRangesTest
}
@Test
public void testMultipleSameRangeRequests() throws Exception
{
StringBuilder stringBuilder = new StringBuilder( );
for(int i = 0; i < 1000; i++)
{
stringBuilder.append( "10-60," );
}
String response;
response = connector.getResponse(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=" + stringBuilder.toString() +"0-2\r\n" +
"\r\n");
int start = response.indexOf("--jetty");
String body = response.substring(start);
String boundary = body.substring(0, body.indexOf("\r\n"));
assertResponseContains("206 Partial", response);
assertResponseContains("Content-Type: multipart/byteranges; boundary=", response);
assertResponseContains("Content-Range: bytes 10-60/80", response);
assertResponseContains("Content-Range: bytes 0-2/80", response);
Assert.assertEquals( "Content range 0-60/80 in response not only 1:" + response , //
2, response.split( "Content-Range: bytes 10-60/80" ).length);
assertTrue(body.endsWith(boundary + "--\r\n"));
}
@Test
public void testMultipleSameRangeRequestsTooLargeHeader() throws Exception
{
StringBuilder stringBuilder = new StringBuilder( );
for(int i = 0; i < 2000; i++)
{
stringBuilder.append( "10-60," );
}
String response;
response = connector.getResponse(
"GET /context/data.txt HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Connection: close\r\n"+
"Range: bytes=" + stringBuilder.toString() +"0-2\r\n" +
"\r\n");
int start = response.indexOf("--jetty");
assertEquals( -1, start );
assertResponseContains("HTTP/1.1 431 Request Header Fields Too Large", response);
}
@Test
public void testOpenEndRange() throws Exception
{
@ -211,17 +266,11 @@ public class DefaultServletRangesTest
private void createFile(File file, String str) throws IOException
{
FileOutputStream out = null;
try
try(OutputStream out = Files.newOutputStream( file.toPath()))
{
out = new FileOutputStream(file);
out.write(str.getBytes(StandardCharsets.UTF_8));
out.flush();
}
finally
{
IO.close(out);
}
}
private void assertResponseNotContains(String forbidden, String response)

View File

@ -1567,75 +1567,6 @@ public abstract class RFC2616BaseTest
assertEquals("--"+boundary+"--",lines.get(i++));
}
/**
* Test Range (Header Field)
*
* @see <a href="http://tools.ietf.org/html/rfc2616#section-14.35">RFC 2616 (section 14.35)</a>
*/
@Test
public void test14_35_Range_Multipart2() throws Exception
{
// Request the last 1 byte, last 2 bytes, and last 3 bytes.
// This is an example of overlapping ranges
String rangedef = "-1,-2,-3";
StringBuffer req1 = new StringBuffer();
req1.append("GET /rfc2616-webapp/alpha.txt HTTP/1.1\n");
req1.append("Host: localhost\n");
req1.append("Range: ").append(rangedef).append("\n");
req1.append("Connection: close\n");
req1.append("\n");
HttpTester.Response response = http.request(req1);
String msg = "Partial (Byte) Range: '" + rangedef + "'";
assertEquals(msg,HttpStatus.PARTIAL_CONTENT_206,response.getStatus());
String contentType = response.get("Content-Type");
// RFC states that multiple parts should result in multipart/byteranges Content type.
StringAssert.assertContains(msg + " Content-Type",contentType,"multipart/byteranges");
// Collect 'boundary' string
String boundary = null;
String parts[] = StringUtil.split(contentType,';');
for (int i = 0; i < parts.length; i++)
{
if (parts[i].trim().startsWith("boundary="))
{
String boundparts[] = StringUtil.split(parts[i],'=');
Assert.assertEquals(msg + " Boundary parts.length",2,boundparts.length);
boundary = boundparts[1];
}
}
Assert.assertNotNull(msg + " Should have found boundary in Content-Type header",boundary);
List<String> lines = StringUtil.asLines(response.getContent().trim());
int i=0;
assertEquals("--"+boundary,lines.get(i++));
assertEquals("Content-Type: text/plain",lines.get(i++));
assertEquals("Content-Range: bytes 26-26/27",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("--"+boundary,lines.get(i++));
assertEquals("Content-Type: text/plain",lines.get(i++));
assertEquals("Content-Range: bytes 25-26/27",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("Z",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("--"+boundary,lines.get(i++));
assertEquals("Content-Type: text/plain",lines.get(i++));
assertEquals("Content-Range: bytes 24-26/27",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("YZ",lines.get(i++));
assertEquals("",lines.get(i++));
assertEquals("--"+boundary+"--",lines.get(i++));
}
/**
* Test Range (Header Field)
*