CAMEL-10451 camel-undertow - Add multipart request support
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/015f8128 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/015f8128 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/015f8128 Branch: refs/heads/master Commit: 015f8128df5a50dba32661c6351b6e1839df0d59 Parents: cf83fca Author: Tomohisa Igarashi <tm.igara...@gmail.com> Authored: Wed Nov 9 01:29:53 2016 +0900 Committer: Claus Ibsen <davscl...@apache.org> Committed: Wed Nov 9 09:47:54 2016 +0100 ---------------------------------------------------------------------- .../undertow/DefaultUndertowHttpBinding.java | 62 +++++++++-- .../component/undertow/UndertowComponent.java | 2 +- .../component/undertow/UndertowConsumer.java | 6 ++ .../component/undertow/MultiPartFormTest.java | 102 +++++++++++++++++++ .../PreservePostFormUrlEncodedBodyTest.java | 71 +++++++++++++ 5 files changed, 236 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/015f8128/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java index a0bd90b..3c231fd 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/DefaultUndertowHttpBinding.java @@ -17,21 +17,30 @@ package org.apache.camel.component.undertow; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.Deque; +import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Map; +import javax.activation.DataHandler; +import javax.activation.FileDataSource; + import io.undertow.client.ClientExchange; import io.undertow.client.ClientRequest; import io.undertow.client.ClientResponse; import io.undertow.predicate.Predicate; import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.FormData; +import io.undertow.server.handlers.form.FormData.FormValue; +import io.undertow.server.handlers.form.FormDataParser; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; @@ -39,6 +48,7 @@ import io.undertow.util.Methods; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.TypeConverter; +import org.apache.camel.impl.DefaultAttachment; import org.apache.camel.impl.DefaultMessage; import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.util.ExchangeHelper; @@ -94,14 +104,33 @@ public class DefaultUndertowHttpBinding implements UndertowHttpBinding { populateCamelHeaders(httpExchange, result.getHeaders(), exchange); - //extract body if the method is allowed to have one - //body is extracted as byte[] then auto TypeConverter kicks in - if (Methods.POST.equals(httpExchange.getRequestMethod()) || Methods.PUT.equals(httpExchange.getRequestMethod())) { - result.setBody(readFromChannel(httpExchange.getRequestChannel())); + // Map form data which is parsed by undertow form parsers + FormData formData = httpExchange.getAttachment(FormDataParser.FORM_DATA); + if (formData != null) { + Map<String, Object> body = new HashMap<>(); + formData.forEach(key -> { + formData.get(key).forEach(value -> { + if (value.isFile()) { + DefaultAttachment attachment = new DefaultAttachment(new FilePartDataSource(value)); + result.addAttachmentObject(key, attachment); + body.put(key, attachment.getDataHandler()); + } else if (headerFilterStrategy != null + && !headerFilterStrategy.applyFilterToExternalHeaders(key, value.getValue(), exchange)) { + UndertowHelper.appendHeader(result.getHeaders(), key, value.getValue()); + UndertowHelper.appendHeader(body, key, value.getValue()); + } + }); + }); + result.setBody(body); } else { - result.setBody(null); + //extract body by myself if undertow parser didn't handle and the method is allowed to have one + //body is extracted as byte[] then auto TypeConverter kicks in + if (Methods.POST.equals(httpExchange.getRequestMethod()) || Methods.PUT.equals(httpExchange.getRequestMethod())) { + result.setBody(readFromChannel(httpExchange.getRequestChannel())); + } else { + result.setBody(null); + } } - return result; } @@ -390,4 +419,25 @@ public class DefaultUndertowHttpBinding implements UndertowHttpBinding { } } } + + class FilePartDataSource extends FileDataSource { + private String name; + private String contentType; + + FilePartDataSource(FormValue value) { + super(value.getPath().toFile()); + this.name = value.getFileName(); + this.contentType = value.getHeaders().getFirst(Headers.CONTENT_TYPE); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getContentType() { + return this.contentType; + } + } } http://git-wip-us.apache.org/repos/asf/camel/blob/015f8128/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java index d757070..37891f4 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowComponent.java @@ -287,7 +287,7 @@ public class UndertowComponent extends UriEndpointComponent implements RestConsu undertowRegistry.put(key, host); } host.validateEndpointURI(uri); - host.registerHandler(consumer.getHttpHandlerRegistrationInfo(), consumer); + host.registerHandler(consumer.getHttpHandlerRegistrationInfo(), consumer.getHttpHandler()); } public void unregisterConsumer(UndertowConsumer consumer) { http://git-wip-us.apache.org/repos/asf/camel/blob/015f8128/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java index 6a80892..846fe63 100644 --- a/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java +++ b/components/camel-undertow/src/main/java/org/apache/camel/component/undertow/UndertowConsumer.java @@ -21,6 +21,7 @@ import java.nio.ByteBuffer; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.EagerFormParsingHandler; import io.undertow.util.Headers; import io.undertow.util.HttpString; import io.undertow.util.Methods; @@ -75,6 +76,11 @@ public class UndertowConsumer extends DefaultConsumer implements HttpHandler { return registrationInfo; } + public HttpHandler getHttpHandler() { + // wrap with EagerFormParsingHandler to enable undertow form parsers + return new EagerFormParsingHandler().setNext(this); + } + @Override public void handleRequest(HttpServerExchange httpExchange) throws Exception { HttpString requestMethod = httpExchange.getRequestMethod(); http://git-wip-us.apache.org/repos/asf/camel/blob/015f8128/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/MultiPartFormTest.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/MultiPartFormTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/MultiPartFormTest.java new file mode 100644 index 0000000..d8a1512 --- /dev/null +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/MultiPartFormTest.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.undertow; + +import java.io.File; +import java.util.Map; + +import javax.activation.DataHandler; + +import org.apache.camel.Exchange; +import org.apache.camel.Message; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.RequestEntity; +import org.apache.commons.httpclient.methods.multipart.FilePart; +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; +import org.apache.commons.httpclient.methods.multipart.Part; +import org.apache.commons.httpclient.methods.multipart.StringPart; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.junit.Test; + +public class MultiPartFormTest extends BaseUndertowTest { + private RequestEntity createMultipartRequestEntity() throws Exception { + File file = new File("src/main/resources/META-INF/NOTICE.txt"); + + Part[] parts = {new StringPart("comment", "A binary file of some kind"), + new FilePart(file.getName(), file)}; + + return new MultipartRequestEntity(parts, new HttpMethodParams()); + + } + + @Test + public void testSendMultiPartForm() throws Exception { + HttpClient httpclient = new HttpClient(); + + PostMethod httppost = new PostMethod("http://localhost:" + getPort() + "/test"); + + httppost.setRequestEntity(createMultipartRequestEntity()); + + int status = httpclient.executeMethod(httppost); + + assertEquals("Get a wrong response status", 200, status); + String result = httppost.getResponseBodyAsString(); + + assertEquals("Get a wrong result", "A binary file of some kind", result); + + } + + @Test + public void testSendMultiPartFormFromCamelHttpComponnent() throws Exception { + String result = template.requestBody("http://localhost:" + getPort() + "/test", createMultipartRequestEntity(), String.class); + assertEquals("Get a wrong result", "A binary file of some kind", result); + } + + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("undertow://http://localhost:{{port}}/test").process(new Processor() { + + public void process(Exchange exchange) throws Exception { + Message in = exchange.getIn(); + assertEquals("Get a wrong attachement size", 1, in.getAttachments().size()); + // The file name is attachment id + DataHandler data = in.getAttachment("NOTICE.txt"); + + assertNotNull("Should get the DataHandler NOTICE.txt", data); + assertEquals("Got the wrong name", "NOTICE.txt", data.getName()); + + assertTrue("We should get the data from the DataHandler", data.getDataSource() + .getInputStream().available() > 0); + + // form data should also be available as a body + Map body = in.getBody(Map.class); + assertEquals("A binary file of some kind", body.get("comment")); + assertEquals(data, body.get("NOTICE.txt")); + exchange.getOut().setBody(in.getHeader("comment")); + } + + }); + // END SNIPPET: e1 + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/015f8128/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/PreservePostFormUrlEncodedBodyTest.java ---------------------------------------------------------------------- diff --git a/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/PreservePostFormUrlEncodedBodyTest.java b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/PreservePostFormUrlEncodedBodyTest.java new file mode 100644 index 0000000..9cd52e8 --- /dev/null +++ b/components/camel-undertow/src/test/java/org/apache/camel/component/undertow/PreservePostFormUrlEncodedBodyTest.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.undertow; + +import java.util.Map; + +import io.undertow.server.handlers.form.FormData; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.http.HttpMethods; +import org.junit.Test; + +public class PreservePostFormUrlEncodedBodyTest extends BaseUndertowTest { + + @Test + public void testSendToUndertow() throws Exception { + Exchange exchange = template.request("http://localhost:{{port}}/myapp/myservice?query1=a&query2=b", new Processor() { + + public void process(Exchange exchange) throws Exception { + exchange.getIn().setBody("b1=x&b2=y"); + exchange.getIn().setHeader("content-type", "application/x-www-form-urlencoded"); + exchange.getIn().setHeader(Exchange.HTTP_METHOD, HttpMethods.POST); + } + + }); + // convert the response to a String + String body = exchange.getOut().getBody(String.class); + assertEquals("Request message is OK", body); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + public void configure() throws Exception { + from("undertow:http://localhost:{{port}}/myapp/myservice?map").process(new Processor() { + public void process(Exchange exchange) throws Exception { + Map body = exchange.getIn().getBody(Map.class); + + // for unit testing make sure we got right message + assertNotNull(body); + assertEquals("x", body.get("b1")); + assertEquals("y", body.get("b2")); + assertEquals("a", exchange.getIn().getHeader("query1")); + assertEquals("b", exchange.getIn().getHeader("query2")); + assertEquals("x", exchange.getIn().getHeader("b1")); + assertEquals("y", exchange.getIn().getHeader("b2")); + + // send a response + exchange.getOut().setBody("Request message is OK"); + } + }); + } + }; + } + +}