使用播放框架和第三方API流式传输大型文件

时间:2012-08-08 08:52:22

标签: java playframework streaming

我正在编写一个play 2应用程序,我正在努力解决文件流问题。 我使用第三方API检索我的文件,其方法具有以下签名:

FileMetadata getFile(OutputStream destination, String fileId)

在传统的Servlet应用程序中,如果我想将内容发送给我的客户端,我会做类似的事情:

HttpServletResponse resp;
myService.getFile(resp.getOutpuStream, fileId);

我的问题是,在我的Play 2 Controller类中,我无法访问底层的OuputStream,因此我的控制器方法的最简单实现是:

public static downloadFile(String id) {
    ByteArrayOutputStream baos = new BAOS(...);
    myApi.getFile(baos,id); //Load inside temp Array      
    ByteArrayInputStream bais = new BAIS(baos.toByteArray())
    return Ok(bais);
 }

它可以工作,但它需要在服务之前将整个内容加载到内存中,因此它不是一个选项(文件可能很大)。

我在想一个包含以下内容的解决方案:

  • 在我的控制器中定义ByteArrayOutputStream(baos)
  • 使用参数
  • 中的此baos调用第三方API
  • 使用play框架的chunk返回发送内容 一旦第三方API写入内容,就会立即获得帮助

问题是我不知道是否可能(调用getFile是阻塞所以它需要多个线程和一个共享的OutputStream),也不知道它是否过度。

有人遇到过这种问题并找到解决方案吗? 我提议的解决方案可以解决我的问题吗?

任何见解都将受到赞赏。

由于

编辑1 基于kheraud的建议,我已经设法有一个有效但仍然不完美的解决方案(下面的代码)。

不幸的是,如果在调用getFile方法期间出现问题,则不会将错误发送回客户端(因为我返回了Ok)并且浏览器无限期地等待一个永远不会出现的文件。

有办法处理这种情况吗?

public static Result downloadFile(String fileId {    
      Thread readerThread = null;
      try {
          PipedOutputStream pos = new PipedOutputStream();
          PipedInputStream pis = new PipedInputStream(pos); 
          //Reading must be done in another thread
          readerThread = new DownloadFileWorker(fileId,pos);
          readerThread.start();

          return ok(pis);
      } catch (Exception ex) {
          ex.printStackTrace();
          return internalServerError(ex.toString());

      }
  }

static class DownloadFileWorker extends Thread{
      String fileId;  
      PipedOutputStream pos;

      public DownloadFileWorker(String fileId, PipedOutputStream pos) {
        super();
        this.fileId = fileId
        this.pos = pos;
    }

    public void run(){
          try {
              myApi.getFile(pos,fileId);
              pos.close();
          } catch (Exception ex) {
              ex.printStackTrace();
          }
      }
}

编辑2

我找到了一种避免无限加载页面的方法,只需在工作线程的catch()部分添加一个pos.close即可。客户端最终得到一个零KB文件,但我想这比无限等待更好。

2 个答案:

答案 0 :(得分:3)

Play2 Scala 框架中有一些内容:Enumerators。这与您的想法非常接近。

您应该查看此doc page for details

我在Play2 Java API中找不到类似的东西,但是查看fw代码源,你有一个:

public static Results.Status ok(java.io.InputStream content, int chunkSize)

接缝是你正在寻找的方法。可以在play.mvc.Resultsplay.core.j.JavaResults类中找到该实现。

答案 1 :(得分:0)

在游戏中!邮件列表,最近有一个关于同一主题的讨论:

https://groups.google.com/forum/#!topic/play-framework/YunJzgxPKsU/discussion

它包含一个小片段,允许非scala-literates(像我一样)使用Play!的scala流媒体界面。