Skip to content

Instantly share code, notes, and snippets.

@stopher
Last active December 7, 2017 16:40
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stopher/5495353 to your computer and use it in GitHub Desktop.
Save stopher/5495353 to your computer and use it in GitHub Desktop.
Byte range requests in Play 2 Java Controllers. Eg. for serving MP4 video files to iPhone etc.
public class ByteRangeRequestsController extends Controller {
// 206 Partial content Byte range requests
private static Result stream(long start, long length, File file) throws IOException {
FileInputStream fis = new FileInputStream(file);
fis.skip(start);
response().setContentType(MimeTypes.forExtension("mp4").get());
response().setHeader(CONTENT_LENGTH, ((length - start) +1l)+"");
response().setHeader(CONTENT_RANGE, String.format("bytes %d-%d/%d", start, length,file.length()));
response().setHeader(ACCEPT_RANGES, "bytes");
response().setHeader(CONNECTION, "keep-alive");
return status(PARTIAL_CONTENT, fis);
}
// GET a file item
public static Result file(Long id, String filename) throws IOException {
Item item = Item.fetch(id);
File file = item.getFile();
if(file== null || !file.exists()) {
Logger.error("File no longer exist item"+id+" filename:"+filename);
return notFound();
}
String rangeheader = request().getHeader(RANGE);
if(rangeheader != null) {
String[] split = rangeheader.substring("bytes=".length()).split("-");
if(Logger.isDebugEnabled()) { Logger.debug("Range header is:"+rangeheader); }
if(split.length == 1) {
long start = Long.parseLong(split[0]);
long length = file.length()-1l;
return stream(start, length, file);
} else {
long start = Long.parseLong(split[0]);
long length = Long.parseLong(split[1]);
return stream(start, length, file);
}
}
// if no streaming is required we simply return the file as a 200 OK
if(Play.isProd()) {
response().setHeader("Cache-Control", "max-age=3600, must-revalidate");
}
return ok(file);
}
}
@felix-schwarz
Copy link

You should not send a Content-Length header together with a Content-Range header. If you do, and the Play application is behind an nginx proxy, nginx will handle the response data from Play as "unchunked" whereas Play sends a "chunked" response when using FileInputStream. If you want a chunked response to be piped through nginx correctly, you should not use Content-Length here.

@mkurz
Copy link

mkurz commented Feb 11, 2015

@felix-schwarz Are you sure? I am not seeing any issues when sending the Content-Length header. Do you have some more information - what exactly should fail when sending the header?

@riteshjha
Copy link

Great post, thank you so much, you made my day. I saw lots of post related to streaming all have some bug or error but your post is complete.

@vgogov
Copy link

vgogov commented Dec 7, 2017

Anyone have issues with Netty using this? I get:

2017-12-07T16:39:35.246Z [warn] com.lightbend.lagom.gateway.NettyServiceGateway [] - Exception caught in proxy connection
java.lang.NullPointerException: null
at io.netty.handler.codec.http.HttpHeaders.isKeepAlive(HttpHeaders.java:576)
at com.lightbend.lagom.gateway.NettyServiceGateway$ProxyHandler$$anon$3.handleLastContent(NettyServiceGateway.scala:298)
at com.lightbend.lagom.gateway.NettyServiceGateway$ProxyHandler$$anon$3.channelRead(NettyServiceGateway.scala:251)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:356)
at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:35)
at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:347)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:399)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:464)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:131)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
at java.lang.Thread.run(Thread.java:748)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment