Input and Output binary streams using JERSEY?
Asked Answered
S

10

115

I'm using Jersey to implement a RESTful API that is primarily retrieve and serve JSON encoded data. But I have some situations where I need to accomplish the following:

  • Export downloadable documents, such as PDF, XLS, ZIP, or other binary files.
  • Retrieve multipart data, such some JSON plus an uploaded XLS file

I have a single-page JQuery-based web client that creates AJAX calls to this web service. At the moment, it doesn't do form submits, and uses GET and POST (with a JSON object). Should I utilize a form post to send data and an attached binary file, or can I create a multipart request with JSON plus binary file?

My application's service layer currently creates a ByteArrayOutputStream when it generates a PDF file. What is the best way to output this stream to the client via Jersey? I've created a MessageBodyWriter, but I don't know how to use it from a Jersey resource. Is that the right approach?

I've been looking through the samples included with Jersey, but haven't found anything yet that illustrates how to do either of these things. If it matters, I'm using Jersey with Jackson to do Object->JSON without the XML step and am not really utilizing JAX-RS.

Shipmate answered 16/8, 2010 at 18:42 Comment(0)
A
109

I managed to get a ZIP file or a PDF file by extending the StreamingOutput object. Here is some sample 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);
            }
        }
    };
}

The PDFGenerator class (my own class for creating the PDF) takes the output stream from the write method and writes to that instead of a newly created output stream.

Don't know if it's the best way to do it, but it works.

Anaerobe answered 17/8, 2010 at 14:48 Comment(6)
It is also possible to return the StreamingOutput as the entity to a Response object. That way you can easily control mediatype, HTTP response code, etc. Let me know if you want me to post code.Careerism
@MyTitle: see exampleCareerism
I used the code examples in this thread as a reference, and found that I needed to flush the OutputStream in StreamingOutput.write() for the client to receive the output reliably. Otherwise I'd sometimes get "Content-Length: 0" in the headers and no body, even though logs told me that the StreamingOutput was executing.Lagging
@JonStewart - I believe I was doing the flush within the generatePDF method.Anaerobe
@Dante617. Would you post the client side code how the Jersey client send the binary stream to the server (with jersey 2.x) ?Jesseniajessey
Is it possible to do with @POST instead of @GET?Diatomaceous
P
30

I had to return a rtf file and this worked for me.

// 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();
Palaeolithic answered 22/10, 2010 at 22:34 Comment(2)
Not that good, because output is sent only after it was completely prepared. A byte[] is not a stream.Catastrophism
This consumes all bytes into memory, which means large files could bring down the server. The purpose of streaming is to avoid consuming all bytes into memory.Jiujitsu
G
23

I'm using this code to export excel (xlsx) file ( Apache Poi ) in jersey as an attachement.

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

}
Ghastly answered 2/4, 2012 at 10:59 Comment(0)
C
16

Here's another example. I'm creating a QRCode as a PNG via a ByteArrayOutputStream. The resource returns a Response object, and the stream's data is the entity.

To illustrate the response code handling, I've added handling of cache headers (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;
}   

Please don't beat me up in case stream.toByteArray() is a no-no memory wise :) It works for my <1KB PNG files...

Careerism answered 24/9, 2012 at 21:27 Comment(2)
I think it's a bad streaming example, as the returned object in the output is a byte array and not a stream.Shoreward
Good example for building a response to a GET resource request, not a good example for stream. This is not a stream at all.Jiujitsu
S
14

I have been composing my Jersey 1.17 services the following way:

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

And the client, if you need it:

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();
}
Shrewmouse answered 31/10, 2013 at 9:5 Comment(0)
D
7

This example shows how to publish log files in JBoss through a rest resource. Note the get method uses the StreamingOutput interface to stream the content of the log file.

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

}

Debbi answered 10/7, 2013 at 16:38 Comment(1)
Just FYI: instead of the pipe method you could also use IOUtils.copy from Apache commons I/O.Justificatory
H
7

Using Jersey 2.16 File download is very easy.

Below is the example for the ZIP file

@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();
}
Hostage answered 12/2, 2015 at 14:12 Comment(4)
Works like a charm. Have any idea about this streaming stuff I don't quite understand it...Cyclonite
It the easiest way if you use Jersey, ThanksNupercaine
Is it possible to do with @POST instead of @GET?Diatomaceous
@Diatomaceous I think yes, it is possible. When the server page responds, it should provide the download windowHostage
D
5

I found the following helpful to me and I wanted to share in case it helps you or someone else. I wanted something like MediaType.PDF_TYPE, which doesn't exist, but this code does the same thing:

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

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

In my case I was posting a PDF document to another site:

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

Then p gets passed as the second parameter to post().

This link was helpful to me in putting this code snippet together: http://jersey.576304.n2.nabble.com/Multipart-Post-td4252846.html

Dropsonde answered 10/1, 2012 at 13:14 Comment(0)
S
3

This worked fine with me 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();
}
Supernatural answered 16/5, 2013 at 6:49 Comment(1)
Not sure about Response.ok("file path null").build();, is it really ok ? You should probably use something like Response.status(Status.BAD_REQUEST).entity(...Kidderminster
V
1

Another sample code where you can upload a file to the REST service, the REST service zips the file, and the client downloads the zip file from the server. This is a good example of using binary input and output streams using Jersey.

https://mcmap.net/q/183469/-password-protected-zip-file-in-java

This answer was posted by me in another thread. Hope this helps.

Venezuela answered 27/8, 2015 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.