Search code examples
javajsonjerseystreamingmultipart

Streaming a multipart in Jersey 2


I currently have Jersey REST code to stream a single file which works great:

StreamingOutput stream = new StreamingOutput() {
    @Override
    public void write(OutputStream out)
    throws IOException {
        final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out);

        // Stream is filled with data in this method.
        restDAO.readData(bufferedOutputStream);
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
    }
};

return Response.ok(body, mimeType).header("filename", getFileName()).build();

However, I was wanting to stream a multipart file which contains both a large file and JSON, doing something like this:

FormDataMultiPart multiPart = new FormDataMultiPart();
multiPart.bodyPart(jsonObject, MediaType.APPLICATION_JSON_TYPE);
String mimeType = "application/octet-stream";

StreamingOutput stream = new StreamingOutput() {
    @Override
    public void write(OutputStream out)
    throws IOException {
        final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(out);

        // Stream is filled with data in this method.
        restDAO.readData(bufferedOutputStream);
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
    }
};
multiPart.bodyPart(stream, MediaTypeUtil.stringToMediaType(mimeType));

return Response.ok(multiPart, MediaType.MULTIPART_FORM_DATA).build();

However, the above code does not work. I get this error when running: javax.ws.rs.BadRequestException: HTTP 400 Bad Request

Is it possible to stream a multiPart in a similar manner? The main problem I see is that the file going into the multipart is coming from a stream itself.


Solution

  • To properly stream the multipart, I ended up using PipedInputStream and PipedOutputStream along with a thread:

    PipedOutputStream pipedOutputStream = new PipedOutputStream();
    PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);
    final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(pipedOutputStream);
    
    FormDataMultiPart multiPart = new FormDataMultiPart();
    multiPart.bodyPart(jsonObject, MediaType.APPLICATION_JSON_TYPE);
    String mimeType = "application/octet-stream";
    
    // Multipart streaming.
    // Write to the PipedOutputStream in a separate thread
    Runnable runnable = new Runnable() {
        public void run() {
            try {
                restDAO.readData(bufferedOutputStream);
                bufferedOutputStream.flush();
                bufferedOutputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
    
    Thread fileThread = new Thread(runnable, "MultiPartFileStreamer");
    fileThread.start();
    final StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart(
        "file", pipedInputStream, data.getContentFileName(), 
        MediaUtils.stringToMediaType(mimeType));
    multiPart.bodyPart(streamDataBodyPart);
    
    return Response.ok(multiPart, MediaType.MULTIPART_FORM_DATA).build();