2010-08-16 104 views
101

我使用Jersey來實現主要檢索並提供JSON編碼數據的RESTful API。但我有一些情況需要完成以下操作:使用JERSEY輸入和輸出二進制流?

  • 導出可下載的文檔,如PDF,XLS,ZIP或其他二進制文件。
  • 檢索多的數據,例如一些JSON加上上傳的XLS文件

我有一個單頁基於jQuery的web客戶端創建AJAX調用這個Web服務。目前,它不會進行表單提交,並使用GET和POST(帶有JSON對象)。我應該利用表單發送數據和附加的二進制文件,還是可以使用JSON加二進制文件創建多部分請求?

當我的應用程序的服務層生成PDF文件時,它當前會創建一個ByteArrayOutputStream。通過Jersey將此流輸出到客戶端的最佳方式是什麼?我創建了一個MessageBodyWriter,但我不知道如何從Jersey資源中使用它。這是正確的方法嗎?

我一直在尋找澤西島包括的樣本,但還沒有發現任何說明如何做這些事情的任何東西。如果重要的話,我使用Jersey和Jackson來做沒有XML步驟的Object-> JSON,而沒有真正使用JAX-RS。

回答

104

我設法通過擴展StreamingOutput對象來獲得ZIP文件或PDF文件。下面是一些示例代碼:

@Path("PDF-file.pdf/") 
@GET 
@Produces({"application/pdf"}) 
public StreamingOutput getPDF() throws Exception { 
    return new StreamingOutput() { 
     public void write(OutputStream output) throws IOException, WebApplicationException { 
      try { 
       PDFGenerator generator = new PDFGenerator(getEntity()); 
       generator.generatePDF(output); 
      } catch (Exception e) { 
       throw new WebApplicationException(e); 
      } 
     } 
    }; 
} 

的的pdfGenerator類(我自己的類用於創建PDF)從寫入方法將輸出流寫入到的,而不是一個新創建的輸出流。

不知道這是否是最好的辦法,但它的工作原理。

+1

+1 - 非常好的例子 – 2011-03-29 08:20:22

+33

也可以將StreamingOutput作爲實體返回給'Response'對象。這樣你可以很容易地控制mediatype,HTTP響應代碼等。讓我知道你是否希望我發佈代碼。 – Hank 2011-06-28 13:29:33

+1

@Hank,是的,請張貼代碼 – MyTitle 2012-09-20 13:37:31

27

我不得不返回一個rtf文件,這對我工作。

// create a byte array of the file in correct format 
byte[] docStream = createDoc(fragments); 

return Response 
      .ok(docStream, MediaType.APPLICATION_OCTET_STREAM) 
      .header("content-disposition","attachment; filename = doc.rtf") 
      .build(); 
+23

不太好,因爲輸出僅在完全準備好後才發送。一個byte []不是一個流。 – 2012-01-28 17:28:21

+5

這消耗了所有字節到內存中,這意味着大文件可能會導致服務器關閉。流傳輸的目的是避免將所有字節消耗到內存中。 – 2016-01-15 00:34:34

5

我發現下面對我很有幫助,我想萬一分享它可以幫助你或別人。我想要的東西像MediaType.PDF_TYPE,它不存在,但是這個代碼做同樣的事情:

DefaultMediaTypePredictor.CommonMediaTypes. 
     getMediaTypeFromFileName("anything.pdf") 

http://jersey.java.net/nonav/apidocs/1.1.0-ea/contribs/jersey-multipart/com/sun/jersey/multipart/file/DefaultMediaTypePredictor.CommonMediaTypes.html

在我來說,我張貼一個PDF文檔到另一個站點:

FormDataMultiPart p = new FormDataMultiPart(); 
p.bodyPart(new FormDataBodyPart(FormDataContentDisposition 
     .name("fieldKey").fileName("document.pdf").build(), 
     new File("path/to/document.pdf"), 
     DefaultMediaTypePredictor.CommonMediaTypes 
       .getMediaTypeFromFileName("document.pdf"))); 

然後p作爲第二個參數傳遞給post()。

此鏈接是對我很有幫助,在把這個代碼片段一起: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html

18

我使用這個代碼在球衣導出Excel(XLSX)文件(Apache的POI)作爲一個附件。

@GET 
@Path("/{id}/contributions/excel") 
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") 
public Response exportExcel(@PathParam("id") Long id) throws Exception { 

    Resource resource = new ClassPathResource("/xls/template.xlsx"); 

    final InputStream inp = resource.getInputStream(); 
    final Workbook wb = WorkbookFactory.create(inp); 
    Sheet sheet = wb.getSheetAt(0); 

    Row row = CellUtil.getRow(7, sheet); 
    Cell cell = CellUtil.getCell(row, 0); 
    cell.setCellValue("TITRE TEST"); 

    [...] 

    StreamingOutput stream = new StreamingOutput() { 
     public void write(OutputStream output) throws IOException, WebApplicationException { 
      try { 
       wb.write(output); 
      } catch (Exception e) { 
       throw new WebApplicationException(e); 
      } 
     } 
    }; 


    return Response.ok(stream).header("content-disposition","attachment; filename = export.xlsx").build(); 

} 
14

這是另一個例子。我通過ByteArrayOutputStream創建QRCode作爲PNG。資源返回一個Response對象,流的數據是實體。

爲了說明響應代碼處理,我添加了處理緩存標頭(If-modified-since,If-none-matches等)。

@Path("{externalId}.png") 
@GET 
@Produces({"image/png"}) 
public Response getAsImage(@PathParam("externalId") String externalId, 
     @Context Request request) throws WebApplicationException { 

    ByteArrayOutputStream stream = new ByteArrayOutputStream(); 
    // do something with externalId, maybe retrieve an object from the 
    // db, then calculate data, size, expirationTimestamp, etc 

    try { 
     // create a QRCode as PNG from data  
     BitMatrix bitMatrix = new QRCodeWriter().encode(
       data, 
       BarcodeFormat.QR_CODE, 
       size, 
       size 
     ); 
     MatrixToImageWriter.writeToStream(bitMatrix, "png", stream); 

    } catch (Exception e) { 
     // ExceptionMapper will return HTTP 500 
     throw new WebApplicationException("Something went wrong …") 
    } 

    CacheControl cc = new CacheControl(); 
    cc.setNoTransform(true); 
    cc.setMustRevalidate(false); 
    cc.setNoCache(false); 
    cc.setMaxAge(3600); 

    EntityTag etag = new EntityTag(HelperBean.md5(data)); 

    Response.ResponseBuilder responseBuilder = request.evaluatePreconditions(
      updateTimestamp, 
      etag 
    ); 
    if (responseBuilder != null) { 
     // Preconditions are not met, returning HTTP 304 'not-modified' 
     return responseBuilder 
       .cacheControl(cc) 
       .build(); 
    } 

    Response response = Response 
      .ok() 
      .cacheControl(cc) 
      .tag(etag) 
      .lastModified(updateTimestamp) 
      .expires(expirationTimestamp) 
      .type("image/png") 
      .entity(stream.toByteArray()) 
      .build(); 
    return response; 
} 

請不要在情況​​stream.toByteArray()打我是一個沒有任何記憶明智:)它適合我的< 1KB PNG文件...

+6

我認爲這是一個糟糕的流式示例,因爲輸出中的返回對象是字節數組而不是流。 – 2013-10-03 17:17:24

+0

構建對GET資源請求的響應的很好示例,而不是流的良好示例。這根本不是流。 – 2016-01-15 00:44:13

3

這個工作我都沒 網址:http://example.com/rest/muqsith/get-file?filePath=C :\ Users \ I066807 \ Desktop \ test.xml

@GET 
@Produces({ MediaType.APPLICATION_OCTET_STREAM }) 
@Path("/get-file") 
public Response getFile(@Context HttpServletRequest request){ 
    String filePath = request.getParameter("filePath"); 
    if(filePath != null && !"".equals(filePath)){ 
     File file = new File(filePath); 
     StreamingOutput stream = null; 
     try { 
     final InputStream in = new FileInputStream(file); 
     stream = new StreamingOutput() { 
      public void write(OutputStream out) throws IOException, WebApplicationException { 
       try { 
        int read = 0; 
         byte[] bytes = new byte[1024]; 

         while ((read = in.read(bytes)) != -1) { 
          out.write(bytes, 0, read); 
         } 
       } catch (Exception e) { 
        throw new WebApplicationException(e); 
       } 
      } 
     }; 
    } catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    } 
     return Response.ok(stream).header("content-disposition","attachment; filename = "+file.getName()).build(); 
     } 
    return Response.ok("file path null").build(); 
} 
+1

不確定'Response.ok(「file path null」)。build();',它真的好嗎?你應該使用類似'Response.status(Status.BAD_REQUEST).entity(...' – 2015-02-09 10:01:09

7

這個例子展示瞭如何通過rest資源發佈JBoss中的日誌文件。請注意,get方法使用StreamingOutput接口來傳輸日誌文件的內容。

@Path("/logs/") 
@RequestScoped 
public class LogResource { 

private static final Logger logger = Logger.getLogger(LogResource.class.getName()); 
@Context 
private UriInfo uriInfo; 
private static final String LOG_PATH = "jboss.server.log.dir"; 

public void pipe(InputStream is, OutputStream os) throws IOException { 
    int n; 
    byte[] buffer = new byte[1024]; 
    while ((n = is.read(buffer)) > -1) { 
     os.write(buffer, 0, n); // Don't allow any extra bytes to creep in, final write 
    } 
    os.close(); 
} 

@GET 
@Path("{logFile}") 
@Produces("text/plain") 
public Response getLogFile(@PathParam("logFile") String logFile) throws URISyntaxException { 
    String logDirPath = System.getProperty(LOG_PATH); 
    try { 
     File f = new File(logDirPath + "/" + logFile); 
     final FileInputStream fStream = new FileInputStream(f); 
     StreamingOutput stream = new StreamingOutput() { 
      @Override 
      public void write(OutputStream output) throws IOException, WebApplicationException { 
       try { 
        pipe(fStream, output); 
       } catch (Exception e) { 
        throw new WebApplicationException(e); 
       } 
      } 
     }; 
     return Response.ok(stream).build(); 
    } catch (Exception e) { 
     return Response.status(Response.Status.CONFLICT).build(); 
    } 
} 

@POST 
@Path("{logFile}") 
public Response flushLogFile(@PathParam("logFile") String logFile) throws URISyntaxException { 
    String logDirPath = System.getProperty(LOG_PATH); 
    try { 
     File file = new File(logDirPath + "/" + logFile); 
     PrintWriter writer = new PrintWriter(file); 
     writer.print(""); 
     writer.close(); 
     return Response.ok().build(); 
    } catch (Exception e) { 
     return Response.status(Response.Status.CONFLICT).build(); 
    } 
}  

}

+1

Just FYI:而不是管道方法,你也可以使用來自Apache公共I/O的IOUtils.copy。 – David 2014-07-10 13:59:38

13

我一直在撰寫我的球衣1.17服務方式如下:

FileStreamingOutput

public class FileStreamingOutput implements StreamingOutput { 

    private File file; 

    public FileStreamingOutput(File file) { 
     this.file = file; 
    } 

    @Override 
    public void write(OutputStream output) 
      throws IOException, WebApplicationException { 
     FileInputStream input = new FileInputStream(file); 
     try { 
      int bytes; 
      while ((bytes = input.read()) != -1) { 
       output.write(bytes); 
      } 
     } catch (Exception e) { 
      throw new WebApplicationException(e); 
     } finally { 
      if (output != null) output.close(); 
      if (input != null) input.close(); 
     } 
    } 

} 

GET

@GET 
@Produces("application/pdf") 
public StreamingOutput getPdf(@QueryParam(value="name") String pdfFileName) { 
    if (pdfFileName == null) 
     throw new WebApplicationException(Response.Status.BAD_REQUEST); 
    if (!pdfFileName.endsWith(".pdf")) pdfFileName = pdfFileName + ".pdf"; 

    File pdf = new File(Settings.basePath, pdfFileName); 
    if (!pdf.exists()) 
     throw new WebApplicationException(Response.Status.NOT_FOUND); 

    return new FileStreamingOutput(pdf); 
} 

而且客戶端,如果你需要它:

Client

private WebResource resource; 

public InputStream getPDFStream(String filename) throws IOException { 
    ClientResponse response = resource.path("pdf").queryParam("name", filename) 
     .type("application/pdf").get(ClientResponse.class); 
    return response.getEntityInputStream(); 
} 
6

使用澤西2.16文件下載是很容易的。

下面是ZIP文件

@GET 
@Path("zipFile") 
@Produces("application/zip") 
public Response getFile() { 
    File f = new File(ZIP_FILE_PATH); 

    if (!f.exists()) { 
     throw new WebApplicationException(404); 
    } 

    return Response.ok(f) 
      .header("Content-Disposition", 
        "attachment; filename=server.zip").build(); 
} 
+1

工程就像一個魅力。對這個流媒體的東西有任何想法我不太明白它... – Oliver 2015-05-21 10:51:09

+1

它是最簡單的方法,如果你使用澤西島,謝謝 – ganchito55 2016-05-04 11:10:06

+0

是否可以用@POST而不是@GET? – spr 2017-01-02 06:56:44

1

另一個示例代碼,你可以將文件上傳到REST服務,REST服務呼嘯而過文件的例子,客戶端從服務器下載的zip文件。 這是使用澤西島使用二進制輸入和輸出流的一個很好的例子。

https://stackoverflow.com/a/32253028/15789

這個答案在另一個線程張貼由我。希望這可以幫助。