2013年4月11日 星期四

利用 JAX-RS 以 Stream 產出檔案下載

在 JAX-RS 的環境中,雖然之前已經有 PO 一篇實現檔案下載的方法 [1]
但該方法實現的方式是把檔案讀取成一個 byte 陣列,再丟給 Response 去回傳。
這會產生一個明顯的問題:這種作法要把整個檔案全部放進記憶體後才能產出,但如果檔案是 10GB 的超大檔案怎麼辦?
因此這次嘗試尋找在 JAX-RS 環境要怎麼辦到以 Stream 來吐檔案下載。

相關的連結可以參考 [2-3] 的討論~主要關鍵是利用 javax.ws.rs.core.StreamingOutput 這個類別。
範例如下:
@Path("/file")
@Provider
public class FileInterface {
  
  @GET
  @Path("/get")
  @Produces(MediaType.APPLICATION_OCTET_STREAM)
  public Response fileDownload () throws IOException {
    System.out.println("REST /file/get is connected.");
    File file = new File("test.zip");
    ChunkStreamingOutput stream = new ChunkStreamingOutput(new FileInputStream(file));
    
    return Response
        .ok(stream, MediaType.APPLICATION_OCTET_STREAM_TYPE)
        .header("content-disposition", "attachment; filename=" + file.getName())
        .build();
  }
  
  private class ChunkStreamingOutput implements StreamingOutput {
    private InputStream is = null;
    
    public ChunkStreamingOutput (InputStream stream) {
      this.is = stream;
    }

    @Override
    public void write(OutputStream output) throws IOException,
        WebApplicationException {
      try {
        byte[] buffer = new byte[1024];
        int len = 0;
        
        while((len = is.read(buffer)) > -1) {
          output.write(buffer, 0, len);
          output.flush();
        }
      } finally {
        if(is != null) is.close();
        if(output != null) output.close();
      }
    }
    
  }
}

特別的地方大概是在 27 行處的 write(OutputStream output)~
輸入的 OutputStream 應該可以看做是 Response 物件用來吐出 Stream 的物件
因此要輸出的檔案的 Stream 都要寫到那個 OutputStream 裡。
這裡我的作法是自己自訂一個 StreamingOutput 的類別,然後允許輸入一個 InputStream
這樣可以允許我需要時,可以用 FileInputStream、也可以用別種 InputStream 的實作類別來提供 Stream 的來源。

參考資料:
1、在 JAX-RS 實現上傳檔案及下載檔案
2、Input and Output binary streams using JERSEY?
3、jersey Example of using StreamingOutput as Response entity

沒有留言: