Using Reactive Routes

Reactive 路由提出了一种实现 HTTP 端点的替代方法,你可以在其中声明和链接 routes。此方法在 JavaScript 世界中非常流行,具有 Express.Js 或 Hapi 之类的框架。Quarkus 也提供了使用 Reactive 路由的可能性。你可以仅使用路由实现 REST API,或将其与 Jakarta REST 资源和 servlet 结合使用。

Reactive routes propose an alternative approach to implement HTTP endpoints where you declare and chain routes. This approach became very popular in the JavaScript world, with frameworks like Express.Js or Hapi. Quarkus also offers the possibility to use reactive routes. You can implement REST API with routes only or combine them with Jakarta REST resources and servlets.

本指南中提供的代码可在 reactive-routes-quickstart directory 下的此 {quickstarts-base-url}[GitHub 存储库] 中获取

The code presented in this guide is available in this {quickstarts-base-url}[GitHub repository] under the reactive-routes-quickstart directory

Reactive 路由最初引入是为了在 Quarkus Reactive Architecture 之上为 HTTP API 提供一个 Reactive 执行模型。通过引入 Quarkus REST (formerly RESTEasy Reactive),你现在可以实现 Reactive HTTP API 并仍然使用 Jakarta REST 注释。Reactive 路由仍然受到支持,尤其是如果你想要一种更 route-based 的方法,以及更接近于底层 Reactive 引擎的东西。

Reactive Routes were initially introduced to provide a reactive execution model for HTTP APIs on top of the Quarkus Reactive Architecture. With the introduction of Quarkus REST (formerly RESTEasy Reactive), you can now implement reactive HTTP APIs and still use Jakarta REST annotations. Reactive Routes are still supported, especially if you want a more route-based approach, and something closer to the underlying reactive engine.

Quarkus HTTP

在继续之前,让我们来看看 Quarkus 的 HTTP 层。Quarkus HTTP 支持基于一个非阻塞和 Reactive 引擎(Eclipse Vert.x 和 Netty)。你的应用程序接收的所有 HTTP 请求都由 event loops(I/O 线程)处理,然后被路由到管理请求的代码。根据目标,它可以在工作线程(Servlet,Jax-RS)上调用管理请求的代码或使用 IO 线程(Reactive 路由)。请注意,由于这个原因,一个 Reactive 路由必须是非阻塞的或明确声明其阻塞性质(这将导致在工作线程上调用)。

Before going further, let’s have a look at the HTTP layer of Quarkus. Quarkus HTTP support is based on a non-blocking and reactive engine (Eclipse Vert.x and Netty). All the HTTP requests your application receive are handled by event loops (I/O Thread) and then are routed towards the code that manages the request. Depending on the destination, it can invoke the code managing the request on a worker thread (Servlet, Jax-RS) or use the IO Thread (reactive route). Note that because of this, a reactive route must be non-blocking or explicitly declare its blocking nature (which would result by being called on a worker thread).

http architecture

请参阅 Quarkus Reactive Architecture documentation以了解有关此主题的更多详细信息。

See the Quarkus Reactive Architecture documentation for further details on this topic.

Declaring reactive routes

使用 Reactive 路由的第一种方法是使用 @Route 注释。要访问此注释,你需要通过运行此命令添加 quarkus-reactive-routes 扩展:

The first way to use reactive routes is to use the @Route annotation. To have access to this annotation, you need to add the quarkus-reactive-routes extension by running this command:

Unresolved directive in reactive-routes.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到构建文件中:

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-reactive-routes</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-reactive-routes")

然后在 bean 中,你可以按如下方式使用 @Route 注释:

Then in a bean, you can use the @Route annotation as follows:

package org.acme.reactive.routes;

import io.quarkus.vertx.web.Route;
import io.quarkus.vertx.web.Route.HttpMethod;
import io.quarkus.vertx.web.RoutingExchange;
import io.vertx.ext.web.RoutingContext;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped (1)
public class MyDeclarativeRoutes {

    // neither path nor regex is set - match a path derived from the method name
    @Route(methods = Route.HttpMethod.GET) (2)
    void hello(RoutingContext rc) { (3)
        rc.response().end("hello");
    }

    @Route(path = "/world")
    String helloWorld() { (4)
        return "Hello world!";
    }

    @Route(path = "/greetings", methods = Route.HttpMethod.GET)
    void greetingsQueryParam(RoutingExchange ex) { (5)
        ex.ok("hello " + ex.getParam("name").orElse("world")); (6)
    }

    @Route(path = "/greetings/:name", methods = Route.HttpMethod.GET) (7)
    void greetingsPathParam(@Param String name, RoutingExchange ex) {
        ex.ok("hello " + name);
    }
}
1 If there is a reactive route found on a class with no scope annotation then @jakarta.inject.Singleton is added automatically.
2 The @Route annotation indicates that the method is a reactive route. Again, by default, the code contained in the method must not block.
3 The method gets a RoutingContext as a parameter. From the RoutingContext you can retrieve the HTTP request (using request()) and write the response using response().end(…​).
4 If the annotated method does not return void the arguments are optional.
5 RoutingExchange is a convenient wrapper of RoutingContext which provides some useful methods.
6 The RoutingExchange is used to retrieve the request query parameter name.
7 The path defines a parameter name which can be injected inside the method parameters using the annotation @Param.

有关使用 RoutingContext 的更多详细信息,请查阅 Vert.x Web documentation

More details about using the RoutingContext is available in the Vert.x Web documentation.

@Route 注释允许您配置:

The @Route annotation allows you to configure:

  • The path - for routing by path, using the Vert.x Web format

  • The regex - for routing with regular expressions, see for more details

  • The methods - the HTTP verbs triggering the route such as GET, POST…​

  • The type - it can be normal (non-blocking), blocking (method dispatched on a worker thread), or failure to indicate that this route is called on failures

  • The order - the order of the route when several routes are involved in handling the incoming request. Must be positive for regular user routes.

  • The produced and consumed mime types using produces, and consumes

例如,您可以按如下方式声明一个阻塞路由:

For instance, you can declare a blocking route as follows:

@Route(methods = HttpMethod.POST, path = "/post", type = Route.HandlerType.BLOCKING)
public void blocking(RoutingContext rc) {
    // ...
}

或者,您可以使用 @io.smallrye.common.annotation.Blocking 并省略 type = Route.HandlerType.BLOCKING

Alternatively, you can use @io.smallrye.common.annotation.Blocking and omit the type = Route.HandlerType.BLOCKING:

@Route(methods = HttpMethod.POST, path = "/post")
@Blocking
public void blocking(RoutingContext rc) {
    // ...
}

使用 @Blocking 时,将忽略 type 中的 @Route 属性。

When @Blocking is used, the type attribute of the @Route is ignored.

@Route 注释是可重复的,因此您可以为单个方法声明多个路由:

The @Route annotation is repeatable and so you can declare several routes for a single method:

@Route(path = "/first") 1
@Route(path = "/second")
public void route(RoutingContext rc) {
    // ...
}
1 Each route can use different paths, methods…​

如果没有设置 content-type 头,我们将尝试使用最可接受的内容类型,方法是将 accept 头与 Route`produces 属性的值进行匹配,如 `io.vertx.ext.web.RoutingContext.getAcceptableContentType() 所定义。

If no content-type header is set, then we will try to use the most acceptable content type by matching the accept header with the value of the Route produces attribute as defined by io.vertx.ext.web.RoutingContext.getAcceptableContentType().

@Route(path = "/person", produces = "text/html") 1
String person() {
    // ...
}
1 If the accept header matches text/html, we set the content type automatically to text/html.

Executing route on a virtual thread

您可以使用 @io.smallrye.common.annotation.RunOnVirtualThread 注释路由方法,以便在虚拟线程上执行该路由。但是,请记住并非所有内容都可以在虚拟线程上安全运行。您应该仔细阅读 Virtual thread support reference 并了解所有详细信息。

You can annotate a route method with @io.smallrye.common.annotation.RunOnVirtualThread in order to execute it on a virtual thread. However, keep in mind that not everything can run safely on virtual threads. You should read the Virtual thread support reference carefully and get acquainted with all the details.

Handling conflicting routes

您最终可能会得到多个与给定路径匹配的路由。在以下示例中,两个路由都与 /accounts/me 匹配:

You may end up with multiple routes matching a given path. In the following example, both route matches /accounts/me:

@Route(path = "/accounts/:id", methods = HttpMethod.GET)
void getAccount(RoutingContext rc) {
  ...
}

@Route(path = "/accounts/me", methods = HttpMethod.GET)
void getCurrentUserAccount(RoutingContext rc) {
  ...
}

结果上,结果并不是预期的,因为第一个路由带有路径参数 id,该参数设置为 me。要避免冲突,请使用 order 属性:

As a consequence, the result is not the expected one as the first route is called with the path parameter id set to me. To avoid the conflict, use the order attribute:

@Route(path = "/accounts/:id", methods = HttpMethod.GET, order = 2)
void getAccount(RoutingContext rc) {
  ...
}

@Route(path = "/accounts/me", methods = HttpMethod.GET, order = 1)
void getCurrentUserAccount(RoutingContext rc) {
  ...
}

通过为第二个路由提供较低的顺序,它会先得到评估。如果请求路径匹配,则会调用它,否则会评估其他路由。

By giving a lower order to the second route, it gets evaluated first. If the request path matches, it is invoked, otherwise the other routes are evaluated.

@RouteBase

该注解可用于配置一个类上声明的响应式的路由的一些默认值。

This annotation can be used to configure some defaults for reactive routes declared on a class.

@RouteBase(path = "simple", produces = "text/plain") 1 2
public class SimpleRoutes {

    @Route(path = "ping") // the final path is /simple/ping
    void ping(RoutingContext rc) {
        rc.response().end("pong");
    }
}
1 The path value is used as a prefix for any route method declared on the class where Route#path() is used.
2 The value of produces() is used for content-based routing for all routes where Route#produces() is empty.

Reactive Route Methods

路由方法必须是 CDI bean 的非私有的非静态方法。如果带注解的方法返回 void,则它必须接受至少一个参数,请参阅下方的受支持类型。如果带注解的方法不返回 void,则参数是可选项。

A route method must be a non-private non-static method of a CDI bean. If the annotated method returns void then it has to accept at least one argument - see the supported types below. If the annotated method does not return void then the arguments are optional.

返回 void 的方法必须 end 响应,否则对该路由的 HTTP 请求永远不会结束。RoutingExchange 的一些方法可以为您执行此操作,另一些方法不能,您必须自己调用响应的 end() 方法,请参阅其 JavaDoc 以了解更多信息。

Methods that return void must end the response or the HTTP request to this route will never end. Some methods of RoutingExchange do it for you, others not and you must call the end() method of the response by yourself, please refer to its JavaDoc for more information.

路由方法可以接受以下类型的参数:

A route method can accept arguments of the following types:

  • io.vertx.ext.web.RoutingContext

  • io.quarkus.vertx.web.RoutingExchange

  • io.vertx.core.http.HttpServerRequest

  • io.vertx.core.http.HttpServerResponse

  • io.vertx.mutiny.core.http.HttpServerRequest

  • io.vertx.mutiny.core.http.HttpServerResponse

此外,可以使用以下类型将 HttpServerRequest 参数注入带 @io.quarkus.vertx.web.Param 注解的方法参数中:

Furthermore, it is possible to inject the HttpServerRequest parameters into method parameters annotated with @io.quarkus.vertx.web.Param using the following types:

Parameter Type Obtained via

java.lang.String

routingContext.request().getParam()

java.util.Optional<String>

routingContext.request().getParam()

java.util.List<String>

routingContext.request().params().getAll()

Request Parameter Example
@Route
String hello(@Param Optional<String> name) {
   return "Hello " + name.orElse("world");
}

可以使用以下类型将 HttpServerRequest 头信息注入带 @io.quarkus.vertx.web.Header 注解的方法参数中:

The HttpServerRequest headers can be injected into method parameters annotated with @io.quarkus.vertx.web.Header using the following types:

Parameter Type Obtained via

java.lang.String

routingContext.request().getHeader()

java.util.Optional<String>

routingContext.request().getHeader()

java.util.List<String>

routingContext.request().headers().getAll()

Request Header Example
@Route
String helloFromHeader(@Header("My-Header") String header) {
   return header;
}

可以使用以下类型将请求主体注入带 @io.quarkus.vertx.web.Body 注解的方法参数中:

The request body can be injected into a method parameter annotated with @io.quarkus.vertx.web.Body using the following types:

Parameter Type Obtained via

java.lang.String

routingContext.getBodyAsString()

io.vertx.core.buffer.Buffer

routingContext.getBody()

io.vertx.core.json.JsonObject

routingContext.getBodyAsJson()

io.vertx.core.json.JsonArray

routingContext.getBodyAsJsonArray()

any other type

routingContext.getBodyAsJson().mapTo(MyPojo.class)

Request Body Example
@Route(produces = "application/json")
Person createPerson(@Body Person person, @Param("id") Optional<String> primaryKey) {
  person.setId(primaryKey.map(Integer::valueOf).orElse(42));
  return person;
}

失败处理程序可以声明一个其类型扩展 Throwable 的单方法参数。参数的类型用于与 RoutingContext#failure() 的结果匹配。

A failure handler can declare a single method parameter whose type extends Throwable. The type of the parameter is used to match the result of RoutingContext#failure().

Failure Handler Example
@Route(type = HandlerType.FAILURE)
void unsupported(UnsupportedOperationException e, HttpServerResponse response) {
  response.setStatusCode(501).end(e.getMessage());
}

Returning Unis

在响应式路由中,您可以直接返回一个 Uni

In a reactive route, you can return a Uni directly:

@Route(path = "/hello")
Uni<String> hello() {
    return Uni.createFrom().item("Hello world!");
}

@Route(path = "/person")
Uni<Person> getPerson() {
    return Uni.createFrom().item(() -> new Person("neo", 12345));
}

当使用响应式客户端时,返回 Unis 很方便:

Returning Unis is convenient when using a reactive client:

@Route(path = "/mail")
Uni<Void> sendEmail() {
    return mailer.send(...);
}

返回的 Uni 生成的项目可以是:

The item produced by the returned Uni can be:

  • A string - written into the HTTP response directly.

  • A io.vertx.core.buffer.Buffer - written into the HTTP response directly.

  • An object - written into the HTTP response after having been encoded into JSON. The content-type header is set to application/json if not already set.

如果返回的 Uni 产生失败(或为 null),则写入一个 HTTP 500 响应。

If the returned Uni produces a failure (or is null), an HTTP 500 response is written.

返回一个 Uni<Void> 产生一个 204 响应(无内容)。

Returning a Uni<Void> produces a 204 response (no content).

Returning results

您也可以直接返回结果:

You can also return a result directly:

@Route(path = "/hello")
String helloSync() {
    return "Hello world";
}

注意,处理过程必须是 non-blocking ,因为反应式路由调用于 IO 线程。否则,将 type 注解的 @Route 属性设置为 Route.HandlerType.BLOCKING ,或使用 @io.smallrye.common.annotation.Blocking 注解。

Be aware, the processing must be non-blocking as reactive routes are invoked on the IO Thread. Otherwise, set the type attribute of the @Route annotation to Route.HandlerType.BLOCKING, or use the @io.smallrye.common.annotation.Blocking annotation.

该方法可以返回:

The method can return:

  • A string - written into the HTTP response directly.

  • A io.vertx.core.buffer.Buffer - written into the HTTP response directly.

  • An object - written into the HTTP response after having been encoded into JSON. The content-type header is set to application/json if not already set.

Returning Multis

反应式路由可以返回 Multi 。元素一项项写入响应中。响应 Transfer-Encoding 头部设置为 chunked

A reactive route can return a Multi. The items are written one by one, in the response. The response Transfer-Encoding header is set to chunked.

@Route(path = "/hello")
Multi<String> hellos() {
    return Multi.createFrom().items("hello", "world", "!");  (1)
}
1 Produces helloworld!

该方法可以返回:

The method can return:

  • A Multi<String> - the items are written one by one (one per chunk) in the response.

  • A Multi<Buffer> - the buffers are written one by one (one per chunk) without any processing.

  • A Multi<Object> - the items are encoded to JSON written one by one in the response.

@Route(path = "/people")
Multi<Person> people() {
    return Multi.createFrom().items(
            new Person("superman", 1),
            new Person("batman", 2),
            new Person("spiderman", 3));
}

之前的代码片段产生:

The previous snippet produces:

{"name":"superman", "id": 1} // chunk 1
{"name":"batman", "id": 2} // chunk 2
{"name":"spiderman", "id": 3} // chunk 3

Streaming JSON Array items

您可以返回 Multi 来生成 JSON 数组,数组中每个元素均来自此数组。响应按元素逐项写入客户端。要执行此操作,请将 produces 属性设置为 "application/json" (或 ReactiveRoutes.APPLICATION_JSON )。

You can return a Multi to produce a JSON Array, where every item is an item from this array. The response is written item by item to the client. To do that set the produces attribute to "application/json" (or ReactiveRoutes.APPLICATION_JSON).

@Route(path = "/people", produces = ReactiveRoutes.APPLICATION_JSON)
Multi<Person> people() {
    return Multi.createFrom().items(
            new Person("superman", 1),
            new Person("batman", 2),
            new Person("spiderman", 3));
}

之前的代码片段产生:

The previous snippet produces:

[
  {"name":"superman", "id": 1} // chunk 1
  ,{"name":"batman", "id": 2} // chunk 2
  ,{"name":"spiderman", "id": 3} // chunk 3
]

produces 属性为数组。当您传递单个值时,可以省略“{”和“}”。请注意, "application/json" 必须是数组中的第一个值。

The produces attribute is an array. When you pass a single value you can omit the "{" and "}". Note that "application/json" must be the first value in the array.

只有 Multi<String>Multi<Object>Multi<Void> 可以写入 JSON 数组。使用 Multi<Void> 会生成一个空数组。您无法使用 Multi<Buffer> 。如果你需要使用 Buffer ,请首先将内容转化为 JSON 或字符串表示。

Only Multi<String>, Multi<Object> and Multi<Void> can be written into the JSON Array. Using a Multi<Void> produces an empty array. You cannot use Multi<Buffer>. If you need to use Buffer, transform the content into a JSON or String representation first.

Deprecation of asJsonArray

ReactiveRoutes.asJsonArray 已弃用,因为它不兼容 Quarkus 的安全层。

The ReactiveRoutes.asJsonArray has been deprecated as it is not compatible with the security layer of Quarkus.

Event Stream and Server-Sent Event support

您可以返回 Multi 来生成事件源(服务器发送事件流)。要启用此功能,请将 produces 属性设置为 "text/event-stream" (或 ReactiveRoutes.EVENT_STREAM ),例如:

You can return a Multi to produce an event source (stream of server sent events). To enable this feature, set the produces attribute to "text/event-stream" (or ReactiveRoutes.EVENT_STREAM), such as in:

@Route(path = "/people", produces = ReactiveRoutes.EVENT_STREAM)
Multi<Person> people() {
    return Multi.createFrom().items(
            new Person("superman", 1),
            new Person("batman", 2),
            new Person("spiderman", 3));
}

该方法将生成:

This method would produce:

data: {"name":"superman", "id": 1}
id: 0

data: {"name":"batman", "id": 2}
id: 1

data: {"name":"spiderman", "id": 3}
id: 2

produces 属性为数组。当您传递单个值时,可以省略“{”和“}”。请注意, "text/event-stream" 必须是数组中的第一个值。

The produces attribute is an array. When you pass a single value you can omit the "{" and "}". Note that "text/event-stream" must be the first value in the array.

您还可以实现 io.quarkus.vertx.web.ReactiveRoutes.ServerSentEvent 接口,以自定义服务器发送事件的 eventid 部分:

You can also implement the io.quarkus.vertx.web.ReactiveRoutes.ServerSentEvent interface to customize the event and id section of the server sent event:

class PersonEvent implements ReactiveRoutes.ServerSentEvent<Person> {
    public String name;
    public int id;

    public PersonEvent(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public Person data() {
        return new Person(name, id); // Will be JSON encoded
    }

    @Override
    public long id() {
        return id;
    }

    @Override
    public String event() {
        return "person";
    }
}

使用 Multi<PersonEvent> 将生成:

Using a Multi<PersonEvent> would produce:

event: person
data: {"name":"superman", "id": 1}
id: 1

event: person
data: {"name":"batman", "id": 2}
id: 2

event: person
data: {"name":"spiderman", "id": 3}
id: 3
Deprecation of asEventStream

ReactiveRoutes.asEventStream 已弃用,因为它不兼容 Quarkus 的安全层。

The ReactiveRoutes.asEventStream has been deprecated as it is not compatible with the security layer of Quarkus.

Json Stream in NDJSON format

您可以返回 Multi 来生成 JSON 值的新行分隔流。要启用此功能,请将 @Route 注解的 produces 属性设置为 "application/x-ndjson" (或 ReactiveRoutes.ND_JSON ):

You can return a Multi to produce a newline delimited stream of JSON values. To enable this feature, set the produces attribute of the @Route annotation to "application/x-ndjson" (or ReactiveRoutes.ND_JSON):

@Route(path = "/people", produces = ReactiveRoutes.ND_JSON)
Multi<Person> people() {
    return ReactiveRoutes.asJsonStream(Multi.createFrom().items(
            new Person("superman", 1),
            new Person("batman", 2),
            new Person("spiderman", 3)
            ));
}

该方法将生成:

This method would produce:

{"name":"superman", "id": 1}
{"name":"batman", "id": 2}
{"name":"spiderman", "id": 3}

produces 属性为数组。当您传递单个值时,可以省略“{”和“}”。请注意, "application/x-ndjson" 必须是数组中的第一个值。

The produces attribute is an array. When you pass a single value you can omit the "{" and "}". Note that "application/x-ndjson" must be the first value in the array.

您还可以提供字符串而不是对象,在这种情况下,字符串将用引号括起来以成为有效的 JSON 值:

You can also provide strings instead of objects, in that case the strings will be wrapped in quotes to become valid JSON values:

@Route(path = "/people", produces = ReactiveRoutes.ND_JSON)
Multi<Person> people() {
    return ReactiveRoutes.asJsonStream(Multi.createFrom().items(
            "superman",
            "batman",
            "spiderman"
            ));
}
"superman"
"batman"
"spiderman"
Deprecation of asJsonStream

ReactiveRoutes.asJsonStream 已被弃用,因为它与 Quarkus 的安全层不兼容。

The ReactiveRoutes.asJsonStream has been deprecated as it is not compatible with the security layer of Quarkus.

Using Bean Validation

您可以组合响应式路由和 Bean 验证。首先,不要忘记将 quarkus-hibernate-validator 扩展添加到您的项目。然后,您可以向路由参数添加约束(使用 @Param@Body 进行注释):

You can combine reactive routes and Bean Validation. First, don’t forget to add the quarkus-hibernate-validator extension to your project. Then, you can add constraints to your route parameter (annotated with @Param or @Body):

@Route(produces = "application/json")
Person createPerson(@Body @Valid Person person, @NonNull @Param("id") String primaryKey) {
  // ...
}

如果参数未通过测试,则返回 HTTP 400 响应。如果请求接受 JSON 有效负载,则响应遵循 Problem 格式。

If the parameters do not pass the tests, it returns an HTTP 400 response. If the request accepts JSON payload, the response follows the Problem format.

在返回对象或 Uni 时,您还可以使用 @Valid 注解:

When returning an object or a Uni, you can also use the @Valid annotation:

@Route(...)
@Valid Uni<Person> createPerson(@Body @Valid Person person, @NonNull @Param("id") String primaryKey) {
  // ...
}

如果路由生成的对`Uni`象未能通过验证,则将返回 HTTP 500 响应。如果请求接受 JSON 有效负载,则响应遵循 Problem 格式。

If the item produced by the route does not pass the validation, it returns an HTTP 500 response. If the request accepts JSON payload, the response follows the Problem format.

请注意,只支持在返回类型上使用 @Valid。返回的类可以使用任何约束。如果是 Uni,则会检查异步生成的对`Uni`象。

Note that only @Valid is supported on the return type. The returned class can use any constraint. In the case of Uni, it checks the item produced asynchronously.

Using the Vert.x Web Router

您还可以在 HTTP routing layer 上直接注册路由,方法是直接在 Router 对象上注册路由。要在启动时检索 Router 实例:

You can also register your route directly on the HTTP routing layer by registering routes directly on the Router object. To retrieve the Router instance at startup:

public void init(@Observes Router router) {
    router.get("/my-route").handler(rc -> rc.response().end("Hello from my route"));
}

查看 Vert.x Web documentation 以了解有关路由注册、选项和可用处理程序的更多信息。

Check the Vert.x Web documentation to know more about the route registration, options, and available handlers.

quarkus-vertx-http 扩展提供了 Router 访问。如果您使用 quarkus-restquarkus-reactive-routes,则会自动添加该扩展。

Router access is provided by the quarkus-vertx-http extension. If you use quarkus-rest or quarkus-reactive-routes, the extension will be added automatically.

您还可以接收 Mutiny variant of the Router (io.vertx.mutiny.ext.web.Router) 。

You can also receive the Mutiny variant of the Router (io.vertx.mutiny.ext.web.Router):

public void init(@Observes io.vertx.mutiny.ext.web.Router router) {
    router.get("/my-route").handler(rc -> rc.response().endAndForget("Hello from my route"));
}

Intercepting HTTP requests

您还可以注册将拦截传入 HTTP 请求的过滤器。请注意,这些过滤器还适用于 Servlet、Jakarta REST 资源和反应式路由。

You can also register filters that would intercept incoming HTTP requests. Note that these filters are also applied for servlets, Jakarta REST resources, and reactive routes.

例如,以下代码片段注册了一个添加 HTTP 标头的过滤器:

For example, the following code snippet registers a filter adding an HTTP header:

package org.acme.reactive.routes;

import io.vertx.ext.web.RoutingContext;

public class MyFilters {

    @RouteFilter(100) 1
    void myFilter(RoutingContext rc) {
       rc.response().putHeader("X-Header", "intercepting the request");
       rc.next(); 2
    }
}
1 The RouteFilter#value() defines the priority used to sort the filters - filters with higher priority are called first.
2 The filter is likely required to call the next() method to continue the chain.

HTTP Compression

默认情况下,不压缩 HTTP 响应的正文。您可以通过 quarkus.http.enable-compression=true 来启用 HTTP 压缩支持。

The body of an HTTP response is not compressed by default. You can enable the HTTP compression support by means of quarkus.http.enable-compression=true.

如果启用了压缩支持,则在以下情况下会压缩响应正文:

If compression support is enabled then the response body is compressed if:

  • the route method is annotated with @io.quarkus.vertx.http.Compressed, or

  • the Content-Type header is set and the value is a compressed media type as configured via quarkus.http.compress-media-types.

如果:

The response body is never compressed if:

  • the route method is annotated with @io.quarkus.vertx.http.Uncompressed, or

  • the Content-Type header is not set.

默认情况下,压缩以下媒体类型列表:text/htmltext/plaintext/xmltext/csstext/javascript`和`application/javascript

By default, the following list of media types is compressed: text/html, text/plain, text/xml, text/css, text/javascript and application/javascript.

如果客户端不支持 HTTP 压缩,那么不会压缩响应体。

If the client does not support HTTP compression then the response body is not compressed.

Adding OpenAPI and Swagger UI

你可以通过使用 quarkus-smallrye-openapi 扩展添加对 OpenAPISwagger UI 的支持。

You can add support for OpenAPI and Swagger UI by using the quarkus-smallrye-openapi extension.

通过运行此命令添加扩展:

Add the extension by running this command:

Unresolved directive in reactive-routes.adoc - include::{includes}/devtools/extension-add.adoc[]

这会将以下内容添加到构建文件中:

This will add the following to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-smallrye-openapi")

这足以从 Vert.x 路由生成基本 OpenAPI 模式文档:

This is enough to generate a basic OpenAPI schema document from your Vert.x Routes:

curl http://localhost:8080/q/openapi

你将看到生成的 OpenAPI 模式文档:

You will see the generated OpenAPI schema document:

---
openapi: 3.0.3
info:
  title: Generated API
  version: "1.0"
paths:
  /greetings:
    get:
      responses:
        "204":
          description: No Content
  /hello:
    get:
      responses:
        "204":
          description: No Content
  /world:
    get:
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: string

另请参阅 the OpenAPI Guide

Also see the OpenAPI Guide.

Adding MicroProfile OpenAPI Annotations

你可以使用 MicroProfile OpenAPI 来更好地记录你的模式,例如,在 void 方法上添加标头信息或指定返回类型可能很有用:

You can use MicroProfile OpenAPI to better document your schema, for instance, adding header info, or specifying the return type on void methods might be useful:

@OpenAPIDefinition( (1)
    info = @Info(
        title="Greeting API",
        version = "1.0.1",
        contact = @Contact(
            name = "Greeting API Support",
            url = "http://exampleurl.com/contact",
            email = "techsupport@example.com"),
        license = @License(
            name = "Apache 2.0",
            url = "https://www.apache.org/licenses/LICENSE-2.0.html"))
)
@ApplicationScoped
public class MyDeclarativeRoutes {

    // neither path nor regex is set - match a path derived from the method name
    @Route(methods = Route.HttpMethod.GET)
    @APIResponse(responseCode="200",
            description="Say hello",
            content=@Content(mediaType="application/json", schema=@Schema(type=SchemaType.STRING))) (2)
    void hello(RoutingContext rc) {
        rc.response().end("hello");
    }

    @Route(path = "/world")
    String helloWorld() {
        return "Hello world!";
    }

    @Route(path = "/greetings", methods = HttpMethod.GET)
    @APIResponse(responseCode="200",
            description="Greeting",
            content=@Content(mediaType="application/json", schema=@Schema(type=SchemaType.STRING)))
    void greetings(RoutingExchange ex) {
        ex.ok("hello " + ex.getParam("name").orElse("world"));
    }
}
1 Header information about your API.
2 Defining the response.

这将生成此 OpenAPI 模块:

This will generate this OpenAPI schema:

---
openapi: 3.0.3
info:
  title: Greeting API
  contact:
    name: Greeting API Support
    url: http://exampleurl.com/contact
    email: techsupport@example.com
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.1
paths:
  /greetings:
    get:
      responses:
        "200":
          description: Greeting
          content:
            application/json:
              schema:
                type: string
  /hello:
    get:
      responses:
        "200":
          description: Say hello
          content:
            application/json:
              schema:
                type: string
  /world:
    get:
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: string

Using Swagger UI

在`dev`或`test`模式下运行时,默认情况下包含 Swagger UI,并可以选择添加到`prod`模式。有关更多信息,请参阅 Swagger UI指南。

Swagger UI is included by default when running in dev or test mode, and can optionally be added to prod mode. For more information, see the Swagger UI guide.

导航到 localhost:8080/q/swagger-ui/并观察 Swagger UI 屏幕:

Navigate to localhost:8080/q/swagger-ui/ and observe the Swagger UI screen:

reactive routes guide screenshot01

Conclusion

本指南介绍了如何使用响应式路由来定义 HTTP 端点。它还描述了 Quarkus HTTP 层的结构以及如何编写过滤器。

This guide has introduced how you can use reactive routes to define an HTTP endpoint. It also describes the structure of the Quarkus HTTP layer and how to write filters.