Các luồng nhị phân Đầu vào và Đầu ra sử dụng JERSEY?


111

Tôi đang sử dụng Jersey để triển khai RESTful API chủ yếu truy xuất và phân phát dữ liệu được mã hóa JSON. Nhưng tôi có một số tình huống mà tôi cần phải hoàn thành những điều sau:

  • Xuất các tài liệu có thể tải xuống, chẳng hạn như PDF, XLS, ZIP hoặc các tệp nhị phân khác.
  • Truy xuất dữ liệu nhiều phần, chẳng hạn như một số JSON cộng với tệp XLS đã tải lên

Tôi có một ứng dụng web dựa trên JQuery một trang tạo lệnh gọi AJAX tới dịch vụ web này. Hiện tại, nó không thực hiện gửi biểu mẫu và sử dụng GET và POST (với một đối tượng JSON). Tôi có nên sử dụng bài đăng trên biểu mẫu để gửi dữ liệu và tệp nhị phân đính kèm hay tôi có thể tạo yêu cầu nhiều phần với JSON cộng với tệp nhị phân không?

Lớp dịch vụ của ứng dụng của tôi hiện tạo ByteArrayOutputStream khi nó tạo tệp PDF. Cách tốt nhất để xuất luồng này tới khách hàng qua Jersey là gì? Tôi đã tạo một MessageBodyWriter, nhưng tôi không biết cách sử dụng nó từ tài nguyên Jersey. Đó có phải là cách tiếp cận đúng?

Tôi đã xem qua các mẫu đi kèm với Jersey, nhưng vẫn chưa tìm thấy bất kỳ thứ gì minh họa cách thực hiện một trong hai điều này. Nếu nó quan trọng, tôi đang sử dụng Jersey với Jackson để thực hiện Object-> JSON mà không có bước XML và tôi không thực sự sử dụng JAX-RS.

Câu trả lời:


109

Tôi đã quản lý để có được tệp ZIP hoặc tệp PDF bằng cách mở rộng StreamingOutputđối tượng. Đây là một số mã mẫu:

@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);
            }
        }
    };
}

Lớp PDFGenerator (lớp của riêng tôi để tạo PDF) lấy luồng đầu ra từ phương thức ghi và ghi vào đó thay vì luồng đầu ra mới được tạo.

Không biết đó có phải là cách tốt nhất để làm điều đó không, nhưng nó có hiệu quả.


33
Cũng có thể trả lại StreamingOutput dưới dạng thực thể cho một Responseđối tượng. Bằng cách đó, bạn có thể dễ dàng kiểm soát loại trung gian, mã phản hồi HTTP, v.v. Hãy cho tôi biết nếu bạn muốn tôi đăng mã.
Hank

3
@MyTitle: xem ví dụ
Hank vào

3
Tôi đã sử dụng các ví dụ mã trong chuỗi này làm tài liệu tham khảo và nhận thấy rằng tôi cần xóa OutputStream trong StreamingOutput.write () để máy khách nhận đầu ra một cách đáng tin cậy. Nếu không, đôi khi tôi sẽ nhận được "Content-Length: 0" trong tiêu đề và không có nội dung, mặc dù các bản ghi cho tôi biết rằng StreamingOutput đang thực thi.
Jon Stewart

@JonStewart - Tôi tin rằng tôi đã thực hiện xả trong phương thức createPDF.
MikeTheReader,

1
@ Dante617. Bạn có đăng mã phía máy khách bằng cách máy khách Jersey gửi luồng nhị phân đến máy chủ (với jersey 2.x) không?
Débora

29

Tôi đã phải trả lại tệp rtf và điều này đã làm việc cho tôi.

// 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();

26
Không tốt lắm, vì đầu ra chỉ được gửi sau khi nó đã được chuẩn bị hoàn toàn. Một byte [] không phải là một luồng.
java.is.for.desktop

7
Điều này tiêu tốn tất cả các byte vào bộ nhớ, có nghĩa là các tệp lớn có thể làm hỏng máy chủ. Mục đích của việc truyền trực tuyến là để tránh tiêu tốn tất cả các byte vào bộ nhớ.
Robert Christian

22

Tôi đang sử dụng mã này để xuất tệp excel (xlsx) (Apache Poi) ở dạng jersey dưới dạng tệp đính kèm.

@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();

}

15

Đây là một ví dụ khác. Tôi đang tạo mã QRCode dưới dạng PNG thông qua a ByteArrayOutputStream. Tài nguyên trả về mộtResponse đối tượng và dữ liệu của luồng là thực thể.

Để minh họa cho việc xử lý mã phản hồi, tôi đã thêm xử lý các tiêu đề bộ nhớ cache ( If-modified-since, If-none-matches, vv).

@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;
}   

Vui lòng không đánh bại tôi trong trường hợp stream.toByteArray()không có trí nhớ :) Nó hoạt động cho các tệp PNG <1KB PNG của tôi ...


6
Tôi nghĩ đó là một ví dụ về luồng không hợp lệ, vì đối tượng được trả về trong đầu ra là một mảng byte chứ không phải một luồng.
AlikElzin-kilaka

Ví dụ tốt để xây dựng phản hồi cho một yêu cầu tài nguyên GET, không phải là một ví dụ tốt cho luồng. Đây hoàn toàn không phải là một luồng.
Robert Christian

14

Tôi đã soạn các dịch vụ Jersey 1.17 của mình theo cách sau:

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);
}

Và khách hàng, nếu bạn cần:

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();
}

7

Ví dụ này cho thấy cách xuất bản tệp nhật ký trong JBoss thông qua tài nguyên còn lại. Lưu ý rằng phương thức get sử dụng giao diện StreamingOutput để truyền trực tuyến nội dung của tệp nhật ký.

@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: thay vì phương thức pipe, bạn cũng có thể sử dụng IOUtils.copy từ Apache commons I / O.
David

7

Sử dụng Jersey 2.16 Tải xuống tệp rất dễ dàng.

Dưới đây là ví dụ cho tệp 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
Hoạt động như một sự quyến rũ. Có bất kỳ ý tưởng nào về nội dung phát trực tuyến này, tôi không hoàn toàn hiểu nó ...
Oliver

1
Đó là cách dễ nhất nếu bạn sử dụng Jersey, Cảm ơn
ganchito55

Có thể thực hiện với @POST thay vì @GET không?
spr

@spr Tôi nghĩ có, nó có thể. Khi trang máy chủ phản hồi, nó sẽ cung cấp cửa sổ tải xuống
orangegiraffa

5

Tôi thấy những điều sau đây hữu ích cho tôi và tôi muốn chia sẻ trong trường hợp nó giúp ích cho bạn hoặc ai đó. Tôi muốn một cái gì đó giống như MediaType.PDF_TYPE, không tồn tại, nhưng mã này thực hiện điều tương tự:

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

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

Trong trường hợp của tôi, tôi đang đăng tài liệu PDF lên một trang web khác:

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")));

Sau đó, p được chuyển làm tham số thứ hai cho post ().

Liên kết này rất hữu ích đối với tôi trong việc ghép đoạn mã này lại với nhau: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html


4

Điều này hoạt động tốt với tôi url: 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
Không chắc chắn về Response.ok("file path null").build();, nó có thực sự ổn không? Có lẽ bạn nên sử dụng một cái gì đó giống nhưResponse.status(Status.BAD_REQUEST).entity(...
Christophe Roussy

1

Một mã mẫu khác nơi bạn có thể tải tệp lên dịch vụ REST, dịch vụ REST nén tệp và máy khách tải xuống tệp zip từ máy chủ. Đây là một ví dụ điển hình về việc sử dụng các luồng đầu vào và đầu ra nhị phân bằng cách sử dụng Jersey.

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

Câu trả lời này đã được tôi đăng trong một chủ đề khác. Hi vọng điêu nay co ich.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.