如何在 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);
            }
        });
    }
}

在代码示例中,MP3HeadersRangeWrapper是方便的类,定义如下。

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

修复此方法,它应该可以工作。

除此之外,您应该注意 rangechunks 是静态字段,您可能会遇到并发问题。同样在 Play 中,控制器默认是异步的,所以我认为没有必要将代码包装在 promise 中。