Skip to content

Commit c11b3d5

Browse files
committed
byte range support fix jooby-project#542
* single byte range support for inputstream and filechannel * Video files not playing on Safari and iOS devices fix jooby-project#523
1 parent 91c376a commit c11b3d5

File tree

19 files changed

+582
-142
lines changed

19 files changed

+582
-142
lines changed

coverage-report/src/test/java/org/jooby/issues/Issue526.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class Issue526 extends ServerFeature {
1515
});
1616
}
1717

18+
@Test
1819
public void shouldAcceptAdvancedRegexPathExpression() throws Exception {
1920
request()
2021
.get("/526/V1234")
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package org.jooby.issues;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.nio.charset.StandardCharsets;
7+
8+
import org.jooby.test.ServerFeature;
9+
import org.junit.Test;
10+
11+
import com.google.common.io.ByteStreams;
12+
13+
public class Issue542 extends ServerFeature {
14+
15+
{
16+
assets("/assets/video.mp4");
17+
18+
get("/no-len", req -> {
19+
return new ByteArrayInputStream("nolen".getBytes(StandardCharsets.UTF_8));
20+
});
21+
}
22+
23+
@Test
24+
public void shouldGetFullContent() throws Exception {
25+
request()
26+
.get("/assets/video.mp4")
27+
.expect(200)
28+
.header("Content-Length", "383631");
29+
}
30+
31+
@Test
32+
public void shouldGetPartialContent() throws Exception {
33+
request()
34+
.get("/assets/video.mp4")
35+
.header("Range", "bytes=0-99")
36+
.expect(206)
37+
.expect(bytes(0, 100))
38+
.header("Accept-Ranges", "bytes")
39+
.header("Content-Range", "bytes 0-99/383631")
40+
.header("Content-Length", "100");
41+
42+
request()
43+
.get("/assets/video.mp4")
44+
.header("Range", "bytes=100-199")
45+
.expect(206)
46+
.expect(bytes(100, 200))
47+
.header("Accept-Ranges", "bytes")
48+
.header("Content-Range", "bytes 100-199/383631")
49+
.header("Content-Length", "100");
50+
}
51+
52+
@Test
53+
public void shouldGetPartialContentByPrefix() throws Exception {
54+
request()
55+
.get("/assets/video.mp4")
56+
.header("Range", "bytes=0-")
57+
.expect(206)
58+
.expect(bytes(0, 383631))
59+
.header("Accept-Ranges", "bytes")
60+
.header("Content-Range", "bytes 0-383630/383631")
61+
.header("Content-Length", "383631");
62+
}
63+
64+
@Test
65+
public void shouldGetPartialContentBySuffix() throws Exception {
66+
request()
67+
.get("/assets/video.mp4")
68+
.header("Range", "bytes=-100")
69+
.expect(206)
70+
.expect(bytes(383631 - 100, 383631))
71+
.header("Accept-Ranges", "bytes")
72+
.header("Content-Range", "bytes 383531-383630/383631")
73+
.header("Content-Length", "100");
74+
}
75+
76+
@Test
77+
public void shouldGetPartialContentWhenEndExceed() throws Exception {
78+
request()
79+
.get("/assets/video.mp4")
80+
.header("Range", "bytes=383629-383632")
81+
.expect(206)
82+
.expect(bytes(383631 - 2, 383631))
83+
.header("Accept-Ranges", "bytes")
84+
.header("Content-Range", "bytes 383629-383630/383631")
85+
.header("Content-Length", "2");
86+
}
87+
88+
@Test
89+
public void shouldGet416OnInvalidRange() throws Exception {
90+
request()
91+
.get("/assets/video.mp4")
92+
.header("Range", "bytes=200-100")
93+
.expect(416)
94+
.header("Content-Range", "bytes */383631");
95+
96+
request()
97+
.get("/assets/video.mp4")
98+
.header("Range", "bytes=x-100")
99+
.expect(416)
100+
.header("Content-Range", "bytes */383631");
101+
102+
request()
103+
.get("/assets/video.mp4")
104+
.header("Range", "bytes=0-x")
105+
.expect(416)
106+
.header("Content-Range", "bytes */383631");
107+
108+
request()
109+
.get("/assets/video.mp4")
110+
.header("Range", "bytes=")
111+
.expect(416)
112+
.header("Content-Range", "bytes */383631");
113+
}
114+
115+
@Test
116+
public void shouldGet416UnknownRange() throws Exception {
117+
request()
118+
.get("/assets/video.mp4")
119+
.header("Range", "foo")
120+
.expect(416)
121+
.header("Content-Range", "bytes */383631");
122+
}
123+
124+
private byte[] bytes(final int offset, final int len) throws IOException {
125+
try (InputStream stream = getClass().getResourceAsStream("/assets/video.mp4")) {
126+
byte[] range = new byte[len - offset];
127+
byte[] bytes = ByteStreams.toByteArray(stream);
128+
System.arraycopy(bytes, offset, range, 0, len - offset);
129+
return range;
130+
}
131+
}
132+
133+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package org.jooby.issues;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
import org.jooby.test.ServerFeature;
7+
import org.junit.Test;
8+
9+
import com.google.common.io.ByteStreams;
10+
11+
public class Issue542b extends ServerFeature {
12+
13+
{
14+
assets("/webjars/**", "/META-INF/resources/webjars/{0}");
15+
}
16+
17+
@Test
18+
public void shouldGetFullContent() throws Exception {
19+
request()
20+
.get("/webjars/jquery/2.1.3/jquery.js")
21+
.expect(200)
22+
.header("Content-Length", "247387");
23+
}
24+
25+
@Test
26+
public void shouldGetPartialContent() throws Exception {
27+
request()
28+
.get("/webjars/jquery/2.1.3/jquery.js")
29+
.header("Range", "bytes=0-99")
30+
.expect(206)
31+
.expect(bytes(0, 100))
32+
.header("Accept-Ranges", "bytes")
33+
.header("Content-Range", "bytes 0-99/247387")
34+
.header("Content-Length", "100");
35+
36+
request()
37+
.get("/webjars/jquery/2.1.3/jquery.js")
38+
.header("Range", "bytes=100-199")
39+
.expect(206)
40+
.expect(bytes(100, 200))
41+
.header("Accept-Ranges", "bytes")
42+
.header("Content-Range", "bytes 100-199/247387")
43+
.header("Content-Length", "100");
44+
}
45+
46+
@Test
47+
public void shouldGetPartialContentByPrefix() throws Exception {
48+
request()
49+
.get("/webjars/jquery/2.1.3/jquery.js")
50+
.header("Range", "bytes=0-")
51+
.expect(206)
52+
.expect(bytes(0, 247387))
53+
.header("Accept-Ranges", "bytes")
54+
.header("Content-Range", "bytes 0-247386/247387")
55+
.header("Content-Length", "247387");
56+
}
57+
58+
@Test
59+
public void shouldGetPartialContentBySuffix() throws Exception {
60+
request()
61+
.get("/webjars/jquery/2.1.3/jquery.js")
62+
.header("Range", "bytes=-100")
63+
.expect(206)
64+
.expect(bytes(247387 - 100, 247387))
65+
.header("Accept-Ranges", "bytes")
66+
.header("Content-Range", "bytes 247287-247386/247387")
67+
.header("Content-Length", "100");
68+
}
69+
70+
@Test
71+
public void shouldGet416() throws Exception {
72+
request()
73+
.get("/webjars/jquery/2.1.3/jquery.js")
74+
.header("Range", "bytes=200-100")
75+
.expect(416)
76+
.header("Content-Range", "bytes */247387");
77+
}
78+
79+
private byte[] bytes(final int offset, final int len) throws IOException {
80+
try (InputStream stream = getClass()
81+
.getResourceAsStream("/META-INF/resources/webjars/jquery/2.1.3/jquery.js")) {
82+
byte[] range = new byte[len - offset];
83+
byte[] bytes = ByteStreams.toByteArray(stream);
84+
System.arraycopy(bytes, offset, range, 0, len - offset);
85+
return range;
86+
}
87+
}
88+
89+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.jooby.issues;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
import org.jooby.test.ServerFeature;
7+
import org.junit.Test;
8+
9+
import com.google.common.io.ByteStreams;
10+
11+
public class Issue542c extends ServerFeature {
12+
13+
{
14+
get("/download/video.mp4", (req, rsp) -> {
15+
rsp.download("video.mp4", "/assets/video.mp4");
16+
});
17+
}
18+
19+
@Test
20+
public void shouldGetFullContent() throws Exception {
21+
request()
22+
.get("/download/video.mp4")
23+
.expect(200)
24+
.header("Content-Length", "383631");
25+
}
26+
27+
@Test
28+
public void shouldGetPartialContent() throws Exception {
29+
request()
30+
.get("/download/video.mp4")
31+
.header("Range", "bytes=0-99")
32+
.expect(206)
33+
.expect(bytes(0, 100))
34+
.header("Accept-Ranges", "bytes")
35+
.header("Content-Range", "bytes 0-99/383631")
36+
.header("Content-Length", "100");
37+
38+
request()
39+
.get("/download/video.mp4")
40+
.header("Range", "bytes=100-199")
41+
.expect(206)
42+
.expect(bytes(100, 200))
43+
.header("Accept-Ranges", "bytes")
44+
.header("Content-Range", "bytes 100-199/383631")
45+
.header("Content-Length", "100");
46+
}
47+
48+
@Test
49+
public void shouldGetPartialContentByPrefix() throws Exception {
50+
request()
51+
.get("/download/video.mp4")
52+
.header("Range", "bytes=0-")
53+
.expect(206)
54+
.expect(bytes(0, 383631))
55+
.header("Accept-Ranges", "bytes")
56+
.header("Content-Range", "bytes 0-383630/383631")
57+
.header("Content-Length", "383631");
58+
}
59+
60+
@Test
61+
public void shouldGetPartialContentBySuffix() throws Exception {
62+
request()
63+
.get("/download/video.mp4")
64+
.header("Range", "bytes=-100")
65+
.expect(206)
66+
.expect(bytes(383631 - 100, 383631))
67+
.header("Accept-Ranges", "bytes")
68+
.header("Content-Range", "bytes 383531-383630/383631")
69+
.header("Content-Length", "100");
70+
}
71+
72+
@Test
73+
public void shouldGet416() throws Exception {
74+
request()
75+
.get("/download/video.mp4")
76+
.header("Range", "bytes=200-100")
77+
.expect(416)
78+
.header("Content-Range", "bytes */383631");
79+
}
80+
81+
private byte[] bytes(final int offset, final int len) throws IOException {
82+
try (InputStream stream = getClass().getResourceAsStream("/assets/video.mp4")) {
83+
byte[] range = new byte[len - offset];
84+
byte[] bytes = ByteStreams.toByteArray(stream);
85+
System.arraycopy(bytes, offset, range, 0, len - offset);
86+
return range;
87+
}
88+
}
89+
90+
}
375 KB
Binary file not shown.

jooby-netty/src/main/java/org/jooby/internal/netty/NettyResponse.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,16 @@ public void send(final InputStream stream) throws Exception {
164164

165165
@Override
166166
public void send(final FileChannel channel) throws Exception {
167-
long len = channel.size();
167+
send(channel, 0, channel.size());
168+
}
168169

170+
@Override
171+
public void send(final FileChannel channel, final long offset, final long count)
172+
throws Exception {
169173
DefaultHttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
170174
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
171175
headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
172-
headers.set(HttpHeaderNames.CONTENT_LENGTH, len);
176+
headers.set(HttpHeaderNames.CONTENT_LENGTH, count);
173177
}
174178

175179
if (keepAlive) {
@@ -183,11 +187,12 @@ public void send(final FileChannel channel) throws Exception {
183187
ctx.channel().eventLoop().execute(() -> {
184188
// send headers
185189
ctx.write(rsp);
186-
ctx.write(new DefaultFileRegion(channel, 0, len));
190+
ctx.write(new DefaultFileRegion(channel, offset, count));
187191
keepAlive(ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT));
188192
});
189193

190194
committed = true;
195+
191196
}
192197

193198
private void send(final ByteBuf buffer) throws Exception {

jooby-servlet/src/main/java/org/jooby/servlet/ServletServletResponse.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,21 @@ public void send(final ByteBuffer buffer) throws Exception {
9696

9797
@Override
9898
public void send(final FileChannel file) throws Exception {
99-
WritableByteChannel channel = Channels.newChannel(rsp.getOutputStream());
100-
ByteStreams.copy(file, channel);
101-
file.close();
102-
channel.close();
99+
try (FileChannel src = file) {
100+
WritableByteChannel channel = Channels.newChannel(rsp.getOutputStream());
101+
src.transferTo(0, file.size(), channel);
102+
channel.close();
103+
}
104+
}
105+
106+
@Override
107+
public void send(final FileChannel channel, final long position, final long count)
108+
throws Exception {
109+
try (FileChannel src = channel) {
110+
WritableByteChannel dest = Channels.newChannel(rsp.getOutputStream());
111+
src.transferTo(position, count, dest);
112+
dest.close();
113+
}
103114
}
104115

105116
@Override

0 commit comments

Comments
 (0)