davsclaus commented on code in PR #26203:
URL: https://github.com/apache/camel/pull/26203#discussion_r4112510909
##########
components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc:
##########
@@ -202,6 +202,126 @@ If any of the validation checks fail, then a
`RestOpenApiValidationException` is
has a `getValidationErrors` method that returns the error messages from the
validator.
+== Unmatched requests
+
+By default, an incoming request that does not match any operation in the
OpenAPI specification is answered by the
+HTTP layer of the runtime, with HTTP 404, and a request that matches a path
but not the HTTP method is answered
+with HTTP 405 and an `Allow` header listing the allowed methods.
+
+A request that matches an operation but whose `Content-Type` or `Accept`
header does not match the `consumes` or
+`produces` of the operation is answered by the HTTP layer, with HTTP 415 or
406 (see the `serverRequestValidation`
+option of the platform-http component, enabled by default). When
`unmatchedRequestHandling` is set to `camel`,
+some runtimes also route these requests to Camel, so the unmatched request
handler answers them with the same
Review Comment:
This (correctly) describes the `camel`-mode-only behaviour, but the code
currently applies it in every mode — see the comment on `RestOpenApiProcessor`.
Code and docs should agree once that's settled.
##########
components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc:
##########
@@ -202,6 +202,126 @@ If any of the validation checks fail, then a
`RestOpenApiValidationException` is
has a `getValidationErrors` method that returns the error messages from the
validator.
+== Unmatched requests
+
+By default, an incoming request that does not match any operation in the
OpenAPI specification is answered by the
+HTTP layer of the runtime, with HTTP 404, and a request that matches a path
but not the HTTP method is answered
+with HTTP 405 and an `Allow` header listing the allowed methods.
+
+A request that matches an operation but whose `Content-Type` or `Accept`
header does not match the `consumes` or
+`produces` of the operation is answered by the HTTP layer, with HTTP 415 or
406 (see the `serverRequestValidation`
+option of the platform-http component, enabled by default). When
`unmatchedRequestHandling` is set to `camel`,
+some runtimes also route these requests to Camel, so the unmatched request
handler answers them with the same
+415 or 406 status code.
+
+To let Camel answer these requests instead, set the `unmatchedRequestHandling`
option to `camel` on the
+rest-openapi consumer endpoint or via the rest DSL `openApi` section. The
rest-openapi component then registers a
+catch-all for the API base path on the HTTP layer so requests that match no
operation are routed to Camel, where
+they are answered by the unmatched request handler.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("rest-openapi:petstore-v3.json?missingOperation=ignore&unmatchedRequestHandling=camel")
+ .to("direct:businessLogic");
+
+// ... or using the rest DSL
+
+rest().openApi()
+ .specification("petstore-v3.json")
+ .missingOperation("ignore")
+ .unmatchedRequestHandling("camel");
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: rest-openapi:petstore-v3.json
+ parameters:
+ missingOperation: ignore
+ unmatchedRequestHandling: camel
+ steps:
+ - to:
+ uri: direct:businessLogic
+
+# ... or using the rest DSL
+
+- rest:
+ openApi:
+ specification: petstore-v3.json
+ missingOperation: ignore
+ unmatchedRequestHandling: camel
+----
+====
+
+The option is supported by the built-in `platform-http` consumer component:
Camel Main when using
+xref:platform-http-component.adoc[Platform HTTP], and Spring Boot when using
the platform-http starter
+(`camel-platform-http-starter`). Other consumer components are not tested and
can decide to handle, ignore or
+reject the parameter.
+
+On Camel Main and Quarkus, the catch-all route is evaluated last on the HTTP
server, so it never shadows the
+operation routes of other APIs served by the same server, even when their base
paths are nested under this API.
+
+[NOTE]
+====
+With nested base paths (for example `/api` and `/api/v3`), the order of the
catch-all follows the order in
+which the consumers start: `PUT /api/v3/pet/123` may get a 404 from `/api`
instead of 405 with an `Allow`
+header from `/api/v3`. To work around this, avoid nesting the base paths of
several APIs in `camel`
+mode, or inspect the request in a custom `RestOpenApiUnmatchedRequestHandler`
and determine the proper response.
+====
+
+On Spring Boot, the catch-all is served by a Spring MVC handler mapping that
is evaluated before the
+application's own controller mappings and the static resources. With a base
path of `/`, in `camel` mode
+every request unmatched by the API will be answered by the unmatched request
handler. Controllers and static
+resources will be shadowed. When the application also serves MVC contet it is
prefered to use a base path other
+than `/` (for example by setting `servers` in the OpenAPI specification or
`contextPath` in the rest
+configuration), so the catch-all only claims requests under that path.
+
+If the API and Spring MVC controllers must keep responding to their own paths
at a base path of `/`, you can
+set register your own `RequestMappingHandlerMapping` bean with a order set to
`-50`. This will still not work for
+serving static resources.
Review Comment:
A few typos and trailing spaces here (please regenerate the catalog copy
after):
```suggestion
every request unmatched by the API is answered by the unmatched request
handler, so controllers and static
resources are shadowed. When the application also serves MVC content, prefer
a base path other
than `/` (for example by setting `servers` in the OpenAPI specification or
`contextPath` in the rest
configuration), so the catch-all only claims requests under that path.
If the API and Spring MVC controllers must keep responding to their own
paths at a base path of `/`, you can
register your own `RequestMappingHandlerMapping` bean with its order set to
`-50`. This still does not work for
serving static resources.
```
##########
components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java:
##########
@@ -100,6 +114,22 @@ public boolean process(Exchange exchange, AsyncCallback
callback) {
RestConsumerContextPathMatcher.ConsumerPath<Operation> m
= RestConsumerContextPathMatcher.matchBestPath(verb, path,
paths);
if (m instanceof RestOpenApiConsumerPath rcp) {
+ // when server request validation is enabled, the HTTP layer
rejects requests whose Content-Type
+ // or Accept header does not match the consumes/produces of the
operation with 415/406. However,
+ // depending on the runtime, such requests may still be routed to
Camel (via an unconstrained
+ // catch-all route or a matchOnUriPrefix endpoint), so the
rejected requests must be answered
+ // here instead of being processed as if they were valid
+ if (serverRequestValidation) {
Review Comment:
This is gated only on `serverRequestValidation`, which defaults to `true` on
platform-http, so it also runs in the default `platform` mode (your unit tests
use `createProcessor(..., null)`, i.e. default mode, and get 415/406). That's a
behaviour change for existing users and contradicts the docs, which say this
only happens in `camel` mode.
It also double-checks requests Vert.x already negotiated, using a cruder
matcher: `RestUtil.isValidOrAcceptedContentType` does
`StringHelper.before(target, ";")` before splitting on `,`, so `Accept:
application/xml;q=0.9, application/json` against `produces=application/json`
becomes `application/xml` → 406, and `application/*` never matches. Could this
be `if (serverRequestValidation && unmatchedRequestCatchAllRegistered)`, with
Accept params stripped per comma-separated part (and a test for a q-valued
Accept)?
##########
core/camel-api/src/main/java/org/apache/camel/spi/RestOpenApiUnmatchedRequestHandler.java:
##########
@@ -0,0 +1,45 @@
+/*
+ * 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.spi;
+
+import java.util.List;
+
+import org.apache.camel.Exchange;
+
+/**
+ * Used for customizing the HTTP responses for incoming requests that Camel
must not process. Exemplary these are
+ * requests that do not match any operation defined in the OpenAPI
specification (404/405) or, when server request
+ * validation is enabled, requests whose Content-Type/Accept header does not
match the consumes/produces of the matched
+ * operation (415/406).
+ * <p>
+ * This allows to plugin different handlers to produce custom error response
bodies.
+ *
+ * @since 4.23
+ */
+public interface RestOpenApiUnmatchedRequestHandler {
Review Comment:
Thanks for moving this. Since the reason for putting it in camel-api is
reuse by `camel-rest-postman`, should the name drop the OpenAPI part (e.g.
`RestUnmatchedRequestHandler`, with the factory key to match)? Once 4.23 is
released the name is frozen. Minor nit on line 24: "Exemplary these are" → "For
example, these are".
--
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]