You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2024/03/05 19:32:37 UTC

(camel-quarkus) 01/07: Add multipart configuration options to servlet extension

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 <ja...@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));
+    }
 }