# 1.5 Functional Endpoints

Spring WebFlux包含WebFlux.fn，这是一个轻量级的函数编程模型，其中的函数用于路由和处理请求，而契约则是为不变性而设计的。它是基于注解的编程模型的替代方案，但可以在相同的Reactive Core基础上运行。

## 1.5.1 总览

在WebFlux.fn中，HTTP请求由HandlerFunction处理：该函数接受ServerRequest并返回延迟的ServerResponse（即Mono \<ServerResponse>）。作为请求对象的请求都具有不可变的协定，这些协定为JDK 8提供了对HTTP请求和响应的友好访问。 HandlerFunction等效于基于注解的编程模型中@RequestMapping方法的主体。

传入的请求通过RouterFunction路由到处理程序函数：该函数接受ServerRequest并返回延迟的HandlerFunction（即Mono \<HandlerFunction>）。当路由器功能匹配时，返回处理程序功能。否则为空Mono。 RouterFunction等效于@RequestMapping批注，但主要区别在于路由器功能不仅提供数据，还提供行为。

RouterFunctions.route（）提供了一个路由器构建器，可简化路由器的创建过程，如以下示例所示：

```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()
    .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) {
        // ...
    }
}
```

运行RouterFunction的一种方法是将其转换为HttpHandler并通过内置服务器适配器之一进行安装：

* RouterFunctions.toHttpHandler（RouterFunction）
* RouterFunctions.toHttpHandler（RouterFunction，HandlerStrategies）

大多数应用程序都可以通过WebFlux Java配置运行，请参阅运行服务器。

## 1.5.2 HandlerFunction

ServerRequest和ServerResponse是不可变的接口，它们提供JDK 8友好的HTTP请求和响应访问。 请求和响应都为反应流提供了对体流的反压力。 请求主体用Reactor Flux或Mono表示。 响应主体由任何Reactive Streams Publisher组成，包括Flux和Mono。 有关更多信息，请参见反应式库。

**ServerRequest**

ServerRequest提供对HTTP方法，URI，标头和查询参数的访问，而通过body方法提供对主体的访问。

下面的示例将请求正文提取到Mono \<String>：

```java
Mono<String> string = request.bodyToMono(String.class);
```

下面的示例将主体提取到Flux \<Person>，其中Person对象从某种序列化形式（例如JSON或XML）解码：

```java
Flux<Person> people = request.bodyToFlux(Person.class);
```

前面的示例是使用更通用的ServerRequest.body（BodyExtractor）的快捷方式，该请求接受BodyExtractor功能策略接口。 实用程序类BodyExtractors提供对许多实例的访问。 例如，前面的示例也可以编写如下：

```java
Mono<String> string = request.body(BodyExtractors.toMono(String.class));
Flux<Person> people = request.body(BodyExtractors.toFlux(Person.class));
```

下面的示例显示如何访问表单数据：

```java
Mono<MultiValueMap<String, String> map = request.body(BodyExtractors.toFormData());
```

以下示例显示了如何以Map形式访问多部分数据：

```java
Mono<MultiValueMap<String, Part> map = request.body(BodyExtractors.toMultipartData());
```

下面的示例演示如何以流方式一次访问多个部分：

```java
Flux<Part> parts = request.body(BodyExtractors.toParts());
```

**ServerResponse**

ServerResponse提供对HTTP响应的访问，并且由于它是不可变的，因此可以使用一种build方法来创建它。您可以使用构建器来设置响应状态，添加响应标题或提供正文。以下示例使用JSON内容创建200（确定）响应：

```java
Mono<Person> person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person, Person.class);
```

下面的示例演示如何构建Location不带标头的201（已创建）响应：

```java
URI location = ...
ServerResponse.created(location).build();
```

**Handler Classes**

我们可以将处理程序函数编写为lambda，如以下示例所示：

```java
HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body(fromObject("Hello World"));
```

这很方便，但是在应用程序中我们需要多个功能，并且多个内联lambda可能会变得凌乱。 因此，将相关的处理程序功能分组到一个处理程序类中很有用，该类的作用与基于注解的应用程序中的@Controller相似。 例如，以下类公开了反应型Person存储库：

```java
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.ServerResponse.ok;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    public Mono<ServerResponse> listPeople(ServerRequest request) { 
        Flux<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people, Person.class);
    }

    public Mono<ServerResponse> createPerson(ServerRequest request) { 
        Mono<Person> person = request.bodyToMono(Person.class);
        return ok().build(repository.savePerson(person));
    }

    public Mono<ServerResponse> getPerson(ServerRequest request) { 
        int personId = Integer.valueOf(request.pathVariable("id"));
        return repository.getPerson(personId)
            .flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromObject(person)))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}
```

**Validation**

功能端点可以使用Spring的验证工具将验证应用于请求正文。 例如，给定Person的自定义Spring Validator实现：

```java
public class PersonHandler {

    private final Validator validator = new PersonValidator(); 

    // ...

    public Mono<ServerResponse> createPerson(ServerRequest request) {
        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(body, "person");
        validator.validate(body, errors);
        if (errors.hasErrors) {
            throw new ServerWebInputException(errors.toString()); 
        }
    }
```

处理程序还可以通过创建和注入基于LocalValidatorFactoryBean的全局Validator实例来使用标准的bean验证API（JSR-303）。

## 1.5.3 RouterFunction

路由器功能用于将请求路由到相应的HandlerFunction。通常，您不是自己编写路由器功能，而是使用RouterFunctions实用工具类上的方法来创建一个。 RouterFunctions.route（）（无参数）为您提供了流畅的生成器，用于创建路由器功能，而RouterFunctions.route（RequestPredicate，HandlerFunction）提供了直接创建路由器的方法。

通常，建议使用route（）构建器，因为它为典型的映射方案提供了便捷的快捷方式，而无需发现静态导入。例如，路由器功能构建器提供了GET（String，HandlerFunction）方法来为GET请求创建映射。和POST（String，HandlerFunction）进行POST。

除了基于HTTP方法的映射外，路由构建器还提供了一种在映射到请求时引入其他谓词的方法。对于每个HTTP方法，都有一个重载的变体，它以RequestPredicate作为参数，尽管可以表达其他约束。

**Predicates**

您可以编写自己的RequestPredicate，但是RequestPredicates实用程序类根据请求路径，HTTP方法，内容类型等提供常用的实现。以下示例使用请求谓词基于Accept头创建约束：

```java
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> Response.ok().body(fromObject("Hello World")));
```

您可以使用以下命令组合多个请求谓词：

* RequestPredicate.and（RequestPredicate）-两者都必须匹配。
* RequestPredicate.or（RequestPredicate）-两者都可以匹配。

RequestPredicates中的许多谓词都是组成的。例如，RequestPredicates.GET（String）由RequestPredicates.method（HttpMethod）和RequestPredicates.path（String）组成。上面显示的示例还使用了两个请求谓词，因为构建器在内部使用RequestPredicates.GET并将其与接受谓词组合在一起。

**Routes**

路由器功能按顺序评估：如果第一个路由不匹配，则评估第二个路由，依此类推。因此，在通用路由之前声明更具体的路由是有意义的。请注意，此行为不同于基于注解的编程模型，在该模型中，将自动选择“最特定”的控制器方法。

使用路由器功能生成器时，所有定义的路由都组成一个RouterFunction，从build（）返回。还有其他方法可以将多个路由器功能组合在一起：

* 在RouterFunctions.route（）构建器上添加（RouterFunction）
* RouterFunction.and（RouterFunction）
* RouterFunction.andRoute（RequestPredicate，HandlerFunction）-带有嵌套RouterFunctions.route（）的RouterFunction.and（）的快捷方式。

以下示例显示了四种路线的组成：

```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) 
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople) 
    .POST("/person", handler::createPerson) 
    .add(otherRoute) 
    .build();
```

**Nested Routes**

一组路由器功能通常具有共享谓词，例如共享路径。 在上面的示例中，共享谓词将是与/ person匹配的路径谓词，由三个路由使用。 使用注解时，您可以通过使用映射到/ person的类型级别@RequestMapping注解来删除此重复项。 在WebFlux.fn中，可以通过路由器功能构建器上的path方法共享路径谓词。 例如，以上示例的最后几行可以通过使用嵌套路由以以下方式进行改进：

```java
RouterFunction<ServerResponse> route = route()
    .path("/person", builder -> builder
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET("", accept(APPLICATION_JSON), handler::listPeople)
        .POST("/person", handler::createPerson))
    .build();
```

请注意，路径的第二个参数是使用路由器构建器的使用者。

尽管基于路径的嵌套是最常见的，但是您可以通过使用构建器上的nest方法来嵌套在任何种类的谓词上。 上面的内容仍然包含一些以共享的Accept-header谓词形式出现的重复。 通过将nest方法与accept一起使用，我们可以进一步改进：

```java
RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .build();
```

## 1.5.4 Running a Server

如何在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
@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...
    }
}
```

## 1.5.5 Filtering Handler Functions

您可以使用路由功能构建器上的before，after或filter方法来过滤处理程序函数。 使用注解，可以通过使用@ ControllerAdvice，ServletFilter或同时使用两者来实现类似的功能。 该过滤器将应用于构建器构建的所有路由。 这意味着在嵌套路由中定义的过滤器不适用于“顶级”路由。 例如，考虑以下示例：

```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) 
                .header("X-RequestHeader", "Value")
                .build()))
        .POST("/person", handler::createPerson))
    .after((request, response) -> logResponse(response)) 
    .build();
```

路由器构建器上的filter方法采用HandlerFilterFunction：该函数采用ServerRequest和HandlerFunction并返回ServerResponse。 handler函数参数代表链中的下一个元素。 这通常是路由到的处理程序，但是如果应用了多个，它也可以是另一个过滤器。

现在，我们可以在路由中添加一个简单的安全过滤器，假设我们拥有一个可以确定是否允许特定路径的SecurityManager。 以下示例显示了如何执行此操作：

```java
SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();
```

前面的示例演示了调用next.handle（ServerRequest）是可选的。 当允许访问时，我们仅允许执行处理函数。

除了在路由器功能构建器上使用filter方法之外，还可以通过RouterFunction.filter（HandlerFilterFunction）将过滤器应用于现有路由器功能。

> 通过专用的CorsWebFilter提供对功能端点的CORS支持。
