อินพุตและเอาต์พุตไบนารีสตรีมโดยใช้ JERSEY?


111

ฉันใช้ Jersey เพื่อใช้ RESTful API ที่ดึงและให้บริการข้อมูลที่เข้ารหัส JSON เป็นหลัก แต่ฉันมีสถานการณ์บางอย่างที่ฉันต้องทำสิ่งต่อไปนี้ให้สำเร็จ:

  • ส่งออกเอกสารที่ดาวน์โหลดได้เช่น PDF, XLS, ZIP หรือไฟล์ไบนารีอื่น ๆ
  • ดึงข้อมูลหลายส่วนเช่น JSON บางส่วนพร้อมไฟล์ XLS ที่อัปโหลด

ฉันมีเว็บไคลเอนต์ที่ใช้ JQuery แบบหน้าเดียวที่สร้างการเรียก AJAX ไปยังบริการเว็บนี้ ในขณะนี้ยังไม่ทำการส่งแบบฟอร์มและใช้ GET และ POST (ด้วยออบเจ็กต์ JSON) ฉันควรใช้แบบฟอร์มโพสต์เพื่อส่งข้อมูลและไฟล์ไบนารีที่แนบมาหรือฉันสามารถสร้างคำขอหลายส่วนด้วยไฟล์ไบนารี JSON บวกได้หรือไม่

ปัจจุบันชั้นบริการของแอปพลิเคชันของฉันสร้าง ByteArrayOutputStream เมื่อสร้างไฟล์ PDF วิธีใดเป็นวิธีที่ดีที่สุดในการส่งกระแสข้อมูลนี้ไปยังลูกค้าผ่าน Jersey ฉันได้สร้าง MessageBodyWriter แล้ว แต่ฉันไม่รู้วิธีใช้จากแหล่งข้อมูล Jersey เป็นแนวทางที่ถูกต้องหรือไม่?

ฉันได้ดูตัวอย่างที่มาพร้อมกับเจอร์ซีย์แล้ว แต่ยังไม่พบอะไรเลยที่แสดงให้เห็นถึงวิธีการทำสิ่งเหล่านี้ ถ้าเป็นเรื่องสำคัญฉันใช้ Jersey กับ Jackson เพื่อทำ Object-> JSON โดยไม่มีขั้นตอน XML และฉันไม่ได้ใช้ JAX-RS จริงๆ

คำตอบ:


109

ฉันจัดการเพื่อรับไฟล์ ZIP หรือไฟล์ PDF โดยการขยายStreamingOutputวัตถุ นี่คือตัวอย่างโค้ด:

@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) รับสตรีมเอาต์พุตจากวิธีการเขียนและเขียนไปยังสิ่งนั้นแทนสตรีมเอาต์พุตที่สร้างขึ้นใหม่

ไม่รู้ว่าเป็นวิธีที่ดีที่สุดหรือเปล่า แต่ได้ผล


33
นอกจากนี้ยังสามารถส่งคืน StreamingOutput เป็นเอนทิตีให้กับResponseออบเจ็กต์ ด้วยวิธีนี้คุณสามารถควบคุมประเภทสื่อรหัสตอบกลับ HTTP ฯลฯ ได้อย่างง่ายดายโปรดแจ้งให้เราทราบหากคุณต้องการให้ฉันโพสต์รหัส
Hank


3
ฉันใช้ตัวอย่างโค้ดในเธรดนี้เป็นข้อมูลอ้างอิงและพบว่าฉันจำเป็นต้องล้าง OutputStream ใน StreamingOutput.write () เพื่อให้ไคลเอ็นต์รับเอาต์พุตได้อย่างน่าเชื่อถือ มิฉะนั้นบางครั้งฉันจะได้รับ "Content-Length: 0" ในส่วนหัวและไม่มีเนื้อหาแม้ว่าบันทึกจะบอกฉันว่า StreamingOutput กำลังทำงานอยู่ก็ตาม
Jon Stewart

@JonStewart - ฉันเชื่อว่าฉันกำลังทำฟลัชด้วยวิธีการสร้างPDF
MikeTheReader

1
@ Dante617 คุณจะโพสต์รหัสฝั่งไคลเอ็นต์ได้อย่างไรว่าไคลเอนต์ Jersey ส่งสตรีมไบนารีไปยังเซิร์ฟเวอร์ (ด้วยเสื้อ 2.x)
Débora

29

ฉันต้องส่งคืนไฟล์ 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();

26
ไม่ดีนักเนื่องจากเอาต์พุตจะถูกส่งหลังจากที่เตรียมไว้อย่างสมบูรณ์เท่านั้น ไบต์ [] ไม่ใช่สตรีม
java.is.for.desktop

7
สิ่งนี้จะใช้ไบต์ทั้งหมดในหน่วยความจำซึ่งหมายความว่าไฟล์ขนาดใหญ่อาจทำให้เซิร์ฟเวอร์ล่มได้ จุดประสงค์ของการสตรีมคือการหลีกเลี่ยงการใช้ไบต์ทั้งหมดในหน่วยความจำ
Robert Christian

22

ฉันใช้รหัสนี้เพื่อส่งออกไฟล์ 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();

}

15

นี่เป็นอีกตัวอย่างหนึ่ง ฉันกำลังสร้าง QRCode เป็น PNG ผ่านไฟล์ ByteArrayOutputStream. ทรัพยากรส่งคืน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
ฉันคิดว่ามันเป็นตัวอย่างการสตรีมที่ไม่ดีเนื่องจากวัตถุที่ส่งคืนในเอาต์พุตเป็นอาร์เรย์ไบต์ไม่ใช่สตรีม
AlikElzin-kilaka

ตัวอย่างที่ดีในการสร้างการตอบกลับคำขอทรัพยากร GET ไม่ใช่ตัวอย่างที่ดีสำหรับสตรีม นี่ไม่ใช่กระแสเลย
Robert Christian

14

ฉันเขียนบริการ Jersey 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();
}

7

ตัวอย่างนี้แสดงวิธีการเผยแพร่ไฟล์บันทึกใน 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
เพียงแค่ FYI: แทนที่จะใช้วิธีไปป์คุณยังสามารถใช้ IOUtils.copy จาก Apache commons I / O
เดวิด

7

การใช้ Jersey 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

1
เป็นวิธีที่ง่ายที่สุดหากคุณใช้เจอร์ซีย์ขอบคุณ
ganchito55

เป็นไปได้ไหมที่จะใช้ @POST แทน @GET
SPR

@spr ฉันคิดว่าใช่มันเป็นไปได้ เมื่อหน้าเซิร์ฟเวอร์ตอบสนองควรมีหน้าต่างดาวน์โหลด
orangegiraffa

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 จะถูกส่งผ่านเป็นพารามิเตอร์ที่สองเพื่อโพสต์ ()

ลิงก์นี้มีประโยชน์สำหรับฉันในการรวมข้อมูลโค้ดนี้เข้าด้วยกัน: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html


4

สิ่งนี้ใช้ได้ดีกับฉัน 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
ไม่แน่ใจว่าResponse.ok("file path null").build();ใช้ได้จริงหรือ? คุณน่าจะใช้บางอย่างเช่นResponse.status(Status.BAD_REQUEST).entity(...
Christophe Roussy

1

โค้ดตัวอย่างอื่นที่คุณสามารถอัปโหลดไฟล์ไปยังเซอร์วิส REST เซอร์วิส REST บีบไฟล์และไคลเอ็นต์จะดาวน์โหลดไฟล์ zip จากเซิร์ฟเวอร์ นี่เป็นตัวอย่างที่ดีของการใช้ไบนารีอินพุตและเอาต์พุตสตรีมโดยใช้ Jersey

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

คำตอบนี้โพสต์โดยฉันในชุดข้อความอื่น หวังว่านี่จะช่วยได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.