• PLAY2.6-SCALA(七) Streaming HTTP response


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

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

    def index = Action { Ok("Hello World")} 

    由于发送的内容十分简单,play可以帮助我们计算内容的长度。看一个用于play.http.HttpEntity指明相应体的例子:

    def action = Action {
      Result(
        header = ResponseHeader(200, Map.empty),
        body = HttpEntity.Strict(ByteString("Hello world"), Some("text/plain"))
      )
    }
    

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

    2.发送大量数据

    如果需要发送很大的数据集该如何处理,来看一个给web客户端返回大文件的例子。首先创建为文件创建一个Source[ByteString, _]

    val file = new java.io.File("/tmp/fileToServe.pdf")
    val path: java.nio.file.Path = file.toPath
    val source: Source[ByteString, _] = FileIO.fromPath(path)
    

    用这个流式HttpEntity来指定响应主体

    def streamed = Action {
    
      val file = new java.io.File("/tmp/fileToServe.pdf")
      val path: java.nio.file.Path = file.toPath
      val source: Source[ByteString, _] = FileIO.fromPath(path)
    
      Result(
        header = ResponseHeader(200, Map.empty),
        body = HttpEntity.Streamed(source, None, Some("application/pdf"))
      )
    }
    

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

    def streamedWithContentLength = Action {
    
      val file = new java.io.File("/tmp/fileToServe.pdf")
      val path: java.nio.file.Path = file.toPath
      val source: Source[ByteString, _] = FileIO.fromPath(path)
    
      val contentLength = Some(file.length())
    
      Result(
        header = ResponseHeader(200, Map.empty),
        body = HttpEntity.Streamed(source, contentLength, Some("application/pdf"))
      )
    }
    

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

    3.处理文件

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

    def file = Action {Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf"))}

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

    也可以提供自己的文件名

    def fileWithName = Action {
      Ok.sendFile(
        content = new java.io.File("/tmp/fileToServe.pdf"),
        fileName = _ => "termsOfService.pdf"
      )
    }
    

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

    def fileAttachment = Action {
      Ok.sendFile(
        content = new java.io.File("/tmp/fileToServe.pdf"),
        inline = false
      )
    }
    

    现在不必指定一个文件名因为浏览器不会尝试去下载文件,仅仅是在窗口中进行展示

    4.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来流化内容。

    val data = getDataStream
    val dataContent: Source[ByteString, _] = StreamConverters.fromInputStream(() => data)
    //We can now stream these data using a Ok.chunked:
    def chunked = Action {
      val data = getDataStream
      val dataContent: Source[ByteString, _] = StreamConverters.fromInputStream(() => data)
      Ok.chunked(dataContent)
    }
    //Of course, we can use any Source to specify the chunked data:
    def chunkedFromSource = Action {
      val source = Source.apply(List("kiki", "foo", "bar"))
      Ok.chunked(source)
    }

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

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

  • 相关阅读:
    个人心情闲扯贴~~
    近阶段学习感悟--大一下半学期
    HDU 1003 Max Sum 解题报告
    开始我的新园地--献给我的那些学长们
    软件公司职位简称
    Sql Server参数化查询之where in和like实现详解 [转]
    21个值得收藏的Javascript技巧
    [转]js刷新父窗体
    Oracle10g 连接 sqlserver 在server2008r2 中连接 iis7 .net4.0
    Oracle10g 连接 sqlserver hsodbc dblink 方式 非透明网关
  • 原文地址:https://www.cnblogs.com/feiyumo/p/9145589.html
Copyright © 2020-2023  润新知