函数式端点
Spring WebFlux 包含 WebFlux.fn,这是一种轻量级的函数式编程模型,其中使用函数来路由和处理请求,并且契约设计为不可变性。 它是基于注解的编程模型的替代方案,但同样运行在相同的 Reactive Core 基础上。
概述
在WebFlux.fn中,HTTP请求是通过一个HandlerFunction处理的:这是一个函数,它接受一个
ServerRequest并返回一个延迟的ServerResponse(即Mono<ServerResponse>)。
请求和响应对象都具有不可变的契约,提供了方便地访问HTTP请求和响应的方法。
HandlerFunction相当于基于注解的编程模型中@RequestMapping方法的主体部分。
传入的请求被路由到一个处理函数,该函数接收 RouterFunction: 一个接受 ServerRequest 并返回延迟的 HandlerFunction(即 Mono<HandlerFunction>)的函数。当路由器函数匹配时,返回一个处理函数;否则返回一个空的 Mono。RouterFunction 等价于 @RequestMapping 注解,但主要区别在于路由器函数不仅提供数据,还提供行为。
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) {
// ...
}
}
| 1 | 使用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 {
// ...
}
}
| 1 | 使用协程路由器DSL创建路由器;响应式替代方案也可通过router { }获得。 |
一种运行RouterFunction的方法是将其转换为HttpHandler并通过内置的服务器适配器之一进行安装:
-
RouterFunctions.toHttpHandler(RouterFunction) -
RouterFunctions.toHttpHandler(RouterFunction, HandlerStrategies)
大多数应用程序可以通过WebFlux Java配置运行,见运行服务器。
处理器函数
ServerRequest 和 ServerResponse 是不可变的接口,为HTTP请求和响应提供了便捷的访问方式。
请求和响应都针对正文流提供了Reactive Streams背压支持。
请求体由Reactor的Flux或Mono表示。
响应体可以是任何Reactive Streams的Publisher,包括Flux和Mono。
更多信息,请参阅响应式库。
服务器请求
ServerRequest 提供对 HTTP 方法、URI、标头和查询参数的访问,
而主体的访问则通过 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()
以下示例展示了如何将多部分数据作为映射来访问:
-
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 提供对 HTTP 响应的访问,并且由于它是不可变的,您可以使用 build 方法来创建它。您可以使用构建器设置响应状态、添加响应头或提供正文。以下示例创建了一个 200 (OK) 响应,带有 JSON 内容:
-
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())
}
}
}
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
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请求。
除了基于HTTP方法的映射外,路由构建器还提供了一种在映射到请求时引入额外谓词的方法。对于每种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()返回。
还有其他方法可以将多个路由器功能组合在一起:
-
add(RouterFunction)在RouterFunctions.route()构建器上 -
RouterFunction.and(RouterFunction) -
RouterFunction.andRoute(RequestPredicate, HandlerFunction)—RouterFunction.and()的快捷方式,其中包含嵌套的RouterFunctions.route()。
以下示例展示了四个路由的组成:
-
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)
嵌套路由
一组路由函数通常会有一个共同的谓词,例如共享路径。在上面的例子中,这个共同的谓词将是一个匹配/person的路径谓词,被三条路由使用。当使用注解时,你可以通过使用类型级别的@RequestMapping注解来消除这种重复。在WebFlux.fn中,可以通过路由器函数构建器上的path方法来共享路径谓词。例如,上面的例子中的最后几行可以通过使用嵌套路由进行改进:
-
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();
| 1 | 请注意,第二个参数path是一个接受路由器构建器的消费者。 |
val route = coRouter { (1)
"/person".nest {
GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
GET(accept(APPLICATION_JSON), handler::listPeople)
POST(handler::createPerson)
}
}
| 1 | 使用协程路由器DSL创建路由器;响应式替代方案也可通过router { }获得。 |
尽管基于路径的嵌套是最常见的,但你可以通过使用构建器上的nest方法来根据任何类型的谓词进行嵌套。
上述代码中仍然包含一些重复的部分,即共享的Accept-header谓词。
我们可以通过结合使用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 配置中启用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+")— 匹配给定版本及以上,直至最高 支持的版本。
参见API版本控制以了解更多关于基础架构和对API版本控制的支持的详细信息。
提供资源
WebFlux.fn 提供了用于服务资源的内置支持。
除了下面描述的功能之外,还可以通过
RouterFunctions#resource(java.util.function.Function) 实现更加灵活的资源处理。 |
重定向到资源
可以将匹配指定谓词的请求重定向到某个资源。例如,这在处理单页应用程序中的重定向时非常有用。
-
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使用,是通过基于DispatcherHandler的设置来运行,通过DispatcherHandler-based setup 通过WebFlux Config,它使用Spring配置来声明处理请求所需的组件。WebFlux Java配置声明了以下基础设施组件以支持功能性端点:
-
RouterFunctionMapping: 检测Spring配置中的一个或多个RouterFunction<?>bean,对其进行排序,通过RouterFunction.andOther将其组合,并将请求路由到生成的组合RouterFunction。 -
HandlerFunctionAdapter: 简单的适配器,允许DispatcherHandler调用 一个被映射到请求的HandlerFunction。 -
ServerResponseResultHandler: 处理通过调用HandlerFunction并调用ServerResponse的writeTo方法的结果。
上述组件让功能端点适应DispatcherHandler请求处理生命周期,并且(可能)与注解控制器并行运行,如果声明了注解控制器的话。这也是Spring Boot WebFluxStarters启用功能端点的方式。
以下示例展示了一个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();
| 1 | 添加自定义请求头的before过滤器仅应用于两个GET路由。 |
| 2 | The 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)
}
}
}
| 1 | 添加自定义请求头的before过滤器仅应用于两个GET路由。 |
| 2 | The after 过滤器会记录响应,它被应用于所有路由,包括嵌套的路由。 |
The filter 方法在路由构建器上接受一个 HandlerFilterFunction: 一个接受 ServerRequest 和 HandlerFunction 并返回一个 ServerResponse 的函数。该处理器函数参数表示链中的下一个元素。这通常是被路由到的处理器,但也可能是应用了多个过滤器时的另一个过滤器。
现在我们可以为我们的路由添加一个简单的安全过滤器,假设我们有一个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为功能性端点提供。 |