JERSEY kullanarak giriş ve çıkış ikili akışları?


111

Jersey'i, öncelikle JSON kodlu verileri alan ve sunan bir RESTful API uygulamak için kullanıyorum. Ancak, aşağıdakileri gerçekleştirmem gereken bazı durumlar var:

  • PDF, XLS, ZIP veya diğer ikili dosyalar gibi indirilebilir belgeleri dışa aktarın.
  • JSON gibi çok parçalı verileri ve yüklenen bir XLS dosyasını alın

Bu web hizmetine AJAX çağrıları oluşturan tek sayfalık JQuery tabanlı bir web istemcim var. Şu anda form gönderimleri yapmıyor ve GET ve POST (bir JSON nesnesiyle) kullanıyor. Verileri ve ekli bir ikili dosyayı göndermek için bir form gönderisi kullanmalı mıyım yoksa JSON artı ikili dosya ile çok parçalı bir istek oluşturabilir miyim?

Uygulamamın hizmet katmanı şu anda bir PDF dosyası oluştururken bir ByteArrayOutputStream oluşturur. Bu akışı müşteriye Jersey aracılığıyla göndermenin en iyi yolu nedir? Bir MessageBodyWriter oluşturdum, ancak bunu Jersey kaynağından nasıl kullanacağımı bilmiyorum. Doğru yaklaşım bu mu?

Jersey'de bulunan örneklere bakıyordum, ancak bunlardan herhangi birinin nasıl yapılacağını gösteren hiçbir şey bulamadım. Önemliyse, XML adımı olmadan Object-> JSON yapmak için Jersey ile Jackson'ı kullanıyorum ve JAX-RS'yi gerçekten kullanmıyorum.

Yanıtlar:


109

StreamingOutputNesneyi genişleterek bir ZIP dosyası veya bir PDF dosyası almayı başardım . İşte bazı örnek kodlar:

@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 sınıfı (PDF'yi oluşturmak için kendi sınıfım) çıktı akışını yazma yönteminden alır ve yeni oluşturulan bir çıktı akışı yerine ona yazar.

Bunu yapmanın en iyi yolu mu bilmiyorum ama işe yarıyor.


33
StreamingOutput'u bir Responsenesneye varlık olarak döndürmek de mümkündür . Bu şekilde ortam türünü, HTTP yanıt kodunu vb. Kolayca kontrol edebilirsiniz. Kod göndermemi istiyorsanız bana bildirin.
Hank

3
@MyTitle: örneğe
Hank

3
Bu iş parçacığındaki kod örneklerini referans olarak kullandım ve istemcinin çıktıyı güvenilir bir şekilde alması için StreamingOutput.write () içindeki OutputStream'i temizlemem gerektiğini buldum. Aksi takdirde, günlükler StreamingOutput'un yürütüldüğünü söylese de bazen başlıklarda "Content-Length: 0" alıyordum ve gövde yok.
Jon Stewart

@JonStewart - GeneratePDF yöntemi içinde flush yaptığımı düşünüyorum.
MikeTheReader

1
Dante617 @. İstemci tarafı kodunu, Jersey istemcisinin ikili akışı sunucuya nasıl gönderdiğini (jersey 2.x ile) gönderir misiniz?
Débora

29

Bir rtf dosyası döndürmem gerekiyordu ve bu benim için çalıştı.

// 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
O kadar iyi değil, çünkü çıktı ancak tamamen hazırlandıktan sonra gönderilir. Bir bayt [] bir akış değildir.
java.is.for.desktop

7
Bu, tüm baytları belleğe tüketir, bu da büyük dosyaların sunucuyu çökertebileceği anlamına gelir. Akışın amacı, tüm baytları belleğe tüketmekten kaçınmaktır.
Robert Christian

22

Jersey'deki excel (xlsx) dosyasını (Apache Poi) bir ek olarak dışa aktarmak için bu kodu kullanıyorum.

@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

İşte başka bir örnek. Bir PNG olarak bir QRCode oluşturuyorum ByteArrayOutputStream. Kaynak bir Responsenesne döndürür ve akışın verileri varlıktır.

Yanıt kodu işlemeyi göstermek için, ben önbellek başlıklarının (taşınması ekledik If-modified-since, If-none-matchesvs.).

@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()Hafızanın olmaması durumunda lütfen beni dövmeyin :) <1KB PNG dosyalarım için çalışıyor ...


6
Bence bu kötü bir akış örneği, çünkü çıktıda döndürülen nesne bir bayt dizisi ve bir akış değil.
AlikElzin-kilaka

Akış için iyi bir örnek değil, GET kaynak isteğine yanıt oluşturmaya iyi bir örnek. Bu hiç de bir akış değil.
Robert Christian

14

Jersey 1.17 servislerimi şu şekilde oluşturuyorum:

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

Ve müşteri, ihtiyacınız olursa:

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

Bu örnek, günlük dosyalarının bir dinlenme kaynağı aracılığıyla JBoss'ta nasıl yayınlanacağını gösterir. Get yönteminin, günlük dosyasının içeriğini akışa almak için StreamingOutput arabirimini kullandığını unutmayın.

@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
Bilginize: Boru yöntemi yerine Apache commons I / O'dan IOUtils.copy dosyasını da kullanabilirsiniz.
David

7

Jersey 2.16'yı kullanmak Dosya indirmek çok kolaydır.

ZIP dosyası örneği aşağıdadır

@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
Tıkır tıkır çalışıyor. Bu akışla ilgili herhangi bir fikrim var, tam olarak anlamıyorum ...
Oliver,

1
Jersey kullanırsanız en kolay yol, Teşekkürler
ganchito55

@GET yerine @POST ile yapmak mümkün mü?
spr

@spr Evet, mümkün olduğunu düşünüyorum. Sunucu sayfası yanıt verdiğinde, indirme penceresini sağlamalıdır
orangegiraffa

5

Aşağıdakileri benim için yararlı buldum ve size veya başka birine yardımcı olması durumunda paylaşmak istedim. Var olmayan MediaType.PDF_TYPE gibi bir şey istedim, ancak bu kod aynı şeyi yapıyor:

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

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

Benim durumumda başka bir siteye bir PDF belgesi gönderiyordum:

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

Ardından post () 'a ikinci parametre olarak p geçilir.

Bu bağlantı, bu kod parçacığını bir araya getirmemde bana yardımcı oldu: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html


4

Bu benim için iyi çalıştı 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
Emin değilim Response.ok("file path null").build();, gerçekten tamam mı? Muhtemelen şöyle bir şey kullanmalısınızResponse.status(Status.BAD_REQUEST).entity(...
Christophe Roussy

1

REST hizmetine dosya yükleyebileceğiniz başka bir örnek kod, REST hizmeti dosyayı sıkıştırır ve istemci zip dosyasını sunucudan indirir. Bu, Jersey kullanarak ikili giriş ve çıkış akışlarını kullanmanın iyi bir örneğidir.

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

Bu cevap benim tarafımdan başka bir başlıkta gönderildi Bu yardımcı olur umarım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.