Play 2.6 Streaming HTTP response

Streaming HTTP response

标准的响应及Content-Length

从HTTP1.1开始,服务端为了在single connection下对HTTP请求及响应提供服务,需要在response中提供响应的Content-Length。

默认情况下,不需要显示的指明Content-Length,比如以下的例子

public Result index() {
    return ok("Hello World");
}

由于发送的内容十分简单,play可以帮助我们计算内容的长度。

看一个用于play.http.HttpEntity指明相应体的例子:

public Result index() {
    return new Result(
        new ResponseHeader(200, Collections.emptyMap()),
        new HttpEntity.Strict(ByteString.fromString("Hello World"), Optional.of("text/plain"))
    );
}

这表明play在计算Content-Length时需要将整个内容读进内存中。

发送大内容的数据

如果需要发送很大的数据集该如何处理,来看一个给web客户端返回大文件的例子。

首先创建为文件创建一个Source[ByteString, _]

java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
java.nio.file.Path path = file.toPath();
Source<ByteString, ?> source = FileIO.fromPath(path);

然后使用streamed HttpEntity来返回相应体

public Result index() {
    java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
    java.nio.file.Path path = file.toPath();
    Source<ByteString, ?> source = FileIO.fromPath(path);

    return new Result(
        new ResponseHeader(200, Collections.emptyMap()),
        new HttpEntity.Streamed(source, Optional.empty(), Optional.of("text/plain"))
    );
}

这里存在一个问题,由于没有指定内容的长度,play需要将整个文件读到内存中来计算长度。在处理大文件时我们不希望这么处理,我们可以指定内容长度

public Result index() {
    java.io.File file = new java.io.File("/tmp/fileToServe.pdf");
    java.nio.file.Path path = file.toPath();
    Source<ByteString, ?> source = FileIO.fromPath(path);

    Optional<Long> contentLength = Optional.of(file.length());

    return new Result(
        new ResponseHeader(200, Collections.emptyMap()),
        new HttpEntity.Streamed(source, contentLength, Optional.of("text/plain"))
    );
}

在这种模式下Play会使用懒加载的方式,一块块的读取内容。

处理文件

Play提供了一种简单的方式来返回本地文件

public Result index() {
    return ok(new java.io.File("/tmp/fileToServe.pdf"));
}

这种方式业务根据文件名来计算Content-Type,然后添加Content-Disposition来告诉浏览器该如何处理文件。默认的方式是通过在相应头添加Content-Disposition: inline; filename=fileToServe.pdf显示文件为inline

也可以提供自己的文件名

public Result index() {
    return ok(new java.io.File("/tmp/fileToServe.pdf"), "fileToServe.pdf");
}

如果希望以attachment的方式提供文件

public Result index() {
    return ok(new java.io.File("/tmp/fileToServe.pdf"), /*inline = */false);
}

现在不必指定一个文件名因为流量拿起不会尝试去下载文件,仅仅是在窗口中进行展示。

Chunked response

现在我们可以很好的处理流式文件,但是如果返回的内容是动态生成的,无法提前获取大小,该如何处理?

对于这种响应可以使用Chunked transfer encoding

Chunked transfer encoding是HTTP1.1中一种数据传输方式,用于web服务以 a series of chunks的形式提供内容。使用Transfer-Encoding的响应头来取代Content-Length。由于Content-Length头没有使用,服务端在开始传送响应时不需要知道返回内容的长度。服务端在了解整个内容的长度前,可以以动态生成的方式传输内容。 在传输每个chunk之前会发送这个chunk的大小,所以客户端可以得知chunk的数据是否接收完毕。当最后一个chunk长度为0时整个传输结束。 https://en.wikipedia.org/wiki/Chunked_transfer_encoding

这么处理的优势是we can serve data live,意味着当数据可用时可以尽快的发送数据。缺点是浏览器不知道整个文件的大小,就无法显示一个正确的下载进度。

假设我们有一个服务会提供一个动态的计算一些数据的InputStream。我们可以让Play直接使用chunked response来流化内容。

public Result index() {
    InputStream is = getDynamicStreamSomewhere();
    return ok(is);
}

也可以构建自己的chunked response builder

public Result index() {
    // Prepare a chunked text stream
    Source<ByteString, ?> source = Source.<ByteString>actorRef(256, OverflowStrategy.dropNew())
        .mapMaterializedValue(sourceActor -> {
            sourceActor.tell(ByteString.fromString("kiki"), null);
            sourceActor.tell(ByteString.fromString("foo"), null);
            sourceActor.tell(ByteString.fromString("bar"), null);
            sourceActor.tell(new Status.Success(NotUsed.getInstance()), null);
            return NotUsed.getInstance();
        });
    // Serves this stream with 200 OK
    return ok().chunked(source);
}

Source.actorRef方法会创建一个Akka StreamsSource来实现一个ActorRef。可以通过向actor发送数据的方式来向流中发布元素。另一种选择是创建一个继承ActorPublish来的actor,然后使用Stream.actorPublisher方法来创建。

我们可以看到服务端发来的http响应

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked

4
kiki
3
foo
3
bar
0

我们得到了三个有数据的chunks和一个空的chunk来结束响应。

为了获取更多Akka Steams的内容,可以参见官方文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值