如何在 Play Framework (Java) v2.4.x 中提供响应式 ByteChunk
How to serve reactive ByteChunks in Play Framework (Java) v2.4.x
我有一个工作代码库,它服务于 Chunks<byte[]>
包装在 HTTP 状态 206 中。代码将 .mp3 文件流式传输到浏览器。
public class ChunksController extends Controller {
private static byte[] song;
private static RangeWrapper range;
private static Chunks<byte[]> chunks;
@With(MP3Headers.class)
public static F.Promise<Result> handler() {
return F.Promise.promise(new Function0<Result>() {
public Result apply() throws Throwable {
// Now we are serving a Promise of a Result
// Is there a way to serve a Promise of Chunks<byte[]>?
song = Files.readAllBytes(Paths.get("public/mp3/song.mp3"));
range = new RangeWrapper(request().getHeader("Range"), song.length);
chunks = new ByteChunks() {
public void onReady(Chunks.Out<byte[]> out) {
out.write(Arrays.copyOfRange(song, range.getFrom(), range.getTo()));
out.close();
}
};
// Set response headers so that the browser knows the content
response().setHeader("Content-Range", String.format("bytes %d-%d/%d", range.getFrom(), range.getTo(), song.length));
response().setHeader("X-Content-Length", Integer.toString(range.getContentLength()));
response().setHeader("Content-Length", Integer.toString(range.getContentLength()));
// I am very much wondering if this is the blocking part of the program...
return status(206, chunks);
}
});
}
}
在代码示例中,MP3Headers
和RangeWrapper
是方便的类,定义如下。
public class MP3Headers extends Action.Simple {
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
ctx.response().setContentType("audio/mpeg");
ctx.response().setHeader("Accept-Ranges", "bytes");
return delegate.call(ctx);
}
}
而 RangeWrapper
仅存储所请求的起始和终止。
public class RangeWrapper {
private int from;
private int to;
public RangeWrapper(String range, int totalLength) {
// check range header for content
if (range != null) {
String[] intermediary = range.split("=")[1].split("-");
this.from = Integer.parseInt(intermediary[0]);
if (intermediary.length > 1) {
this.to = Integer.parseInt(intermediary[1]);
} else {
this.to = totalLength - 1;
}
} else {
this.from = 0;
this.to = totalLength - 1;
}
}
/** Getters */
public int getFrom() {
return this.from;
}
public int getTo() {
return this.to;
}
public int getContentLength() {
return this.to - this.from + 1;
}
}
现在解决实际问题。 Play Server 可以处理的实例数量似乎非常有限,我认为这与 ByteChunks
的同步性质有关。在下图中,您可以看到只有五个请求得到了数据的满足。其余的似乎没有得到任何内容。有人能解决这个问题吗?
此致,蒂姆
您的 getContentLength
方法不正确,因为它 returns 负长度:
MP3 size = 12000
from = 0
to = 5000
getContentLength = -4999
修复此方法,它应该可以工作。
除此之外,您应该注意 range
和 chunks
是静态字段,您可能会遇到并发问题。同样在 Play 中,控制器默认是异步的,所以我认为没有必要将代码包装在 promise 中。
我有一个工作代码库,它服务于 Chunks<byte[]>
包装在 HTTP 状态 206 中。代码将 .mp3 文件流式传输到浏览器。
public class ChunksController extends Controller {
private static byte[] song;
private static RangeWrapper range;
private static Chunks<byte[]> chunks;
@With(MP3Headers.class)
public static F.Promise<Result> handler() {
return F.Promise.promise(new Function0<Result>() {
public Result apply() throws Throwable {
// Now we are serving a Promise of a Result
// Is there a way to serve a Promise of Chunks<byte[]>?
song = Files.readAllBytes(Paths.get("public/mp3/song.mp3"));
range = new RangeWrapper(request().getHeader("Range"), song.length);
chunks = new ByteChunks() {
public void onReady(Chunks.Out<byte[]> out) {
out.write(Arrays.copyOfRange(song, range.getFrom(), range.getTo()));
out.close();
}
};
// Set response headers so that the browser knows the content
response().setHeader("Content-Range", String.format("bytes %d-%d/%d", range.getFrom(), range.getTo(), song.length));
response().setHeader("X-Content-Length", Integer.toString(range.getContentLength()));
response().setHeader("Content-Length", Integer.toString(range.getContentLength()));
// I am very much wondering if this is the blocking part of the program...
return status(206, chunks);
}
});
}
}
在代码示例中,MP3Headers
和RangeWrapper
是方便的类,定义如下。
public class MP3Headers extends Action.Simple {
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
ctx.response().setContentType("audio/mpeg");
ctx.response().setHeader("Accept-Ranges", "bytes");
return delegate.call(ctx);
}
}
而 RangeWrapper
仅存储所请求的起始和终止。
public class RangeWrapper {
private int from;
private int to;
public RangeWrapper(String range, int totalLength) {
// check range header for content
if (range != null) {
String[] intermediary = range.split("=")[1].split("-");
this.from = Integer.parseInt(intermediary[0]);
if (intermediary.length > 1) {
this.to = Integer.parseInt(intermediary[1]);
} else {
this.to = totalLength - 1;
}
} else {
this.from = 0;
this.to = totalLength - 1;
}
}
/** Getters */
public int getFrom() {
return this.from;
}
public int getTo() {
return this.to;
}
public int getContentLength() {
return this.to - this.from + 1;
}
}
现在解决实际问题。 Play Server 可以处理的实例数量似乎非常有限,我认为这与 ByteChunks
的同步性质有关。在下图中,您可以看到只有五个请求得到了数据的满足。其余的似乎没有得到任何内容。有人能解决这个问题吗?
此致,蒂姆
您的 getContentLength
方法不正确,因为它 returns 负长度:
MP3 size = 12000
from = 0
to = 5000
getContentLength = -4999
修复此方法,它应该可以工作。
除此之外,您应该注意 range
和 chunks
是静态字段,您可能会遇到并发问题。同样在 Play 中,控制器默认是异步的,所以我认为没有必要将代码包装在 promise 中。