Kai Sternad
Kai Sternad

Reputation: 22830

Apache Camel multipart HTTP post (file upload)

How can I do multipart file uploads using the Apache Camel HTTP component ?

Upvotes: 10

Views: 14280

Answers (6)

Dennie
Dennie

Reputation: 912

Not sure about your question, but I was looking for a way to consume files that are sent to an HTTP endpoint and encoded in multi-form post data. The following code uses Jetty component and Split pattern (EIP) to write those files to disk:

The Camel route:

        from("jetty:http://0.0.0.0/my_endpoint")
            .split(method(new MyAttachmentSplitter())).streaming()
            .to("file:c:/temp/attachments");

The splitter bean:

public static class MyAttachmentSplitter {

    public List<Object> split(final Exchange ex) {
        final List<Object> out = new ArrayList<>();

        final AttachmentMessage attachmentMessage = ex.getIn().getBody(AttachmentMessage.class);
        final Map<String, DataHandler> attachments = attachmentMessage.getAttachments();

        for (final Entry<String, DataHandler> entry : attachments.entrySet()) {
            final DataHandler dh = entry.getValue();
            byte[] bytes;
            try {
                bytes = ByteStreams.toByteArray(dh.getInputStream());
            } catch (final IOException e) {
                throw new IllegalStateException(e);
            }

            final DefaultMessage msg = new DefaultMessage(ex.getContext());
            msg.setHeader(FileConstants.FILE_NAME, entry.getKey());
            msg.setBody(bytes);
            out.add(msg);
        }

        return out;
    }
}

Example usage:

curl -X POST http://127.0.0.1/my_endpoint -F "[email protected]" -F "[email protected]"

Upvotes: 0

Stepan
Stepan

Reputation: 51

You can do it in programmatically process

routes:

from("jetty:{{camel.address}}/" + method)
    .process(ex -> SomeClassProcessing.ProcessMultipart(ex));

processing:

public class SomeClassProcessing {
public static void ProcessMultipart(Exchange exchange) {
    AttachmentMessage attIn = exchange.getIn(AttachmentMessage.class);
    Map<String, DataHandler> atts = attIn.getAttachments();
    
    for (var pair: atts.entrySet()) {
        var fileName = pair.getKey();
        var att = pair.getValue();
        
        var is = att.getInputStream();
        var fileContent = new byte[is.available()];
        is.read(fileContent);
    
        // Now file content is in array of byte
    }
}

Upvotes: 1

Aliti
Aliti

Reputation: 2095

I had working on a web project by below features:

  1. Login Form: people login and can upload the file; (Camel: Jetty, Http, JDBC)

  2. Upload Form; upload servlet: if people can login; can upload xml file to ftp or web server; (Camel: file)

3.File is validated by my .xsd file; (Camel: Validator)

  1. File is checked by my .xsl schema file; (Camel: XSLT)

I was create web project by my favorite IDE (IntelliJ IDEA by Jetbrains); I describe part of my scenario with source code and hope this is useful ☺

1) index.html

<form action="http://0.0.0.0:8080/hello" method="post">

<fieldset title="Login" >

    username:<input type="text" id="user" name="user"/>
    password:<input type="password" id="pass" name="pass" />

    <input type="submit" id="submit" value="submit"/>

</fieldset>

First you have to create database and login table; then add some sample data; for example add these files:

2) schema.sql

DROP TABLE IF EXISTS CONTACT;
CREATE TABLE CONTACT (
   ID INT NOT NULL AUTO_INCREMENT
 , NAME VARCHAR(40) NOT NULL
 , USERNAME VARCHAR(40) NOT NULL
 , PASSWORD VARCHAR(60) NOT NULL
 , VERSION INT NOT NULL DEFAULT 0
 , UNIQUE UQ_CONTACT_1 (USERNAME)
 , PRIMARY KEY (ID));

3) test-data.sql

insert into contact (name, username, password) values ('ali', 'aliti', '123');
insert into contact (name, username, password) values ('shahab', 'shahab', '147');
insert into contact (name, username, password) values ('farhad', 'farhad', '159');

4) config spring-context.xml

Then, you can use embedded databases like derby, H2, mysql or others. Add below config to your spring config file:

<jdbc:embedded-database id="dataSource" type="H2">
    <jdbc:script location="classpath:schema.sql"/>
    <jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>

5) camel-context.xml

Now, you can run your project; before do that you have to add this route to your camel context:

<route>
        <from uri="jetty:http://0.0.0.0:8080/hello"/>

        <setBody>
            <simple>
                select * from contact where USERNAME = '${in.header.user}' and PASSWORD = '${in.header.pass}'
            </simple>
        </setBody>

        <to uri="jdbc:dataSource"/>

        <process ref="loginProcessor"/>

        <log message=">>>header: ${in.header.name}"/>

        <choice>
            <when>
                <simple>${in.header.name} == null</simple>
                <to uri="jetty://http://localhost:9090/fail.html?bridgeEndpoint=true"/>
            </when>

            <otherwise>
                <to uri="jetty://http://localhost:9090/file.html?bridgeEndpoint=true"/>
            </otherwise>
        </choice>

When you run our project; index.html page was shown and you can put the username and password text boxes and send your form.

Actually Camel was listening to this jetty port and got your post information. You can get these information by Camel’s header like '${in.header.user}'.

As you can see, I set my select query in Camel’s Body, Thus the select result is also store in Camel’s Body. I want to read my result and got some decisions, for this reason I add Camel processor as below:

6) LoginProcessor.java

public class LoginProcessor implements Processor {
public void process(Exchange exchange) throws Exception {

    int size = ((ArrayList) exchange.getIn().getBody()).size();
    if (size > 0) {
        Object name = ((LinkedHashMap) (((ArrayList) exchange.getIn().getBody()).get(0))).get("NAME");
        System.out.println("welcome user: " + name);
        exchange.getOut().setHeader("name",name);
    } else {
        System.out.println("user or pass are invalid. ");
        exchange.getOut().setHeader("name",null);
    }
}

}

In LoginProcessor I checked the body and if input username and password are valid; Add Camel’s header property and named by ‘name’ by field name of table. Otherwise set null value in Camel’s header property.

Back to Camel context xml file and continue the route. If Camel’s header is null; redirect user to fail.html page; otherwise redirect to page that get file from user(file.html).

Note: bridgeEndpoint property You are setting the http endpoint to be bridgeEndpoint which means the request url will be updated with request URI.

Upvotes: 1

pegli
pegli

Reputation: 690

As long as your message body is in multipart/form-data format, you can use the Camel http component to POST it to another server. The trick is to set your Content-Type properly and set the request method to be POST:

<route>
  <from uri="direct:start"/>
  <setBody>
    <![CDATA[
    --__MyCoolBoundary__
    Content-Disposition: form-data; name="name"

    Paul Mietz Egli
    --__MyCoolBoundary__
    Content-Disposition: form-data; name="email"

    [email protected]
    --__MyCoolBoundary__--
    ]]>
  </setBody>
  <setHeader headerName="Content-Type">
    <constant>multipart/form-data; boundary="__MyCoolBoundary__"</constant>
  </setHeader>
  <setHeader headerName="CamelHttpMethod">
    <constant>POST</constant>
  </setHeader>
  <to uri="http://www.example.com/mywebservice.php"/>
</route>

Obviously, the example body above isn't that useful because it's all static data. There are a number of ways you can construct the body -- I've used XSLT outputting in text mode, a scripted expression (e.g. <groovy>...</groovy>), and a Spring bean. XSLT works well when your incoming message body is already an XML document:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
--__MyCoolBoundary__
Content-Disposition: form-data; name="name"

<xsl:value-of select="//name"/>
--__MyCoolBoundary__--
</xsl:stylesheet>

You do need to be careful about extra whitespace, however. Hope this helps!

Upvotes: 3

Archer
Archer

Reputation: 5147

Could you please provide more details how do you want multipart form reach apache camel?

Should it be some form on a webpage that send directly to Camel route? Or AMQ queue? I'd suggest you checking Apache HTTP and Apache Jetty components.

Upvotes: 0

Henryk Konsek
Henryk Konsek

Reputation: 9168

I don't know is it possible to send multipart forms using the HTTP component.

If you need the workaround, you can create POJO Spring Bean that uses the Apache Http Client (and its MultipartPostMethod). Then you can route your message to that bean:

from("activemq:uploadQueue").to("bean:myApacheHttpClientBean?method=sendMultiPart")

Upvotes: 3

Related Questions