Skip to content

Commit d06d13c

Browse files
committed
Merge pull request #1134 from restlet/1132_support_non_bytes_ranges
Added support of non-bytes ranges. Issue #1132.
2 parents c900125 + 73f3086 commit d06d13c

File tree

7 files changed

+468
-459
lines changed

7 files changed

+468
-459
lines changed

modules/org.restlet.test/src/org/restlet/test/data/RangeTestCase.java

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,7 @@ public void handle(Request request, Response response) {
150150
}
151151

152152
// Create a temporary directory for the tests
153-
private static final File testDir = new File(
154-
System.getProperty("java.io.tmpdir"), "rangeTestCase");
153+
private static final File testDir = new File(System.getProperty("java.io.tmpdir"), "rangeTestCase");
155154

156155
// Sample string.
157156
private static String str1000;
@@ -168,11 +167,9 @@ protected void setUp() throws Exception {
168167
component.getDefaultHost().attach(new TestRangeApplication());
169168
component.start();
170169

171-
StringBuilder sb = new StringBuilder();
172-
for (int i = 0; i < 1000; i++) {
173-
sb.append("1");
174-
}
175-
str1000 = sb.toString();
170+
char[] tab = new char[1000];
171+
Arrays.fill(tab, '1');
172+
str1000 = new String(tab);
176173
}
177174

178175
@Override
@@ -191,8 +188,7 @@ public void testGet() throws Exception {
191188
Client client = new Client(Protocol.HTTP);
192189

193190
// Test partial Get.
194-
Request request = new Request(Method.GET, "http://localhost:"
195-
+ TEST_PORT + "/testGet");
191+
Request request = new Request(Method.GET, "http://localhost:" + TEST_PORT + "/testGet");
196192
Response response;
197193

198194
response = client.handle(request);
@@ -201,8 +197,7 @@ public void testGet() throws Exception {
201197
assertEquals(10, response.getEntity().getSize());
202198
assertEquals(10, response.getEntity().getAvailableSize());
203199

204-
request = new Request(Method.GET, "http://localhost:" + TEST_PORT
205-
+ "/testGet");
200+
request = new Request(Method.GET, "http://localhost:" + TEST_PORT + "/testGet");
206201
request.setRanges(Arrays.asList(new Range(0, 10)));
207202
response = client.handle(request);
208203
assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus());
@@ -278,8 +273,7 @@ public void testConditionalRanges() throws Exception {
278273
Client client = new Client(Protocol.HTTP);
279274

280275
// Test partial Get.
281-
Request request = new Request(Method.GET, "http://localhost:"
282-
+ TEST_PORT + "/testGet");
276+
Request request = new Request(Method.GET, "http://localhost:" + TEST_PORT + "/testGet");
283277
Response response = client.handle(request);
284278
Tag entityTag = response.getEntity().getTag();
285279

@@ -317,34 +311,29 @@ public void testPut() throws Exception {
317311
client.getContext().getParameters().add("tracing", "true");
318312

319313
// PUT on a file that does not exist
320-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
321-
+ "/testPut/essai.txt");
314+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
322315
request.setEntity(new StringRepresentation("1234567890"));
323316
request.setRanges(Arrays.asList(new Range(0, 10)));
324317
response = client.handle(request);
325318
assertTrue(response.getStatus().isSuccess());
326-
response = client.handle(new Request(Method.GET, request
327-
.getResourceRef()));
319+
response = client.handle(new Request(Method.GET, request.getResourceRef()));
328320
assertEquals(Status.SUCCESS_OK, response.getStatus());
329321
assertEquals("1234567890", response.getEntity().getText());
330322

331323
// Partial PUT on a file, the provided representation overflowed the
332324
// existing file
333-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
334-
+ "/testPut/essai.txt");
325+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
335326
request.setEntity(new StringRepresentation("0000000000"));
336327
request.setRanges(Arrays.asList(new Range(1, 10)));
337328
response = client.handle(request);
338329
assertTrue(response.getStatus().isSuccess());
339-
response = client.handle(new Request(Method.GET, request
340-
.getResourceRef()));
330+
response = client.handle(new Request(Method.GET, request.getResourceRef()));
341331
assertEquals(Status.SUCCESS_OK, response.getStatus());
342332
assertEquals("10000000000", response.getEntity().getText());
343333

344334
// Partial PUT on a file that does not exists, the provided range
345335
// does not start at the 0 index.
346-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
347-
+ "/testPut/essai2.txt");
336+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai2.txt");
348337
request.setEntity(new StringRepresentation("0000000000"));
349338
request.setRanges(Arrays.asList(new Range(1, 10)));
350339
response = client.handle(request);
@@ -355,34 +344,29 @@ public void testPut() throws Exception {
355344
assertEquals("0000000000", response.getEntity().getText());
356345

357346
// Partial PUT on a file, simple range
358-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
359-
+ "/testPut/essai.txt");
347+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
360348
request.setEntity(new StringRepresentation("22"));
361349
request.setRanges(Arrays.asList(new Range(2, 2)));
362350
response = client.handle(request);
363351
assertTrue(response.getStatus().isSuccess());
364-
response = client.handle(new Request(Method.GET, request
365-
.getResourceRef()));
352+
response = client.handle(new Request(Method.GET, request.getResourceRef()));
366353
assertEquals(Status.SUCCESS_OK, response.getStatus());
367354
assertEquals("10220000000", response.getEntity().getText());
368355

369356
// Partial PUT on a file, the provided representation will be padded
370357
// at the very end of the file.
371-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
372-
+ "/testPut/essai.txt");
358+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
373359
request.setEntity(new StringRepresentation("888"));
374360
request.setRanges(Arrays.asList(new Range(8, Range.SIZE_MAX)));
375361
response = client.handle(request);
376362
assertTrue(response.getStatus().isSuccess());
377-
response = client.handle(new Request(Method.GET, request
378-
.getResourceRef()));
363+
response = client.handle(new Request(Method.GET, request.getResourceRef()));
379364
assertEquals(Status.SUCCESS_OK, response.getStatus());
380365
assertEquals("10220000888", response.getEntity().getText());
381366

382367
// Partial PUT on a file that does not exist, the range does not
383368
// specify the range size.
384-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
385-
+ "/testPut/essai3.txt");
369+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai3.txt");
386370
request.setEntity(new StringRepresentation("888"));
387371
request.setRanges(Arrays.asList(new Range(8, Range.SIZE_MAX)));
388372
response = client.handle(request);
@@ -394,24 +378,32 @@ public void testPut() throws Exception {
394378

395379
// Partial PUT on a file, the provided representation will be padded
396380
// just before the end of the file.
397-
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT
398-
+ "/testPut/essai.txt");
381+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
399382
request.setEntity(new StringRepresentation("99"));
400383
request.setRanges(Arrays.asList(new Range(8, Range.SIZE_MAX)));
401384
response = client.handle(request);
402385
assertTrue(response.getStatus().isSuccess());
403-
response = client.handle(new Request(Method.GET, request
404-
.getResourceRef()));
386+
response = client.handle(new Request(Method.GET, request.getResourceRef()));
405387
assertEquals(Status.SUCCESS_OK, response.getStatus());
406388
assertEquals("10220000998", response.getEntity().getText());
407389

408-
request = new Request(Method.GET, "http://localhost:" + TEST_PORT
409-
+ "/testPut/essai.txt");
390+
request = new Request(Method.GET, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
410391
request.setRanges(Arrays.asList(new Range(3, Range.SIZE_MAX)));
411392
response = client.handle(request);
412393
assertEquals(Status.SUCCESS_PARTIAL_CONTENT, response.getStatus());
413394
assertEquals("20000998", response.getEntity().getText());
414395

396+
397+
// Partial PUT on a file, with a non-bytes range, not taken into account
398+
request = new Request(Method.PUT, "http://localhost:" + TEST_PORT + "/testPut/essai.txt");
399+
request.setEntity(new StringRepresentation("1234567890"));
400+
request.setRanges(Arrays.asList(new Range(8, Range.SIZE_MAX, 10, "test")));
401+
response = client.handle(request);
402+
assertTrue(response.getStatus().isSuccess());
403+
response = client.handle(new Request(Method.GET, request.getResourceRef()));
404+
assertEquals(Status.SUCCESS_OK, response.getStatus());
405+
assertEquals("1234567890", response.getEntity().getText());
406+
415407
IoUtils.delete(testDir, true);
416408
client.stop();
417409
}

modules/org.restlet/src/org/restlet/data/Range.java

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,48 @@
3232
public class Range {
3333

3434
/**
35-
* Index for the first byte of an entity.
35+
* Index for the first byte (or range unit) of an entity.
3636
*/
3737
public final static long INDEX_FIRST = 0;
3838

3939
/**
40-
* Index for the last byte of an entity.
40+
* Index for the last byte (or range unit) of an entity.
4141
*/
4242
public final static long INDEX_LAST = -1;
4343

44+
public final static String RANGE_BYTES_UNIT = "bytes";
45+
4446
/**
4547
* Maximum size available from the index.
4648
*/
4749
public final static long SIZE_MAX = -1;
4850

51+
/**
52+
* Indicates if the unit of the given range is "bytes".
53+
*
54+
* @param range
55+
* The range.
56+
* @return true if the unit of the given range is "bytes".
57+
*/
58+
public static boolean isBytesRange(Range range) {
59+
return RANGE_BYTES_UNIT.equals(range.getUnitName());
60+
}
61+
4962
/**
5063
* Index from which to start the range. If the index is superior or equal to
51-
* zero, the index will define the start of the range. If its value is
52-
* {@value #INDEX_LAST} (-1), then it defines the end of the range. The
53-
* default value is {@link #INDEX_FIRST} (0), starting at the first byte.
64+
* zero, the index will define the start of the range. If its value is {@value #INDEX_LAST} (-1), then it defines
65+
* the end of the range. The default value is {@link #INDEX_FIRST} (0), starting at the first byte.
5466
*/
5567
private volatile long index;
5668

5769
/**
58-
* Size of the range in number of bytes. If the size is the maximum
70+
* Total size of the instance in number of bytes (or range unit). In case of "bytes" range, this attribute is
71+
* ignored, as the instance size is taken from the entity.
72+
*/
73+
private volatile long instanceSize;
74+
75+
/**
76+
* Size of the range in number of bytes (or range unit). If the size is the maximum
5977
* available from the index, then use the {@value #SIZE_MAX} constant.
6078
*/
6179
private volatile long size;
@@ -97,7 +115,26 @@ public Range(long size) {
97115
public Range(long index, long size) {
98116
this.index = index;
99117
this.size = size;
100-
this.unitName = "bytes";
118+
this.unitName = RANGE_BYTES_UNIT;
119+
}
120+
121+
/**
122+
* Constructor. Sets the name of the range unit as "bytes" by default.
123+
*
124+
* @param index
125+
* Index from which to start the range
126+
* @param size
127+
* Size of the range in number of bytes.
128+
* @param instanceSize
129+
* Size of the instance in number of bytes.
130+
* @param unitName
131+
* Unit of the range.
132+
*/
133+
public Range(long index, long size, long instanceSize, String unitName) {
134+
this.index = index;
135+
this.instanceSize = instanceSize;
136+
this.size = size;
137+
this.unitName = unitName;
101138
}
102139

103140
@Override
@@ -120,10 +157,19 @@ public long getIndex() {
120157
return index;
121158
}
122159

160+
/**
161+
* Returns the total size of the instance in number of bytes (or range unit). In case of "bytes" range, this
162+
* attribute is ignored, as the instance size is taken from the entity.
163+
*
164+
* @return The total size of the instance.
165+
*/
166+
public long getInstanceSize() {
167+
return instanceSize;
168+
}
169+
123170
/**
124171
* Returns the size of the range in number of bytes. If the size is the
125-
* maximum available from the index, then use the {@value #SIZE_MAX}
126-
* constant.
172+
* maximum available from the index, then use the {@value #SIZE_MAX} constant.
127173
*
128174
* @return The size of the range in number of bytes.
129175
*/
@@ -185,6 +231,16 @@ public void setIndex(long index) {
185231
this.index = index;
186232
}
187233

234+
/**
235+
* Sets the total size of the instance in number of bytes (or range unit).
236+
*
237+
* @param instanceSize
238+
* The total size of the instance.
239+
*/
240+
public void setInstanceSize(long instanceSize) {
241+
this.instanceSize = instanceSize;
242+
}
243+
188244
/**
189245
* Sets the size of the range in number of bytes. If the size is the maximum
190246
* available from the index, then use the {@value #SIZE_MAX} constant.

modules/org.restlet/src/org/restlet/engine/application/RangeFilter.java

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
package org.restlet.engine.application;
2626

27+
import static org.restlet.data.Range.isBytesRange;
28+
2729
import org.restlet.Context;
2830
import org.restlet.Request;
2931
import org.restlet.Response;
@@ -57,11 +59,11 @@ protected void afterHandle(Request request, Response response) {
5759
response.getServerInfo().setAcceptingRanges(true);
5860

5961
if (request.getMethod().isSafe() && response.isEntityAvailable()) {
60-
boolean rangedEntity = response.getEntity().getRange() != null;
62+
Range responseRange = response.getEntity().getRange();
63+
boolean rangedEntity = responseRange != null && isBytesRange(responseRange);
6164

6265
if (response.getStatus().isSuccess()) {
63-
if (Status.SUCCESS_PARTIAL_CONTENT.equals(response
64-
.getStatus())) {
66+
if (Status.SUCCESS_PARTIAL_CONTENT.equals(response.getStatus())) {
6567
if (!rangedEntity) {
6668
getLogger()
6769
.warning(
@@ -73,43 +75,37 @@ protected void afterHandle(Request request, Response response) {
7375
} else {
7476
// At this time, list of ranges are not supported.
7577
if (request.getRanges().size() == 1
76-
&& (!request.getConditions().hasSomeRange() || request
77-
.getConditions()
78-
.getRangeStatus(response.getEntity())
79-
.isSuccess())) {
78+
&& (!request.getConditions().hasSomeRange()
79+
|| request.getConditions().getRangeStatus(response.getEntity()).isSuccess())) {
8080
Range requestedRange = request.getRanges().get(0);
8181

8282
if ((!response.getEntity().hasKnownSize())
83-
&& ((requestedRange.getIndex() == Range.INDEX_LAST || requestedRange
84-
.getSize() == Range.SIZE_MAX) && !(requestedRange
85-
.getIndex() == Range.INDEX_LAST && requestedRange
86-
.getSize() == Range.SIZE_MAX))) {
83+
&& ((requestedRange.getIndex() == Range.INDEX_LAST
84+
|| requestedRange.getSize() == Range.SIZE_MAX)
85+
&& !(requestedRange.getIndex() == Range.INDEX_LAST
86+
&& requestedRange.getSize() == Range.SIZE_MAX))) {
8787
// The end index cannot be properly computed
8888
response.setStatus(Status.SERVER_ERROR_INTERNAL);
8989
getLogger()
9090
.warning(
9191
"Unable to serve this range since at least the end index of the range cannot be computed.");
9292
response.setEntity(null);
93-
} else if (!requestedRange.equals(response
94-
.getEntity().getRange())) {
93+
} else if (!requestedRange.equals(responseRange)) {
9594
if (rangedEntity) {
96-
getLogger()
97-
.info("The range of the response entity is not equal to the requested one.");
95+
getLogger().info(
96+
"The range of the response entity is not equal to the requested one.");
9897
}
9998

10099
if (response.getEntity().hasKnownSize()
101-
&& requestedRange.getSize() > response
102-
.getEntity().getAvailableSize()) {
100+
&& requestedRange.getSize() > response.getEntity().getAvailableSize()) {
103101
requestedRange.setSize(Range.SIZE_MAX);
104102
}
105103

106-
response.setEntity(new RangeRepresentation(
107-
response.getEntity(), requestedRange));
104+
response.setEntity(new RangeRepresentation(response.getEntity(), requestedRange));
108105
response.setStatus(Status.SUCCESS_PARTIAL_CONTENT);
109106
}
110107
} else if (request.getRanges().size() > 1) {
111-
// Return a server error as this feature isn't
112-
// supported yet
108+
// Return a server error as this feature isn't supported yet
113109
response.setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED);
114110
getLogger()
115111
.warning(

modules/org.restlet/src/org/restlet/engine/application/RangeRepresentation.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ public long getAvailableSize() {
8282

8383
// [ifndef gwt] method
8484
@Override
85-
public java.nio.channels.ReadableByteChannel getChannel()
86-
throws IOException {
85+
public java.nio.channels.ReadableByteChannel getChannel() throws IOException {
8786
return IoUtils.getChannel(getStream());
8887
}
8988

0 commit comments

Comments
 (0)