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 2026/02/07 14:12:17 UTC
(camel) branch cf created (now 5ab955b4f8c9)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a change to branch cf
in repository https://gitbox.apache.org/repos/asf/camel.git
at 5ab955b4f8c9 CAMEL-22971: camel-platform-http-vertx: Using rest-dsl contract-first should use fine grained vertx-web router
This branch includes the following new commits:
new 5ab955b4f8c9 CAMEL-22971: camel-platform-http-vertx: Using rest-dsl contract-first should use fine grained vertx-web router
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(camel) 01/01: CAMEL-22971: camel-platform-http-vertx: Using rest-dsl contract-first should use fine grained vertx-web router
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch cf
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 5ab955b4f8c9708dc91faefed5909ae44a72b6e5
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Feb 7 15:12:01 2026 +0100
CAMEL-22971: camel-platform-http-vertx: Using rest-dsl contract-first should use fine grained vertx-web router
---
.../http/vertx/VertxPlatformHttpConsumer.java | 101 ++++++++++++++++++---
...PlatformHttpRestOpenApiConsumerRestDslTest.java | 2 +-
.../vertx/PlatformHttpRestOpenApiConsumerTest.java | 1 +
.../rest/openapi/RestOpenApiProcessor.java | 25 +++--
.../camel/component/rest/DefaultRestRegistry.java | 36 +++++++-
.../java/org/apache/camel/spi/RestRegistry.java | 26 ++++++
.../services/org/apache/camel/model.properties | 3 +
.../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc | 10 ++
8 files changed, 179 insertions(+), 25 deletions(-)
diff --git a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
index fd088b7f2a03..3168bd64b4e4 100644
--- a/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
+++ b/components/camel-platform-http-vertx/src/main/java/org/apache/camel/component/platform/http/vertx/VertxPlatformHttpConsumer.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.platform.http.vertx;
import java.io.File;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -81,7 +82,7 @@ public class VertxPlatformHttpConsumer extends DefaultConsumer
private final boolean handleWriteResponseError;
private Set<Method> methods;
private String path;
- private Route route;
+ private final List<Route> routes = new ArrayList<>();
private VertxPlatformHttpRouter router;
private HttpRequestBodyHandler httpRequestBodyHandler;
private CookieConfiguration cookieConfiguration;
@@ -135,20 +136,22 @@ public class VertxPlatformHttpConsumer extends DefaultConsumer
protected void doStart() throws Exception {
super.doStart();
- final Route newRoute = router.route(path);
+ if (startRestServicesContractFirst()) {
+ // rest-dsl contract first using multiple routers per api endpoint
+ return;
+ }
+ // standard http consumer using a single router
+ final Route newRoute = router.route(path);
if (getEndpoint().getRequestTimeout() > 0) {
newRoute.handler(TimeoutHandler.create(getEndpoint().getRequestTimeout()));
}
-
if (getEndpoint().getCamelContext().getRestConfiguration().isEnableCORS() && getEndpoint().getConsumes() != null) {
((RouteImpl) newRoute).setEmptyBodyPermittedWithConsumes(true);
}
-
if (!methods.equals(Method.getAll())) {
methods.forEach(m -> newRoute.method(HttpMethod.valueOf(m.name())));
}
-
if (getEndpoint().getComponent().isServerRequestValidation()) {
if (getEndpoint().getConsumes() != null) {
//comma separated contentTypes has to be registered one by one
@@ -163,26 +166,98 @@ public class VertxPlatformHttpConsumer extends DefaultConsumer
}
}
}
-
httpRequestBodyHandler.configureRoute(newRoute);
for (Handler<RoutingContext> handler : handlers) {
newRoute.handler(handler);
}
-
newRoute.handler(this::handleRequest);
-
- this.route = newRoute;
+ this.routes.add(newRoute);
}
@Override
protected void doStop() throws Exception {
- if (route != null) {
- route.remove();
- route = null;
- }
+ this.routes.forEach(Route::remove);
+ this.routes.clear();
super.doStop();
}
+ /**
+ * Special start logic for Rest DSL with contract-first, which need to use fine-grained vertx router to make this
+ * consistent with Camel, otherwise there is only 1 vertx router to handle all the API endpoints (coarse grained)
+ * which distorts the observability in vertx and camel-quarkus.
+ *
+ * @return true if in rest-dsl contract-first mode, false if standard mode
+ */
+ protected boolean startRestServicesContractFirst() throws Exception {
+ boolean matched = false;
+ for (var r : getEndpoint().getCamelContext().getRestRegistry().listAllRestServices()) {
+ String target = path;
+ if (target.endsWith("*")) {
+ target = target.substring(0, target.length() - 1);
+ }
+ if (r.isContractFirst() && target.equals(r.getBasePath())) {
+ matched = true;
+ // contract-first, then lets build up fine-grained router for vertx
+ String u = r.getBasePath() + r.getBaseUrl();
+ // in vertx-web we should replace path parameters from {xxx} to :xxx syntax
+ u = u.replaceAll("\\{([a-zA-Z0-9]+)\\}", ":$1");
+ String v = r.getMethod();
+ String c = r.getConsumes();
+ String p = r.getProduces();
+
+ Route sr = router.route(u);
+ sr.method(HttpMethod.valueOf(v));
+ if (getEndpoint().getComponent().isServerRequestValidation()) {
+ if (c != null) {
+ for (String cc : c.split(",")) {
+ sr.consumes(cc);
+ }
+ }
+ if (p != null) {
+ for (String pp : p.split(",")) {
+ sr.produces(pp);
+ }
+ }
+ }
+ httpRequestBodyHandler.configureRoute(sr);
+ for (Handler<RoutingContext> handler : handlers) {
+ sr.handler(handler);
+ }
+ sr.handler(this::handleRequest);
+ this.routes.add(sr);
+ }
+ }
+ for (var r : getEndpoint().getCamelContext().getRestRegistry().listAllRestSpecifications()) {
+ String target = path;
+ if (target.endsWith("*")) {
+ target = target.substring(0, target.length() - 1);
+ }
+ if (r.isSpecification() && target.equals(r.getBasePath())) {
+ // contract-first, then lets build up fine-grained router for vertx
+ String u = r.getBasePath() + r.getBaseUrl();
+ String v = r.getMethod();
+ String p = r.getProduces();
+
+ Route sr = router.route(u);
+ sr.method(HttpMethod.valueOf(v));
+ if (getEndpoint().getComponent().isServerRequestValidation()) {
+ if (p != null) {
+ for (String pp : p.split(",")) {
+ sr.produces(pp);
+ }
+ }
+ }
+ httpRequestBodyHandler.configureRoute(sr);
+ for (Handler<RoutingContext> handler : handlers) {
+ sr.handler(handler);
+ }
+ sr.handler(this::handleRequest);
+ this.routes.add(sr);
+ }
+ }
+ return matched;
+ }
+
private String configureEndpointPath(PlatformHttpEndpoint endpoint) {
String path = endpoint.getPath();
if (endpoint.isMatchOnUriPrefix() && !path.endsWith("*")) {
diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslTest.java
index e320de9ed0f2..6df49cd56ee6 100644
--- a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslTest.java
+++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslTest.java
@@ -224,7 +224,7 @@ public class PlatformHttpRestOpenApiConsumerRestDslTest {
context.start();
given()
- .when()
+ .when().contentType("application/json")
.put("/api/v3/pet")
.then()
.statusCode(400); // no request body
diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
index e494ff86b058..74969e7595d1 100644
--- a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
+++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
@@ -231,6 +231,7 @@ public class PlatformHttpRestOpenApiConsumerTest {
given()
.when()
+ .contentType("application/json")
.put("/api/v3/pet")
.then()
.statusCode(400); // no request body
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
index 531b401a7d00..e1816fcd17cb 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
@@ -19,7 +19,6 @@ package org.apache.camel.component.rest.openapi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Optional;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
@@ -184,6 +183,20 @@ public class RestOpenApiProcessor extends AsyncProcessorSupport implements Camel
}
openApiUtils.clear(); // no longer needed
+ // register api-doc in rest registry
+ if (endpoint.getSpecificationUri() != null && apiContextPath != null) {
+ String url = basePath + apiContextPath;
+ String produces = null;
+ if (endpoint.getSpecificationUri().endsWith("json")) {
+ produces = "application/json";
+ } else if (endpoint.getSpecificationUri().endsWith("yaml") || endpoint.getSpecificationUri().endsWith("yml")) {
+ produces = "text/yaml";
+ }
+ // register api-doc
+ camelContext.getRestRegistry().addRestSpecification(consumer, true, url, apiContextPath, basePath, "GET", produces,
+ null);
+ }
+
for (var p : paths) {
if (p instanceof RestOpenApiConsumerPath rcp) {
ServiceHelper.startService(rcp.getBinding());
@@ -216,15 +229,9 @@ public class RestOpenApiProcessor extends AsyncProcessorSupport implements Camel
bc.setClientResponseValidation(config.isClientResponseValidation() || endpoint.isClientResponseValidation());
bc.setEnableNoContentResponse(config.isEnableNoContentResponse());
bc.setSkipBindingOnErrorCode(config.isSkipBindingOnErrorCode());
-
- String consumes = Optional.ofNullable(openApiUtils.getConsumes(o)).orElse(endpoint.getConsumes());
- String produces = Optional.ofNullable(openApiUtils.getProduces(o)).orElse(endpoint.getProduces());
-
- bc.setConsumes(consumes);
- bc.setProduces(produces);
-
+ bc.setConsumes(openApiUtils.getConsumes(o));
+ bc.setProduces(openApiUtils.getProduces(o));
bc.setRequiredBody(openApiUtils.isRequiredBody(o));
-
bc.setRequiredQueryParameters(openApiUtils.getRequiredQueryParameters(o));
bc.setRequiredHeaders(openApiUtils.getRequiredHeaders(o));
bc.setQueryDefaultValues(openApiUtils.getQueryParametersDefaultValue(o));
diff --git a/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java b/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
index ac219ca61d4a..8d50ad31c8b6 100644
--- a/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
+++ b/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
@@ -43,6 +43,7 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
private CamelContext camelContext;
private final Map<Consumer, List<RestService>> registry = new LinkedHashMap<>();
+ private final Map<Consumer, List<RestService>> specs = new LinkedHashMap<>();
private transient Producer apiProducer;
@Override
@@ -51,15 +52,28 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
String method,
String consumes, String produces, String inType, String outType, String routeId, String description) {
RestServiceEntry entry = new RestServiceEntry(
- consumer, contractFirst, url, baseUrl, basePath, uriTemplate, method, consumes, produces, inType, outType,
+ consumer, false, contractFirst, url, baseUrl, basePath, uriTemplate, method, consumes, produces, inType,
+ outType,
description);
List<RestService> list = registry.computeIfAbsent(consumer, c -> new ArrayList<>());
list.add(entry);
}
+ @Override
+ public void addRestSpecification(
+ Consumer consumer, boolean contractFirst, String url, String baseUrl, String basePath, String method,
+ String produces, String description) {
+ RestServiceEntry entry = new RestServiceEntry(
+ consumer, true, contractFirst, url, baseUrl, basePath, null, method, null, produces, null, null,
+ description);
+ List<RestService> list = specs.computeIfAbsent(consumer, c -> new ArrayList<>());
+ list.add(entry);
+ }
+
@Override
public void removeRestService(Consumer consumer) {
registry.remove(consumer);
+ specs.remove(consumer);
}
@Override
@@ -71,6 +85,15 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
return answer;
}
+ @Override
+ public List<RestService> listAllRestSpecifications() {
+ List<RestRegistry.RestService> answer = new ArrayList<>();
+ for (var list : specs.values()) {
+ answer.addAll(list);
+ }
+ return answer;
+ }
+
@Override
public int size() {
int count = 0;
@@ -160,6 +183,7 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
@Override
protected void doStop() throws Exception {
registry.clear();
+ specs.clear();
}
/**
@@ -168,6 +192,7 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
private static final class RestServiceEntry implements RestService {
private final Consumer consumer;
+ private final boolean specification;
private final boolean contractFirst;
private final String url;
private final String baseUrl;
@@ -180,10 +205,12 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
private final String outType;
private final String description;
- private RestServiceEntry(Consumer consumer, boolean contractFirst, String url, String baseUrl, String basePath,
+ private RestServiceEntry(Consumer consumer, boolean specification, boolean contractFirst, String url, String baseUrl,
+ String basePath,
String uriTemplate, String method, String consumes, String produces,
String inType, String outType, String description) {
this.consumer = consumer;
+ this.specification = specification;
this.contractFirst = contractFirst;
this.url = url;
this.baseUrl = baseUrl;
@@ -202,6 +229,11 @@ public class DefaultRestRegistry extends ServiceSupport implements RestRegistry,
return consumer;
}
+ @Override
+ public boolean isSpecification() {
+ return specification;
+ }
+
@Override
public boolean isContractFirst() {
return contractFirst;
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java b/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java
index 51ba3c1c2882..aad6ad1119bf 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java
@@ -37,6 +37,11 @@ public interface RestRegistry extends StaticService {
*/
Consumer getConsumer();
+ /**
+ * Is this the API contract specification (ie api-doc)
+ */
+ boolean isSpecification();
+
/**
* Is the rest service based on code-first or contract-first
*/
@@ -139,6 +144,27 @@ public interface RestRegistry extends StaticService {
*/
List<RestService> listAllRestServices();
+ /**
+ * Adds information about the API specification (ie api-doc)
+ *
+ * @param consumer the consumer
+ * @param contractFirst is the rest service based on code-first or contract-first
+ * @param url the absolute url of the REST service
+ * @param baseUrl the base url of the REST service
+ * @param basePath the base path
+ * @param method the HTTP method
+ * @param produces optional details about what media-types the REST service returns
+ * @param description optional description about the service
+ */
+ void addRestSpecification(Consumer consumer, boolean contractFirst, String url, String baseUrl, String basePath, String method, String produces, String description);
+
+ /**
+ * List all REST API specification (ie api-doc)
+ *
+ * @return all the API specification (ie api-doc)
+ */
+ List<RestService> listAllRestSpecifications();
+
/**
* Number of rest services in the registry.
*
diff --git a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
index 2607d21e1e52..9a641b9986d3 100644
--- a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
+++ b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
@@ -222,6 +222,7 @@ threads
thrift
throttle
throwException
+tidyMarkup
to
toD
tokenize
@@ -231,6 +232,7 @@ transacted
transform
transformDataType
transformers
+typeFilter
univocityCsv
univocityFixed
univocityHeader
@@ -243,6 +245,7 @@ variable
wasm
weightedLoadBalancer
when
+whenSkipSendToEndpoint
wireTap
xmlSecurity
xpath
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
index 605ac9179024..5c5a2f90c47d 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
@@ -42,6 +42,16 @@ Even if the interface HostApplicationEventHandler is public, I do not expect Cam
Consequently, there is an API break `org.apache.camel.tahu.handlers.TahuHostApplicationEventHandler` has been removed. It is replaced by `org.apache.camel.tahu.handlers.MultiTahuHostApplicationEventHandler`.
+=== camel-platform-http-vertx and Rest DSL contract-first
+
+When using Rest DSL in _contract first_ style, then the HTTP engine (vertx-web) instead of a single
+router to handle all incoming Rest API calls, is now one unique router per API endpoint. This change
+can affect HTTP request validation as vertx/Quarkus is now also performing this per API endpoint according
+to the API specification.
+
+All together this would make Camel behave similar for Rest DSL for both _code first_ and _contract first_ style.
+
+
=== Component deprecation
The `camel-olingo2` and `camel-olingo4` component are deprecated.