| = REST OpenApi Component |
| :doctitle: REST OpenApi |
| :shortname: rest-openapi |
| :artifactid: camel-rest-openapi |
| :description: To call and expose REST services using OpenAPI specification as contract. |
| :since: 3.1 |
| :supportlevel: Stable |
| :tabs-sync-option: |
| :component-header: Both producer and consumer are supported |
| |
| |
| *Since Camel {since}* |
| |
| *{component-header}* |
| |
| The REST OpenApi configures rest producers from |
| https://www.openapis.org/[OpenApi] (Open API) specification document and |
| delegates to a component implementing the _RestProducerFactory_ |
| interface. Currently, known working components are: |
| |
| * xref:http-component.adoc[http] |
| * xref:netty-http-component.adoc[netty-http] |
| * xref:undertow-component.adoc[undertow] |
| * xref:vertx-http-component.adoc[vertx-http] |
| |
| IMPORTANT: Only OpenAPI spec version 3.x is supported. You cannot use the old Swagger 2.0 spec. |
| |
| Maven users will need to add the following dependency to their |
| `pom.xml` for this component: |
| |
| [source,xml] |
| ---- |
| <dependency> |
| <groupId>org.apache.camel</groupId> |
| <artifactId>camel-rest-openapi</artifactId> |
| <version>x.x.x</version> |
| <!-- use the same version as your Camel core version --> |
| </dependency> |
| ---- |
| |
| == URI format |
| |
| ---- |
| rest-openapi:[specificationPath#]operationId |
| ---- |
| |
| Where `operationId` is the ID of the operation in the OpenApi |
| specification, and `specificationPath` is the path to the |
| specification. |
| If the `specificationPath` is not specified it defaults to |
| `openapi.json`. The lookup mechanism uses Camels `ResourceHelper` to |
| load the resource, which means that you can use CLASSPATH resources |
| (`classpath:my-specification.json`), files |
| (`file:/some/path.json`), the web |
| (`\http://api.example.com/openapi.json`) or reference a bean |
| (`ref:nameOfBean`) or use a method of a bean |
| (`bean:nameOfBean.methodName`) to get the specification resource, |
| failing that OpenApi's own resource loading support. |
| |
| This component does not act as an HTTP client. |
| It delegates that to another component mentioned above. |
| The lookup mechanism searches for a single component that |
| implements the `RestProducerFactory` interface and |
| uses that. |
| If the `_CLASSPATH_` contains more than one, then the property |
| `componentName` should be set to indicate which component to delegate |
| to. |
| |
| Most of the configuration is taken from the OpenApi specification, but |
| the option exists to override those by specifying them on the component |
| or on the endpoint. Typically, you would need to override the |
| `host` or `basePath` if those differ from the specification. |
| |
| [NOTE] |
| ==== |
| The `host` parameter should contain the absolute URI containing |
| scheme, hostname and port number, for instance: |
| `\https://api.example.com` |
| ==== |
| |
| With `componentName` you specify what component is used to perform the |
| requests, this named component needs to be present in the Camel context |
| and implement the required _RestProducerFactory_ interface -- as do the |
| components listed at the top. |
| |
| If you do not specify the _componentName_ at either component or |
| endpoint level, a suitable delegate is looked up: a component already |
| in the Camel context that implements `RestProducerFactory`, then the |
| registry, then the default components in this order: `http`, |
| `vertx-http`, `undertow`, `netty-http`. So with both camel-http and |
| camel-vertx-http on the classpath, camel-http is used unless you name |
| the other. The Camel CLI (`camel run`) adds camel-http for a |
| rest-openapi producer. |
| |
| This component's endpoint URI is lenient which means that in addition |
| to message headers you can specify REST operation's parameters as |
| endpoint parameters, these will be constant for all subsequent |
| invocations, so it makes sense to use this feature only for parameters |
| that are indeed constant for all invocations -- for example API version |
| in path such as `/api/\{version}/users/\{id}`. |
| |
| // component options: START |
| include::partial$component-configure-options.adoc[] |
| include::partial$component-endpoint-options.adoc[] |
| include::partial$component-endpoint-headers.adoc[] |
| // component options: END |
| |
| == Usage |
| |
| === Contract-first REST DSL |
| |
| To serve the operations of an OpenAPI contract, use the Rest DSL in _contract-first_ mode rather than a |
| `rest-openapi` consumer endpoint. Camel loads the contract at startup and routes every operation to the |
| route that starts from `direct:` and its _operation id_; see |
| xref:manual::rest-dsl-openapi.adoc[REST DSL with contract-first OpenAPI]. |
| |
| [tabs] |
| ==== |
| Java:: |
| + |
| [source,java] |
| ---- |
| rest().openApi("petstore-v3.json"); |
| |
| from("direct:getPetById") |
| .setBody(simple("{\"id\": ${header.petId}, \"name\": \"Rex\"}")); |
| ---- |
| |
| XML:: |
| + |
| [source,xml] |
| ---- |
| <rest> |
| <openApi specification="petstore-v3.json"/> |
| </rest> |
| <route> |
| <from uri="direct:getPetById"/> |
| <setBody> |
| <simple>{"id": ${header.petId}, "name": "Rex"}</simple> |
| </setBody> |
| </route> |
| ---- |
| |
| YAML:: |
| + |
| [source,yaml] |
| ---- |
| - rest: |
| openApi: |
| specification: petstore-v3.json |
| - route: |
| from: |
| uri: direct:getPetById |
| steps: |
| - setBody: |
| simple: '{"id": ${header.petId}, "name": "Rex"}' |
| ---- |
| ==== |
| |
| === Calling an operation of an API |
| |
| To call an API by its contract, send to a `rest-openapi` endpoint with the contract and the _operation id_. Camel |
| builds the HTTP request from the contract: |
| |
| * a path or query parameter, such as `+{petId}+` in `+/pet/{petId}+`, is taken from the message header of the same name; |
| * the message body is the request body (a JSON string for a JSON API, with a `Content-Type` header); |
| * the response body becomes the message body, as text: unmarshal it to read its fields. |
| |
| The server comes from the `servers` of the contract; set `host` to call another one (for example |
| `http://localhost:8080` while developing). The HTTP call itself is done by an HTTP component on the classpath, such |
| as `camel-http`; set `componentName` when there is more than one. |
| |
| [tabs] |
| ==== |
| Java:: |
| + |
| [source,java] |
| ---- |
| from("timer:pets?repeatCount=1") |
| .setHeader("petId", constant(10)) |
| .to("rest-openapi:petstore-v3.json#getPetById") |
| .unmarshal().json() |
| .log("Pet ${body[name]}") |
| .setBody(constant("{\"id\": 11, \"name\": \"Rex\"}")) |
| .setHeader(Exchange.CONTENT_TYPE, constant("application/json")) |
| .to("rest-openapi:petstore-v3.json#addPet?host={{petstore.url}}") |
| .log("Added ${body}"); |
| ---- |
| |
| XML:: |
| + |
| [source,xml] |
| ---- |
| <route> |
| <from uri="timer:pets?repeatCount=1"/> |
| <setHeader name="petId"> |
| <constant>10</constant> |
| </setHeader> |
| <to uri="rest-openapi:petstore-v3.json#getPetById"/> |
| <unmarshal> |
| <json/> |
| </unmarshal> |
| <log message="Pet ${body[name]}"/> |
| <setBody> |
| <constant>{"id": 11, "name": "Rex"}</constant> |
| </setBody> |
| <setHeader name="Content-Type"> |
| <constant>application/json</constant> |
| </setHeader> |
| <to uri="rest-openapi:petstore-v3.json#addPet?host={{petstore.url}}"/> |
| <log message="Added ${body}"/> |
| </route> |
| ---- |
| |
| YAML:: |
| + |
| [source,yaml] |
| ---- |
| - route: |
| from: |
| uri: timer:pets |
| parameters: |
| repeatCount: 1 |
| steps: |
| - setHeader: |
| name: petId |
| expression: |
| constant: |
| expression: "10" |
| - to: |
| uri: rest-openapi |
| parameters: |
| specificationUri: petstore-v3.json |
| operationId: getPetById |
| - unmarshal: |
| json: {} |
| - log: |
| message: "Pet ${body[name]}" |
| - setBody: |
| expression: |
| constant: |
| expression: '{"id": 11, "name": "Rex"}' |
| - setHeader: |
| name: Content-Type |
| expression: |
| constant: |
| expression: application/json |
| - to: |
| uri: rest-openapi |
| parameters: |
| specificationUri: petstore-v3.json |
| operationId: addPet |
| host: "{{petstore.url}}" |
| - log: |
| message: "Added ${body}" |
| ---- |
| ==== |
| |
| An answer with an error status (a `404` or a `409`) fails the exchange with an `HttpOperationFailedException`; handle |
| it with `onException` to read `${exception.statusCode}` and `${exception.responseBody}`. |
| |
| === Consumer OAuth Bearer token validation |
| |
| When `rest-openapi` is used as a consumer, HTTP requests are served by the selected |
| `RestOpenApiConsumerFactory` delegate, such as the built-in `platform-http` delegate or a custom |
| delegate. |
| To validate incoming `Authorization: Bearer` tokens from Rest DSL contract-first routes, pass the |
| delegate endpoint option `oauthProfile` through the REST configuration endpoint properties. |
| |
| ._Java-only: configuring REST with OAuth profile_ |
| [source,java] |
| ---- |
| restConfiguration() |
| .component("platform-http") |
| .endpointProperty("oauthProfile", "myprofile"); |
| |
| rest().openApi("petstore-v3.json"); |
| ---- |
| |
| For direct `rest-openapi` consumer routes, pass the delegate endpoint option directly on the |
| consumer endpoint URI. Consumer URIs identify the OpenAPI specification and do not include an |
| `#operationId` fragment. |
| |
| [tabs] |
| ==== |
| Java:: |
| + |
| [source,java] |
| ---- |
| from("rest-openapi:petstore-v3.json?consumerComponentName=platform-http&oauthProfile=myprofile") |
| .to("direct:businessLogic"); |
| ---- |
| |
| XML:: |
| + |
| [source,xml] |
| ---- |
| <route> |
| <from uri="rest-openapi:petstore-v3.json?consumerComponentName=platform-http&oauthProfile=myprofile"/> |
| <to uri="direct:businessLogic"/> |
| </route> |
| ---- |
| |
| YAML:: |
| + |
| [source,yaml] |
| ---- |
| - route: |
| from: |
| uri: rest-openapi:petstore-v3.json |
| parameters: |
| consumerComponentName: platform-http |
| oauthProfile: myprofile |
| steps: |
| - to: |
| uri: direct:businessLogic |
| ---- |
| ==== |
| |
| The selected delegate component must support the `oauthProfile` endpoint option. The built-in |
| `platform-http` delegate supports this option. An `OAuthTokenValidationFactory` must be available, for example from |
| xref:components:others:oauth.adoc[camel-oauth] or from the runtime integration. |
| OpenAPI `securitySchemes` and operation security requirements are not converted into `oauthProfile` |
| configuration; select the OAuth profile explicitly with the REST endpoint property or direct endpoint |
| URI option. |
| The `oauthProfile` option is a first-class `rest-openapi` endpoint option that is forwarded to the |
| selected delegate, which is responsible for enforcing it. The route fails at startup when the |
| resolved `RestOpenApiConsumerFactory` does not declare that its consumers enforce `oauthProfile`, |
| so a misconfigured delegate cannot start without the expected protection. Custom factories that |
| enforce the option must override `RestOpenApiConsumerFactory.supportsOAuthProfile()` to return |
| `true`. |
| |
| == Request validation |
| |
| API requests can be validated against the configured OpenAPI specification before they are sent by setting the `requestValidationEnabled` option to `true`. |
| Validation is provided by the https://bitbucket.org/atlassian/swagger-request-validator/src/master/[swagger-request-validator]. |
| |
| The validator checks for the following conditions: |
| |
| * request body - Checks if the request body is required and whether there is any body on the Camel Exchange. |
| |
| * valid json - Checks if the content-type is `application/json` that the message body can be parsed as valid JSon. |
| |
| * content-type - Validates whether the `Content-Type` header for the request is valid for the API operation. |
| The value is taken from the `Content-Type` Camel message exchange header. |
| |
| * request parameters - Validates whether an HTTP header required by the API operation is present. The header is |
| expected to be present among the Camel message exchange headers. |
| |
| * query parameters - Validates whether an HTTP query parameter required by the API operation is present. The query parameter is |
| expected to be present among the Camel message exchange headers. |
| |
| * path parameters - Validates that every parameter in the path of the API operation, such as `sku` in |
| `/stock/\{sku}/reserve`, has a value: a Camel message exchange header or an exchange variable of that name. Without |
| the check, a missing value is sent as the literal `\{sku}` in the path. |
| |
| If any of the validation checks fail, then a `RestOpenApiValidationException` is thrown. The exception object |
| 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 `RestUnmatchedRequestHandler` 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 is answered by the unmatched request handler, so controllers and static |
| resources are shadowed. When the application also serves MVC content, prefer 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 |
| register your own `RequestMappingHandlerMapping` bean with its order set to `-50`. This still does not work for |
| serving static resources. |
| |
| When the request has been routed to Camel, the response body (and headers) can be customized by registering a |
| bean in the xref:manual::registry.adoc[Registry] that implements the `RestUnmatchedRequestHandler` interface. |
| The handler is called with the exchange, the status code (`404`, `405`, `415` or `406`) and the list of allowed HTTP |
| methods (empty for `404`, `415` and `406`), and can then set the response body, status code and headers as needed. |
| The handler can also be registered using a factory finder on the classpath. This is done by adding a |
| resource file `META-INF/services/org/apache/camel/rest-unmatched-request-handler-factory` |
| with the content `class=com.example.MyHandler`. |
| |
| A single handler bean in the registry takes precedence over a handler found via the factory finder, which in |
| turn takes precedence over the default handler. Regardless of how the handler is registered *only one* handler |
| is used: when two or more beans of this type are found in the registry, the handler from the factory finder is |
| used (if present), otherwise the default handler is used. |
| |
| [source,java] |
| ---- |
| public class JsonErrorHandler implements RestUnmatchedRequestHandler { |
| |
| @Override |
| public void handle(Exchange exchange, int statusCode, List<String> allowedMethods) { |
| String message = switch (statusCode) { |
| case 404 -> "The requested resource was not found."; |
| case 405 -> "The HTTP method is not allowed for this resource."; |
| case 415 -> "The Content-Type of the request is not supported by this resource."; |
| case 406 -> "No representation matching the Accept header can be produced for this resource."; |
| default -> "The request cannot be processed."; |
| }; |
| |
| exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, statusCode); |
| exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, "application/json"); |
| if (!allowedMethods.isEmpty()) { |
| exchange.getMessage().setHeader("Allow", String.join(", ", allowedMethods)); |
| } |
| exchange.getMessage().setBody(String.format( |
| "{\"message\":\"%s\"}", message)); |
| } |
| } |
| ---- |
| |
| |
| == Examples |
| |
| === PetStore |
| |
| Checkout the `rest-openapi-simple` example project in |
| the https://github.com/apache/camel-spring-boot-examples[camel-spring-boot-examples] repository. |
| |
| For example, if you wanted to use the |
| https://petstore3.swagger.io/api/v3/[_PetStore_] provided REST API simply |
| reference the specification URI and desired operation id from the |
| OpenApi specification or download the specification and store it as |
| `openapi.json` (in the root) of `_CLASSPATH_` that way it will be |
| automatically used. Let's use the xref:http-component.adoc[HTTP] |
| component to perform all the requests and Camel's excellent support for |
| Spring Boot. |
| |
| Here are our dependencies defined in Maven POM file: |
| |
| .Example pom.xml |
| [source,xml] |
| ---- |
| <dependency> |
| <groupId>org.apache.camel.springboot</groupId> |
| <artifactId>camel-http-starter</artifactId> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.apache.camel.springboot</groupId> |
| <artifactId>camel-rest-openapi-starter</artifactId> |
| </dependency> |
| ---- |
| |
| Start by defining a `RestOpenApiComponent` bean: |
| |
| ._Java-only: defining a RestOpenApiComponent bean for PetStore_ |
| [source,java] |
| ---- |
| @Bean |
| public Component petstore(CamelContext camelContext) { |
| RestOpenApiComponent petstore = new RestOpenApiComponent(camelContext); |
| petstore.setSpecificationUri("https://petstore3.swagger.io/api/v3/openapi.json"); |
| petstore.setHost("https://petstore3.swagger.io"); |
| return petstore; |
| } |
| ---- |
| |
| [NOTE] |
| ==== |
| Support in Camel for Spring Boot will auto create the |
| `HttpComponent` Spring bean, and you can configure it using |
| `application.properties` (or `application.yml`) using prefix |
| `camel.component.http.`. We are defining the `petstore` |
| component here to have a named component in the Camel context |
| that we can use to interact with the PetStore REST API, if this is the |
| only `rest-openapi` component used we might configure it in the same |
| manner (using `application.properties`). |
| |
| In this example, there is no need to explicitly associate the `petstore` |
| component with the `HttpComponent` as Camel will use the first class on |
| the `_CLASSPATH_` that implements `RestProducerFactory`. However, if a different |
| component is required, then calling `petstore.setComponentName("http")` |
| would use the named component from the Camel registry. |
| |
| ==== |
| |
| Now in our application we can simply use the `ProducerTemplate` to |
| invoke PetStore REST methods: |
| |
| ._Java-only: invoking PetStore REST methods with ProducerTemplate_ |
| [source,java] |
| ---- |
| @Autowired |
| ProducerTemplate template; |
| |
| String getPetJsonById(int petId) { |
| return template.requestBodyAndHeader("petstore:getPetById", null, "petId", petId); |
| } |
| ---- |