allthingssecurity opened a new pull request, #29039: URL: https://github.com/apache/camel/pull/29039
# Description [CAMEL-26460](https://issues.apache.org/jira/browse/CAMEL-26460) When one route consumes a plain undertow path template (such as `/orders/{id}`) and another route the static path of its base path (`/orders`, exact or with `matchOnUriPrefix=true`), `CamelRootHandler` serves both through one `CamelPathTemplateHandler`, registered as the *prefix* path of the base path, with the static path as its default handler. Several things went wrong, depending on which route started first: ```java from("undertow:http://0.0.0.0:8080/orders/{id}").routeId("order").setBody(simple("order ${header.id}")); from("undertow:http://0.0.0.0:8080/orders").routeId("orders").setBody(constant("all orders")); ``` | Case | On main | |---|---| | template route first, then the static route; `GET /orders` | answered by the template route, `order ` (empty `id`): Undertow's `PathTemplateMatcher` matches the empty rest of the base path against `/{id}` | | `/items?matchOnUriPrefix=true` first, then `/items/{id}`; `GET /items` (or `/items/`) | answered by the template route, `item ` | | `/?matchOnUriPrefix=true` and `/{id}`; `GET /` | answered by the template route, with an empty `id` | | `/items?matchOnUriPrefix=true` first, then `/items/{id}`; stop the template route; `GET /items/a/b` | 404: removing the last template removed the prefix path of `/items`, which served the `matchOnUriPrefix` route too (`CamelPathTemplateHandler.isEmpty()` only counts templates) | | `/items/{id}` declared before `/items?matchOnUriPrefix=true` | the CamelContext does not start: `Duplicate handlers on a path '/items'` (the other order works) | | static exact route first, then the template; stop the template route | the static route still runs, but its requests get "connection refused" when these were the only paths of the server: `CamelPathHandler` keeps one map for exact and prefix paths, the template's prefix path had replaced the exact path in it, so after the removal the map was empty and `DefaultUndertowHost` stopped the server | | template route first, then the static route; stop the template route | `GET /orders` gets 404: the static path lived only in the default handler of the template handler, which went with the last template (and when these were the only paths of the server, it stopped) | When the static exact route starts first, `GET /orders` while both run is fine on main: Undertow's exact path wins over the prefix path. rest-dsl consumers go through `RestRootHandler` (CAMEL-25854) and are not affected. Path templates of plain undertow endpoints are not described on the component page, but they work and the variable comes in as a header. ## The fix - `CamelPathHandler` keeps the exact and the prefix paths in two maps; `getHandler(path)` returns the prefix handler (the template handler when there is one), else the exact one; a new `getExactHandler(path)`; `isEmpty()` counts both. Both maps are changed and read under its lock (`getHandler` and `isEmpty` did not take it before). - `CamelPathTemplateHandler.handleRequest`: a request for the base path itself goes to the static handler of the base path when it has one: a relative path of `""` (the prefix match of `/orders`) or `/` (`/orders/`, and the root path, as Undertow serves the prefix path `/` as its default handler, which keeps the whole request path as the relative path). With no static path it goes to the templates as before (so a template-only `/orders/{id}` still answers `/orders` with an empty `id`, unchanged). - `CamelRootHandler.add`: - a static exact path added when the templates of its base path came first is also registered as an exact path, as when it comes first. That registration is what tells a static exact path from a static prefix one later. (It is not added when the template handler's default already serves a static prefix route: that route keeps the prefix semantics, as before.) - a `matchOnUriPrefix` route added when the templates of its base path came first becomes the static path of the base path (the template handler's default) when the base path has no static path yet, as when it comes first; it was refused. A prefix route added when the base path already has a static route (exact or prefix) and templates is still refused, as on main. - `CamelRootHandler.remove`: - last template removed: the template handler goes; a static prefix path it served is registered again as the prefix path, an exact one stays registered as it is; - static path removed while templates remain: its exact registration goes, the templates stay; - static path without templates: the registration its handler has is removed (exact or prefix), instead of trusting the `matchOnUriPrefix` flag of the route being removed (routes of both kinds on one path share the handler of the first one; main left Undertow's exact path behind when the prefix route was the last to stop); - removing a template that is not registered returns (it threw a `NullPointerException`, or a `ClassCastException` when the base path has only a static path). The locking order is unchanged: `CamelRootHandler.add/remove` hold its lock (and `DefaultUndertowHost` holds its own around them), and the two maps change only under `CamelPathHandler`'s lock. Replacing the template handler by the static prefix handler is one `put`. Not changed (on main as well): - removing a plain consumer that never started (a route with `autoStartup=false`, or one whose start failed) still unregisters its path, so it decrements the count of a running consumer of the same path (it can drop the static path, or stop the server). `UndertowConsumer.doStop` always unregisters; CAMEL-26083 fixed this pairing for rest-dsl consumers only. Follow-up: CAMEL-26463. - an exact and a prefix static route of the same path still share the handler of the first one, registered with its kind. - path templates of plain endpoints are still not described on the component page; I can add a paragraph there in a follow-up if you want. Backport: main only (the routing change has an upgrade-guide entry, and it builds on the fallback handler of CAMEL-25854, which is on main only). ## Behaviour changes - `GET /orders` (the base path, also `/orders/`, and `/` for templates of the root path) now reaches the static route instead of the template route in the cases above. When the static route has `httpMethodRestrict`, such a request with another method now gets 405 from it instead of reaching the template route. After the static route stopped while the template route runs, `GET /orders` reaches the template route (empty `id`) as a template route on its own does (main answered 500 in that sequence: the exact path of the stopped static route was left behind). - A `matchOnUriPrefix` route declared after the template route of its base path now starts. - Stopping the template route keeps the static path served and its server running. Upgrade guide (4.24): one entry, next to the CAMEL-25854 entry about plain and rest-dsl consumers on one port, for the routing of the base path, the 405, and the prefix route that now starts. There is nothing to configure back: the static route is the one for that path (a route that wants the template to answer the base path does not also consume the static path). ## Tests New `UndertowTemplatePathAndStaticPathTest` (camel-undertow, JUnit assertions as in the module, 8 tests, real Undertow servers on three ports; each step asserts the HTTP code and body; no sleeps): - template first: `GET /orders`, `/orders/` and `/orders/7` while both run; - template first: stop the template route (static path served, template path 404), start it again; - stop the static route then the template route, start the static route again (control, passes on main); - prefix route first: `GET /items`, `/items/`, `/items/a/b`, `/items/7`; - prefix route first: stop the template route (`/items/a/b`, `/items/7`, `/items` reach the prefix route), start it again; - template first, then a static route with `httpMethodRestrict=POST`: `GET /m` gets 405, `POST /m` reaches the static route, `GET /m/7` the template; - a `matchOnUriPrefix` route of `/` and a template `/{id}` on the third server: `GET /`, `/7`, `/a/b`; - static route first on a server of its own: stop the template route, the static path is still served; stop and start the static route. New `UndertowPrefixPathAfterTemplatePathTest` (1 test, its own class as the CamelContext does not start on main): the template route declared before the `matchOnUriPrefix` route: both start, `GET /goods`, `/goods/a/b`, `/goods/7`; the prefix route stops and starts again while the template route runs; the template route stops. `CamelRootHandlerTest` +5: both start orders, removing the template keeps the root handler non-empty and removing both leaves it empty; static removed first then template (control); prefix + template; removing a template that is not registered; an exact and a prefix route sharing one handler leave nothing behind. Negative controls (the main versions of the files compiled over the branch, run in plain JVMs): - all three files from main: 12 of the 16 tests of the three classes fail (7 of the 8 route tests, the prefix-after-template test, 4 of the 7 handler tests; the controls and the two existing handler tests pass); - only `CamelPathTemplateHandler` from main: 3 fail (the base path of the prefix route, the root path, the prefix route declared after the template); - only `CamelRootHandler` from main: 8 fail (2 route tests, the prefix-after-template test, 5 handler tests). - The new and changed classes 20 times in one JVM: 320 tests, 0 failures. - camel-undertow, all tests of the module: 269, 0 failures, 1 skipped. - Formatter and import-sort: no changes. # Target - [x] I checked that the commit is targeting the correct branch (Camel 4 uses the `main` branch) # Tracking - [x] If this is a large change, bug fix, or code improvement, I checked there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL-26460) filed for the change (usually before you start working on it): [CAMEL-26460](https://issues.apache.org/jira/browse/CAMEL-26460). # Apache Camel coding standards and style - [x] I checked that each commit in the pull request has a meaningful subject line and body. - [ ] I have run `mvn clean install -DskipTests` locally from root folder and I have committed all auto-generated changes. (I built the core modules and camel-undertow and ran the tests above, including the formatter and import-sort plugins. No generated file changes. I did not run the full root build.) # AI-assisted contributions - [x] If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR description identifies the AI tool used. This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a `Co-Authored-By` trailer. _Claude Code on behalf of allthingssecurity_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
