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 da678be61e4b CAMEL-24274: camel-tui - Send Message uses HTTP for 
platform-http routes (#25162)
da678be61e4b is described below

commit da678be61e4b564812aa5f04e52d9a5f2b8f5656
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 27 19:07:13 2026 +0200

    CAMEL-24274: camel-tui - Send Message uses HTTP for platform-http routes 
(#25162)
    
    Extract shared HttpHelper utility for HTTP request/response handling used by
    both SendMessagePopup and HttpTab. Detect platform-http consumer-only routes
    in Send Message and send actual HTTP requests via Java HttpClient instead of
    going through the dev console ProducerTemplate (which fails because
    platform-http is consumer-only). Add tests for platform-http URI parsing.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  |   2 +-
 .../dsl/jbang/core/commands/tui/HttpHelper.java    | 121 +++++++++++++++++++++
 .../camel/dsl/jbang/core/commands/tui/HttpTab.java |  97 ++++-------------
 .../jbang/core/commands/tui/SendMessagePopup.java  |  66 ++++++++++-
 .../jbang/core/commands/tui/HttpHelperTest.java    |  90 +++++++++++++++
 5 files changed, 296 insertions(+), 80 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index c5aff66eb649..0319f66cafed 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -1180,7 +1180,7 @@ class ActionsPopup {
             setNotification("No routes available", true);
             return;
         }
-        sendMessagePopup.open(ctx, pid, info.name, info.routes, 
preSelectedRouteId, info.directory);
+        sendMessagePopup.open(ctx, pid, info.name, info.routes, 
preSelectedRouteId, info.directory, info.httpServer);
         preSelectedRouteId = null;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpHelper.java
new file mode 100644
index 000000000000..dc036f3cde12
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpHelper.java
@@ -0,0 +1,121 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+final class HttpHelper {
+
+    private HttpHelper() {
+    }
+
+    record HttpResult(int statusCode, long elapsed, List<String> headerLines, 
String body, String error) {
+    }
+
+    static HttpResult sendRequest(String url, String method, String body, 
List<FormHelper.HeaderEntry> headers) {
+        try {
+            HttpClient client = HttpClient.newBuilder()
+                    .connectTimeout(Duration.ofSeconds(10))
+                    .build();
+
+            boolean hasBody = body != null && !body.isEmpty();
+            HttpRequest.BodyPublisher bodyPublisher = hasBody
+                    ? HttpRequest.BodyPublishers.ofString(body)
+                    : HttpRequest.BodyPublishers.noBody();
+
+            HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
+                    .uri(URI.create(url))
+                    .timeout(Duration.ofSeconds(20))
+                    .method(method, bodyPublisher);
+
+            if (headers != null) {
+                for (FormHelper.HeaderEntry he : headers) {
+                    String k = he.keyInput().text().trim();
+                    String v = he.valueInput().text();
+                    if (!k.isEmpty()) {
+                        reqBuilder.header(k, v);
+                    }
+                }
+            }
+
+            long start = System.currentTimeMillis();
+            HttpResponse<String> response = client.send(reqBuilder.build(),
+                    HttpResponse.BodyHandlers.ofString());
+            long elapsed = System.currentTimeMillis() - start;
+
+            int statusCode = response.statusCode();
+
+            List<String> headerLines = new ArrayList<>();
+            for (Map.Entry<String, List<String>> entry : 
response.headers().map().entrySet()) {
+                String k = entry.getKey();
+                if (k == null || k.startsWith(":")) {
+                    continue;
+                }
+                for (String v : entry.getValue()) {
+                    headerLines.add(k + ": " + v);
+                }
+            }
+
+            String responseBody = response.body();
+            if (responseBody != null && responseBody.isEmpty()) {
+                responseBody = null;
+            }
+
+            return new HttpResult(statusCode, elapsed, headerLines, 
responseBody, null);
+        } catch (Exception e) {
+            String msg = e.getMessage();
+            return new HttpResult(0, 0, List.of(), null, msg != null ? msg : 
e.getClass().getSimpleName());
+        }
+    }
+
+    static String extractPlatformHttpPath(String fromUri) {
+        String path = fromUri.substring("platform-http:".length());
+        int q = path.indexOf('?');
+        if (q >= 0) {
+            path = path.substring(0, q);
+        }
+        while (path.startsWith("//")) {
+            path = path.substring(1);
+        }
+        if (!path.startsWith("/")) {
+            path = "/" + path;
+        }
+        return path;
+    }
+
+    static String extractHttpMethod(String fromUri, String body) {
+        int q = fromUri.indexOf('?');
+        if (q >= 0) {
+            String query = fromUri.substring(q + 1);
+            for (String param : query.split("&")) {
+                if (param.startsWith("httpMethodRestrict=")) {
+                    String methods = 
param.substring("httpMethodRestrict=".length());
+                    int comma = methods.indexOf(',');
+                    return comma > 0 ? methods.substring(0, comma).trim() : 
methods.trim();
+                }
+            }
+        }
+        return (body != null && !body.isEmpty()) ? "POST" : "GET";
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index a450d58120ee..7eab78d39ad9 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -18,17 +18,12 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.io.IOException;
 import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -720,72 +715,24 @@ class HttpTab extends AbstractTableTab {
             String baseUrl, String method, String path, String body, 
List<FormHelper.HeaderEntry> hdrs) {
 
         String url = baseUrl + path;
-        String statusText;
-        long elapsed = 0;
-        boolean error = false;
-        List<String> headerLines = new ArrayList<>();
-        String rawBody = null;
-        int httpStatus = 0;
-
-        try {
-            HttpClient client = HttpClient.newBuilder()
-                    .connectTimeout(Duration.ofSeconds(10))
-                    .build();
-
-            boolean hasBody = body != null && !body.isEmpty();
-            HttpRequest.BodyPublisher bodyPublisher = hasBody
-                    ? HttpRequest.BodyPublishers.ofString(body)
-                    : HttpRequest.BodyPublishers.noBody();
-
-            HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
-                    .uri(URI.create(url))
-                    .timeout(Duration.ofSeconds(20))
-                    .method(method, bodyPublisher);
-
-            // Add user headers
-            if (hdrs != null) {
-                for (FormHelper.HeaderEntry he : hdrs) {
-                    String k = he.keyInput().text().trim();
-                    String v = he.valueInput().text();
-                    if (!k.isEmpty()) {
-                        reqBuilder.header(k, v);
-                    }
-                }
-            }
-
-            long start = System.currentTimeMillis();
-            HttpResponse<String> response = client.send(reqBuilder.build(),
-                    HttpResponse.BodyHandlers.ofString());
-            elapsed = System.currentTimeMillis() - start;
-
-            httpStatus = response.statusCode();
-            statusText = String.valueOf(httpStatus);
+        HttpHelper.HttpResult result = HttpHelper.sendRequest(url, method, 
body, hdrs);
 
-            // Response headers
-            for (Map.Entry<String, List<String>> entry : 
response.headers().map().entrySet()) {
-                String k = entry.getKey();
-                if (k == null || k.startsWith(":")) {
-                    continue;
-                }
-                for (String v : entry.getValue()) {
-                    headerLines.add(k + ": " + v);
-                }
-            }
-
-            // Response body
-            String responseBody = response.body();
-            if (responseBody != null && !responseBody.isEmpty()) {
-                rawBody = responseBody;
-            }
+        String statusText;
+        boolean error;
+        List<String> headerLines;
+        int httpStatus;
 
-            if (httpStatus >= 400) {
-                error = true;
-            }
-        } catch (Exception e) {
+        if (result.error() != null) {
             statusText = "Error";
             error = true;
-            String msg = e.getMessage();
-            headerLines.add(msg != null ? msg : e.getClass().getSimpleName());
+            httpStatus = 0;
+            headerLines = new ArrayList<>();
+            headerLines.add(result.error());
+        } else {
+            httpStatus = result.statusCode();
+            statusText = String.valueOf(httpStatus);
+            error = httpStatus >= 400;
+            headerLines = result.headerLines();
         }
 
         // Build history entry
@@ -795,21 +742,17 @@ class HttpTab extends AbstractTableTab {
         }
         ProbeHistoryEntry histEntry = new ProbeHistoryEntry(
                 method, path, histHeaders, body,
-                httpStatus, elapsed, statusText, error);
+                httpStatus, result.elapsed(), statusText, error);
 
         // Apply results on render thread
-        String finalStatus = statusText;
-        long finalElapsed = elapsed;
-        boolean finalError = error;
-        List<String> finalHeaderLines = headerLines;
-        String finalRawBody = rawBody;
+        String finalRawBody = result.body();
 
         if (ctx.runner != null) {
             ctx.runner.runOnRenderThread(() -> {
-                probeResponseStatus = finalStatus;
-                probeResponseElapsed = finalElapsed;
-                probeResponseError = finalError;
-                probeResponseHeaderLines = finalHeaderLines;
+                probeResponseStatus = statusText;
+                probeResponseElapsed = result.elapsed();
+                probeResponseError = error;
+                probeResponseHeaderLines = headerLines;
                 probeResponseRawBody = finalRawBody;
                 probeResponseScroll = 0;
                 rebuildResponseLines();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
index 3125e4c6cc7e..84f898e630fa 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SendMessagePopup.java
@@ -17,6 +17,8 @@
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -67,6 +69,7 @@ class SendMessagePopup {
     private boolean sending;
     private String pid;
     private String integrationName;
+    private String httpServer;
     private List<RouteInfo> routes;
     private int selectedRouteIndex;
     private final TextAreaState bodyState = new TextAreaState("");
@@ -104,17 +107,18 @@ class SendMessagePopup {
     }
 
     void open(MonitorContext ctx, String pid, String name, List<RouteInfo> 
routes, String preSelectRouteId) {
-        open(ctx, pid, name, routes, preSelectRouteId, null);
+        open(ctx, pid, name, routes, preSelectRouteId, null, null);
     }
 
     void open(
             MonitorContext ctx, String pid, String name, List<RouteInfo> 
routes,
-            String preSelectRouteId, String sourceDirectory) {
+            String preSelectRouteId, String sourceDirectory, String 
httpServer) {
         if (pid == null || routes == null || routes.isEmpty()) {
             return;
         }
         this.pid = pid;
         this.integrationName = name;
+        this.httpServer = httpServer;
         this.routes = new ArrayList<>(routes);
         this.selectedRouteIndex = findSmartDefault(preSelectRouteId);
         this.bodyState.clear();
@@ -507,6 +511,18 @@ class SendMessagePopup {
         List<FormHelper.HeaderEntry> hdrs = headers != null ? new 
ArrayList<>(headers) : null;
         String routeId = route.routeId;
 
+        // platform-http routes are consumer-only so we send an HTTP request 
directly
+        if (route.from != null && route.from.startsWith("platform-http:") && 
httpServer != null) {
+            executor.execute(() -> {
+                try {
+                    doSendHttp(route, sendBody, hdrs, captureInOut, routeId);
+                } finally {
+                    sending = false;
+                }
+            });
+            return;
+        }
+
         executor.execute(() -> {
             try {
                 JsonObject root = new JsonObject();
@@ -627,6 +643,52 @@ class SendMessagePopup {
         });
     }
 
+    private void doSendHttp(
+            RouteInfo route, String body, List<FormHelper.HeaderEntry> hdrs,
+            boolean captureInOut, String routeId) {
+        try {
+            String path = HttpHelper.extractPlatformHttpPath(route.from);
+            String baseUrl = httpServer.replace("0.0.0.0", "localhost");
+            String url = baseUrl + path;
+            String method = HttpHelper.extractHttpMethod(route.from, body);
+
+            // read file content if body references a file
+            String sendBody = body;
+            if (sendBody != null && sendBody.startsWith("file:")) {
+                sendBody = Files.readString(Path.of(sendBody.substring(5)));
+            }
+
+            HttpHelper.HttpResult result = HttpHelper.sendRequest(url, method, 
sendBody, hdrs);
+
+            if (result.error() != null) {
+                applyResult(routeId, body, hdrs, captureInOut,
+                        null, 0, null, null, null,
+                        true, "Error: " + result.error());
+                return;
+            }
+
+            boolean error = result.statusCode() >= 400;
+
+            List<String> hdrLines = new ArrayList<>();
+            hdrLines.add("HTTP " + method + " " + url);
+            hdrLines.add("status: " + result.statusCode());
+
+            if (captureInOut) {
+                hdrLines.addAll(result.headerLines());
+            }
+
+            String rawBody = captureInOut ? result.body() : null;
+
+            applyResult(routeId, body, hdrs, captureInOut,
+                    String.valueOf(result.statusCode()), result.elapsed(), 
null, hdrLines, rawBody,
+                    error, error ? "HTTP " + result.statusCode() : null);
+        } catch (Exception e) {
+            applyResult(routeId, body, hdrs, captureInOut,
+                    null, 0, null, null, null,
+                    true, "Error: " + e.getMessage());
+        }
+    }
+
     private void applyResult(
             String routeId, String body, List<FormHelper.HeaderEntry> hdrs, 
boolean wasInOut,
             String status, long elapsed, String exchangeId,
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpHelperTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpHelperTest.java
new file mode 100644
index 000000000000..7c9e36ce0e57
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpHelperTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class HttpHelperTest {
+
+    @Test
+    void extractPlatformHttpPathSimple() {
+        assertThat(HttpHelper.extractPlatformHttpPath("platform-http:/hello"))
+                .isEqualTo("/hello");
+    }
+
+    @Test
+    void extractPlatformHttpPathTripleSlash() {
+        
assertThat(HttpHelper.extractPlatformHttpPath("platform-http:///api/users";))
+                .isEqualTo("/api/users");
+    }
+
+    @Test
+    void extractPlatformHttpPathStripsQueryParams() {
+        
assertThat(HttpHelper.extractPlatformHttpPath("platform-http:/greet?httpMethodRestrict=GET"))
+                .isEqualTo("/greet");
+    }
+
+    @Test
+    void extractPlatformHttpPathNoLeadingSlash() {
+        assertThat(HttpHelper.extractPlatformHttpPath("platform-http:hello"))
+                .isEqualTo("/hello");
+    }
+
+    @Test
+    void extractPlatformHttpPathDoubleSlash() {
+        
assertThat(HttpHelper.extractPlatformHttpPath("platform-http://orders";))
+                .isEqualTo("/orders");
+    }
+
+    @Test
+    void extractHttpMethodFromRestrict() {
+        
assertThat(HttpHelper.extractHttpMethod("platform-http:/api?httpMethodRestrict=PUT",
 "body"))
+                .isEqualTo("PUT");
+    }
+
+    @Test
+    void extractHttpMethodFirstFromCommaSeparated() {
+        
assertThat(HttpHelper.extractHttpMethod("platform-http:/api?httpMethodRestrict=GET,POST",
 null))
+                .isEqualTo("GET");
+    }
+
+    @Test
+    void extractHttpMethodPostWhenBodyPresent() {
+        assertThat(HttpHelper.extractHttpMethod("platform-http:/api", "some 
body"))
+                .isEqualTo("POST");
+    }
+
+    @Test
+    void extractHttpMethodGetWhenNoBody() {
+        assertThat(HttpHelper.extractHttpMethod("platform-http:/api", null))
+                .isEqualTo("GET");
+    }
+
+    @Test
+    void extractHttpMethodGetWhenEmptyBody() {
+        assertThat(HttpHelper.extractHttpMethod("platform-http:/api", ""))
+                .isEqualTo("GET");
+    }
+
+    @Test
+    void extractHttpMethodWithOtherQueryParams() {
+        
assertThat(HttpHelper.extractHttpMethod("platform-http:/api?consumes=application/json&httpMethodRestrict=DELETE",
 null))
+                .isEqualTo("DELETE");
+    }
+}

Reply via email to