Skip to content

Commit

Permalink
byte range support fix #542
Browse files Browse the repository at this point in the history
* single byte range support for inputstream and filechannel
* Video files not playing on Safari and iOS devices fix #523
  • Loading branch information
jknack committed Nov 5, 2016
1 parent a92b093 commit d6c1bcf
Show file tree
Hide file tree
Showing 19 changed files with 582 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class Issue526 extends ServerFeature {
});
}

@Test
public void shouldAcceptAdvancedRegexPathExpression() throws Exception {
request()
.get("/526/V1234")
Expand Down
133 changes: 133 additions & 0 deletions coverage-report/src/test/java/org/jooby/issues/Issue542.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package org.jooby.issues;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import org.jooby.test.ServerFeature;
import org.junit.Test;

import com.google.common.io.ByteStreams;

public class Issue542 extends ServerFeature {

{
assets("/assets/video.mp4");

get("/no-len", req -> {
return new ByteArrayInputStream("nolen".getBytes(StandardCharsets.UTF_8));
});
}

@Test
public void shouldGetFullContent() throws Exception {
request()
.get("/assets/video.mp4")
.expect(200)
.header("Content-Length", "383631");
}

@Test
public void shouldGetPartialContent() throws Exception {
request()
.get("/assets/video.mp4")
.header("Range", "bytes=0-99")
.expect(206)
.expect(bytes(0, 100))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 0-99/383631")
.header("Content-Length", "100");

request()
.get("/assets/video.mp4")
.header("Range", "bytes=100-199")
.expect(206)
.expect(bytes(100, 200))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 100-199/383631")
.header("Content-Length", "100");
}

@Test
public void shouldGetPartialContentByPrefix() throws Exception {
request()
.get("/assets/video.mp4")
.header("Range", "bytes=0-")
.expect(206)
.expect(bytes(0, 383631))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 0-383630/383631")
.header("Content-Length", "383631");
}

@Test
public void shouldGetPartialContentBySuffix() throws Exception {
request()
.get("/assets/video.mp4")
.header("Range", "bytes=-100")
.expect(206)
.expect(bytes(383631 - 100, 383631))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 383531-383630/383631")
.header("Content-Length", "100");
}

@Test
public void shouldGetPartialContentWhenEndExceed() throws Exception {
request()
.get("/assets/video.mp4")
.header("Range", "bytes=383629-383632")
.expect(206)
.expect(bytes(383631 - 2, 383631))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 383629-383630/383631")
.header("Content-Length", "2");
}

@Test
public void shouldGet416OnInvalidRange() throws Exception {
request()
.get("/assets/video.mp4")
.header("Range", "bytes=200-100")
.expect(416)
.header("Content-Range", "bytes */383631");

request()
.get("/assets/video.mp4")
.header("Range", "bytes=x-100")
.expect(416)
.header("Content-Range", "bytes */383631");

request()
.get("/assets/video.mp4")
.header("Range", "bytes=0-x")
.expect(416)
.header("Content-Range", "bytes */383631");

request()
.get("/assets/video.mp4")
.header("Range", "bytes=")
.expect(416)
.header("Content-Range", "bytes */383631");
}

@Test
public void shouldGet416UnknownRange() throws Exception {
request()
.get("/assets/video.mp4")
.header("Range", "foo")
.expect(416)
.header("Content-Range", "bytes */383631");
}

private byte[] bytes(final int offset, final int len) throws IOException {
try (InputStream stream = getClass().getResourceAsStream("/assets/video.mp4")) {
byte[] range = new byte[len - offset];
byte[] bytes = ByteStreams.toByteArray(stream);
System.arraycopy(bytes, offset, range, 0, len - offset);
return range;
}
}

}
89 changes: 89 additions & 0 deletions coverage-report/src/test/java/org/jooby/issues/Issue542b.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.jooby.issues;

import java.io.IOException;
import java.io.InputStream;

import org.jooby.test.ServerFeature;
import org.junit.Test;

import com.google.common.io.ByteStreams;

public class Issue542b extends ServerFeature {

{
assets("/webjars/**", "/META-INF/resources/webjars/{0}");
}

@Test
public void shouldGetFullContent() throws Exception {
request()
.get("/webjars/jquery/2.1.3/jquery.js")
.expect(200)
.header("Content-Length", "247387");
}

@Test
public void shouldGetPartialContent() throws Exception {
request()
.get("/webjars/jquery/2.1.3/jquery.js")
.header("Range", "bytes=0-99")
.expect(206)
.expect(bytes(0, 100))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 0-99/247387")
.header("Content-Length", "100");

request()
.get("/webjars/jquery/2.1.3/jquery.js")
.header("Range", "bytes=100-199")
.expect(206)
.expect(bytes(100, 200))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 100-199/247387")
.header("Content-Length", "100");
}

@Test
public void shouldGetPartialContentByPrefix() throws Exception {
request()
.get("/webjars/jquery/2.1.3/jquery.js")
.header("Range", "bytes=0-")
.expect(206)
.expect(bytes(0, 247387))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 0-247386/247387")
.header("Content-Length", "247387");
}

@Test
public void shouldGetPartialContentBySuffix() throws Exception {
request()
.get("/webjars/jquery/2.1.3/jquery.js")
.header("Range", "bytes=-100")
.expect(206)
.expect(bytes(247387 - 100, 247387))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 247287-247386/247387")
.header("Content-Length", "100");
}

@Test
public void shouldGet416() throws Exception {
request()
.get("/webjars/jquery/2.1.3/jquery.js")
.header("Range", "bytes=200-100")
.expect(416)
.header("Content-Range", "bytes */247387");
}

private byte[] bytes(final int offset, final int len) throws IOException {
try (InputStream stream = getClass()
.getResourceAsStream("/META-INF/resources/webjars/jquery/2.1.3/jquery.js")) {
byte[] range = new byte[len - offset];
byte[] bytes = ByteStreams.toByteArray(stream);
System.arraycopy(bytes, offset, range, 0, len - offset);
return range;
}
}

}
90 changes: 90 additions & 0 deletions coverage-report/src/test/java/org/jooby/issues/Issue542c.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.jooby.issues;

import java.io.IOException;
import java.io.InputStream;

import org.jooby.test.ServerFeature;
import org.junit.Test;

import com.google.common.io.ByteStreams;

public class Issue542c extends ServerFeature {

{
get("/download/video.mp4", (req, rsp) -> {
rsp.download("video.mp4", "/assets/video.mp4");
});
}

@Test
public void shouldGetFullContent() throws Exception {
request()
.get("/download/video.mp4")
.expect(200)
.header("Content-Length", "383631");
}

@Test
public void shouldGetPartialContent() throws Exception {
request()
.get("/download/video.mp4")
.header("Range", "bytes=0-99")
.expect(206)
.expect(bytes(0, 100))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 0-99/383631")
.header("Content-Length", "100");

request()
.get("/download/video.mp4")
.header("Range", "bytes=100-199")
.expect(206)
.expect(bytes(100, 200))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 100-199/383631")
.header("Content-Length", "100");
}

@Test
public void shouldGetPartialContentByPrefix() throws Exception {
request()
.get("/download/video.mp4")
.header("Range", "bytes=0-")
.expect(206)
.expect(bytes(0, 383631))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 0-383630/383631")
.header("Content-Length", "383631");
}

@Test
public void shouldGetPartialContentBySuffix() throws Exception {
request()
.get("/download/video.mp4")
.header("Range", "bytes=-100")
.expect(206)
.expect(bytes(383631 - 100, 383631))
.header("Accept-Ranges", "bytes")
.header("Content-Range", "bytes 383531-383630/383631")
.header("Content-Length", "100");
}

@Test
public void shouldGet416() throws Exception {
request()
.get("/download/video.mp4")
.header("Range", "bytes=200-100")
.expect(416)
.header("Content-Range", "bytes */383631");
}

private byte[] bytes(final int offset, final int len) throws IOException {
try (InputStream stream = getClass().getResourceAsStream("/assets/video.mp4")) {
byte[] range = new byte[len - offset];
byte[] bytes = ByteStreams.toByteArray(stream);
System.arraycopy(bytes, offset, range, 0, len - offset);
return range;
}
}

}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,16 @@ public void send(final InputStream stream) throws Exception {

@Override
public void send(final FileChannel channel) throws Exception {
long len = channel.size();
send(channel, 0, channel.size());
}

@Override
public void send(final FileChannel channel, final long offset, final long count)
throws Exception {
DefaultHttpResponse rsp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH)) {
headers.remove(HttpHeaderNames.TRANSFER_ENCODING);
headers.set(HttpHeaderNames.CONTENT_LENGTH, len);
headers.set(HttpHeaderNames.CONTENT_LENGTH, count);
}

if (keepAlive) {
Expand All @@ -183,11 +187,12 @@ public void send(final FileChannel channel) throws Exception {
ctx.channel().eventLoop().execute(() -> {
// send headers
ctx.write(rsp);
ctx.write(new DefaultFileRegion(channel, 0, len));
ctx.write(new DefaultFileRegion(channel, offset, count));
keepAlive(ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT));
});

committed = true;

}

private void send(final ByteBuf buffer) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,21 @@ public void send(final ByteBuffer buffer) throws Exception {

@Override
public void send(final FileChannel file) throws Exception {
WritableByteChannel channel = Channels.newChannel(rsp.getOutputStream());
ByteStreams.copy(file, channel);
file.close();
channel.close();
try (FileChannel src = file) {
WritableByteChannel channel = Channels.newChannel(rsp.getOutputStream());
src.transferTo(0, file.size(), channel);
channel.close();
}
}

@Override
public void send(final FileChannel channel, final long position, final long count)
throws Exception {
try (FileChannel src = channel) {
WritableByteChannel dest = Channels.newChannel(rsp.getOutputStream());
src.transferTo(position, count, dest);
dest.close();
}
}

@Override
Expand Down
Loading

0 comments on commit d6c1bcf

Please sign in to comment.