Functional Endpoints
-
ServerResponse
-
RouterFunction
-
RouterFunctions
-
PathVariable
-
Mono
-
Flux
-
Validator :description: Spring WebFlux.fn 是一个轻量级的函数式编程模型,其中函数用于路由和处理,而契约被设计为不可变。它在 Reactive Core 的基础上运行,旨在替代基于注解的编程模型。WebFlux.fn 通过 HandlerFunctions 和 ServerResponse 来处理 HTTP 请求和响应。HandlerFunction 是一个获取 ServerRequest 并返回延迟 ServerResponse 的函数。RouterFunction 是一个获取 ServerRequest 并返回延迟 HandlerFunction 的函数。可以使用 RouterFunctions.route() 来创建路由,其中路由函数接受谓词和处理函数。通过使用嵌套路由,可以将常见的谓词分组到具有共享路径的路由器函数中。WebFlux.fn 还允许将请求重定向到资源,并提供对在根位置提供服务的资源的支持。 See equivalent in the Servlet stack
Spring WebFlux 包含 WebFlux.fn,这是一个轻量级的函数式编程模型,其中函数用于路由和处理请求,并且合约旨在不可变。它是一种基于注释的编程模型的替代方法,但在其他方面运行在相同的 Reactive Core 基础上。
Spring WebFlux includes WebFlux.fn, a lightweight functional programming model in which functions are used to route and handle requests and contracts are designed for immutability. It is an alternative to the annotation-based programming model but otherwise runs on the same Reactive Core foundation.
Overview
在 WebFlux.fn 中,通过 HandlerFunction
处理 HTTP 请求:一个取用`ServerRequest` 并返回延迟的 ServerResponse
的函数(即 Mono<ServerResponse>
)。请求和响应对象都具有不可变契约,这些契约为 HTTP 请求和响应提供了符合 JDK 8 的访问。HandlerFunction
等效于基于注解的编程模型中 @RequestMapping
方法的主体。
In WebFlux.fn, an HTTP request is handled with a HandlerFunction
: a function that takes
ServerRequest
and returns a delayed ServerResponse
(i.e. Mono<ServerResponse>
).
Both the request and the response object have immutable contracts that offer JDK 8-friendly
access to the HTTP request and response.
HandlerFunction
is the equivalent of the body of a @RequestMapping
method in the
annotation-based programming model.
传入请求将路由到带有 RouterFunction
的处理程序函数:一个将接受 ServerRequest
并返回延迟的 HandlerFunction
(即 Mono<HandlerFunction>
)的函数。当路由器函数匹配时,将返回处理程序函数;否则为空 Mono
。RouterFunction
相当于一个 @RequestMapping
注解,但主要区别在于,路由器函数不仅仅提供数据,而且还提供行为。
Incoming requests are routed to a handler function with a RouterFunction
: a function that
takes ServerRequest
and returns a delayed HandlerFunction
(i.e. Mono<HandlerFunction>
).
When the router function matches, a handler function is returned; otherwise an empty Mono.
RouterFunction
is the equivalent of a @RequestMapping
annotation, but with the major
difference that router functions provide not just data, but also behavior.
RouterFunctions.route()
提供了一个路由构建器,它使用户能够轻松创建路由,如下图例所示:
RouterFunctions.route()
provides a router builder that facilitates the creation of routers,
as the following example shows:
- Java
-
import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; import static org.springframework.web.reactive.function.server.RouterFunctions.route; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction<ServerResponse> route = route() 1 .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) .POST("/person", handler::createPerson) .build(); public class PersonHandler { // ... public Mono<ServerResponse> listPeople(ServerRequest request) { // ... } public Mono<ServerResponse> createPerson(ServerRequest request) { // ... } public Mono<ServerResponse> getPerson(ServerRequest request) { // ... } }
1 | Create router using route() .
|
2 | Create router using Coroutines router DSL; a Reactive alternative is also available via router { } . |
运行`RouterFunction`的一种方法是将其转换为`HttpHandler`,并通过一个内置的server adapters安装:
One way to run a RouterFunction
is to turn it into an HttpHandler
and install it
through one of the built-in server adapters:
-
RouterFunctions.toHttpHandler(RouterFunction)
-
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
大多数应用程序可以通过 WebFlux Java 配置运行,请参阅Running a Server。
Most applications can run through the WebFlux Java configuration, see Running a Server.
HandlerFunction
ServerRequest`和`ServerResponse`是不可变接口,可提供对 HTTP 请求和响应的 JDK 8 友好访问。请求和响应均对正文流提供https://www.reactive-streams.org[反应流]背压。请求正文使用 Reactor `Flux`或`Mono`表示。响应正文使用任何反应流`Publisher`表示,包括`Flux`和`Mono
。有关更多详细信息,请参阅Reactive Libraries。
ServerRequest
and ServerResponse
are immutable interfaces that offer JDK 8-friendly
access to the HTTP request and response.
Both request and response provide Reactive Streams back pressure
against the body streams.
The request body is represented with a Reactor Flux
or Mono
.
The response body is represented with any Reactive Streams Publisher
, including Flux
and Mono
.
For more on that, see Reactive Libraries.
ServerRequest
ServerRequest
提供对 HTTP 方法、URI、标头和查询参数的访问,而对主体的访问是通过 body
方法提供的。
ServerRequest
provides access to the HTTP method, URI, headers, and query parameters,
while access to the body is provided through the body
methods.
以下示例将请求正文提取到 Mono<String>
:
The following example extracts the request body to a Mono<String>
:
-
Java
-
Kotlin
Mono<String> string = request.bodyToMono(String.class);
val string = request.awaitBody<String>()
以下示例将正文提取到 Flux<Person>
(或 Kotlin 中的 Flow<Person>
),其中`Person` 对象是从某种已序列化形式(例如 JSON 或 XML)解码而来:
The following example extracts the body to a Flux<Person>
(or a Flow<Person>
in Kotlin),
where Person
objects are decoded from some serialized form, such as JSON or XML:
-
Java
-
Kotlin
Flux<Person> people = request.bodyToFlux(Person.class);
val people = request.bodyToFlow<Person>()
前面的示例是快捷方式,它使用了更通用的 ServerRequest.body(BodyExtractor)
,它接受 BodyExtractor
函数式策略接口。实用程序类 BodyExtractors
可访问许多实例。例如,前面的示例也可以写成以下形式:
The preceding examples are shortcuts that use the more general ServerRequest.body(BodyExtractor)
,
which accepts the BodyExtractor
functional strategy interface. The utility class
BodyExtractors
provides access to a number of instances. For example, the preceding examples can
also be written as follows:
-
Java
-
Kotlin
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
val string = request.body(BodyExtractors.toMono(String::class.java)).awaitSingle()
val people = request.body(BodyExtractors.toFlux(Person::class.java)).asFlow()
以下示例展示如何访问表单数据:
The following example shows how to access form data:
-
Java
-
Kotlin
Mono<MultiValueMap<String, String>> map = request.formData();
val map = request.awaitFormData()
以下示例展示如何以映射形式访问 multipart 数据:
The following example shows how to access multipart data as a map:
-
Java
-
Kotlin
Mono<MultiValueMap<String, Part>> map = request.multipartData();
val map = request.awaitMultipartData()
以下示例展示如何一次访问 multipart 数据并以流式传输形式访问:
The following example shows how to access multipart data, one at a time, in streaming fashion:
-
Java
-
Kotlin
Flux<PartEvent> allPartEvents = request.bodyToFlux(PartEvent.class);
allPartsEvents.windowUntil(PartEvent::isLast)
.concatMap(p -> p.switchOnFirst((signal, partEvents) -> {
if (signal.hasValue()) {
PartEvent event = signal.get();
if (event instanceof FormPartEvent formEvent) {
String value = formEvent.value();
// handle form field
}
else if (event instanceof FilePartEvent fileEvent) {
String filename = fileEvent.filename();
Flux<DataBuffer> contents = partEvents.map(PartEvent::content);
// handle file upload
}
else {
return Mono.error(new RuntimeException("Unexpected event: " + event));
}
}
else {
return partEvents; // either complete or error signal
}
}));
val parts = request.bodyToFlux<PartEvent>()
allPartsEvents.windowUntil(PartEvent::isLast)
.concatMap {
it.switchOnFirst { signal, partEvents ->
if (signal.hasValue()) {
val event = signal.get()
if (event is FormPartEvent) {
val value: String = event.value();
// handle form field
} else if (event is FilePartEvent) {
val filename: String = event.filename();
val contents: Flux<DataBuffer> = partEvents.map(PartEvent::content);
// handle file upload
} else {
return Mono.error(RuntimeException("Unexpected event: " + event));
}
} else {
return partEvents; // either complete or error signal
}
}
}
}
请注意,PartEvent
对象的正文内容必须完全被消耗、中继或释放,以避免内存泄漏。
Note that the body contents of the PartEvent
objects must be completely consumed, relayed, or released to avoid memory leaks.
ServerResponse
ServerResponse
提供对 HTTP 响应的访问,而且因为它不可变,所以你可以使用 build
方法来创建它。你可以使用该构建器设置响应状态、添加响应标头或提供一个主体。下例创建了一个 200 (OK) 响应(带有 JSON 内容):
ServerResponse
provides access to the HTTP response and, since it is immutable, you can use
a build
method to create it. You can use the builder to set the response status, to add response
headers, or to provide a body. The following example creates a 200 (OK) response with JSON
content:
-
Java
-
Kotlin
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
val person: Person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)
下例展示了如何构建一个 201 (CREATED) 响应(带有 Location
标头和空主体):
The following example shows how to build a 201 (CREATED) response with a Location
header and no body:
-
Java
-
Kotlin
URI location = ...
ServerResponse.created(location).build();
val location: URI = ...
ServerResponse.created(location).build()
根据所使用的编解码器,可以传递提示参数来自定义主体的序列化或反序列化方式。例如,要指定一个https://www.baeldung.com/jackson-json-view-annotation[Jackson JSON 视图]:
Depending on the codec used, it is possible to pass hint parameters to customize how the body is serialized or deserialized. For example, to specify a Jackson JSON view:
-
Java
-
Kotlin
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
ServerResponse.ok().hint(Jackson2CodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
Handler Classes
我们可以将一个处理函数编写为一个 lambda,如下图例所示:
We can write a handler function as a lambda, as the following example shows:
-
Java
-
Kotlin
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
这样做很方便,但在应用程序中,我们需要多个函数,并且多个内联 lambda 可能很混乱。因此,将相关的处理函数分组在一个处理函数类中很有用,这个类在基于注解的应用程序中所扮演的角色与 @Controller
类似。例如,下例公开了一个响应式的 Person
存储库:
That is convenient, but in an application we need multiple functions, and multiple inline
lambda’s can get messy.
Therefore, it is useful to group related handler functions together into a handler class, which
has a similar role as @Controller
in an annotation-based application.
For example, the following class exposes a reactive Person
repository:
- Java
-
import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.ServerResponse.ok; public class PersonHandler { private final PersonRepository repository; public PersonHandler(PersonRepository repository) { this.repository = repository; } public Mono<ServerResponse> listPeople(ServerRequest request) { (1) Flux<Person> people = repository.allPeople(); return ok().contentType(APPLICATION_JSON).body(people, Person.class); } public Mono<ServerResponse> createPerson(ServerRequest request) { (2) Mono<Person> person = request.bodyToMono(Person.class); return ok().build(repository.savePerson(person)); } public Mono<ServerResponse> getPerson(ServerRequest request) { (3) int personId = Integer.valueOf(request.pathVariable("id")); return repository.getPerson(personId) .flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person)) .switchIfEmpty(ServerResponse.notFound().build()); } }
1 | listPeople is a handler function that returns all Person objects found in the repository as
JSON. |
2 | createPerson is a handler function that stores a new Person contained in the request body.
Note that PersonRepository.savePerson(Person) returns Mono<Void> : an empty Mono that emits
a completion signal when the person has been read from the request and stored. So we use the
build(Publisher<Void>) method to send a response when that completion signal is received (that is,
when the Person has been saved). |
3 | getPerson is a handler function that returns a single person, identified by the id path
variable. We retrieve that Person from the repository and create a JSON response, if it is
found. If it is not found, we use switchIfEmpty(Mono<T>) to return a 404 Not Found response.
|
4 | listPeople is a handler function that returns all Person objects found in the repository as
JSON. |
5 | createPerson is a handler function that stores a new Person contained in the request body.
Note that PersonRepository.savePerson(Person) is a suspending function with no return type. |
6 | getPerson is a handler function that returns a single person, identified by the id path
variable. We retrieve that Person from the repository and create a JSON response, if it is
found. If it is not found, we return a 404 Not Found response. |
Validation
函数式端点可以使用 Spring 的 validation facilities 对请求正文进行验证。例如,给定 Person
的自定义 Spring Validator 实现:
A functional endpoint can use Spring’s validation facilities to
apply validation to the request body. For example, given a custom Spring
Validator implementation for a Person
:
- Java
-
public class PersonHandler { private final Validator validator = new PersonValidator(); (1) // ... public Mono<ServerResponse> createPerson(ServerRequest request) { Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); (2) return ok().build(repository.savePerson(person)); } private void validate(Person person) { Errors errors = new BeanPropertyBindingResult(person, "person"); validator.validate(person, errors); if (errors.hasErrors()) { throw new ServerWebInputException(errors.toString()); (3) } } }
1 | Create Validator instance. |
2 | Apply validation. |
3 | Raise exception for a 400 response.
|
4 | Create Validator instance. |
5 | Apply validation. |
6 | Raise exception for a 400 response. |
处理程序还可以创建一个全局`Validator`实例并将其注入,进而使用标准 Bean 验证 API (JSR-303),该实例基于`LocalValidatorFactoryBean`。请参见Spring Validation。
Handlers can also use the standard bean validation API (JSR-303) by creating and injecting
a global Validator
instance based on LocalValidatorFactoryBean
.
See Spring Validation.
RouterFunction
路由函数用于将请求路由到对应的 HandlerFunction
。通常,您不会自己编写路由函数,而是使用 RouterFunctions
实用类上的一个方法来创建一个。RouterFunctions.route()
(无参数)为一个流畅的构建器,用于创建一个路由函数,而 RouterFunctions.route(RequestPredicate, HandlerFunction)
提供了一个创建一个路由的直接方式。
Router functions are used to route the requests to the corresponding HandlerFunction
.
Typically, you do not write router functions yourself, but rather use a method on the
RouterFunctions
utility class to create one.
RouterFunctions.route()
(no parameters) provides you with a fluent builder for creating a router
function, whereas RouterFunctions.route(RequestPredicate, HandlerFunction)
offers a direct way
to create a router.
通常建议使用 route()
构建器,因为它为典型的映射场景提供方便的快捷方式,而无需使用难以发现的静态导入。例如,路由函数构建器提供 GET(String, HandlerFunction)
方法来为 GET 请求创建一个映射;POST(String, HandlerFunction)
为 POST 请求创建一个映射。
Generally, it is recommended to use the route()
builder, as it provides
convenient short-cuts for typical mapping scenarios without requiring hard-to-discover
static imports.
For instance, the router function builder offers the method GET(String, HandlerFunction)
to create a mapping for GET requests; and POST(String, HandlerFunction)
for POSTs.
除了基于 HTTP 方法的映射之外,路由生成器还提供了一种方法,以便在映射到请求时引入附加谓词。对于每个 HTTP 方法,都有一个重载变量,它将 RequestPredicate
作为参数,尽管可以表达哪些附加约束条件。
Besides HTTP method-based mapping, the route builder offers a way to introduce additional
predicates when mapping to requests.
For each HTTP method there is an overloaded variant that takes a RequestPredicate
as a
parameter, though which additional constraints can be expressed.
Predicates
您可以编写自己的 RequestPredicate
,但 RequestPredicates
实用类提供基于请求路径、HTTP 方法、内容类型等的常用实现。以下示例使用请求谓词基于 Accept
头创建约束:
You can write your own RequestPredicate
, but the RequestPredicates
utility class
offers commonly used implementations, based on the request path, HTTP method, content-type,
and so on.
The following example uses a request predicate to create a constraint based on the Accept
header:
-
Java
-
Kotlin
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
val route = coRouter {
GET("/hello-world", accept(TEXT_PLAIN)) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
您可以通过使用以下方法组合多个请求谓词:
You can compose multiple request predicates together by using:
-
RequestPredicate.and(RequestPredicate)
— both must match. -
RequestPredicate.or(RequestPredicate)
— either can match.
RequestPredicates
中的许多谓词是组合的。例如,RequestPredicates.GET(String)
由 RequestPredicates.method(HttpMethod)
和 RequestPredicates.path(String)
组合而成。上面所示的示例还使用两个请求谓词,因为构建器在内部使用 RequestPredicates.GET
,并将其与 accept
谓词组合。
Many of the predicates from RequestPredicates
are composed.
For example, RequestPredicates.GET(String)
is composed from RequestPredicates.method(HttpMethod)
and RequestPredicates.path(String)
.
The example shown above also uses two request predicates, as the builder uses
RequestPredicates.GET
internally, and composes that with the accept
predicate.
Routes
路由函数按顺序评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。在将路由函数注册为 Spring bean 时,这一点也很重要,如下所述。请注意,此行为不同于基于注释的编程模型,其中“最具体”的控制器方法是自动选择的。
Router functions are evaluated in order: if the first route does not match, the second is evaluated, and so on. Therefore, it makes sense to declare more specific routes before general ones. This is also important when registering router functions as Spring beans, as will be described later. Note that this behavior is different from the annotation-based programming model, where the "most specific" controller method is picked automatically.
使用路由函数构建器时,所有已定义的路由都组合成一个 RouterFunction
,该 RouterFunction
从 build()
返回。还有其他方法可以将多个路由函数组合在一起:
When using the router function builder, all defined routes are composed into one
RouterFunction
that is returned from build()
.
There are also other ways to compose multiple router functions together:
-
add(RouterFunction)
on theRouterFunctions.route()
builder -
RouterFunction.and(RouterFunction)
-
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
— shortcut forRouterFunction.and()
with nestedRouterFunctions.route()
.
以下示例显示了四个路由的组合:
The following example shows the composition of four routes:
- Java
-
import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; PersonRepository repository = ... PersonHandler handler = new PersonHandler(repository); RouterFunction<ServerResponse> otherRoute = ... RouterFunction<ServerResponse> route = route() .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) (1) .GET("/person", accept(APPLICATION_JSON), handler::listPeople) (2) .POST("/person", handler::createPerson) (3) .add(otherRoute) (4) .build();
1 | GET /person/{id} with an Accept header that matches JSON is routed to
PersonHandler.getPerson |
2 | GET /person with an Accept header that matches JSON is routed to
PersonHandler.listPeople |
3 | POST /person with no additional predicates is mapped to
PersonHandler.createPerson , and |
4 | otherRoute is a router function that is created elsewhere, and added to the route built.
|
5 | GET /person/{id} with an Accept header that matches JSON is routed to
PersonHandler.getPerson |
6 | GET /person with an Accept header that matches JSON is routed to
PersonHandler.listPeople |
7 | POST /person with no additional predicates is mapped to
PersonHandler.createPerson , and |
8 | otherRoute is a router function that is created elsewhere, and added to the route built. |
Nested Routes
通常,一组路由器函数具有共享谓词,例如共享路径。在上面的示例中,共享谓词将是路径谓词,它匹配 /person
,三个路由使用该谓词。在使用注解时,可以通过使用映射到 /person
的类型级 @RequestMapping
注解来消除此重复行为。在 WebFlux.fn 中,可以通过路由函数生成器上的 path
方法共享路径谓词。例如,可以通过使用嵌套路由以以下方式改进上面示例的最后几行:
It is common for a group of router functions to have a shared predicate, for instance a
shared path. In the example above, the shared predicate would be a path predicate that
matches /person
, used by three of the routes. When using annotations, you would remove
this duplication by using a type-level @RequestMapping
annotation that maps to
/person
. In WebFlux.fn, path predicates can be shared through the path
method on the
router function builder. For instance, the last few lines of the example above can be
improved in the following way by using nested routes:
- Java
-
RouterFunction<ServerResponse> route = route() .path("/person", builder -> builder (1) .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) .GET(accept(APPLICATION_JSON), handler::listPeople) .POST(handler::createPerson)) .build();
1 | Note that second parameter of path is a consumer that takes the router builder.
|
2 | Create router using Coroutines router DSL; a Reactive alternative is also available via router { } . |
虽然基于路径的嵌套是最常见的,但是您可以通过使用构建器上的 nest
方法嵌套在任何类型的谓词上。上面仍然包含一些重复,形式为共享的 Accept
头谓词。我们可以通过将 nest
方法与 accept
一起使用来进一步改进:
Though path-based nesting is the most common, you can nest on any kind of predicate by using
the nest
method on the builder.
The above still contains some duplication in the form of the shared Accept
-header predicate.
We can further improve by using the nest
method together with accept
:
-
Java
-
Kotlin
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
val route = coRouter {
"/person".nest {
accept(APPLICATION_JSON).nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
POST(handler::createPerson)
}
}
}
Serving Resources
WebFlux.fn 为提供服务提供内置支持。
WebFlux.fn provides built-in support for serving resources.
除了以下描述的功能之外,还可以实现更灵活的资源处理,这得益于 |
In addition to the capabilities described below, it is possible to implement even more flexible resource handling thanks to
|
Redirecting to a resource
可以将与指定谓词匹配的请求重定向到资源。例如,这对于处理单页面应用程序中的重定向非常有用。
It is possible to redirect requests matching a specified predicate to a resource. This can be useful, for example, for handling redirects in Single Page Applications.
-
Java
-
Kotlin
ClassPathResource index = new ClassPathResource("static/index.html");
List<String> extensions = List.of("js", "css", "ico", "png", "jpg", "gif");
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).or(pathExtension(extensions::contains)).negate();
RouterFunction<ServerResponse> redirectToIndex = route()
.resource(spaPredicate, index)
.build();
val redirectToIndex = router {
val index = ClassPathResource("static/index.html")
val extensions = listOf("js", "css", "ico", "png", "jpg", "gif")
val spaPredicate = !(path("/api/**") or path("/error") or
pathExtension(extensions::contains))
resource(spaPredicate, index)
}
Serving resources from a root location
还可以将与给定模式匹配的请求路由到相对于给定根位置的资源。
It is also possible to route requests that match a given pattern to resources relative to a given root location.
-
Java
-
Kotlin
Resource location = new FileSystemResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
val location = FileSystemResource("public-resources/")
val resources = router { resources("/resources/**", location) }
Running a Server
如何在 HTTP 服务器中运行路由器函数?一种简单的选择是使用以下方法之一将路由器函数转换为一个 HttpHandler
:
How do you run a router function in an HTTP server? A simple option is to convert a router
function to an HttpHandler
by using one of the following:
-
RouterFunctions.toHttpHandler(RouterFunction)
-
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
然后,你可以对返回的`HttpHandler`执行一系列服务器适配器,按照HttpHandler的服务器特定说明进行操作。
You can then use the returned HttpHandler
with a number of server adapters by following
HttpHandler for server-specific instructions.
另一个更典型的选项(也由 Spring Boot 使用)是通过使用 Spring 配置来声明处理请求所需的组件的WebFlux Config,基于DispatcherHandler
运行设置。WebFlux Java 配置声明以下基础设施组件来支持功能性终端:
A more typical option, also used by Spring Boot, is to run with a
DispatcherHandler
-based setup through the
WebFlux Config, which uses Spring configuration to declare the
components required to process requests. The WebFlux Java configuration declares the following
infrastructure components to support functional endpoints:
-
RouterFunctionMapping
: Detects one or moreRouterFunction<?>
beans in the Spring configuration, orders them, combines them throughRouterFunction.andOther
, and routes requests to the resulting composedRouterFunction
. -
HandlerFunctionAdapter
: Simple adapter that letsDispatcherHandler
invoke aHandlerFunction
that was mapped to a request. -
ServerResponseResultHandler
: Handles the result from the invocation of aHandlerFunction
by invoking thewriteTo
method of theServerResponse
.
前面的组件允许功能性端点在 DispatcherHandler
请求处理生命周期中以及(可能)并排随注释控制器一起运行(如果声明了的话)。Spring Boot WebFlux 启动器启用功能性端点的方式也是如此。
The preceding components let functional endpoints fit within the DispatcherHandler
request
processing lifecycle and also (potentially) run side by side with annotated controllers, if
any are declared. It is also how functional endpoints are enabled by the Spring Boot WebFlux
starter.
下面的示例显示了 WebFlux Java 配置(请参见 DispatcherHandler 以了解如何运行它):
The following example shows a WebFlux Java configuration (see DispatcherHandler for how to run it):
-
Java
-
Kotlin
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
@Bean
fun routerFunctionA(): RouterFunction<*> {
// ...
}
@Bean
fun routerFunctionB(): RouterFunction<*> {
// ...
}
// ...
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
// configure message conversion...
}
override fun addCorsMappings(registry: CorsRegistry) {
// configure CORS...
}
override fun configureViewResolvers(registry: ViewResolverRegistry) {
// configure view resolution for HTML rendering...
}
}
Filtering Handler Functions
可使用路由函数生成器中的 before
、after
或 filter
方法过滤处理程序函数。借助标注,可通过使用 @ControllerAdvice
、ServletFilter
或同时使用两者,实现类似功能。过滤器将应用到生成器构建的所有路由。这意味着在嵌套路由中定义的过滤器不会应用到“顶级”路由。例如,考虑以下示例:
You can filter handler functions by using the before
, after
, or filter
methods on the routing
function builder.
With annotations, you can achieve similar functionality by using @ControllerAdvice
, a ServletFilter
, or both.
The filter will apply to all routes that are built by the builder.
This means that filters defined in nested routes do not apply to "top-level" routes.
For instance, consider the following example:
- Java
-
RouterFunction<ServerResponse> route = route() .path("/person", b1 -> b1 .nest(accept(APPLICATION_JSON), b2 -> b2 .GET("/{id}", handler::getPerson) .GET(handler::listPeople) .before(request -> ServerRequest.from(request) (1) .header("X-RequestHeader", "Value") .build())) .POST(handler::createPerson)) .after((request, response) -> logResponse(response)) (2) .build();
1 | The before filter that adds a custom request header is only applied to the two GET routes. |
2 | The after filter that logs the response is applied to all routes, including the nested ones.
|
3 | The before filter that adds a custom request header is only applied to the two GET routes. |
4 | The after filter that logs the response is applied to all routes, including the nested ones. |
路由生成器中的 filter
方法接收一个 HandlerFilterFunction
:一个接收 ServerRequest
和 HandlerFunction
并返回 ServerResponse
的函数。处理程序函数参数代表链中的下一个元素。这通常是指被路由到的处理程序,但如果应用了多个过滤器,它也可以是另一个过滤器。
The filter
method on the router builder takes a HandlerFilterFunction
: a
function that takes a ServerRequest
and HandlerFunction
and returns a ServerResponse
.
The handler function parameter represents the next element in the chain.
This is typically the handler that is routed to, but it can also be another
filter if multiple are applied.
现在,假设我们有一个可以确定特定路径是否被允许的 SecurityManager
,我们可以向路由中添加一个简单的安全性过滤器。以下示例展示了如何执行此操作:
Now we can add a simple security filter to our route, assuming that we have a SecurityManager
that
can determine whether a particular path is allowed.
The following example shows how to do so:
-
Java
-
Kotlin
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
val securityManager: SecurityManager = ...
val route = router {
("/person" and accept(APPLICATION_JSON)).nest {
GET("/{id}", handler::getPerson)
GET("", handler::listPeople)
POST(handler::createPerson)
filter { request, next ->
if (securityManager.allowAccessTo(request.path())) {
next(request)
}
else {
status(UNAUTHORIZED).build();
}
}
}
}
上述示例表明调用 next.handle(ServerRequest)
是可选的。我们只有在允许访问时才运行处理程序函数。
The preceding example demonstrates that invoking the next.handle(ServerRequest)
is optional.
We only let the handler function be run when access is allowed.
除了在路由函数生成器中使用 filter
方法之外,还可通过 RouterFunction.filter(HandlerFilterFunction)
将过滤器应用到现有路由函数。
Besides using the filter
method on the router function builder, it is possible to apply a
filter to an existing router function via RouterFunction.filter(HandlerFilterFunction)
.
通过专用 xref:web/webflux-cors.adoc#webflux-cors-webfilter[ |
CORS support for functional endpoints is provided through a dedicated
|