写点什么

【SpringCloud 技术专题】「Gateway 网关系列」微服务网关服务的 Gateway 全流程开发实践指南(2.2.X)

作者:浩宇天尚
  • 2022 年 1 月 15 日
  • 本文字数:5342 字

    阅读完需:约 18 分钟

【SpringCloud技术专题】「Gateway网关系列」微服务网关服务的Gateway全流程开发实践指南(2.2.X)

开发指南须知

本次实践主要在版本:2.2.0.BUILD-SNAPSHOT 上进行构建,这个项目提供了构建在 Spring 生态系统之上 API 网关。

Spring Cloud Gateway 的介绍

Spring Cloud Gateway 目标是用一个简单、有效的方式路由到 API,并且提供横切的一些关注点,例如:安全、监控、系统性能和弹性等。


API 网关介绍

API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:


  1. 客户端会多次请求不同的微服务,增加了客户端的复杂性。

  2. 存在跨域请求,在一定场景下处理相对复杂。

  3. 认证复杂,每个服务都需要独立认证。

  4. 难以重构,随着项目的迭代,可能需要重新划分微服务。

  5. 例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。

  6. 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。


以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性。

SpringCloud Gateway 技术基础

它是基于 spring 官方 Spring 5.0、Spring Boot2.0 和 Project Reactor 等技术开发的网关,Spring Cloud Gateway 旨在为微服务架构提供简单、有效和统一的 API 路由管理方式,Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且还基于 Filer 链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。

如何引用 Spring Cloud Gateway

maven 坐标为:

<dependency>   <groupId>org.springframework.cloud</groupId>   <artifactId>spring-cloud-starter-gateway</artifactId></dependency>
复制代码


有关使用当前 Spring Cloud 构建系统的详细信息,如果你引入了 starter,但不想开启 gateway,可以设置:


spring.cloud.gateway.enabled=false。
复制代码

注意

  1. Spring Cloud Gateway 构建在 Spring Boot 2.0, Spring WebFlux, and Project Reactor 之上,因此,许多熟悉的同步库(例如:Spring Data 、Spring Security)或模式不适用于 Spring Cloud Gateway。

  2. Spring Cloud Gateway 需要 SpringBoot 和 SpringWebFlux 提供的 netty 运行时,它不再运行于传统的 Servlet 容器或一个 WAR 包。

Route

网关基本构件块,也是网关最基础的部分,路由信息有一个 ID、一个目的 URL、一组断言 predicates 和一组 filters 组成。如果聚合断言为真,则匹配路由,说明请求的 URL 和配置。

Predicate

Java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring5.0 框架中的 ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自于 http request 中的任何信息,比如请求头和参数等。


Java 8 Function Predicate. 输入类型是 SpringFramework ServerWebExchange. 这允许开发人员匹配来自 HTTP 请求的任何内容,例如头或参数。

Filter

使用特定工厂构造的 Spring Framework GatewayFilter 实例。在这里,可以在发送 downstream 请求之前或之后修改 requests 和 responses。


一个标准的 Spring webFilter。Spring cloud gateway 中的 filter 分为两种类型的 Filter,分别是 Gateway Filter 和 Global Filter。过滤器 Filter 将会对请求和响应进行修改处理。

理解:
  1. 断言(Predicate):请求匹配;

  2. 过滤器(Filter):对请求或者返回进行过滤增强。


网关提供 API 全托管服务,丰富的 API 管理功能,辅助企业管理大规模的 API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。一般来说网关对外暴露的 URL 或者接口信息,我们统称为路由信息。

Spring Cloud Gateway 的工作原理

GatewayClient 请求 Spring Cloud Gateway,如果 Gateway Handler Mapping 确定请求与路由匹配,该请求被发送到 Gateway Web Handler。此 Handler 运行时发送请求到具体的请求,其中通过过滤器链。



过滤器链被虚线分隔的原因是过滤器可以在发送代理请求之前或之后执行逻辑。执行所有“预”过滤逻辑,然后发出代理请求。在发出代理请求后,将执行“post”过滤器逻辑。URIs 在路由中没有设置端口,则按照 HTTP 和 HTTPS 默认端口设置为 80 和 443。


Spring cloud Gateway 发出请求。然后再由 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway web handler。Handler 再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。

Spring Cloud Gateway-路由断言工厂

Spring Cloud Gateway 匹配路由作为 SpringWebFlux HandlerMapping 基础设施的一部分。Spring Cloud Gateway 包含许多内置的路由断言工厂,这些断言匹配不同属性的 HTTP 请求,可以组合多个路由断言工厂,并通过逻辑组合。

Before Route Predicate Factory

Before Route Predicate Factory 有一个时间参数,此断言匹配发生在该时间参数之前的请求。



这个路由匹配发生在 Jan 20, 2017 17:42 Mountain Time (Denver)之前的请求。

After Route Predicate Factory

After Route Predicate Factory 有一个时间参数,此断言匹配发生在该时间参数之后的请求。



这个路由匹配发生在 Jan 20, 2017 17:42 Mountain Time (Denver)之后的请求。

Between Route Predicate Factory

Between Route Predicate Factory 有两个时间参数。此断言匹配发生在这两个时间之间的请求。



这个路由匹配发生在 Jan 20, 2017 17:42 Mountain Time (Denver)与 Jan 21, 2017 17:42 Mountain Time (Denver)之间的请求。可应用于维护窗口。

Cookie Route Predicate Factory

Cookie Route Predicate Factory 有两个参数,包括 cookie 名称和正则表达式。此断言匹配 cookies 包括给定的名称和符合正则表达式的值。



此路由匹配 cookie 名称为 chocolate ,cookie 值为 ch.p 正则表达式,匹配 chap,chbp 等。

Header Route Predicate Factory

Header Route Predicate Factory 包括两个参数包括头名称和值的正则表达式。此断言匹配一个头信息包括该名称和符合该正则表达式值得请求。


此路由匹配头名称为 X-Request-Id 且值匹配\d+ 表达式(包含一个或多个数字)。


Host Route Predicate Factory

Host Route Predicate Factory 包括一个参数 host 名称模式列表。此模式是一种 Ant 风格模式,以 "." 作为分隔符。此断言匹配 Host 头。另外 Host 头来源有两种:第一种是请求地址;第二种是自己在 http 的 header 头中放入 Host 变量值。


URI 模板变量也支持这种格式 {sub}.myhost.org。

此路由匹配头文件中的 Host 值 www.somehost.org,beta.somehost.org 或 www.anotherhost.org。此示例均为默认端口 80,如果为其他端口,需要在表达式中定义。


此断言提取 URI 模板变量(如上面示例中定义的子变量)作为名称和值的映射,并将其放置在 ServerWebExchange.getAttributes()中,其键在 ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE 属性中定义。

在过滤其中加入如下代码示例:
Map uri_template_variables_attribute=exchange.getAttribute(ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE);   // Map variables=ServerWebExchangeUtils.getUriTemplateVariables(exchange);     uri_template_variables_attribute.forEach((K,V)->{            System.out.println("K:"+K+"--V:"+V);      });
复制代码


  • 访问 Host 为:www.myhost.org

  • 输出结果:  K:sub--V:www

Filter 规律器

Global Filter 接口与 GatewayFilter 具有相同的签名。这些是有条件地应用于所有路由的特殊过滤器。

Combined Global Filter and GatewayFilter Ordering
  • 当请求进入(并匹配路由)时,Filtering Web Handler 会将 GlobalFilter 的所有实例和 GatewayFilter 的所有路由特定实例添加到过滤器链。

  • 此组合过滤器链通过 org.springframework.core.Ordered 接口进行排序。可以通过实现 getOrder()方法或者使用 @Order 注解。


Spring Cloud Gateway 区分了过滤器逻辑执行的“请求”和“响应”阶段,具有最高优先级的过滤器将是“请求”阶段的第一个和“响应”阶段的最后一个 。


ANT 通配符有三种:

而上面多数的匹配规则运算符号都是有 AntPathMatcher 对象进行实现的


private AntPathMatcher antPathMatcher = new AntPathMatcher();
复制代码


  • ?:匹配任意一个单个字符

  • :匹配 0 或者任意数量的字符

  • ** :匹配 0 和或者更多目录的字符


@Componentpublic class AuthGlobalFilter implements GlobalFilter, Ordered {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); //商城api接口,校验用户必须登录 if(antPathMatcher.match("/api/**/auth/**", path)) { List<String> tokenList = request.getHeaders().get("token"); if(null == tokenList) { ServerHttpResponse response = exchange.getResponse(); return out(response); } else {// Boolean isCheck = JwtUtils.checkToken(tokenList.get(0));// if(!isCheck) { ServerHttpResponse response = exchange.getResponse(); return out(response);// } } } //内部服务接口,不允许外部访问 if(antPathMatcher.match("/**/inner/**", path)) { ServerHttpResponse response = exchange.getResponse(); return out(response); } return chain.filter(exchange); }
@Override public int getOrder() { return 0; }
private Mono<Void> out(ServerHttpResponse response) { JsonObject message = new JsonObject(); message.addProperty("success", false); message.addProperty("code", 28004); message.addProperty("data", "鉴权失败"); byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8); DataBuffer buffer = response.bufferFactory().wrap(bits); //response.setStatusCode(HttpStatus.UNAUTHORIZED); //指定编码,否则在浏览器中会中文乱码 response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); return response.writeWith(Mono.just(buffer)); }}
复制代码

自定义异常处理

服务网关调用服务时可能会有一些异常或服务不可用,它返回错误信息不友好,需要我们覆盖处理。

ErrorHandlerConfig
@Configuration@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})public class ErrorHandlerConfig {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfig(ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { this.serverProperties = serverProperties; this.applicationContext = applicationContext; this.resourceProperties = resourceProperties; this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; }
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) { JsonExceptionHandler exceptionHandler = new JsonExceptionHandler( errorAttributes, this.resourceProperties, this.serverProperties.getError(), this.applicationContext); exceptionHandler.setViewResolvers(this.viewResolvers); exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders()); return exceptionHandler; }}
复制代码

参考资料

  • https://blog.csdn.net/qq_37989738/article/details/107205863

发布于: 2022 年 01 月 15 日阅读数: 2
用户头像

浩宇天尚

关注

🏆InfoQ写作平台-签约作者🏆 2020.03.25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
【SpringCloud技术专题】「Gateway网关系列」微服务网关服务的Gateway全流程开发实践指南(2.2.X)