Accept Multipart file upload as camel restlet or cxfrs endpoint

4.5k views Asked by At

I am looking to implement a route where reslet/cxfrs end point will accept file as multipart request and process. (Request may have some JSON data as well.

Thanks in advance. Regards. [EDIT] Have tried following code. Also tried sending file using curl. I can see file related info in headers and debug output, but not able to retrieve attachment.

from("servlet:///hello").process(new Processor() {
   @Override
   public void process(Exchange exchange) throws Exception {
      Message in = exchange.getIn();
      StringBuffer v = new StringBuffer();
       HttpServletRequest request = (HttpServletRequest) in
          .getHeaders().get(Exchange.HTTP_SERVLET_REQUEST);

       DiskFileItemFactory diskFile = new DiskFileItemFactory();
       FileItemFactory factory = diskFile;
       ServletFileUpload upload = new ServletFileUpload(factory);
       List items = upload.parseRequest(request);
..... 

curl : curl -vvv -i -X POST -H "Content-Type: multipart/form-data" -F "image=@/Users/navaltiger/1.jpg; type=image/jpg" http://:8080/JettySample/camel/hello

following code works (but can't use as it embeds jetty, and we would like to deploy it on tomcat/weblogic)

public void configure() throws Exception {
        // getContext().getProperties().put("CamelJettyTempDir", "target");
        getContext().setStreamCaching(true);
        getContext().setTracing(true);

         from("jetty:///test").process(new Processor() {
//      from("servlet:///hello").process(new Processor() {
            public void process(Exchange exchange) throws Exception {

                String body = exchange.getIn().getBody(String.class);
                HttpServletRequest request = exchange.getIn().getBody(
                        HttpServletRequest.class);

                StringBuffer v = new StringBuffer();
                // byte[] picture = (request.getParameter("image")).getBytes();

                v.append("\n Printing All Request Parameters From HttpSerlvetRequest: \n+"+body +" \n\n");

                Enumeration<String> requestParameters = request
                        .getParameterNames();
                while (requestParameters.hasMoreElements()) {
                    String paramName = (String) requestParameters.nextElement();
                    v.append("\n Request Paramter Name: " + paramName
                            + ", Value - " + request.getParameter(paramName));
                }
4

There are 4 answers

0
Balaban Mario On

I had a similar problem and managed to resolve inspired by the answer of brentos. The rest endpoint in my case is defined via xml:

<restContext id="UploaderServices"  xmlns="http://camel.apache.org/schema/spring">

    <rest path="/uploader">
        <post bindingMode="off" uri="/upload"  produces="application/json">
            <to uri="bean:UploaderService?method=uploadData"/>
        </post>
    </rest>

</restContext>

I had to use "bindingMode=off" to disable xml/json unmarshalling because the HttpRequest body contains multipart data (json/text+file) and obviously the standard unmarshaling process was unable to process the request because it's expecting a string in the body and not a multipart payload.

The file and other parameters are sent from a front end that uses the file upload angular module: https://github.com/danialfarid/ng-file-upload

To solve CORS problems I had to add a CORSFilter filter in the web.xml like the one here:

public class CORSFilter implements Filter {

@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
        ServletException {
    HttpServletResponse httpResp = (HttpServletResponse) resp;
    HttpServletRequest httpReq = (HttpServletRequest) req;

    httpResp.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH");
    httpResp.setHeader("Access-Control-Allow-Origin", "*");
    if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) {
        httpResp.setHeader("Access-Control-Allow-Headers",
                httpReq.getHeader("Access-Control-Request-Headers"));
    }
    chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig arg0) throws ServletException {
}

@Override
public void destroy() {
}
}

Also, I had to modify a little bit the unmarshaling part:

public String uploadData(Message exchange) {
    String contentType=(String) exchange.getIn().getHeader(Exchange.CONTENT_TYPE);
    MediaType mediaType = MediaType.valueOf(contentType); //otherwise the boundary parameter is lost
    InputRepresentation representation = new InputRepresentation(exchange
            .getBody(InputStream.class), mediaType);

    try {
        List<FileItem> items = new RestletFileUpload(
                new DiskFileItemFactory())
                .parseRepresentation(representation);

        for (FileItem item : items) {
            if (!item.isFormField()) {
                InputStream inputStream = item.getInputStream();
                // Path destination = Paths.get("MyFile.jpg");
                // Files.copy(inputStream, destination,
                // StandardCopyOption.REPLACE_EXISTING);
                System.out.println("found file in request:" + item);
            }else{
                System.out.println("found string in request:" + new String(item.get(), "UTF-8"));
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

    return "200";
}
0
Cedric Dumont On

you can do this with restdsl even if you are not using restlet (exemple jetty) for your restdsl component.

you need to turn restdinding of first for that route and reate two classes to handle the multipart that is in your body.

you need two classes :

  • DWRequestContext
  • DWFileUpload

and then you use them in your custom processor

here is the code :

DWRequestContext.java

    import org.apache.camel.Exchange;
    import org.apache.commons.fileupload.RequestContext;

    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;

    public class DWRequestContext implements RequestContext {

        private Exchange exchange;

        public DWRequestContext(Exchange exchange) {
            this.exchange = exchange;
        }

        public String getCharacterEncoding() {
            return StandardCharsets.UTF_8.toString();
        }

        //could compute here (we have stream cache enabled)
        public int getContentLength() {
            return (int) -1;
        }

        public String getContentType() {
            return exchange.getIn().getHeader("Content-Type").toString();
        }

        public InputStream getInputStream() throws IOException {
            return this.exchange.getIn().getBody(InputStream.class);
        }
    }

DWFileUpload.java

    import org.apache.camel.Exchange;
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.FileItemFactory;
    import org.apache.commons.fileupload.FileUpload;
    import org.apache.commons.fileupload.FileUploadException;

    import java.util.List;

    public class DWFileUpload extends
            FileUpload {

        public DWFileUpload() {
            super();
        }

        public DWFileUpload(FileItemFactory fileItemFactory) {
            super(fileItemFactory);
        }

        public List<FileItem> parseInputStream(Exchange exchange)
                throws FileUploadException {
            return parseRequest(new DWRequestContext(exchange));
        }
    }

you can define your processor like this:

    routeDefinition.process(new Processor() {
                    @Override
                    public void process(Exchange exchange) throws Exception {
                        // Create a factory for disk-based file items
                        DiskFileItemFactory factory = new DiskFileItemFactory();
                        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));

                        DWFileUpload upload = new DWFileUpload(factory);

                        java.util.List<FileItem> items = upload.parseInputStream(exchange);

                        //here I assume I have only one, but I could split it here somehow and link them to camel properties...
                        //with this, the first file sended with your multipart replaces the body
                        // of the exchange for the next processor to handle it
                        exchange.getIn().setBody(items.get(0).getInputStream());
                    }
                });
0
Gro On

I stumbled into the same requirement of having to consume a multipart request (containing file data including binary) through Apache Camel Restlet component. Even though 2.17.x is out, since my project was part of a wider framework / application, I had to be using version 2.12.4.

Initially, my solution drew a lot from restlet-jdbc example yielded data in exchange that although was successfully retrieving text files but I was unable to retrieve correct binary content.

I attempted to dump the data directly into a file to inspect the content using following code (abridged).

from("restlet:/upload?restletMethod=POST")
.to("direct:save-files");

from("direct:save-files")
.process(new org.apache.camel.Processor(){
    public void process(org.apache.camel.Exchange exchange){
    /*
     * Code to sniff exchange content
     */
     }
 })
 .to("file:///C:/<path to a folder>");
 ;

I used org.apache.commons.fileupload.MultipartStream from apache fileuplaod library to write following utility class to parse Multipart request from a file. It worked successfully when the output of a mulitpart request from Postman was fed to it. However, failed to parse content of the file created by Camel (even through to eyes content of both files looked similar).

public class MultipartParserFileCreator{

    public static final String DELIMITER = "\\r?\\n";

    public static void main(String[] args) throws Exception {       
        // taking it from the content-type in exchange 
        byte[] boundary = "------5lXVNrZvONBWFXxd".getBytes();
        FileInputStream fis = new FileInputStream(new File("<path-to-file>"));  
        extractFile(fis, boundary);
    }

    public static void extractFile(InputStream is, byte[] boundary) throws Exception {
        MultipartStream multipartStream = new MultipartStream(is, boundary, 1024*4, null);
        boolean nextPart = multipartStream.skipPreamble();
        while (nextPart) {          
            String headers = multipartStream.readHeaders();
            if(isFileContent(headers)) {
                String filename = getFileName(headers);             
                File file = new File("<dir-where-file-created>"+filename);
                if(!file.exists()) {
                    file.createNewFile();
                }
                FileOutputStream fos = new FileOutputStream(file);                      
                multipartStream.readBodyData(fos);
                fos.flush();
                fos.close();
            }else {
                multipartStream.readBodyData(System.out);
            }
            nextPart = multipartStream.readBoundary();
        }
    }

    public static String[] getContentDispositionTokens(String headersJoined) {      
        String[] headers = headersJoined.split(DELIMITER, -1);
        for(String header: headers) {
            System.out.println("Processing header: "+header);
            if(header != null && header.startsWith("Content-Disposition:")) {
                return header.split(";");
            }
        }       
        throw new RuntimeException(
                String.format("[%s] header not found in supplied headers [%s]", "Content-Disposition:", headersJoined));

    }

    public static boolean isFileContent(String header) {        
        String[] tokens = getContentDispositionTokens(header);
        for (String token : tokens) {
            if (token.trim().startsWith("filename")) {
                return true;
            }
        }       
        return false;
    }

    public static String getFileName(String header) {
        String[] tokens = getContentDispositionTokens(header);
        for (String token : tokens) {
            if (token.trim().startsWith("filename")) {
                String filename =  token.substring(token.indexOf("=") + 2, token.length()-1);
                System.out.println("fileName is " + filename);
                return filename;
            }
        }       
        return null;
    }
} 

On debugging through the Camel code, I noticed that at one stage Camel is converting the entire content into String. After a point I had to stop pursuing this approach as there was very little on net applicable for version 2.12.4 and my work was not going anywhere.

Finally, I resorted to following solution

  1. Write an implementation of HttpServletRequestWrapper to allow multiple read of input stream. One can get an idea from How to read request.getInputStream() multiple times
  2. Create a filter that uses the above to wrap HttpServletRequest object, reads and extract the file to a directory Convenient way to parse incoming multipart/form-data parameters in a Servlet and attach the path to the request using request.setAttribute() method. With web.xml, configure this filter on restlet servlet
  3. In the process method of camel route, type cast the exchange.getIn().getBody() in HttpServletRequest object, extract the attribute (path) use it to read the file as ByteStreamArray for further processing

Not the cleanest, but I could achieve the objective.

0
brentos On

I'm using the Camel REST DSL with Restlet and was able to get file uploads working with the following code.

rest("/images").description("Image Upload Service")
.consumes("multipart/form-data").produces("application/json")
.post().description("Uploads image")
        .to("direct:uploadImage");

from("direct:uploadImage")
.process(new Processor() {

    @Override
    public void process(Exchange exchange) throws Exception {

        MediaType mediaType = 
            exchange.getIn().getHeader(Exchange.CONTENT_TYPE, MediaType.class);
        InputRepresentation representation =
            new InputRepresentation(
                exchange.getIn().getBody(InputStream.class), mediaType);

        try {
            List<FileItem> items = 
                new RestletFileUpload(
                    new DiskFileItemFactory()).parseRepresentation(representation);

            for (FileItem item : items) {
                if (!item.isFormField()) {
                    InputStream inputStream = item.getInputStream();
                    Path destination = Paths.get("MyFile.jpg");
                    Files.copy(inputStream, destination,
                                StandardCopyOption.REPLACE_EXISTING);
                }
            }
        } catch (FileUploadException | IOException e) {
            e.printStackTrace();
        }


    }

});