<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-openapi</artifactId>
<version>4.2.7</version>
</dependency>
Vert.x OpenAPI
Vert.x OpenAPI extends Vert.x Web to support OpenAPI 3, bringing to you a simple interface to build a Vert.x Web Router conforming your API contract.
Vert.x OpenAPI can:
-
Parse and validate the your OpenAPI 3 contract
-
Generate a router according to your spec, with correct path & methods
-
Provide request parsing and validation based on your contract using Vert.x Web Validation
-
Mount required security handlers
-
Path conversion between OpenAPI style and Vert.x style
-
Route requests to event bus using Vert.x Web API Service
Using Vert.x OpenAPI
To use Vert.x OpenAPI, add the following dependency to the dependencies section of your build descriptor:
-
Maven (in your
pom.xml
):
-
Gradle (in your
build.gradle
file):
dependencies {
compile 'io.vertx:vertx-web-openapi:4.2.7'
}
RouterBuilder
RouterBuilder
is the main element of this module: It provides the interface to mount request handlers and generates the final Router
To start using Vert.x Web OpenAPI, you must instantiate RouterBuilder
with your contract using RouterBuilder.create
For example to load a spec from the local filesystem:
RouterBuilder.create(vertx, "src/main/resources/petstore.yaml")
.onSuccess(routerBuilder -> {
// Spec loaded with success
})
.onFailure(err -> {
// Something went wrong during router builder initialization
});
You can construct a router builder from a remote spec:
RouterBuilder.create(
vertx,
"https://raw.githubusercontent" +
".com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml")
.onSuccess(routerBuilder -> {
// Spec loaded with success
})
.onFailure(err -> {
// Something went wrong during router builder initialization
});
You can access a private remote spec configuring OpenAPILoaderOptions
:
OpenAPILoaderOptions loaderOptions = new OpenAPILoaderOptions()
.putAuthHeader("Authorization", "Bearer xx.yy.zz");
RouterBuilder.create(
vertx,
"https://raw.githubusercontent" +
".com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml",
loaderOptions)
.onSuccess(routerBuilder -> {
// Spec loaded with success
})
.onFailure(err -> {
// Something went wrong during router builder initialization
});
You can modify different behaviours of the router builder with RouterBuilderOptions
:
routerBuilder.setOptions(new RouterBuilderOptions());
Access to operations
To access to an Operation defined in contract, use operation
. This method returns an Operation
instance that you can use to both access the model and assign handlers
To mount an handler to an operation use handler
, to mount a failure handler use failureHandler
You can add multiple handlers to same operation, without overwrite the existing ones.
For example:
routerBuilder
.operation("awesomeOperation")
.handler(routingContext -> {
RequestParameters params =
routingContext.get(ValidationHandler.REQUEST_CONTEXT_KEY);
RequestParameter body = params.body();
JsonObject jsonBody = body.getJsonObject();
// Do something with body
}).failureHandler(routingContext -> {
// Handle failure
});
You can’t access to contract operations without |
Vert.x OpenAPI mounts the correct ValidationHandler
for you, so you can access to request parameters and request body. Refer to Vert.x Web Validation documentation to learn how to get request parameters & request body and how to manage validation failures
Configuring `AuthenticationHandler`s defined in the OpenAPI document
Security is a important aspect of any API. OpenAPI defines how security is expected to be enforced in the api document.
All security scheme information resided under the /components/securitySchemes
component. The information in this object is different and specific for each type of authentication. To avoid double configuration, this module allows you to provide factories for authentication handlers that will receive the source configuration from the source document.
For example, given an document that defines Basic Authentication
as follows:
openapi: 3.0.0 ... components: securitySchemes: basicAuth: # <-- arbitrary name for the security scheme type: http scheme: basic
This can be configured with a factory as:
routerBuilder
.securityHandler("basicAuth")
.bindBlocking(config -> BasicAuthHandler.create(authProvider));
While this example is quite simple to configure, creating an authentication handler that requires the configuration such as the API Key handler can extract the config:
openapi: 3.0.0 ... # 1) Define the key name and location components: securitySchemes: ApiKeyAuth: # arbitrary name for the security scheme type: apiKey in: header # can be "header", "query" or "cookie" name: X-API-KEY # name of the header, query parameter or cookie
routerBuilder
.securityHandler("ApiKeyAuth")
.bindBlocking(config ->
APIKeyHandler.create(authProvider)
.header(config.getString("name")));
Or you can configure more complex scenarios such as OpenId Connect which require server discovery.
openapi: 3.0.0 ... # 1) Define the security scheme type and attributes components: securitySchemes: openId: # <--- Arbitrary name for the security scheme. Used to refer to it from elsewhere. type: openIdConnect openIdConnectUrl: https://example.com/.well-known/openid-configuration
routerBuilder
.securityHandler("openId")
.bind(config ->
OpenIDConnectAuth
.discover(vertx, new OAuth2Options()
.setClientId("client-id") // user provided
.setClientSecret("client-secret") // user provided
.setSite(config.getString("openIdConnectUrl")))
.compose(authProvider -> {
AuthenticationHandler handler =
OAuth2AuthHandler.create(vertx, authProvider);
return Future.succeededFuture(handler);
}))
.onSuccess(self -> {
// Creation completed with success
})
.onFailure(err -> {
// Something went wrong
});
The API is designed to be fluent so it can be used in a short notation, for example:
routerBuilder
.securityHandler("api_key")
.bindBlocking(config -> APIKeyHandler.create(authProvider))
.operation("listPetsSingleSecurity")
.handler(routingContext -> {
routingContext
.response()
.setStatusCode(200)
.setStatusMessage("Cats and Dogs")
.end();
});
// non-blocking bind
routerBuilder
.securityHandler("oauth")
.bind(config -> OpenIDConnectAuth.discover(vertx, new OAuth2Options(config))
.compose(oidc -> Future.succeededFuture(
OAuth2AuthHandler.create(vertx, oidc))))
.onSuccess(self -> {
self
.operation("listPetsSingleSecurity")
.handler(routingContext -> {
routingContext
.response()
.setStatusCode(200)
.setStatusMessage("Cats and Dogs")
.end();
});
});
Blocking vs NonBlocking
From the examples above it is noticeable that handlers can be added in a blocking or not blocking way. The reason for non blocking way usage is not just to support handlers like OAuth2
. The non-blocking way can be useful for handlers like JWT or basic authentication where the authentication provider requires loading of keys or configuration files.
Here is an example with JWT:
routerBuilder
.securityHandler("oauth")
.bind(config ->
// as we don't want to block while reading the
// public key, we use the non blocking bind
vertx.fileSystem()
.readFile("public.key")
// we map the future to a authentication provider
.map(key ->
JWTAuth.create(vertx, new JWTAuthOptions()
.addPubSecKey(new PubSecKeyOptions()
.setAlgorithm("RS256")
.setBuffer(key))))
// and map again to create the final handler
.map(JWTAuthHandler::create))
.onSuccess(self ->
self
.operation("listPetsSingleSecurity")
.handler(routingContext -> {
routingContext
.response()
.setStatusCode(200)
.setStatusMessage("Cats and Dogs")
.end();
}));
Map AuthenticationHandler
to OpenAPI security schemes
You have seen how you can map an AuthenticationHandler
to a security schema defined in the contract. The previous examples are validating and will fail your route builder if the configuration is missing.
There could be cases where the contract is incomplete and you explicitly want to define security handlers. In this case the API is slightly different and will not enforce any contract validation. Yet, the security handlers will be available to the builder regardless.
For example, given your contract has a security schema named security_scheme_name
:
routerBuilder.securityHandler(
"security_scheme_name",
authenticationHandler);
You can mount AuthenticationHandler
included in Vert.x Web, for example:
routerBuilder.securityHandler("jwt_auth",
JWTAuthHandler.create(jwtAuthProvider));
When you generate the Router
the router builder will solve the security schemes required for an operation. It fails if there is a missing AuthenticationHandler
required by a configured operation.
For debugging/testing purpose you can disable this check with setRequireSecurityHandlers
Not Implemented Error
Router builder automatically mounts a default handler for operations without a specified handler. This default handler fails the routing context with 405 Method Not Allowed
/501 Not Implemented
error. You can enable/disable it with setMountNotImplementedHandler
and you can customize this error handling with errorHandler
Response Content Type Handler
Router builder automatically mounts a ResponseContentTypeHandler
handler when contract requires it. You can disable this feature with setMountResponseContentTypeHandler
Operation model
If you need to access to your operation model while handling the request, you can configure the router builder to push it inside the RoutingContext
with setOperationModelKey
:
options.setOperationModelKey("operationModel");
routerBuilder.setOptions(options);
// Add an handler that uses the operation model
routerBuilder
.operation("listPets")
.handler(
routingContext -> {
JsonObject operation = routingContext.get("operationModel");
routingContext
.response()
.setStatusCode(200)
.setStatusMessage("OK")
// Write the response with operation id "listPets"
.end(operation.getString("operationId"));
});
Body Handler
Router builder automatically mounts a BodyHandler
to manage request bodies. You can configure the instance of BodyHandler
(e.g. to change upload directory) with bodyHandler
.
multipart/form-data
validation
The validation handler separates file uploads and form attributes as explained:
-
If the parameter doesn’t have an encoding associated field:
-
If the parameter has
type: string
andformat: base64
orformat: binary
is a file upload with content-typeapplication/octet-stream
-
Otherwise is a form attribute
-
-
If the parameter has the encoding associated field is a file upload
The form attributes are parsed, converted in json and validated, while for file uploads the validation handler just checks the existence and the content type.
Custom global handlers
If you need to mount handlers that must be executed for each operation in your router before the operation specific handlers, you can use rootHandler
Router builder handlers mount order
Handlers are loaded by the router builder in this order:
-
Body handler
-
Custom global handlers
-
Configured `AuthenticationHandler`s
-
Generated
ValidationHandler
-
User handlers or "Not implemented" handler (if enabled)
Generate the router
When you are ready, generate the router and use it:
Router router = routerBuilder.createRouter();
HttpServer server =
vertx.createHttpServer(new HttpServerOptions().setPort(8080).setHost(
"localhost"));
server.requestHandler(router).listen();
This method can fail with a RouterBuilderException
.
If you need to mount all the router generated by router builder under the same parent path, you can use |