功能端点
Spring WebFlux 包含了 WebFlux.fn,这是一种轻量级的函数式编程模型,其中使用函数来路由和处理请求,并且契约设计遵循不可变性原则。它是基于注解的编程模型的一种替代方案,但在其他方面仍然运行在相同的 Reactive Core 基础之上。
概述
在WebFlux.fn中,HTTP请求是通过HandlerFunction来处理的:这个函数接收一个ServerRequest,并返回一个延迟的ServerResponse(即Mono<ServerResponse>)。请求和响应对象都具有不可变的契约,这些契约提供了对HTTP请求和响应的、符合JDK 8规范的访问方式。HandlerFunction相当于基于注解的编程模型中@RequestMapping方法的主体部分。
传入的请求会被路由到一个名为RouterFunction的处理函数中:这个函数接收ServerRequest,并返回一个延迟执行的HandlerFunction(即Mono<HandlerFunction>)。当路由函数匹配到请求时,就会返回相应的处理函数;否则则返回一个空值Mono。RouterFunction相当于@RequestMapping注解,但主要区别在于RouterFunction不仅提供数据,还提供相应的处理逻辑(行为)。
RouterFunctions.route() 提供了一个路由器构建器,便于创建路由器,如下例所示:
- Java
- Kotlin
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) {
// ...
}
}
使用
route()创建路由。
val repository: PersonRepository = ...
val handler = PersonHandler(repository)
val route = coRouter { 1
accept(APPLICATION_JSON).nest {
GET("/person/{id}", handler::getPerson)
GET("/person", handler::listPeople)
}
POST("/person", handler::createPerson)
)
class PersonHandler(private val repository: PersonRepository) {
// ...
suspend fun listPeople(request: ServerRequest): ServerResponse {
// ...
}
suspend fun createPerson(request: ServerRequest): ServerResponse {
// ...
}
suspend fun getPerson(request: ServerRequest): ServerResponse {
// ...
}
}
使用
route()创建路由。
运行RouterFunction的一种方法是将其转换为HttpHandler,然后通过内置的服务器适配器之一进行安装:
-
RouterFunctions.toHttpHandler(RouterFunction) -
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
大多数应用程序都可以通过WebFlux Java配置来运行,详见运行服务器。
HandlerFunction
ServerRequest 和 ServerResponse 是不可变的接口,它们提供了对 HTTP 请求和响应的访问方式,且与 JDK 8 兼容。请求和响应都支持通过 Reactive Streams 对请求体流(body streams)进行背压(back pressure)操作。请求体由 Reactor 的 Flux 或 Mono 表示;响应体则可由任何 Reactive Streams 的 Publisher 表示,包括 Flux 和 Mono。有关更多详细信息,请参阅 Reactive Libraries。
ServerRequest
ServerRequest 提供了对 HTTP 方法、URI、请求头(headers)和查询参数(query parameters)的访问权限,而通过 body 方法则可以访问请求体(body)。
以下示例将请求体提取为Mono<String>:
- Java
- Kotlin
Mono<String> string = request.bodyToMono(String.class);
val string = request.awaitBody<String>()
以下示例将主体提取到一个Flux<Person>(在Kotlin中为Flow<Person>)中,其中Person对象是从某种序列化形式(如JSON或XML)解码而来的:
- Java
- Kotlin
Flux<Person> people = request.bodyToFlux(Person.class);
val people = request.bodyToFlow<Person>()
前面的例子都是使用了更通用的 ServerRequest.body(BodyExtractor) 的快捷方式,该方法接受 BodyExtractor 函数式策略接口。工具类 BodyExtractors 提供了对多个这类实例的访问。例如,前面的例子也可以写成如下形式:
- 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()
以下示例展示了如何访问表单数据:
- Java
- Kotlin
Mono<MultiValueMap<String, String>> map = request.formData();
val map = request.awaitFormData()
以下示例展示了如何将多部分数据作为映射(map)来访问:
- Java
- Kotlin
Mono<MultiValueMap<String, Part>> map = request.multipartData();
val map = request.awaitMultipartData()
以下示例展示了如何以流式方式一次访问一个部分的多部分数据:
- Java
- Kotlin
request.bodyToFlux(PartEvent.class).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
}
return Mono.empty();
}));
request.bodyToFlux<PartEvent>().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@switchOnFirst Mono.error(RuntimeException("Unexpected event: $event"))
}
} else {
return@switchOnFirst partEvents // either complete or error signal
}
Mono.empty()
}
}
必须完全处理、传递或释放PartEvent对象的正文内容,以避免内存泄漏。
以下展示了如何通过DataBinder绑定请求参数、URI变量或头部信息,同时也展示了如何自定义DataBinder:
- Java
- Kotlin
Mono<Pet> pet = request.bind(Pet.class, dataBinder -> dataBinder.setAllowedFields("name"));
val pet: Pet? = request.bindAndAwait<Pet>{ dataBinder -> dataBinder.setAllowedFields("name") }
ServerResponse
ServerResponse 提供了对 HTTP 响应的访问,由于它是不可变的,因此你可以使用 build 方法来创建它。你可以通过这个构建器设置响应状态、添加响应头,或者提供响应体。以下示例创建了一个包含 JSON 内容的 200(OK)响应:
- Java
- Kotlin
Mono<Person> person = getPerson();
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
val person: Person = getPerson()
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValueWithTypeAndAwait<Person>(person)
以下示例展示了如何构建一个包含Location头部但不包含正文的201(已创建)响应:
- Java
- Kotlin
URI location = ...
return ServerResponse.created(location).build();
val location: URI = ...
return ServerResponse.created(location).build()
根据所使用的编解码器,可以传递提示参数来自定义数据体的序列化或反序列化方式。例如,要指定一个Jackson JSON 视图:
- Java
- Kotlin
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView.class).body(...);
return ServerResponse.ok().hint(JacksonCodecSupport.JSON_VIEW_HINT, MyJacksonView::class.java).body(...)
处理器类
我们可以像下面的例子所示,将处理函数写成lambda表达式:
- Java
- Kotlin
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().bodyValue("Hello World");
val helloWorld = HandlerFunction<ServerResponse> { ServerResponse.ok().bodyValue("Hello World") }
那确实很方便,但在实际应用中我们需要多个函数,而过多的内联lambda表达式会让人代码阅读起来变得混乱。因此,将相关的处理函数组合到一个处理类中会很有用,这个处理类的作用类似于基于注解的应用程序中的@Controller。例如,以下类暴露了一个响应式的Person仓库:
- Java
- Kotlin
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
// listPeople is a handler function that returns all Person objects found
// in the repository as JSON
public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people, Person.class);
}
// 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)
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person));
}
// 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.
public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
return repository.getPerson(personId)
.flatMap(person -> ok().contentType(APPLICATION_JSON).bodyValue(person))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
class PersonHandler(private val repository: PersonRepository) {
// listPeople is a handler function that returns all Person objects found
// in the repository as JSON
suspend fun listPeople(request: ServerRequest): ServerResponse {
val people: Flow<Person> = repository.allPeople()
return ServerResponse.ok().contentType(APPLICATION_JSON).bodyAndAwait(people)
}
// 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)
suspend fun createPerson(request: ServerRequest): ServerResponse {
val person = request.awaitBody<Person>()
repository.savePerson(person)
return ServerResponse.ok().buildAndAwait()
}
// 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.
suspend fun getPerson(request: ServerRequest): ServerResponse {
val personId = request.pathVariable("id").toInt()
return repository.getPerson(personId)?.let { ServerResponse.ok().contentType(APPLICATION_JSON).bodyValueAndAwait(it) }
?: ServerResponse.notFound().buildAndAwait()
}
}
验证
- Java
- Kotlin
public class PersonHandler {
// Create Validator instance
private final Validator validator = new PersonValidator();
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
// Apply validation
Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate);
return ok().build(repository.savePerson(person));
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
// Raise exception for a 400 response
throw new ServerWebInputException(errors.toString());
}
}
}
class PersonHandler(private val repository: PersonRepository) {
// Create Validator instance
private val validator = PersonValidator()
suspend fun createPerson(request: ServerRequest): ServerResponse {
val person = request.awaitBody<Person>()
// Apply validation
validate(person)
repository.savePerson(person)
return ServerResponse.ok().buildAndAwait()
}
private fun validate(person: Person) {
val errors: Errors = BeanPropertyBindingResult(person, "person")
validator.validate(person, errors)
if (errors.hasErrors()) {
// Raise exception for a 400 response
throw ServerWebInputException(errors.toString())
}
}
}
处理程序还可以通过创建并注入基于LocalValidatorFactoryBean的全局Validator实例来使用标准的Bean验证API(JSR-303)。请参阅Spring Validation。
RouterFunction
路由器函数用于将请求路由到相应的HandlerFunction。通常,你不需要自己编写路由器函数,而是使用RouterFunctions工具类中的方法来创建它。RouterFunctions.route()(无参数)提供了一个流畅的构建器来创建路由器函数,而Router Functions.route(RequestPredicate, HandlerFunction)则提供了一种直接创建路由器的方法。
通常,建议使用route()构建器,因为它为典型的映射场景提供了方便的快捷方式,而无需难以发现的静态导入。例如,路由器函数构建器提供了GET(String, HandlerFunction)方法来创建GET请求的映射;以及POST(String, HandlerFunction)方法来处理POST请求。
除了基于HTTP方法的映射之外,路由构建器还提供了一种在映射请求时引入额外谓词(predicate)的方法。对于每种HTTP方法,都有一个重载版本,该版本接受一个RequestPredicate作为参数,通过这种方式可以表达额外的约束条件。
谓语
你可以编写自己的 RequestPredicate,但 RequestPredicates 工具类提供了针对常见需求的内置选项,这些选项可以根据 HTTP 方法、请求路径、请求头、API 版本 等来进行匹配。
以下示例使用了Accept头部和请求谓词:
- 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(MediaType.TEXT_PLAIN)) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
你可以通过使用以下方法将多个请求谓词组合在一起:
-
RequestPredicate.and(RequestPredicate)— 两者都必须匹配。 -
RequestPredicate.or(RequestPredicate)— 只要有一个匹配即可。
RequestPredicates 中的许多谓词都是组合而成的。例如,RequestPredicates.GET(String) 是由 RequestPredicates.method(HttpMethod) 和 RequestPredicates.path(String) 组合而成的。上面展示的例子中也使用了两个请求谓词,因为构建器在内部使用了 RequestPredicates.GET,并将其与 accept 谓词组合在一起。
路由
路由器的功能会按顺序进行评估:如果第一个路由不匹配,就会评估第二个路由,依此类推。因此,在定义通用路由之前,先定义更具体的路由是合理的。当将路由器功能注册为Spring Bean时,这一点也很重要,后面会有相关描述。需要注意的是,这种行为与基于注解的编程模型不同,在基于注解的模型中,“最具体”的控制器方法会自动被选中。
在使用路由器函数构建器时,所有定义的路由会被组合成一个RouterFunction,这个RouterFunction会从build()方法中返回。当然,还有其他方法可以将多个路由器函数组合在一起:
-
在
RouterFunctions.route()构建器上使用add(RouterFunction) -
RouterFunction.and(RouterFunction) -
RouterFunction.andRoute(RequestPredicate, HandlerFunction)—— 这是带有嵌套RouterFunctions.route()的RouterFunction.and()的快捷方式。
以下示例展示了四条路由的组成:
- Java
- Kotlin
PersonRepository repository = getPersonRepository();
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = getOtherRoute();
RouterFunction<ServerResponse> route = route()
// GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
// GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
// POST /person with no additional predicates is mapped to PersonHandler.createPerson
.POST("/person", handler::createPerson)
// otherRoute is a router function that is created elsewhere and added to the route built
.add(otherRoute)
.build();
val repository: PersonRepository = getPersonRepository()
val handler = PersonHandler(repository)
val otherRoute: RouterFunction<ServerResponse> = getOtherRoute()
val route = coRouter {
// GET /person/{id} with an Accept header that matches JSON is routed to PersonHandler.getPerson
GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
// GET /person with an Accept header that matches JSON is routed to PersonHandler.listPeople
GET("/person", accept(APPLICATION_JSON), handler::listPeople)
// POST /person with no additional predicates is mapped to PersonHandler.createPerson
POST("/person", handler::createPerson)
// otherRoute is a router function that is created elsewhere and added to the route built
}.and(otherRoute)
嵌套路由
通常情况下,一组路由函数会共享一个相同的谓词(predicate),例如一个共同的路径。在上面的例子中,这个共享的谓词是一个与“/person”匹配的路径谓词,被三个路由所使用。当使用注解时,可以通过使用类型级别的@RequestMapping注解来消除这种重复,该注解会将请求路径映射到“/person”。在WebFlux.fn中,可以通过路由函数构建器(router function builder)上的path方法来共享路径谓词。例如,上面例子中的最后几行可以通过使用嵌套路由(nested routes)以以下方式进行优化:
- Java
- Kotlin
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();
注意
path方法的第二个参数是一个消费者(consumer),它接收路由构建器(router builder)。
val route = coRouter { 1
"/person".nest {
GET($"{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION.JSON), handler::listPeople)
POST(handler::createPerson)
}
}
使用协程(Coroutines)的路由DSL来创建路由;也可以通过
router { }来使用Reactive编程风格来创建路由。
虽然基于路径的嵌套是最常见的方法,但通过使用构建器(builder)上的nest方法,你也可以对任何类型的谓词(predicate)进行嵌套。上述代码中仍然存在一些重复,比如共享的Accept头部谓词(shared Accept-header predicate)。我们可以通过结合使用nest方法和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)
}
}
}
API版本
路由器功能支持按API版本进行匹配。
首先,在WebFlux Config中启用API版本控制,然后你就可以如下使用version 谓词了:
- Java
- Kotlin
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", version("1.2"),
request -> ServerResponse.ok().bodyValue("Hello World")).build();
val route = coRouter {
GET("/hello-world", version("1.2")) {
ServerResponse.ok().bodyValueAndAwait("Hello World")
}
}
version谓词可以是:
- 固定版本(“1.2”)——仅匹配给定的版本。
- 基线版本(“1.2+”)——匹配给定版本及以上版本,直至最高支持的版本(见 webmvc/mvc-config/api-version.md)。
有关底层基础设施和API版本控制的更多详细信息,请参阅API版本控制。
提供资源
WebFlux.fn提供了内置的支持来提供资源服务。
除了下面描述的功能外,还可以借助RouterFunctions#resource(java.util.function.Function)来实现更加灵活的资源处理。
重定向到资源
可以将与指定谓词匹配的请求重定向到某个资源。例如,在单页应用程序(Single Page Applications)中处理重定向时,这种方式会非常有用。
- Java
- Kotlin
ClassPathResource index = new ClassPathResource("static/index.html");
RequestPredicate spaPredicate = path("/api/**").or(path("/error")).negate();
RouterFunction<ServerResponse> redirectToIndex = route()
.resource(spaPredicate, index)
.build();
val redirectToIndex = router {
val index = ClassPathResource("static/index.html")
val spaPredicate = !(path("/api/**") or path("/error"))
resource(spaPredicate, index)
}
从根位置提供资源
也可以将匹配给定模式的请求路由到相对于给定根位置的资源。
- Java
- Kotlin
Resource location = new FileUrlResource("public-resources/");
RouterFunction<ServerResponse> resources = RouterFunctions.resources("/resources/**", location);
val location = FileUrlResource("public-resources/")
val resources = router { resources("/resources/**", location) }
运行服务器
如何在HTTP服务器中运行路由函数?一个简单的方法是使用以下方法之一将路由函数转换为HttpHandler:
-
RouterFunctions.toHttpHandler(RouterFunction) -
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
然后,你可以使用返回的HttpHandler与多种服务器适配器一起使用,具体操作请参考HttpHandler中的服务器特定说明。
一个更为常见的选择(Spring Boot也采用这种方式)是通过WebFlux Config来运行基于DispatcherHandler的配置,该配置利用Spring配置来声明处理请求所需的组件。WebFlux Java配置声明了以下基础设施组件以支持函数式端点:
-
RouterFunctionMapping:在Spring配置中检测一个或多个RouterFunction<?>bean,对它们进行排序,通过RouterFunction.andOther将它们组合起来,然后将请求路由到组合后的RouterFunction。 -
HandlerFunctionAdapter:一个简单的适配器,允许DispatcherHandler调用被映射到请求的HandlerFunction。 -
ServerResponseResultHandler:通过调用ServerResponse的writeTo方法来处理HandlerFunction调用结果。
前述组件使得函数式端点能够适应DispatcherHandler的请求处理生命周期,并且(如果声明了注解控制器的话)还可以与这些控制器并行运行。Spring Boot WebFlux启动器正是通过这种方式来启用函数式端点的。
以下示例展示了一个WebFlux Java配置(有关如何运行它,请参见DispatcherHandler):
- Java
- Kotlin
@Configuration
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
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...
}
}
过滤处理函数
你可以通过在路由函数构建器上使用before、after或filter方法来过滤处理函数。通过注释,你也可以使用@ControllerAdvice、ServletFilter或两者结合来实现类似的功能。这些过滤器将应用于构建器创建的所有路由。这意味着在嵌套路由中定义的过滤器不会应用于“顶级”路由。例如,考虑以下示例:
- Java
- Kotlin
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();
}
before过滤器用于添加自定义请求头,仅应用于这两个GET路由。after过滤器用于记录响应,适用于所有路由,包括嵌套路由。
val route = router {
"/person" and accept(APPLICATION_JSON)).nest {
GET("/{id}", handler::getPerson)
GET(handler::listPeople)
.before { 1
ServerRequest.from(it)
.header("X-RequestHeader", "Value").build()
}
POST(handler::createPerson)
.after { _, response -> 2
logResponse(response)
}
}
}
before过滤器用于添加自定义请求头,仅应用于这两个GET路由。after过滤器用于记录响应,适用于所有路由,包括嵌套路由。
路由器构建器(router builder)上的filter方法接受一个HandlerFilterFunction:这个函数接收一个ServerRequest和一个HandlerFunction,并返回一个ServerResponse。HandlerFunction参数代表处理链中的下一个元素。这通常是被路由到的处理器(handler),但如果应用了多个过滤器,它也可能是一个其他的过滤器。
现在我们可以在我们的路由中添加一个简单的安全过滤器,前提是我们有一个SecurityManager,它可以判断某个路径是否被允许。以下示例展示了如何实现这一点:
- Java
- Kotlin
SecurityManager securityManager = getSecurityManager();
RouterFunction<ServerResponse> route = RouterFunctions.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 = getSecurityManager()
val route = coRouter {
("/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 {
ServerResponse.status(UNAUTHORIZED).buildAndAwait()
}
}
}
}
前面的例子表明,调用next.handle(ServerRequest)是可选的。我们只在允许访问时才运行处理函数。
除了在路由器函数构建器上使用filter方法外,还可以通过RouterFunction.filter(HandlerFilterFunction)对现有的路由器函数应用过滤器。
函数端点的CORS支持是通过一个专用的CorsWebFilter提供的。