JERSEY를 사용하는 입력 및 출력 바이너리 스트림?


111

저는 Jersey를 사용하여 주로 JSON 인코딩 데이터를 검색하고 제공하는 RESTful API를 구현하고 있습니다. 그러나 다음을 수행해야하는 몇 가지 상황이 있습니다.

  • PDF, XLS, ZIP 또는 기타 바이너리 파일과 같은 다운로드 가능한 문서를 내 보냅니다.
  • 일부 JSON 및 업로드 된 XLS 파일과 같은 멀티 파트 데이터 검색

이 웹 서비스에 대한 AJAX 호출을 생성하는 단일 페이지 JQuery 기반 웹 클라이언트가 있습니다. 현재 양식 제출을 수행하지 않고 GET 및 POST (JSON 객체 사용)를 사용합니다. 데이터 및 첨부 된 이진 파일을 보내기 위해 양식 게시물을 사용해야합니까, 아니면 JSON과 이진 파일이 포함 된 다중 요청을 만들 수 있습니까?

내 애플리케이션의 서비스 레이어는 현재 PDF 파일을 생성 할 때 ByteArrayOutputStream을 생성합니다. Jersey를 통해이 스트림을 클라이언트에 출력하는 가장 좋은 방법은 무엇입니까? MessageBodyWriter를 만들었지 만 Jersey 리소스에서 사용하는 방법을 모르겠습니다. 이것이 올바른 접근 방식입니까?

Jersey에 포함 된 샘플을 살펴 보았지만 아직 이러한 작업을 수행하는 방법을 보여주는 어떤 것도 찾지 못했습니다. 중요한 경우 Jackson과 Jersey를 사용하여 XML 단계없이 Object-> JSON을 수행하고 실제로 JAX-RS를 사용하지 않습니다.

답변:


109

StreamingOutput객체 를 확장하여 ZIP 파일이나 PDF 파일을 얻을 수있었습니다 . 다음은 몇 가지 샘플 코드입니다.

@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 응답 코드 등을 쉽게 제어 할 수 있습니다. 코드를 게시하려면 알려주세요.
— Hank

3
@MyTitle : 예제
— Hank

3
이 스레드의 코드 예제를 참조로 사용했으며 클라이언트가 출력을 안정적으로 수신하려면 StreamingOutput.write ()에서 OutputStream을 플러시해야한다는 것을 알았습니다. 그렇지 않으면 로그에 StreamingOutput이 실행 중이라고 말 했음에도 불구하고 헤더에 "Content-Length : 0"이 표시되고 본문이 표시되지 않습니다.
— Jon Stewart

@JonStewart-저는 generatePDF 메서드 내에서 플러시를하고 있다고 생각합니다.
— MikeTheReader jul.

1
@ Dante617. Jersey 클라이언트가 서버에 바이너리 스트림을 보내는 방법 (jersey 2.x 사용) 클라이언트 측 코드를 게시 하시겠습니까?
— Débora

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
출력이 완전히 준비된 후에 만 ​​전송되기 때문에 좋지 않습니다. byte []는 스트림이 아닙니다.
— java.is.for.desktop

7
이는 모든 바이트를 메모리로 소비하므로 대용량 파일로 인해 서버가 다운 될 수 있습니다. 스트리밍의 목적은 모든 바이트를 메모리로 소비하지 않도록하는 것입니다.
— Robert Christian

22

이 코드를 사용하여 jersey의 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

여기 또 다른 예가 있습니다. 을 통해 QRCode를 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()메모리가없는 현명한 경우 나를 때리지 마십시오 :) 내 <1KB PNG 파일에서 작동합니다 ...


6
출력에 반환 된 객체가 스트림이 아닌 바이트 배열이기 때문에 잘못된 스트리밍 예제라고 생각합니다.
— AlikElzin-kilaka

스트림에 대한 좋은 예가 아니라 GET 리소스 요청에 대한 응답을 빌드하는 좋은 예입니다. 이것은 전혀 스트림이 아닙니다.
— Robert Christian

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
참고로, 파이프 방법 대신 Apache commons I / O에서 IOUtils.copy를 사용할 수도 있습니다.
— David

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
매력처럼 작동합니다. 이 스트리밍에 대해 잘 모르겠습니다.
— Oliver

1
Jersey를 사용한다면 가장 쉬운 방법입니다. 감사합니다
— ganchito55

@GET 대신 @POST로 할 수 있습니까?
— spr

@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(...
— Christophe Roussy 2015

1

REST 서비스에 파일을 업로드 할 수있는 또 다른 샘플 코드는 REST 서비스가 파일을 압축하고 클라이언트가 서버에서 zip 파일을 다운로드합니다. 이것은 Jersey를 사용하여 이진 입력 및 출력 스트림을 사용하는 좋은 예입니다.

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

이 답변은 내가 다른 스레드에 게시했습니다. 도움이 되었기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.