2013年11月22日 星期五

使用 Jersey + Multi-part 接受檔案上傳(二):實作即時上傳

依照上篇文章(使用 Jersey + Multi-part 接受檔案上傳)的方法做 Multi-part 的上傳時
依據 Jersey 的實作方法,實際上會先在伺服器上把使用者上傳的檔案全部接收、暫存到暫存的資料夾內,然後才會進入使用者定義的 Multi-part 的介面,並獲得代表檔案內容的 FormDataMultiPart。
也就是說實際上如果檔案是要存到伺服器上,伺服器對這樣一個檔案上傳的動作,會花費兩次的磁碟 I/O 時間來接收和儲存這個檔案。

解決方法目前看到的大概就是直接透過 Servlet 接收 request,然後自行處理 Multi-part 的內容。
接收 request 的動作即使透過 Jersey,還是有蠻簡單的方法處理;Multi-part 的部份則可以利用 Apache Commons Streaming API [1-2](Apache Commons FileUpload 的一部分)。

範例如下:
@POST
@Path("/upload_file")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileByMultiPart(@Context HttpServletRequest request) throws IOException, FileUploadException {
  Logger log = Logger.getLogger("MultiPartTest");
  log.debug("Get request in multipart.");
  // Create a new file upload handler
  ServletFileUpload upload = new ServletFileUpload();
  InputStream stream = null;
  // Parse the request
  FileItemIterator iter = upload.getItemIterator(request);
  while (iter.hasNext()) {
    FileItemStream item = iter.next();
    String name = item.getFieldName();

    if (!item.isFormField()) {
      log.debug("File field " + name + " with file name " + item.getName() + " detected.");
      // Process the input stream
      stream = item.openStream();
      // TODO Process the stream.....
    }
  }
  return Response.status(400).build();
}

第 11 行是讓 FileUpload API 去分析 Multi-part 的內容,並且開始做 Multi-part 的列舉
第 19 行就是拿到其中一個 Multi-part 的 body stream,可以開始做接收檔案內容的動作。
不過這裡要特別注意的是,Multi-part 似乎不能跳著拿,也不能同時拿好幾段,一定要照順序跑
所以 TODO 的地方在處理 stream 時,在 stream 處理完以前不能讓程式往下繼續跑下一個 iter.next()
否則就會如 [3] 那樣跑出 ItemSkippedException。

另外在查資料時也有看到 StackOverflow 上有網友討論說,同樣的問題也有 JEE 的實作方法,因此如果有 JEE 的時候,就不需要額外放 Apache 的函式庫了。
不過一時找不到那篇在哪裡了,改天找到再補上來.....。

參考資料:
1、Jersey multipart streaming without disk buffering
2、Apache Commons Streaming API
3、Why ItemSkippedException?

沒有留言: