110 votes

Entrer et sortir des flux binaires en utilisant JERSEY ?

J'utilise Jersey pour implémenter une API RESTful qui récupère et sert principalement des données encodées en JSON. Mais dans certaines situations, j'ai besoin d'accomplir ce qui suit :

  • Exportez des documents téléchargeables, tels que des fichiers PDF, XLS, ZIP ou autres fichiers binaires.
  • Récupérer des données en plusieurs parties, telles que JSON et un fichier XLS téléchargé.

J'ai un client web à une seule page basé sur JQuery qui crée des appels AJAX vers ce service web. Pour l'instant, il ne fait pas de soumission de formulaire et utilise GET et POST (avec un objet JSON). Dois-je utiliser un formulaire post pour envoyer des données et un fichier binaire joint, ou puis-je créer une requête multipart avec JSON et fichier binaire ?

La couche de service de mon application crée actuellement un ByteArrayOutputStream lorsqu'elle génère un fichier PDF. Quelle est la meilleure façon de transmettre ce flux au client via Jersey ? J'ai créé un MessageBodyWriter, mais je ne sais pas comment l'utiliser à partir d'une ressource Jersey. Est-ce la bonne approche ?

J'ai parcouru les échantillons inclus dans Jersey, mais je n'ai encore rien trouvé qui illustre comment faire l'une ou l'autre de ces choses. Si cela est important, j'utilise Jersey avec Jackson pour faire Object->JSON sans l'étape XML et je n'utilise pas vraiment JAX-RS.

109voto

MikeTheReader Points 2739

J'ai réussi à obtenir un fichier ZIP ou un fichier PDF en étendant la fonction StreamingOutput objet. Voici un exemple de code :

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

La classe PDFGenerator (ma propre classe pour créer le PDF) prend le flux de sortie de la méthode write et écrit dans ce flux au lieu d'un flux de sortie nouvellement créé.

Je ne sais pas si c'est la meilleure façon de faire, mais ça marche.

29voto

Abhishek Rakshit Points 430

Je devais renvoyer un fichier rtf et cela a fonctionné pour moi.

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

22voto

Grégory Points 554

J'utilise ce code pour exporter un fichier excel (xlsx) (Apache Poi) dans le jersey en tant que pièce jointe.

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

}

15voto

Hank Points 1328

Voici un autre exemple. Je crée un QRCode en tant que PNG via un fichier ByteArrayOutputStream . La ressource renvoie un Response et les données du flux sont l'entité.

Pour illustrer la gestion du code de réponse, j'ai ajouté la gestion des en-têtes de cache ( If-modified-since , If-none-matches etc).

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

S'il vous plaît, ne me frappez pas au cas où stream.toByteArray() n'est pas bon pour la mémoire :) Cela fonctionne pour mes fichiers PNG <1KB...

14voto

Daniel Szalay Points 1080

J'ai composé mes services Jersey 1.17 de la manière suivante :

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

Et le client, si vous en avez besoin :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X