Input dan Output aliran biner menggunakan JERSEY?

111

Saya menggunakan Jersey untuk mengimplementasikan RESTful API yang utamanya mengambil dan melayani data yang dikodekan JSON. Tetapi saya memiliki beberapa situasi di mana saya perlu mencapai yang berikut:

  • Ekspor dokumen yang dapat diunduh, seperti PDF, XLS, ZIP, atau file biner lainnya.
  • Ambil data multi bagian, seperti JSON ditambah file XLS yang diunggah

Saya memiliki klien web berbasis JQuery satu halaman yang membuat panggilan AJAX ke layanan web ini. Saat ini, ia tidak melakukan pengiriman formulir, dan menggunakan GET dan POST (dengan objek JSON). Apakah saya harus menggunakan postingan formulir untuk mengirim data dan file biner terlampir, atau dapatkah saya membuat permintaan multi bagian dengan file biner JSON plus?

Lapisan layanan aplikasi saya saat ini membuat ByteArrayOutputStream saat membuat file PDF. Apa cara terbaik untuk mengeluarkan aliran ini ke klien melalui Jersey? Saya telah membuat MessageBodyWriter, tetapi saya tidak tahu bagaimana menggunakannya dari sumber Jersey. Apakah itu pendekatan yang benar?

Saya telah melihat-lihat sampel yang disertakan dengan Jersey, tetapi belum menemukan apa pun yang menggambarkan cara melakukan salah satu dari hal ini. Jika penting, saya menggunakan Jersey dengan Jackson untuk melakukan Object-> JSON tanpa langkah XML dan saya tidak benar-benar menggunakan JAX-RS.

Tauren
sumber

Jawaban:

109

Saya berhasil mendapatkan file ZIP atau file PDF dengan memperluas StreamingOutputobjek. Berikut beberapa contoh kode:

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

Kelas PDFGenerator (kelas saya sendiri untuk membuat PDF) mengambil aliran keluaran dari metode tulis dan menulis ke itu alih-alih aliran keluaran yang baru dibuat.

Tidak tahu apakah itu cara terbaik untuk melakukannya, tetapi berhasil.

MikeTheReader
sumber
33
Juga dimungkinkan untuk mengembalikan StreamingOutput sebagai entitas ke Responseobjek. Dengan begitu Anda dapat dengan mudah mengontrol tipe mediatipe, kode respons HTTP, dll. Beri tahu saya jika Anda ingin saya mengirimkan kode.
Hank
3
@MyTitle: lihat contoh
Hank
3
Saya menggunakan contoh kode di utas ini sebagai referensi, dan menemukan bahwa saya perlu membersihkan OutputStream di StreamingOutput.write () agar klien dapat menerima output dengan andal. Jika tidak, terkadang saya mendapatkan "Panjang Konten: 0" di header dan tidak ada isi, meskipun log memberi tahu saya bahwa StreamingOutput sedang dijalankan.
Jon Stewart
@JonStewart - Saya yakin saya sedang melakukan flush dalam metode generatePDF.
MikeTheReader
1
@Dante_jogja. Apakah Anda akan memposting kode sisi klien bagaimana klien Jersey mengirim streaming biner ke server (dengan jersey 2.x)?
Débora
29

Saya harus mengembalikan file rtf dan ini berhasil untuk saya.

// 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();
Abhishek Rakshit
sumber
26
Tidak terlalu bagus, karena keluaran dikirim hanya setelah selesai disiapkan. Byte [] bukanlah aliran.
java.is.for.desktop
7
Ini menghabiskan semua byte ke dalam memori, yang berarti file besar dapat merusak server. Tujuan streaming adalah untuk menghindari penggunaan semua byte ke dalam memori.
Robert Christian
22

Saya menggunakan kode ini untuk mengekspor file excel (xlsx) (Apache Poi) di jersey sebagai lampiran.

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

}
Grégory
sumber
15

Berikut contoh lainnya. Saya membuat QRCode sebagai PNG melalui file ByteArrayOutputStream. Sumber daya mengembalikan Responseobjek, dan data aliran adalah entitas.

Untuk menggambarkan penanganan kode respon, saya telah menambahkan penanganan cache header ( If-modified-since, If-none-matches, dll).

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

Tolong jangan memukuli saya jika stream.toByteArray()tidak ada memori yang bijaksana :) Ini berfungsi untuk file <1KB PNG saya ...

Gulungan
sumber
6
Saya pikir ini adalah contoh streaming yang buruk, karena objek yang dikembalikan dalam output adalah array byte dan bukan aliran.
AlikElzin-kilaka
Contoh bagus untuk membangun respons terhadap permintaan resource GET, bukan contoh yang baik untuk streaming. Ini sama sekali bukan aliran.
Robert Christian
14

Saya telah membuat layanan Jersey 1.17 saya dengan cara berikut:

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

Dan klien, jika Anda membutuhkannya:

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

Contoh ini menunjukkan cara mempublikasikan file log di JBoss melalui sumber daya lainnya. Perhatikan bahwa metode get menggunakan antarmuka StreamingOutput untuk mengalirkan konten file log.

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

}

Jaime Casero
sumber
1
FYI: selain metode pipa Anda juga bisa menggunakan IOUtils.copy dari Apache commons I / O.
David
7

Menggunakan Jersey 2.16 Mengunduh file sangat mudah.

Di bawah ini adalah contoh untuk file 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();
}
orangegiraffa
sumber
1
Bekerja seperti pesona. Punya ide tentang streaming ini Saya tidak begitu mengerti ...
Oliver
1
Ini cara termudah jika Anda menggunakan Jersey, Terima kasih
ganchito55
Apakah mungkin dilakukan dengan @POST daripada @GET?
spr
@spr Saya pikir ya, itu mungkin. Ketika halaman server merespon, itu harus menyediakan jendela download
orangegiraffa
5

Saya menemukan yang berikut bermanfaat bagi saya dan saya ingin berbagi jika itu membantu Anda atau orang lain. Saya menginginkan sesuatu seperti MediaType.PDF_TYPE, yang tidak ada, tetapi kode ini melakukan hal yang sama:

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

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

Dalam kasus saya, saya memposting dokumen PDF ke situs lain:

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

Kemudian p diteruskan sebagai parameter kedua ke post ().

Tautan ini sangat membantu saya dalam menyusun cuplikan kode ini: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html

Dovev Hefetz
sumber
4

Ini berfungsi dengan baik dengan saya 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();
}
Muqsith
sumber
1
Tidak yakin Response.ok("file path null").build();, apakah benar-benar oke? Anda mungkin harus menggunakan sesuatu sepertiResponse.status(Status.BAD_REQUEST).entity(...
Christophe Roussy
1

Kode contoh lain di mana Anda dapat mengunggah file ke layanan REST, layanan REST membuat file zip, dan klien mengunduh file zip dari server. Ini adalah contoh yang baik dalam menggunakan input dan output stream biner menggunakan Jersey.

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

Jawaban ini telah saya posting di utas lain. Semoga ini membantu.

RuntimeException
sumber