You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/05/13 09:03:57 UTC

[camel] branch camel-3.x updated: CAMEL-19344: camel-jbang - Upload/delete files in --source-dir via http

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

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


The following commit(s) were added to refs/heads/camel-3.x by this push:
     new 416e8faaff1 CAMEL-19344: camel-jbang - Upload/delete files in --source-dir via http
416e8faaff1 is described below

commit 416e8faaff164b29873851171e8ddb30ab292d93
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat May 13 11:03:27 2023 +0200

    CAMEL-19344: camel-jbang - Upload/delete files in --source-dir via http
---
 .../modules/ROOT/pages/camel-jbang.adoc            | 59 +++++++++++++++++
 .../java/org/apache/camel/main/KameletMain.java    |  4 ++
 .../apache/camel/main/http/VertxHttpServer.java    | 74 ++++++++++++++++++++++
 3 files changed, 137 insertions(+)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index d7d9dc53fbd..331698b77ba 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -212,6 +212,65 @@ and reloaded. You can also delete files to remove routes.
 NOTE: You cannot use both files and source dir together.
 The following is not allowed: `camel run abc.java --source-dir=mycode`.
 
+==== Uploading files to source directory via HTTP
+
+When running Camel JBang with `--source-dir`, `--console` and `--dev` (reloading) then you can
+change the source files on-the-fly by copying,modifying or deleting the files in the source directory.
+
+This can also be done via HTTP using the `q/upload/:filename` HTTP endpoint using PUT and DELETE verbs.
+
+Given you run Camel JBang with:
+
+[source,bash]
+----
+camel run --source-dir=mycode --console --dev
+----
+
+Then you can upload or modify a source file named `bar.java` you can send a PUT request via curl:
+
+[source,bash]
+----
+curl -X PUT http://0.0.0.0:8080/q/upload/bar.java --data-binary "@bar.java"
+----
+
+Or via:
+
+[source,bash]
+----
+curl -T bar.java http://0.0.0.0:8080/q/upload/bar.java
+----
+
+To send the data via PUT then the file body can be included when using `Content-Type: application/x-www-form-urlencoded`:
+
+For example from a CURL `--ascii-trace`:
+
+[source,text]
+----
+0000: PUT /q/upload/bar.java HTTP/1.1
+0021: Host: 0.0.0.0:8080
+0035: User-Agent: curl/7.87.0
+004e: Accept: */*
+005b: Content-Length: 385
+0070: Content-Type: application/x-www-form-urlencoded
+00a1:
+=> Send data, 385 bytes (0x181)
+0000: // camel-k: language=java..import org.apache.camel.builder.Route
+0040: Builder;..public class bar extends RouteBuilder {..    @Override
+0080: .    public void configure() throws Exception {..        // Writ
+00c0: e your routes here, for example:.        from("timer:java?period
+0100: ={{time:1000}}").            .setBody().                .simple(
+0140: "XXXCamel from ${routeId}").            .log("${body}");.    }.}
+0180: .
+== Info: Mark bundle as not supporting multiuse
+<= Recv header, 17 bytes (0x11)
+0000: HTTP/1.1 200 OK
+<= Recv header, 19 bytes (0x13)
+0000: content-length: 0
+<= Recv header, 2 bytes (0x2)
+0000:
+== Info: Connection #0 to host 0.0.0.0 left intact
+----
+
 === Developer Console
 
 You can enable the developer console, which presents a variety of information to the developer.
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index c73ef22bca9..4af57aab194 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -455,6 +455,10 @@ public class KameletMain extends MainCommandLineSupport {
             // reloader
             String sourceDir = getInitialProperties().getProperty("camel.jbang.sourceDir");
             if (sourceDir != null) {
+                if (console || health) {
+                    // allow to upload source via http when HTTP console enabled
+                    VertxHttpServer.registerUploadSourceDir(answer, sourceDir);
+                }
                 RouteOnDemandReloadStrategy reloader = new RouteOnDemandReloadStrategy(sourceDir, true);
                 reloader.setPattern("*");
                 answer.addService(reloader);
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/http/VertxHttpServer.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/http/VertxHttpServer.java
index ad8be611078..00d51275bad 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/http/VertxHttpServer.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/http/VertxHttpServer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.main.http;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -32,8 +34,10 @@ import java.util.stream.Collectors;
 
 import io.vertx.core.Handler;
 import io.vertx.core.http.HttpMethod;
+import io.vertx.ext.web.RequestBody;
 import io.vertx.ext.web.Route;
 import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.BodyHandler;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.RuntimeCamelException;
@@ -51,6 +55,8 @@ import org.apache.camel.health.HealthCheckRegistry;
 import org.apache.camel.main.util.CamelJBangSettingsHelper;
 import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.support.SimpleEventNotifierSupport;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.json.JsonObject;
@@ -70,6 +76,7 @@ public final class VertxHttpServer {
     private static final AtomicBoolean REGISTERED = new AtomicBoolean();
     private static final AtomicBoolean CONSOLE = new AtomicBoolean();
     private static final AtomicBoolean HEALTH_CHECK = new AtomicBoolean();
+    private static final AtomicBoolean UPLOAD = new AtomicBoolean();
 
     private VertxHttpServer() {
     }
@@ -460,4 +467,71 @@ public final class VertxHttpServer {
         return trace;
     }
 
+    public static void registerUploadSourceDir(CamelContext camelContext, String dir) {
+        if (UPLOAD.compareAndSet(false, true)) {
+            doRegisterUploadSourceDir(camelContext, dir);
+        }
+    }
+
+    private static void doRegisterUploadSourceDir(CamelContext context, final String dir) {
+        final Route upload = router.route("/q/upload/:filename")
+                .method(HttpMethod.PUT)
+                // need body handler to handle file uploads
+                .handler(BodyHandler.create(true));
+
+        final Route uploadDelete = router.route("/q/upload/:filename");
+        uploadDelete.method(HttpMethod.DELETE);
+
+        Handler<RoutingContext> handler = new Handler<RoutingContext>() {
+            @Override
+            public void handle(RoutingContext ctx) {
+                String name = ctx.pathParam("filename");
+                if (name == null) {
+                    ctx.response().setStatusCode(400);
+                    ctx.end();
+                    return;
+                }
+
+                int status = 200;
+                boolean delete = HttpMethod.DELETE == ctx.request().method();
+                if (delete) {
+                    LOG.info("Deleting file: {}/{}", dir, name);
+                    File f = new File(dir, name);
+                    if (f.exists() && f.isFile()) {
+                        FileUtil.deleteFile(f);
+                    }
+                } else {
+                    File f = new File(dir, name);
+                    boolean exists = f.isFile() && f.exists();
+                    LOG.info("{} file: {}/{}", exists ? "Updating" : "Creating", dir, name);
+
+                    File tmp = new File(dir, name + ".tmp");
+                    FileOutputStream fos = null;
+                    try {
+                        fos = new FileOutputStream(tmp, false);
+                        RequestBody rb = ctx.body();
+                        IOHelper.writeText(rb.asString(), fos);
+
+                        FileUtil.renameFileUsingCopy(tmp, f);
+                        FileUtil.deleteFile(tmp);
+                    } catch (Exception e) {
+                        // some kind of error
+                        LOG.warn("Error saving file: {}/{} due to: {}", dir, name, e.getMessage(), e);
+                        status = 500;
+                    } finally {
+                        IOHelper.close(fos);
+                        FileUtil.deleteFile(tmp);
+                    }
+                }
+
+                ctx.response().setStatusCode(status);
+                ctx.end();
+            }
+        };
+        upload.handler(handler);
+        uploadDelete.handler(handler);
+
+        phc.addHttpEndpoint("/q/upload", "PUT,DELETE", null);
+    }
+
 }