我们的各种微服务是通过REST API接口提供给外部应用调用,而客户端直接与各个微服务通讯则会出现一些问题:
而我们就是要通过微服务网关解决这些问题,网关的用处有许多方面:
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的URL,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,在API网关中进行权限控制,同时API网关将请求以负载均衡的方式发送给后端服务。
微服务网关架构:
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway作为Spring Cloud 生态系统中的网关,是基于WebFlux框架是实现的,而WebFlux框架是使用高性能的Reactor模式通信框架Netty。Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0 构建
能够匹配任何请求属性的路由。
谓词和过滤器特定于路由。
断路器集成。
Spring Cloud Discovery客户端集成
易于编写谓词和过滤器
请求速率限制
路径重写
比较重要的三个概念:过滤器(Filter)、路由、断言(易于编写谓词)
(1)Filter(过滤器):
和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对下游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。
(2)Route(路由):
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
(3)Predicate(断言):
这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
路由是网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
基础路由配置方式
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: service1
uri: https://blog.csdn.net
predicates:
- Path=/csdn
含义:该网关微服务的名称gateway,配置了一个id为service1的URI代理规则,这个路由规则是:当我们访问地址为 域名:端口号/csdn/xx.jsp时,会路由到上游地址https://blog.csdn.net/xx.jsp。
和注册中心相结合的路由配置方式
基于代码的路由配置方式:我们在启动类GateWayApplication中添加方法,用于定制转发规则:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/csdn")
.uri("https://blog.csdn.net"))
.build();
}
不过这种方式不常使用。
在uri的schema协议部分为自定义的lb:类型,表示从微服务注册中心(如Eureka)订阅服务,并且通过负载均衡进行服务的路由。
server:
port: 9005
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: service1
uri: https://blog.csdn.net
predicates:
- Path=/csdn
- id: service2
# uri: http://127.0.0.1:9001
uri: lb://cloud-payment-service
predicates:
- Path=/payment/**
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9004/eureka
注册中心相结合的路由配置方式,与单个URI的路由配置,区别其实很小,仅仅在于URI的schema协议不同。单个URI的地址的schema协议,一般为http或者https协议。启动多个支付微服务,可以会发现我们配置的相关端口会轮流出现。
Spring Cloud Gateway的主要功能之一是转发请求,转发规则的定义主要包含三个部分:
Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。
Predicate 断言条件
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
Predicate 就是为了实现一组匹配规则,方便让请求过来找到对应的 Route 进行处理,接下来我们接下 Spring Cloud GateWay 内置几种 Predicate 的使用。
举个例子:
spring:
cloud:
gateway:
routes:
- id: service
uri: lb://cloud-payment-service
predicates:
- Query=num,1
- Header=X-Request-Id, \d+
- Cookie=sessionName,Tom
- Host=**.csdn.net
- Method=POST
- Path=/payment/{segment}
这里的组合Predicates条件含义:该路由配置的id为service,路由的uri地址表示使用负载均衡策略调用cloud-payment-service这个微服务,断言条件:访问路径中必须含有参数num,且值为1;请求头包含X-Request-Id,且参数这里使用的正则表达式表示任何整数值;cookie包含sessionName,且值为Tom;host主机地址需要以csdn.net结尾;请求方法为POST;请求路径/payment/{segment},表示/payment/后的一个子片段(可以作为一个变量)。
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。一个请求满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
过滤器规则
常见过滤器:
示例:
spring:
application:
name: api-gateway
# Spring Cloud Gateway 路由配置
cloud:
gateway:
routes:
# 过滤器规则
# 1. PrefixPath 对所有的请求路径添加前缀
- id: service10
uri: http://127.0.0.1:9001
predicates:
- Path=/{segment}
filters:
- PrefixPath=/payment
# 2. StripPrefix 跳过指定路径
- id: service11
uri: http://127.0.0.1:9001
predicates:
- Path=/api/{segment}
filters: # 这里是将第一个路径跳过,并且添加/payment/前缀
- StripPrefix=1
- PrefixPath=/payment
# 3. RewritePath 重写路径
- id: service12
uri: http://127.0.0.1:9001
predicates:
- Path=/api/payment/**
filters:
- RewritePath=/api/(?.*), /$\{segment} # 将前面的正则,改写为后面的正则(重写)
# 4. SetPath SetPath和Rewrite类似
- id: service13
uri: http://127.0.0.1:9001
predicates:
- Path=/api/payment/{segment}
filters:
- SetPath=/payment/{segment}
# 5. RemoveRequestHeader 去掉某个请求头信息
- id: removerequestheader_route
uri: https://example.org
predicates:
- Method=GET
filters:
- RemoveRequestHeader=X-Request-Foo
# 6. RemoveResponseHeader 去掉某个回执头信息
- id: removerequestheader_route
uri: https://example.org
predicates:
- Method=GET
filters:
- RemoveResponseHeader=X-Request-Foo
# 7. RemoveRequestParameter 去掉某个请求参数信息
- id: removerequestparameter_route
uri: https://example.org
predicates:
- Method=GET
filters:
- RemoveRequestParameter=red
# # 8. SetRequestHeader 设置请求头信息
- id: setrequestheader_route
uri: https://example.org
predicates:
- Method=GET
filters:
- SetRequestHeader=X-Request-Red, Blue
# 9. default-filters 对所有的请求添加过滤器
- id: service13
uri: http://127.0.0.1:9000
predicates:
- Path=/9000/{segment}
- id: service14
uri: http://127.0.0.1:9001
predicates:
- Path=/9001/{segement}
default-filters:
- StripPrefix=1
- PrefixPath=/payment
Spring-Cloud-Gateway 基于过滤器实现,同 zuul 类似,有pre和post两种方式的 filter,分别处理前置逻辑和后置逻辑。客户端的请求先经过pre类型的 filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过post类型的 filter 处理,最后返回响应到客户端。
过滤器分为全局过滤器和局部过滤器。
全局过滤器
全局过滤器需要实现GlobalFilter 和 Ordered两个接口,重写相关方法,加入到spring容器管理即可,无需配置,全局过滤器对所有的路由都有效。
/**
* 全局过滤器
* 实现 GlobalFilter 和 Ordered,重写相关方法,加入到spring容器管理即可,无需配置,全局过滤器对所有的路由都有效。
*/
@Configuration // 注释后,该配置类不再生效
@Slf4j
public class FilterConfig {
/**
* 将自定义的全局过滤器,通过@Bean注册到spring容器中
* @return
*/
@Bean
public GlobalFilter a() {
return new AFilter();
}
@Bean
public GlobalFilter b() {
return new BFilter();
}
@Bean
public GlobalFilter c() {
return new CFilter();
}
@Bean
public MyAuthFilter myAuthFilter() {
return new MyAuthFilter();
}
static class AFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("AFilter前置逻辑。。。");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("AFilter后置逻辑。。。");
}));
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 200;
}
}
static class BFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("BFilter前置逻辑。。。");
// chain.filter(exchange) 表示将exchange请求信息传递到下一个过滤器(执行过滤器链)
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("BFilter后置逻辑。。。");
}));
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 100;
}
}
static class CFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("CFilter前置逻辑。。。");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("CFilter后置逻辑。。。");
}));
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 300;
}
}
static class MyAuthFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("MyAuthFilter过滤器执行");
String token = exchange.getRequest().getHeaders().getFirst("token");
if (StringUtils.isBlank(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // 设置响应码为401
return exchange.getResponse().setComplete(); // 结束此次请求
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 400;
}
}
}
定义了4个全局过滤器,顺序为A>B>C>MyAuthFilter,其中全局过滤器MyAuthFilter中判断令牌是否存在,如果令牌不存在,则返回401状态码,表示没有权限访问。
局部过滤器
定义局部过滤器步骤如下。
(1)需要实现GatewayFilter, Ordered,实现相关的方法
(2)加入到过滤器工厂,并且注册到spring容器中。
(3)在配置文件中进行配置,如果不配置则不启用此过滤器规则。
举例:对于请求头user-id校验,如果不存在user-id请求头,直接返回状态码406。
/**
* 局部过滤器
* 定义类名需以 名称+ GatewayFilterFactory 格式,并且需继承AbstractGatewayFilterFactory
及yml配置文件中的配置如下:
spring:
cloud:
gateway:
routes:
- id: service
uri: http://127.0.0.1:9001
predicates:
- Path=/{segment}
default-filters:
- PrefixPath=/payment
- UserIdCheck
跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域。
我们在某一个微服务下的资源目录创建一个index.html文件,用于ajax请求调用另一个微服务,但是由于浏览器同源资源策略,会出现跨域访问问题:CORS错误
其中一个方法,在需要请求调用另一个微服务的类上添加注解 @CrossOrigin,那么就可以跨域访问该类,但是不推荐,如果需要跨域访问的资源比较多,我们需要在每个资源上添加这个注解。
另一个方法,跨域配置:
现在请求经过gatway网关是,可以通过网关统一配置跨域访问。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns: "*" # spring boot2.4配置
# allowed-origins: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- GET
- POST
- DELETE
- PUT
- OPTION