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");
+ }
+}