Skip to content

Commit b96b5ed

Browse files
committed
Fix Range Requests
1 parent 2b5064e commit b96b5ed

File tree

10 files changed

+88
-46
lines changed

10 files changed

+88
-46
lines changed

src/main/java/httpserver/ResponderSupplierFactory.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ public ResponderSupplier makeResponderSupplier() {
1212
responderSupplier.registerResponder(Method.GET, new GetResponder(
1313
getRouteMap(),
1414
new PathExaminer(),
15-
new Html()));
15+
new Html(),
16+
new RangeHeaderValueParser()));
1617
responderSupplier.registerResponder(Method.HEAD, new HeadResponder(
1718
getRouteMap(),
1819
new PathExaminer(),
19-
new Html()));
20+
new Html(),
21+
new RangeHeaderValueParser()));
2022
responderSupplier.registerResponder(Method.POST, new PostResponder(
2123
new PathExaminer(),
2224
new FileOperator()));

src/main/java/httpserver/ResponseWriter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public ResponseWriter(OutputStream outputStream) {
2222
statuses.put(500, "Internal Server Error");
2323
statuses.put(204, "No Content");
2424
statuses.put(409, "Conflict");
25+
statuses.put(206, "Partial Content");
2526
}
2627

2728
public void write(Response response) {

src/main/java/httpserver/responder/GetResponder.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ public class GetResponder implements Responder {
1919
private final PathExaminer pathExaminer;
2020
private final RouteMap specialCaseRouteMap;
2121
private final Html html;
22+
private RangeHeaderValueParser rangeHeaderValueParser;
2223

23-
public GetResponder(RouteMap getRouteMap, PathExaminer pathExaminer, Html html) {
24+
public GetResponder(RouteMap getRouteMap,
25+
PathExaminer pathExaminer,
26+
Html html,
27+
RangeHeaderValueParser rangeHeaderValueParser) {
2428
this.pathExaminer = pathExaminer;
2529
this.specialCaseRouteMap = getRouteMap;
2630
this.html = html;
31+
this.rangeHeaderValueParser = rangeHeaderValueParser;
2732
}
2833

2934
@Override
@@ -68,7 +73,7 @@ private Response responseForFile(Path path, Request request) {
6873
Response response = null;
6974
if(request.hasHeader("Range")) {
7075
String rangeHeaderValue = request.getHeaderValue("Range");
71-
Range range = new RangeHeaderValueParser().parse(rangeHeaderValue);
76+
Range range = rangeHeaderValueParser.parse(rangeHeaderValue, payload.length);
7277
response = new PartialContentResponse(range, payload);
7378
} else {
7479
response = new OkResponse(payload);

src/main/java/httpserver/responder/HeadResponder.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
import java.io.IOException;
1010

1111
public class HeadResponder extends GetResponder {
12-
public HeadResponder(RouteMap getRouteMap, PathExaminer pathExaminer, Html html) {
13-
super(getRouteMap, pathExaminer, html);
12+
public HeadResponder(RouteMap getRouteMap,
13+
PathExaminer pathExaminer,
14+
Html html,
15+
RangeHeaderValueParser rangeHeaderValueParser) {
16+
super(getRouteMap, pathExaminer, html, rangeHeaderValueParser);
1417
}
1518

1619
@Override

src/main/java/httpserver/responder/RangeHeaderValueParser.java

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,44 @@
33
import httpserver.Range;
44

55
public class RangeHeaderValueParser {
6-
public Range parse(String headerValue) {
7-
String rangeString = headerValue.substring(6);
8-
String[] parts = rangeString.split("-", 2);
9-
int start = parseRangeValue(parts[0], 0);
10-
int end = parseRangeValue(parts[1], Integer.MAX_VALUE);
6+
public Range parse(String headerValue, int payloadLength) {
7+
String[] parts = getParts(headerValue);
8+
String firstPart = parts[0];
9+
String secondPart = parts[1];
10+
11+
if (firstPart.equals("")) {
12+
return getRangeAtEnd(payloadLength, secondPart);
13+
}
14+
15+
if (secondPart.equals("")) {
16+
return getRangeAtStart(payloadLength, firstPart);
17+
}
18+
19+
return getRange(parts);
20+
}
21+
22+
private Range getRangeAtStart(int payloadLength, String firstPart) {
23+
int start = parseRangeValue(firstPart);
24+
return new Range(start, payloadLength - 1);
25+
}
26+
27+
private Range getRangeAtEnd(int payloadLength, String secondPart) {
28+
int start = payloadLength - parseRangeValue(secondPart);
29+
return new Range(start, payloadLength - 1);
30+
}
31+
32+
private Range getRange(String[] parts) {
33+
int start = parseRangeValue(parts[0]);
34+
int end = parseRangeValue(parts[1]);
1135
return new Range(start, end);
1236
}
1337

14-
private int parseRangeValue(String value, int fallback) {
15-
try {
16-
return Integer.parseInt(value);
17-
} catch (NumberFormatException e) {
18-
return fallback;
19-
}
38+
private String[] getParts(String headerValue) {
39+
String rangeString = headerValue.substring(6);
40+
return rangeString.split("-", 2);
41+
}
42+
43+
private int parseRangeValue(String value) {
44+
return Integer.parseInt(value);
2045
}
2146
}

src/main/java/httpserver/response/PartialContentResponse.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ public PartialContentResponse(Range range, byte[] payload) {
1111
}
1212

1313
private byte[] getPartialPayload(Range range, byte[] payload) {
14-
if (range.getEnd() > payload.length) {
15-
return Arrays.copyOfRange(payload, range.getStart(), payload.length);
16-
}
17-
return Arrays.copyOfRange(payload, range.getStart(), range.getEnd());
14+
return Arrays.copyOfRange(payload, range.getStart(), range.getEnd() + 1);
1815
}
1916

2017
@Override

src/test/java/httpserver/responder/GetResponderTest.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package httpserver.responder;
22

33
import httpserver.AppConfig;
4+
import httpserver.Range;
45
import httpserver.file.Html;
56
import httpserver.file.PathExaminer;
67
import httpserver.header.Header;
@@ -23,13 +24,18 @@ public class GetResponderTest {
2324
private final PathExaminer pathExaminerMock;
2425
private final Path rootMock;
2526
private final Html htmlMock;
27+
private final RangeHeaderValueParser rangeHeaderValueParserMock;
2628

2729
public GetResponderTest() throws IOException {
2830
routeMapMock = mock(RouteMap.class);
2931
pathExaminerMock = mock(PathExaminer.class);
3032

3133
htmlMock = mock(Html.class);
32-
getResponder = new GetResponder(routeMapMock, pathExaminerMock, htmlMock);
34+
rangeHeaderValueParserMock = mock(RangeHeaderValueParser.class);
35+
getResponder = new GetResponder(routeMapMock,
36+
pathExaminerMock,
37+
htmlMock,
38+
rangeHeaderValueParserMock);
3339
appConfigMock = mock(AppConfig.class);
3440
rootMock = mock(Path.class);
3541
when(appConfigMock.getRoot()).thenReturn(rootMock);
@@ -90,14 +96,17 @@ public void respondsToRangeRequest() throws Exception {
9096
byte[] payloadMock = "range test string".getBytes();
9197
when(pathExaminerMock.fileContents(fullPathMock)).thenReturn(payloadMock);
9298

93-
Header[] headers = new Header[]{new Header("Range", "bytes=3-8")};
99+
String rangeHeaderValue = "bytes=3-8";
100+
Header[] headers = new Header[]{new Header("Range", rangeHeaderValue)};
101+
Range rangeMock = mock(Range.class);
102+
when(rangeHeaderValueParserMock.parse(rangeHeaderValue, payloadMock.length)).thenReturn(rangeMock);
103+
94104
Request request = new Request("GET", "/filename", headers, "");
95105

96106
Response response = getResponder.respond(appConfigMock, request);
97107

98108
assertEquals(206, response.getStatusCode());
99-
assertEquals("ge te", new String(response.getPayload()));
100-
assertEquals(new Header("Content-Length", "5"), response.getContentLengthHeader());
109+
verify(rangeHeaderValueParserMock).parse(rangeHeaderValue, payloadMock.length);
101110
}
102111

103112
@Test

src/test/java/httpserver/responder/HeadResponderTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ public void getPayloadReturnsEmptyByteArrayButContentLengthHeaderIsFull() throws
3636
when(appConfigMock.getRoot()).thenReturn(rootMock);
3737
RouteMap routeMapMock = mock(RouteMap.class);
3838
when(routeMapMock.hasRoute(any())).thenReturn(false);
39-
HeadResponder headResponder = new HeadResponder(routeMapMock, pathExaminerMock, mock(Html.class));
39+
HeadResponder headResponder = new HeadResponder(routeMapMock,
40+
pathExaminerMock,
41+
mock(Html.class),
42+
mock(RangeHeaderValueParser.class));
4043

4144
Request request = new Request("HEAD", "/filename", new Header[0], "");
4245

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,48 @@
11
package httpserver.responder;
22

33
import httpserver.Range;
4+
import org.junit.Ignore;
45
import org.junit.Test;
56

67
import static org.junit.Assert.*;
78

89
public class RangeHeaderValueParserTest {
910

1011
private final RangeHeaderValueParser rangeHeaderValueParser;
12+
private final int payloadLength;
1113

1214
public RangeHeaderValueParserTest() {
1315
rangeHeaderValueParser = new RangeHeaderValueParser();
16+
payloadLength = 10;
1417
}
1518

1619
@Test
1720
public void parsesSimpleRange() throws Exception {
18-
String headerValue = "bytes=6-10";
21+
String headerValue = "bytes=2-3";
1922

20-
Range range = rangeHeaderValueParser.parse(headerValue);
23+
Range range = rangeHeaderValueParser.parse(headerValue, payloadLength);
2124

22-
assertEquals(6, range.getStart());
23-
assertEquals(10, range.getEnd());
25+
assertEquals(2, range.getStart());
26+
assertEquals(3, range.getEnd());
2427
}
2528

2629
@Test
2730
public void parsesRangeWithEmptyStart() throws Exception {
28-
String headerValue = "bytes=-10";
31+
String headerValue = "bytes=-3";
2932

30-
Range range = rangeHeaderValueParser.parse(headerValue);
33+
Range range = rangeHeaderValueParser.parse(headerValue, payloadLength);
3134

32-
assertEquals(0, range.getStart());
33-
assertEquals(10, range.getEnd());
35+
assertEquals(7, range.getStart());
36+
assertEquals(9, range.getEnd());
3437
}
3538

3639
@Test
3740
public void rangeWithEmptyEndHasMaxIntegerEndValue() throws Exception {
38-
String headerValue = "bytes=8-";
41+
String headerValue = "bytes=3-";
3942

40-
Range range = rangeHeaderValueParser.parse(headerValue);
43+
Range range = rangeHeaderValueParser.parse(headerValue, payloadLength);
4144

42-
assertEquals(8, range.getStart());
43-
assertEquals(Integer.MAX_VALUE, range.getEnd());
45+
assertEquals(3, range.getStart());
46+
assertEquals(9, range.getEnd());
4447
}
4548
}

src/test/java/httpserver/response/PartialContentResponseTest.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ public class PartialContentResponseTest {
1111
public void partialRange() throws Exception {
1212
PartialContentResponse partialContentResponse = new PartialContentResponse(new Range(3, 8), "ranged text".getBytes());
1313
assertEquals(206, partialContentResponse.getStatusCode());
14-
assertEquals(new Header("Content-Length", "5"), partialContentResponse.getContentLengthHeader());
15-
assertEquals("ged t", new String(partialContentResponse.getPayload()));
16-
}
17-
18-
@Test
19-
public void partialRangeWithHighEnd() throws Exception {
20-
PartialContentResponse partialContentResponse = new PartialContentResponse(new Range(3, 10000), "ranged text".getBytes());
21-
assertEquals("ged text", new String(partialContentResponse.getPayload()));
14+
assertEquals(new Header("Content-Length", "6"), partialContentResponse.getContentLengthHeader());
15+
assertEquals("ged te", new String(partialContentResponse.getPayload()));
2216
}
2317
}

0 commit comments

Comments
 (0)