This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch 3.8.x in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 3228a32d49a1e4550601ea24a30229548583f5b1 Author: James Netherton <jamesnether...@gmail.com> AuthorDate: Fri Mar 1 13:30:51 2024 +0000 Add multipart configuration options to servlet extension Fixes #5326 --- .../ROOT/pages/reference/extensions/servlet.adoc | 48 ++++++++++++++++++++++ .../servlet/deployment/ServletProcessor.java | 11 +++++ .../servlet/runtime/CamelServletConfig.java | 36 ++++++++++++++++ integration-tests/servlet/pom.xml | 17 ++++++++ .../quarkus/component/servlet/CamelRoute.java | 17 ++++---- .../component/servlet/MultiPartProcessor.java | 37 +++++++++++++++++ .../src/main/resources/application.properties | 6 +++ .../component/servlet/CamelServletTest.java | 41 ++++++++++++++++-- 8 files changed, 202 insertions(+), 11 deletions(-) diff --git a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc index 39c76cf246..fd56d2364a 100644 --- a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc +++ b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc @@ -83,6 +83,30 @@ A servletName as it would be defined in a `web.xml` file or in the `jakarta.serv | `string` | `CamelServlet` +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.multipart.location]]`link:#quarkus.camel.servlet.multipart.location[quarkus.camel.servlet.multipart.location]` + +An absolute path to a directory on the file system to store files temporarily while the parts are processed or when the size of the file exceeds the specified file-size-threshold configuration value. +| `string` +| `${java.io.tmpdir}` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.multipart.max-file-size]]`link:#quarkus.camel.servlet.multipart.max-file-size[quarkus.camel.servlet.multipart.max-file-size]` + +The maximum size allowed in bytes for uploaded files. The default size (-1) allows an unlimited size. +| `long` +| `-1` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.multipart.max-request-size]]`link:#quarkus.camel.servlet.multipart.max-request-size[quarkus.camel.servlet.multipart.max-request-size]` + +The maximum size allowed in bytes for a multipart/form-data request. The default size (-1) allows an unlimited size. +| `long` +| `-1` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.multipart.file-size-threshold]]`link:#quarkus.camel.servlet.multipart.file-size-threshold[quarkus.camel.servlet.multipart.file-size-threshold]` + +The file size in bytes after which the file will be temporarily stored on disk. +| `int` +| `0` + |icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.url-patterns]]`link:#quarkus.camel.servlet.-named-servlets-.url-patterns[quarkus.camel.servlet."named-servlets".url-patterns]` A comma separated list of path patterns under which the CamelServlet should be accessible. Example path patterns: `/++*++`, `/services/++*++` @@ -100,6 +124,30 @@ A fully qualified name of a servlet class to serve paths that match `url-pattern A servletName as it would be defined in a `web.xml` file or in the `jakarta.servlet.annotation.WebServlet++#++name()` annotation. | `string` | `CamelServlet` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.multipart.location]]`link:#quarkus.camel.servlet.-named-servlets-.multipart.location[quarkus.camel.servlet."named-servlets".multipart.location]` + +An absolute path to a directory on the file system to store files temporarily while the parts are processed or when the size of the file exceeds the specified file-size-threshold configuration value. +| `string` +| `${java.io.tmpdir}` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.multipart.max-file-size]]`link:#quarkus.camel.servlet.-named-servlets-.multipart.max-file-size[quarkus.camel.servlet."named-servlets".multipart.max-file-size]` + +The maximum size allowed in bytes for uploaded files. The default size (-1) allows an unlimited size. +| `long` +| `-1` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.multipart.max-request-size]]`link:#quarkus.camel.servlet.-named-servlets-.multipart.max-request-size[quarkus.camel.servlet."named-servlets".multipart.max-request-size]` + +The maximum size allowed in bytes for a multipart/form-data request. The default size (-1) allows an unlimited size. +| `long` +| `-1` + +|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.multipart.file-size-threshold]]`link:#quarkus.camel.servlet.-named-servlets-.multipart.file-size-threshold[quarkus.camel.servlet."named-servlets".multipart.file-size-threshold]` + +The file size in bytes after which the file will be temporarily stored on disk. +| `int` +| `0` |=== [.configuration-legend] diff --git a/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java b/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java index c6d159f413..308f5489db 100644 --- a/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java +++ b/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java @@ -26,8 +26,10 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.undertow.deployment.ServletBuildItem; import io.quarkus.undertow.deployment.ServletBuildItem.Builder; +import jakarta.servlet.MultipartConfigElement; import org.apache.camel.quarkus.servlet.runtime.CamelServletConfig; import org.apache.camel.quarkus.servlet.runtime.CamelServletConfig.ServletConfig; +import org.apache.camel.quarkus.servlet.runtime.CamelServletConfig.ServletConfig.MultipartConfig; class ServletProcessor { private static final String FEATURE = "camel-servlet"; @@ -81,6 +83,15 @@ class ServletProcessor { builder.addMapping(pattern); } + MultipartConfig multipartConfig = servletConfig.multipart; + if (multipartConfig != null) { + builder.setMultipartConfig(new MultipartConfigElement( + multipartConfig.location, + multipartConfig.maxFileSize, + multipartConfig.maxRequestSize, + multipartConfig.fileSizeThreshold)); + } + return builder.build(); } } diff --git a/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java b/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java index 311b95e47f..62ac5c7131 100644 --- a/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java +++ b/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java @@ -64,6 +64,11 @@ public final class CamelServletConfig { @ConfigItem(defaultValue = DEFAULT_SERVLET_NAME) public String servletName; + /** + * Servlet multipart request configuration. + */ + public MultipartConfig multipart; + /** * @return {@code true} if this {@link ServletConfig} is valid as a whole. This currently translates to * {@link #urlPatterns} being non-empty because {@link #servletClass} and {@link #servletName} have @@ -87,6 +92,37 @@ public final class CamelServletConfig { return DEFAULT_SERVLET_NAME.equals(servletName) ? key : servletName; } + /** + * Servlet multipart request configuration. + */ + @ConfigGroup + public static class MultipartConfig { + /** + * An absolute path to a directory on the file system to store files temporarily while the parts are + * processed or when the size of the file exceeds the specified file-size-threshold configuration value. + */ + @ConfigItem(defaultValue = "${java.io.tmpdir}") + public String location; + + /** + * The maximum size allowed in bytes for uploaded files. The default size (-1) allows an unlimited size. + */ + @ConfigItem(defaultValue = "-1") + public long maxFileSize; + + /** + * The maximum size allowed in bytes for a multipart/form-data request. The default size (-1) allows an unlimited + * size. + */ + @ConfigItem(defaultValue = "-1") + public long maxRequestSize; + + /** + * The file size in bytes after which the file will be temporarily stored on disk. + */ + @ConfigItem(defaultValue = "0") + public int fileSizeThreshold; + } } } diff --git a/integration-tests/servlet/pom.xml b/integration-tests/servlet/pom.xml index c488681fc9..d5337b30f8 100644 --- a/integration-tests/servlet/pom.xml +++ b/integration-tests/servlet/pom.xml @@ -31,6 +31,10 @@ <description>Integration tests for Camel Servlet component</description> <dependencies> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-attachments</artifactId> + </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-core-cloud</artifactId> @@ -99,6 +103,19 @@ </activation> <dependencies> <!-- The following dependencies guarantee that this module is built after them. You can update them by running `mvn process-resources -Pformat -N` from the source tree root directory --> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-attachments-deployment</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>*</groupId> + <artifactId>*</artifactId> + </exclusion> + </exclusions> + </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-core-cloud-deployment</artifactId> diff --git a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java index 7e984433ad..aabc992357 100644 --- a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java +++ b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java @@ -17,12 +17,13 @@ package org.apache.camel.quarkus.component.servlet; import jakarta.enterprise.context.ApplicationScoped; -import org.apache.camel.Exchange; -import org.apache.camel.Processor; +import jakarta.inject.Inject; import org.apache.camel.builder.RouteBuilder; @ApplicationScoped public class CamelRoute extends RouteBuilder { + @Inject + MultiPartProcessor multiPartProcessor; @Override public void configure() { @@ -49,13 +50,13 @@ public class CamelRoute extends RouteBuilder { .setBody(constant("GET: /favorite")); from("direct:echoMethodPath") - .process(new Processor() { - @Override - public void process(Exchange exchange) throws Exception { - exchange.toString(); - } - }) .setBody().simple("${header.CamelHttpMethod}: ${header.CamelServletContextPath}"); + + from("servlet://multipart/default?attachmentMultipartBinding=true") + .process(multiPartProcessor); + + from("servlet://multipart?servletName=my-named-servlet&attachmentMultipartBinding=true") + .process(multiPartProcessor); } } diff --git a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/MultiPartProcessor.java b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/MultiPartProcessor.java new file mode 100644 index 0000000000..7d0cff8801 --- /dev/null +++ b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/MultiPartProcessor.java @@ -0,0 +1,37 @@ +/* + * 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.quarkus.component.servlet; + +import jakarta.activation.DataHandler; +import jakarta.inject.Singleton; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.attachment.AttachmentMessage; + +@Singleton +public class MultiPartProcessor implements Processor { + @Override + public void process(Exchange exchange) throws Exception { + AttachmentMessage message = exchange.getMessage(AttachmentMessage.class); + DataHandler file = message.getAttachment("file"); + if (file == null) { + throw new IllegalStateException("Attachment named 'file' is not present"); + } + + message.setBody(file.getInputStream()); + } +} diff --git a/integration-tests/servlet/src/main/resources/application.properties b/integration-tests/servlet/src/main/resources/application.properties index 7f11f67737..0251262894 100644 --- a/integration-tests/servlet/src/main/resources/application.properties +++ b/integration-tests/servlet/src/main/resources/application.properties @@ -19,7 +19,13 @@ # Quarkus :: Camel :: Servlet # quarkus.camel.servlet.url-patterns=/folder-1/*,/folder-2/* + quarkus.camel.servlet.my-named-servlet.url-patterns=/my-named-folder/* quarkus.camel.servlet.my-named-servlet.servlet-class=org.apache.camel.quarkus.component.servlet.CustomServlet +quarkus.camel.servlet.my-named-servlet.multipart.location=${java.io.tmpdir}/my-named-servlet +quarkus.camel.servlet.my-named-servlet.multipart.max-file-size=11 +quarkus.camel.servlet.my-named-servlet.multipart.max-request-size=11 +quarkus.camel.servlet.my-named-servlet.multipart.file-size-threshold=5 + quarkus.camel.servlet.ignored-key.servlet-name=my-favorite-servlet quarkus.camel.servlet.ignored-key.url-patterns=/my-favorite-folder/* diff --git a/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java b/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java index 24e7672309..593e56cb9b 100644 --- a/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java +++ b/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java @@ -16,16 +16,21 @@ */ package org.apache.camel.quarkus.component.servlet; +import java.nio.charset.StandardCharsets; + import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import org.hamcrest.core.IsEqual; import org.junit.jupiter.api.Test; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; + @QuarkusTest public class CamelServletTest { @Test - public void multiplePaths() throws Throwable { + public void multiplePaths() { RestAssured.when().get("/folder-1/rest-get").then().body(IsEqual.equalTo("GET: /rest-get")); RestAssured.when().get("/folder-2/rest-get").then().body(IsEqual.equalTo("GET: /rest-get")); RestAssured.when().post("/folder-1/rest-post").then().body(IsEqual.equalTo("POST: /rest-post")); @@ -35,15 +40,45 @@ public class CamelServletTest { } @Test - public void namedWithservletClass() throws Throwable { + public void namedWithservletClass() { RestAssured.when().get("/my-named-folder/custom").then() .body(IsEqual.equalTo("GET: /custom")) .and().header("x-servlet-class-name", CustomServlet.class.getName()); } @Test - public void ignoredKey() throws Throwable { + public void ignoredKey() { RestAssured.when().get("/my-favorite-folder/favorite").then() .body(IsEqual.equalTo("GET: /favorite")); } + + @Test + public void multipartDefaultConfig() { + String body = "Hello World"; + RestAssured.given() + .multiPart("file", "file", body.getBytes(StandardCharsets.UTF_8)) + .post("/folder-1/multipart/default") + .then() + .statusCode(200) + .body(is(body)); + } + + @Test + public void multipartCustomConfig() { + String body = "Hello World"; + RestAssured.given() + .multiPart("file", "file", body.getBytes(StandardCharsets.UTF_8)) + .post("/folder-1/multipart/default") + .then() + .statusCode(200) + .body(is(body)); + + // Request body exceeding the limits defined on the multipart config + RestAssured.given() + .multiPart("test-multipart", "file", body.repeat(10).getBytes(StandardCharsets.UTF_8)) + .post("/my-named-folder/multipart") + .then() + // TODO: Expect 413 only - https://github.com/apache/camel-quarkus/issues/5830 + .statusCode(oneOf(413, 500)); + } }