Вхідні та вихідні бінарні потоки за допомогою JERSEY?


111

Я використовую Джерсі, щоб реалізувати API RESTful, який в основному отримує і обслуговує кодовані JSON дані. Але в мене є деякі ситуації, коли мені потрібно виконати наступне:

  • Експортуйте завантажувані документи, такі як PDF, XLS, ZIP або інші двійкові файли.
  • Отримайте багаточастинні дані, такі як JSON плюс завантажений XLS-файл

У мене є веб-клієнт на базі JQuery, який створює дзвінки AJAX до цієї веб-служби. На даний момент він не подає форму, а використовує GET і POST (з об'єктом JSON). Чи слід використовувати форму форми для надсилання даних та вкладеного бінарного файлу, або я можу створити запит на багато частин із двійковим файлом JSON плюс?

Наразі сервісний рівень моєї програми створює ByteArrayOutputStream, коли він створює файл PDF. Який найкращий спосіб вивести цей потік клієнту через Джерсі? Я створив MessageBodyWriter, але не знаю, як ним користуватися з ресурсу Джерсі. Це правильний підхід?

Я переглядав зразки, включені до Джерсі, але ще не знайшов нічого, що б ілюструвало, як робити будь-яку з цих речей. Якщо це важливо, я використовую Джерсі з Джексоном, щоб робити 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) приймає вихідний потік з методу write і записує його замість новоствореного вихідного потоку.

Не знаю, чи це найкращий спосіб зробити це, але це працює.


33
Також можливо повернути StreamingOutput як сутність Responseоб'єкту. Таким чином, ви можете легко керувати медіатипом, кодом відповіді HTTP тощо. Повідомте мене, чи хочете ви, щоб я надсилав код.
Ганк

3
@MyTitle: див. Приклад
Hank

3
Я використав приклади коду в цій темі в якості посилання і виявив, що мені потрібно промити OutputStream в StreamingOutput.write (), щоб клієнт надійно отримав вихід. В іншому випадку я інколи отримую "Content-length: 0" в заголовках і без тіла, хоча журнали повідомляли мені, що StreamingOutput виконується.
Джон Стюарт

@JonStewart - Я вважаю, що я робив флеш в методі createPDF.
MikeTheReader

1
@ Dante617. Чи розміщуєте ви код клієнта, як клієнт Джерсі надсилає бінарний потік на сервер (з майкою 2.x)?
Дебора

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
Це споживає всі байти в пам'яті, а значить, великі файли можуть збити сервер. Мета потоку - уникнути використання всіх байтів у пам'яті.
Роберт Крістіан

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

Ось ще один приклад. Я створюю QR-код як 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()не пам'ятаєте пам'яті :) Це працює для моїх файлів <1 КБ PNG ...


6
Я думаю, що це поганий приклад потоку, оскільки повернутий об'єкт у висновку - це байтовий масив, а не потік.
АлікЕльзін-кілака

Хороший приклад для створення відповіді на запит на ресурс GET, а не хороший приклад для потоку. Це зовсім не потік.
Роберт Крістіан

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: замість методу pipe ви також можете використовувати IOUtils.copy з Apache, що використовується введенням / виведенням.
Девід

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
Працює як шарм. Маєте якусь ідею щодо цього потокового матеріалу, я його зовсім не розумію ...
Олівер

1
Це найпростіший спосіб, якщо ви користуєтеся Джерсі, Дякую
ganchito55

Чи можна робити з @POST замість @GET?
сприн

@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 передається як другий параметр для post ().

Це посилання мені було корисним для з’єднання цього фрагмента коду: 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(...
Крістоф Руссі,

1

Ще один зразок коду, куди ви можете завантажити файл до служби REST, служба REST перетягує файл, а клієнт завантажує zip-файл із сервера. Це хороший приклад використання двійкових потоків вводу та виводу за допомогою Джерсі.

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

Ця відповідь була розміщена мною в іншій темі. Сподіваюся, це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.