Soul网关源码阅读(六)—— Soul网关之WebFlux

概要

上一篇我们学习了Soul网关是如何进行请求转发的,通过分析源码我们知道,soul网关是利用了WebFlux服务框架,使其具备异步、非阻塞的特性。究竟什么是WebFlux呢?

这一篇我们的思路是:自己动手实现一个基于WebFlux进行请求转发的demo,模拟服务器网关请求转发的功能,以此来熟悉其中的WebFlux的核心概念及设计思路。

WebFlux简介

WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。该模块中包含了对反应式 HTTP、服务器推送事件和 WebSocket 的客户端和服务器端的支持。对于开发人员来说,比较重要的是服务器端的开发,这也是本文的重点。

在服务器端,WebFlux 支持两种不同的编程模型:

  1. 第一种是 Spring MVC 中使用的基于 Java 注解的方式;
  2. 第二种是基于 Java 8 的 lambda 表达式的函数式编程模型。

这两种编程模型只是在代码编写方式上存在不同。它们运行在同样的反应式底层架构之上,因此在运行时是相同的。WebFlux 需要底层提供运行时的支持,WebFlux 可以运行在支持 Servlet 3.1 非阻塞 IO API 的 Servlet 容器上,或是其他异步运行时环境,如 Netty 。

本文从三个方面对 WebFlux 进行介绍。首先是使用经典的基于 Java 注解的编程模型来进行开发,其次是使用 WebFlux 新增的函数式编程模型来进行开发,最后介绍 WebFlux 应用的测试。通过这样循序渐进的方式让读者了解 WebFlux 应用开发的细节。

实践操作

首先我们用idea创建一个web-flux-dem的Spring Initializr的工程,添加Web->Spring Reactive Web依赖。
在这里插入图片描述

Java 注解编程模型

基于 Java 注解的编程模型,对于使用过 Spring MVC 的开发人员来说是再熟悉不过的。在 WebFlux 应用中使用同样的模式,容易理解和上手。我们先从最经典的 Hello World 的示例开始说明。代码清单 1 中的 BasicController 是 REST API 的控制器,通过@RestController 注解来声明。在 BasicController 中声明了一个 URI 为/hello_world 的映射。其对应的方法 sayHelloWorld()的返回值是 Mono类型,其中包含的字符串”Hello World”会作为 HTTP 的响应内容。

清单 1. Hello World 示例
@RestController
public class BasicController {
    
    
    @GetMapping("/hello_world")
    public Mono<String> sayHelloWorld() {
    
    
        return Mono.just("Hello World");
    }
}

从代码清单 1 中可以看到,使用 WebFlux 与 Spring MVC 的不同在于,WebFlux 所使用的类型是与反应式编程相关的 Flux 和 Mono 等,而不是简单的对象。对于简单的 Hello World 示例来说,这两者之间并没有什么太大的差别。对于复杂的应用来说,反应式编程和负压的优势会体现出来,可以带来整体的性能的提升。

函数式编程模型

在上节中介绍了基于 Java 注解的编程模型,WebFlux 还支持基于 lambda 表达式的函数式编程模型。与基于 Java 注解的编程模型相比,函数式编程模型的抽象层次更低,代码编写更灵活,可以满足一些对动态性要求更高的场景。

我们上一篇分析了soul网关需要动态加载数据,因此函数式的编程模型更适合它。

在函数式编程模型中,每个请求是由一个函数来处理的, 通过接口 org.springframework.web.reactive.function.server.HandlerFunction 来表示。HandlerFunction 是一个函数式接口,其中只有一个方法 Mono handle(ServerRequest request),因此可以用 labmda 表达式来实现该接口。接口 ServerRequest 表示的是一个 HTTP 请求。通过该接口可以获取到请求的相关信息,如请求路径、HTTP 头、查询参数和请求内容等。方法 handle 的返回值是一个 Mono对象。接口 ServerResponse 用来表示 HTTP 响应。ServerResponse 中包含了很多静态方法来创建不同 HTTP 状态码的响应对象。本节中通过一个简单的GreetingHandler来进行演示。代码清单2中,直接返回了一个字符串的结果返回,当然也可以根据request的参数做一些运算后返回,原理都是相同的。

清单 2. 处理请求的类 GreetingHandler
@Component
public class GreetingHandler {
    
    

    public Mono<ServerResponse> hello(ServerRequest request) {
    
    
        return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
                .body(BodyInserters.fromValue("Hello, Spring!"));
    }
}

在创建了处理请求的 HandlerFunction 之后,下一步是为这些 HandlerFunction 提供路由信息,也就是这些 HandlerFunction 被调用的条件。这是通过函数式接口 org.springframework.web.reactive.function.server.RouterFunction 来完成的。接口 RouterFunction 的方法 Mono route(ServerRequest request)对每个 ServerRequest,都返回对应的 0 个或 1 个 HandlerFunction 对象,以 Mono来表示。当找到对应的 HandlerFunction 时,该 HandlerFunction 被调用来处理该 ServerRequest,并把得到的 ServerResponse 返回。在使用 WebFlux 的 Spring Boot 应用中,只需要创建 RouterFunction 类型的 bean,就会被自动注册来处理请求并调用相应的 HandlerFunction。

代码清单 3 给了示例相关的配置类 Config。方法 RouterFunctions.route 用来根据 Predicate 是否匹配来确定 HandlerFunction 是否被应用。RequestPredicates 中包含了很多静态方法来创建常用的基于不同匹配规则的 Predicate。如 RequestPredicates.path 用来根据 HTTP 请求的路径来进行匹配。此处我们检查请求的路径是/hello。在清单 3 中,我们添加了一个对路由/hello的处理,请求方法为GET请求,并使用greetingHandler的hello方法来进行处理,并返回值。

清单 3. 注册 RouterFunction
@Configuration
public class RoutingConfiguration {
    
    

    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(GreetingHandler greetingHandler) {
    
    
        return RouterFunctions.route(RequestPredicates.GET("/hello")
                .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), greetingHandler::hello);
    }
}

使用WebClient调用后端服务

上一小节我们介绍了,从客户端发来的http请求WebFlux是怎么进行编程配置处理和响应的。大多数时候,我们的HandlerFunction逻可能远比demo中实现的GreetingHandler复杂,像我们的soul网关,它可能设计远程调用(比如请求后端的服务)。

这个时候可以使用 WebFlux 模块中的类 org.springframework.web.reactive.function.client.WebClient,来进行http调用。代码清单4 中的 RESTClient 用来访问前面小节中创建的 REST API。首先使用 WebClient.create 方法来创建一个新的 WebClient 对象,然后使用方法 get 来创建一个 GET 请求,方法 exchange 的作用是发送请求并得到以 Mono表示的 HTTP 响应。最后对得到的响应进行处理并输出结果。ServerResponse 的 bodyToMono 方法把响应内容转换成String 类型(也可以是其他自定义对象),最终得到的结果是 Mono对象。调用result.block()方法的作用是等待请求完成并得到所产生的类 String 的对象。

清单 4. 使用 WebClient 访问 REST API
/**
 * @author Lijiajun
 * @date 2021/01/21 1:07 AM
 */
public class RESTClient {
    
    
    public static void main(final String[] args) {
    
    
        final WebClient client = WebClient.create("http://localhost:8080/hello");
        final Mono<String> result = client.get()
                .uri("")
                .accept(MediaType.TEXT_PLAIN)
                .exchange()
                .flatMap(response -> response.bodyToMono(String.class));
        System.out.println(result.block());
    }
}

执行上面的main方法,可以看到以下输出:
在这里插入图片描述
说明我们用WebClient成功调用了我们的api服务,把这部分代码逻辑集成到我们的Handler中,就可以实现请求的转发功能。

由此我们实现了从前端请求到WebFlux,再通过WebFlux转发到后端的逻辑,已经具备一个网关最基本的请求转发的功能了!

思考总结

今天我们做了一个最简单的demo,来实现一个基于webflux服务的请求转发的功能,代码非常简单。通过这个demo我们了解了,webflux基本的运行原理,以及一些核心概念,比如Mono,Flux,ServerRequest,ServerResponse,WebClient等。这些概念在我们的soul网关里也是频繁出现,因此掌握他们对我们接下来分析soul网关源码起着至关重要的作用。

明天我们将具体soul网关是如何基于WebFlux设计请求转发的,敬请期待。

猜你喜欢

转载自blog.csdn.net/u010084384/article/details/112915038