This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new ab2106eb672 CAMEL-21103: camel-platform-http-main - Add download 
functionality (#15255)
ab2106eb672 is described below

commit ab2106eb672ee415c3bfd85ca4084d4c42ba2b4e
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Aug 21 15:56:26 2024 +0200

    CAMEL-21103: camel-platform-http-main - Add download functionality (#15255)
---
 .../main/camel-main-configuration-metadata.json    |   1 +
 .../http/main/DefaultMainHttpServerFactory.java    |   1 +
 .../platform/http/main/MainHttpServer.java         | 130 +++++++++++++++++++++
 .../camel/spi/PackageScanResourceResolver.java     |   2 +-
 ...ttpServerConfigurationPropertiesConfigurer.java |   6 +
 .../camel-main-configuration-metadata.json         |   1 +
 core/camel-main/src/main/docs/main.adoc            |   3 +-
 .../main/HttpServerConfigurationProperties.java    |  24 ++++
 8 files changed, 166 insertions(+), 2 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 6a6a55e1292..51b045e5ed9 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -261,6 +261,7 @@
     { "name": "camel.server.authenticationPath", "description": "Set HTTP url 
path of embedded server that is protected by authentication configuration.", 
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", 
"type": "string", "javaType": "java.lang.String" },
     { "name": "camel.server.basicPropertiesFile", "description": "Name of the 
file that contains basic authentication info for Vert.x file auth provider.", 
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", 
"type": "string", "javaType": "java.lang.String" },
     { "name": "camel.server.devConsoleEnabled", "description": "Whether to 
enable developer console (not intended for production use). Dev console must 
also be enabled on CamelContext. For example by setting 
camel.context.dev-console=true in application.properties, or via code 
camelContext.setDevConsole(true); If enabled then you can access a basic 
developer console on context-path: \/q\/dev.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"j [...]
+    { "name": "camel.server.downloadEnabled", "description": "Whether to 
enable file download via HTTP. This makes it possible to browse and download 
resource source files such as Camel XML or YAML routes. Only enable this for 
development, troubleshooting or special situations for management and 
monitoring.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.server.enabled", "description": "Whether embedded HTTP 
server is enabled. By default, the server is not enabled.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.server.healthCheckEnabled", "description": "Whether to 
enable health-check console. If enabled then you can access health-check status 
on context-path: \/q\/health", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.server.host", "description": "Hostname to use for binding 
embedded HTTP server", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "defaultValue": "0.0.0.0" },
diff --git 
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/DefaultMainHttpServerFactory.java
 
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/DefaultMainHttpServerFactory.java
index 61acbaf4613..d0de015667a 100644
--- 
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/DefaultMainHttpServerFactory.java
+++ 
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/DefaultMainHttpServerFactory.java
@@ -61,6 +61,7 @@ public class DefaultMainHttpServerFactory implements 
CamelContextAware, MainHttp
         server.setMetricsEnabled(configuration.isMetricsEnabled());
         server.setUploadEnabled(configuration.isUploadEnabled());
         server.setUploadSourceDir(configuration.getUploadSourceDir());
+        server.setDownloadEnabled(configuration.isDownloadEnabled());
 
         if (configuration.isAuthenticationEnabled()) {
             configureAuthentication(server, configuration);
diff --git 
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
 
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
index fb7d8c90bd2..1f20f3fb44d 100644
--- 
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
+++ 
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/MainHttpServer.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.platform.http.main;
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.lang.management.ManagementFactory;
@@ -32,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.StringJoiner;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
@@ -65,8 +67,13 @@ import org.apache.camel.health.HealthCheck;
 import org.apache.camel.health.HealthCheckHelper;
 import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.spi.CamelEvent;
+import org.apache.camel.spi.PackageScanResourceResolver;
 import org.apache.camel.spi.ReloadStrategy;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.ResourceLoader;
 import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.LoggerHelper;
+import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.ResolverHelper;
 import org.apache.camel.support.SimpleEventNotifierSupport;
 import org.apache.camel.support.jsse.SSLContextParameters;
@@ -103,6 +110,7 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
     private boolean metricsEnabled;
     private boolean uploadEnabled;
     private String uploadSourceDir;
+    private boolean downloadEnabled;
 
     @Override
     public CamelContext getCamelContext() {
@@ -203,6 +211,18 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
         this.uploadSourceDir = uploadSourceDir;
     }
 
+    @ManagedAttribute(description = "Whether file download is enabled 
(q/download)")
+    public boolean isDownloadEnabled() {
+        return downloadEnabled;
+    }
+
+    /**
+     * Whether file download is enabled (q/download)
+     */
+    public void setDownloadEnabled(boolean downloadEnabled) {
+        this.downloadEnabled = downloadEnabled;
+    }
+
     @ManagedAttribute(description = "HTTP server port number")
     public int getPort() {
         return configuration.getBindPort();
@@ -333,6 +353,9 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
             }
             setupUploadConsole(uploadSourceDir);
         }
+        if (downloadEnabled) {
+            setupDownloadConsole();
+        }
         // metrics will be setup in camel-micrometer-prometheus
     }
 
@@ -973,4 +996,111 @@ public class MainHttpServer extends ServiceSupport 
implements CamelContextAware,
                 "multipart/form-data", null, null);
     }
 
+    protected void setupDownloadConsole() {
+        final Route download = router.route("/q/download/*")
+                .produces("text/plain")
+                .produces("application/octet-stream")
+                .method(HttpMethod.GET);
+
+        final AntPathMatcher matcher = AntPathMatcher.INSTANCE;
+        Handler<RoutingContext> handler = new Handler<RoutingContext>() {
+            @Override
+            public void handle(RoutingContext ctx) {
+                String name = StringHelper.after(ctx.normalizedPath(), 
"/q/download/");
+                boolean cp = "true".equals(ctx.queryParams().get("classpath"));
+                if (name == null || name.isBlank() || matcher.isPattern(name)) 
{
+                    Set<String> names = new TreeSet<>();
+                    if (cp) {
+                        // also look inside classpath
+                        PackageScanResourceResolver resolver = 
PluginHelper.getPackageScanResourceResolver(camelContext);
+                        
resolver.addClassLoader(camelContext.getApplicationContextClassLoader());
+                        try {
+                            String pattern = "**/*";
+                            if (name != null && !name.isBlank()) {
+                                pattern = "**/" + name;
+                            }
+                            for (Resource res : 
resolver.findResources(pattern)) {
+                                String loc = res.getLocation();
+                                loc = LoggerHelper.sourceNameOnly(loc);
+                                names.add(loc);
+                            }
+                        } catch (Exception e) {
+                            // ignore
+                        }
+                    }
+                    // always include routes
+                    for (org.apache.camel.Route route : 
camelContext.getRoutes()) {
+                        String loc = route.getSourceLocation();
+                        if (loc != null) {
+                            loc = LoggerHelper.sourceNameOnly(loc);
+                            if (name == null || name.isBlank() || 
matcher.match(name, loc)) {
+                                names.add(loc);
+                            }
+                        }
+                    }
+
+                    String acp = ctx.request().getHeader("Accept");
+                    boolean html = acp != null && acp.contains("html");
+                    StringJoiner sj;
+                    if (html) {
+                        String prefix = 
StringHelper.after(ctx.normalizedPath(), "/q/download/");
+                        if (prefix == null) {
+                            prefix = "/q/download/";
+                        } else {
+                            prefix = "";
+                        }
+                        ctx.response().putHeader("Content-Type", "text/html");
+                        sj = new StringJoiner("<br/>");
+                        for (String n : names) {
+                            sj.add("<a href=" + prefix + n + ">" + n + "</a>");
+                        }
+                    } else {
+                        ctx.response().putHeader("Content-Type", "text/plain");
+                        sj = new StringJoiner("\n");
+                        names.forEach(sj::add);
+                    }
+                    ctx.response().setStatusCode(200);
+                    ctx.end(sj.toString());
+                } else {
+                    // load file as resource
+                    ResourceLoader loader = 
PluginHelper.getResourceLoader(camelContext);
+                    Resource res = loader.resolveResource("classpath:" + name);
+                    if (res == null || !res.exists()) {
+                        for (org.apache.camel.Route route : 
camelContext.getRoutes()) {
+                            String loc = route.getSourceLocation();
+                            if (loc != null) {
+                                loc = LoggerHelper.sourceNameOnly(loc);
+                                if (matcher.match(name, loc)) {
+                                    res = route.getSourceResource();
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    if (res != null && res.exists()) {
+                        ctx.response().putHeader("Content-Type", 
"application/octet-stream");
+                        ctx.response().putHeader("Content-Disposition",
+                                "attachment; filename=\"" + 
FileUtil.stripPath(name) + "\"");
+                        ctx.response().setStatusCode(200);
+                        String data = null;
+                        try {
+                            data = IOHelper.loadText(res.getInputStream());
+                        } catch (IOException e) {
+                            // ignore
+                        }
+                        ctx.end(data);
+                    } else {
+                        ctx.response().setStatusCode(204);
+                        ctx.end();
+                    }
+                }
+            }
+        };
+        // use blocking handler as the task can take longer time to complete
+        download.handler(new BlockingHandlerDecorator(handler, true));
+
+        platformHttpComponent.addHttpEndpoint("/q/download", "GET",
+                null, "text/plain,application/octet-stream", null);
+    }
+
 }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java
index e158791c3d0..44e9a53bdb7 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/PackageScanResourceResolver.java
@@ -59,7 +59,7 @@ public interface PackageScanResourceResolver extends 
StaticService {
      * The location can be prefixed with either file: or classpath: to look in 
either file system or classpath. By
      * default classpath is assumed if no prefix is specified.
      *
-     * Wildcards is supported using a ANT pattern style paths, such as 
classpath:&#42;&#42;/&#42;camel&#42;.xml
+     * Wildcards is supported using an ANT pattern style paths, such as 
classpath:&#42;&#42;/&#42;camel&#42;.xml
      *
      * Notice when using wildcards, then there is additional overhead as the 
classpath is scanned, where as if you
      * specific the exact name for each XML file is faster as no classpath 
scanning is needed.
diff --git 
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
 
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
index f258ced1bdc..d90376adc33 100644
--- 
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
+++ 
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
@@ -31,6 +31,8 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         case "basicPropertiesFile": 
target.setBasicPropertiesFile(property(camelContext, java.lang.String.class, 
value)); return true;
         case "devconsoleenabled":
         case "devConsoleEnabled": 
target.setDevConsoleEnabled(property(camelContext, boolean.class, value)); 
return true;
+        case "downloadenabled":
+        case "downloadEnabled": 
target.setDownloadEnabled(property(camelContext, boolean.class, value)); return 
true;
         case "enabled": target.setEnabled(property(camelContext, 
boolean.class, value)); return true;
         case "healthcheckenabled":
         case "healthCheckEnabled": 
target.setHealthCheckEnabled(property(camelContext, boolean.class, value)); 
return true;
@@ -72,6 +74,8 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         case "basicPropertiesFile": return java.lang.String.class;
         case "devconsoleenabled":
         case "devConsoleEnabled": return boolean.class;
+        case "downloadenabled":
+        case "downloadEnabled": return boolean.class;
         case "enabled": return boolean.class;
         case "healthcheckenabled":
         case "healthCheckEnabled": return boolean.class;
@@ -114,6 +118,8 @@ public class HttpServerConfigurationPropertiesConfigurer 
extends org.apache.came
         case "basicPropertiesFile": return target.getBasicPropertiesFile();
         case "devconsoleenabled":
         case "devConsoleEnabled": return target.isDevConsoleEnabled();
+        case "downloadenabled":
+        case "downloadEnabled": return target.isDownloadEnabled();
         case "enabled": return target.isEnabled();
         case "healthcheckenabled":
         case "healthCheckEnabled": return target.isHealthCheckEnabled();
diff --git 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 6a6a55e1292..51b045e5ed9 100644
--- 
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ 
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -261,6 +261,7 @@
     { "name": "camel.server.authenticationPath", "description": "Set HTTP url 
path of embedded server that is protected by authentication configuration.", 
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", 
"type": "string", "javaType": "java.lang.String" },
     { "name": "camel.server.basicPropertiesFile", "description": "Name of the 
file that contains basic authentication info for Vert.x file auth provider.", 
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", 
"type": "string", "javaType": "java.lang.String" },
     { "name": "camel.server.devConsoleEnabled", "description": "Whether to 
enable developer console (not intended for production use). Dev console must 
also be enabled on CamelContext. For example by setting 
camel.context.dev-console=true in application.properties, or via code 
camelContext.setDevConsole(true); If enabled then you can access a basic 
developer console on context-path: \/q\/dev.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"j [...]
+    { "name": "camel.server.downloadEnabled", "description": "Whether to 
enable file download via HTTP. This makes it possible to browse and download 
resource source files such as Camel XML or YAML routes. Only enable this for 
development, troubleshooting or special situations for management and 
monitoring.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.server.enabled", "description": "Whether embedded HTTP 
server is enabled. By default, the server is not enabled.", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.server.healthCheckEnabled", "description": "Whether to 
enable health-check console. If enabled then you can access health-check status 
on context-path: \/q\/health", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean", 
"javaType": "boolean", "defaultValue": "false" },
     { "name": "camel.server.host", "description": "Hostname to use for binding 
embedded HTTP server", "sourceType": 
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", 
"javaType": "java.lang.String", "defaultValue": "0.0.0.0" },
diff --git a/core/camel-main/src/main/docs/main.adoc 
b/core/camel-main/src/main/docs/main.adoc
index f4165a700b7..64529ec5376 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -170,7 +170,7 @@ The camel.routecontroller supports 12 options, which are 
listed below.
 
 
 === Camel Embedded HTTP Server (only for standalone; not Spring Boot or 
Quarkus) configurations
-The camel.server supports 19 options, which are listed below.
+The camel.server supports 20 options, which are listed below.
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
@@ -179,6 +179,7 @@ The camel.server supports 19 options, which are listed 
below.
 | *camel.server.authentication{zwsp}Path* | Set HTTP url path of embedded 
server that is protected by authentication configuration. |  | String
 | *camel.server.basicProperties{zwsp}File* | Name of the file that contains 
basic authentication info for Vert.x file auth provider. |  | String
 | *camel.server.devConsoleEnabled* | Whether to enable developer console (not 
intended for production use). Dev console must also be enabled on CamelContext. 
For example by setting camel.context.dev-console=true in 
application.properties, or via code camelContext.setDevConsole(true); If 
enabled then you can access a basic developer console on context-path: /q/dev. 
| false | boolean
+| *camel.server.downloadEnabled* | Whether to enable file download via HTTP. 
This makes it possible to browse and download resource source files such as 
Camel XML or YAML routes. Only enable this for development, troubleshooting or 
special situations for management and monitoring. | false | boolean
 | *camel.server.enabled* | Whether embedded HTTP server is enabled. By 
default, the server is not enabled. | false | boolean
 | *camel.server.healthCheck{zwsp}Enabled* | Whether to enable health-check 
console. If enabled then you can access health-check status on context-path: 
/q/health | false | boolean
 | *camel.server.host* | Hostname to use for binding embedded HTTP server | 
0.0.0.0 | String
diff --git 
a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
 
b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
index 91ed89ac025..c5dd5ec54f9 100644
--- 
a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
+++ 
b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
@@ -46,6 +46,7 @@ public class HttpServerConfigurationProperties implements 
BootstrapCloseable {
     private boolean metricsEnabled;
     private boolean uploadEnabled;
     private String uploadSourceDir;
+    private boolean downloadEnabled;
 
     @Metadata(label = "security")
     private boolean authenticationEnabled;
@@ -223,6 +224,19 @@ public class HttpServerConfigurationProperties implements 
BootstrapCloseable {
         this.uploadSourceDir = uploadSourceDir;
     }
 
+    public boolean isDownloadEnabled() {
+        return downloadEnabled;
+    }
+
+    /**
+     * Whether to enable file download via HTTP. This makes it possible to 
browse and download resource source files
+     * such as Camel XML or YAML routes. Only enable this for development, 
troubleshooting or special situations for
+     * management and monitoring.
+     */
+    public void setDownloadEnabled(boolean downloadEnabled) {
+        this.downloadEnabled = downloadEnabled;
+    }
+
     public boolean isAuthenticationEnabled() {
         return authenticationEnabled;
     }
@@ -401,6 +415,16 @@ public class HttpServerConfigurationProperties implements 
BootstrapCloseable {
         return this;
     }
 
+    /**
+     * Whether to enable file download via HTTP. This makes it possible to 
browse and download resource source files
+     * such as Camel XML or YAML routes. Only enable this for development, 
troubleshooting or special situations for
+     * management and monitoring.
+     */
+    public HttpServerConfigurationProperties withDownloadEnabled(boolean 
downloadEnabled) {
+        this.downloadEnabled = downloadEnabled;
+        return this;
+    }
+
     /**
      * Whether to enable HTTP authentication for embedded server (for 
standalone applications; not Spring Boot or
      * Quarkus).

Reply via email to