Search code examples
resteasymultipart-mixed-replace

How do I get a mixed multipart in a RESTEasy response?


I am trying to use resteasy. While I am able to do send a mixed multipart as a request to a webservice, I am unable to do get a mixed multipart in the response. For eg: Requesting for a file (byte[] or stream) and the file name in a single Response. Following is what I have tested:

Service code:

@Path("/myfiles")
public class MyMultiPartWebService {

@POST
@Path("/filedetail")
@Consumes("multipart/form-data")
@Produces("multipart/mixed")
public MultipartOutput fileDetail(MultipartFormDataInput input) throws IOException {
       MultipartOutput multipartOutput = new MultipartOutput();
       //some logic based on input to locate a file(s)
       File myFile = new File("samplefile.pdf");
       multipartOutput.addPart("fileName:"+ myFile.getName(), MediaType.TEXT_PLAIN_TYPE);
       multipartOutput.addPart(file, MediaType.APPLICATION_OCTET_STREAM_TYPE);
       return multipartOutput;
 }
}

Client code:

public void getFileDetails(/*input params*/){
    HttpClient client = new DefaultHttpClient();
    HttpPost postRequest = new HttpPost("urlString");
    MultipartEntity multiPartEntity = new MultipartEntity();
    //prepare the request details        
    postRequest.setEntity(multiPartEntity);

    HttpResponse response = client.execute(postRequest);
    HttpEntity returnEntity = response.getEntity();

    //extracting data from the response
    Header header = returnEntity.getContentType();
    InputStream is = returnEntity.getContent();
    if (is != null) {
        byte[] bytes = IOUtils.toByteArray(is);
        //Can we see the 2 parts that were added?
        //Able to get a single InputStream only, and hence unable to differentiate two objects in the response
        //Trying to see the contents - printing as string
        System.out.println("Output from Response :: " + new String(bytes));
     }
 }

The output is as follows - able to see 2 different objects with different content types, but unable to extract them separately.

Output from Response :: 
--af481055-4e4f-4860-9c0b-bb636d86d639
Content-Type: text/plain

fileName: samplefile.pdf
--af481055-4e4f-4860-9c0b-bb636d86d639
Content-Length: 1928
Content-Type: application/octet-stream

%PDF-1.4
<<pdf content printed as junk chars>>

How can I extract the 2 objects from the response?

UPDATE:

Tried the following approach to extract the different parts - use the 'boundary' to break the MultipartStream; use the content type string to extract approp object.

    private void getResponeObject(HttpResponse response) throws IllegalStateException, IOException {
        HttpEntity returnEntity = response.getEntity();
        Header header = returnEntity.getContentType();
        String boundary = header.getValue();
        boundary = boundary.substring("multipart/mixed; boundary=".length(), boundary.length());
        System.out.println("Boundary" + boundary); // --af481055-4e4f-4860-9c0b-bb636d86d639
        InputStream is = returnEntity.getContent();
        splitter(is, boundary);
    }

    //extract subsets from the input stream based on content type
    private void splitter(InputStream is, String boundary) throws IOException {
        ByteArrayOutputStream boas = null;
        FileOutputStream fos = null;

        MultipartStream multipartStream = new MultipartStream(is, boundary.getBytes());
        boolean nextPart = multipartStream.skipPreamble();
        System.out.println("NEXT PART :: " + nextPart);
        while (nextPart) {
            String header = multipartStream.readHeaders();
            if (header.contains("Content-Type: "+MediaType.APPLICATION_OCTET_STREAM_TYPE)) {
                fos = new FileOutputStream(new File("myfilename.pdf"));
                multipartStream.readBodyData(fos);
            } else if (header.contains("Content-Type: "+MediaType.TEXT_PLAIN_TYPE)) {
                boas = new ByteArrayOutputStream();
                multipartStream.readBodyData(boas);
                String newString = new String( boas.toByteArray());
            } else if (header.contains("Content-Type: "+ MediaType.APPLICATION_JSON_TYPE)) {
                //extract string and create JSONObject from it
            } else if (header.contains("Content-Type: "+MediaType.APPLICATION_XML_TYPE)) {
                //extract string and create XML object from it
            }
            nextPart = multipartStream.readBoundary();
        }
    }

Is this the right approach?

UPDATE 2: The logic above seems to work. But got another block, when receiving the RESPONSE from the webservice. I could not find any references to handle such issues in the Response. The logic assumes that there is ONE part for a part type. If there are, say, 2 JSON parts in the response, it would be difficult to identify which part is what. In other words, though we can add the part with a key name while creating the response, we are unable to extract the key names int he client side. Any clues?


Solution

  • You can try the following approach...

    At the server side...

    1. Create a wrapper object that can encapsulate all types. For eg., it could have a Map for TEXT and another Map for Binary data.
    2. Convert the TEXT content to bytes (octet stream).
    3. Create a MetaData which contains references to the Key names and their type. Eg., STR_MYKEY1, BYTES_MYKEY2. This metadata can also be converted into octet stream.
    4. Add the metadata and the wrapped entity as parts to the multipart response.

    At the Client side...

    1. Read the MetaData to get the key names.

    2. Use the key name to interpret each part. Since the Keyname from the metadata tells if the original data is a TEXT or BINARY, you should be able to extract the actual content with appropriate logic.

    The same approach can be used for upstream, from client to service. On top of this, you can compress the TEXT data which will help in reducing the content size...