概述
Spring Cloud Gateway是在Spring生态系统之上构建的API网关服务,基于Spring Boot 2.x,Spring WebFlux和Project Reactor 构建的。旨在提供一种简单而有效的方法来进行API路由,并为它们提供强大的过滤功能,例如熔断、限流、重试等。Spring Cloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。Spring Cloud Gateway的目的是提供统一的路由方式,基于Filter链的方式提供网关的基本功能,例如:安全性,监控/指标和限流。
Spring Cloud Gateway 主要功能:反向代理、鉴权、流量监控、熔断、日志监控等等。Spring Cloud Gateway所处的位置
Spring Cloud Gateway 是基于异步非阻塞模型上进行开发的。具有以下的特性
1、动态路由:能够匹配任何请求属性
2、可以对路由指定Predicate (断言) 和Filter (过滤器)
3、集成Hystrix的断路器功能
4、集成SpringCloud 服务发现的功能
5、易于编写的Predicate (断言) 和Filter (过滤器)
6、请求限流功能
7、支持路径重写
三个核心概念
Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true匹配该路由。
Predicate(断言):路由转发的判断条件,可以匹配HTTP请求中的所有内容,如果请求与断言相匹配则进行路由。
Filter(过滤):指的是Spring框架中的GatewayFilter的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改。
Web请求,通过一系列的匹配条件,定位到真正的服务节点,并在这个转发的过程前后,进行一些精细化的控制。Predicate就是匹配条件,而Filter,可以理解为无所不能的拦截器。有了这两个元素,在开始目标uri,就可以实现一个具体的路由了。
SpringCloudGateway工作方式总结
Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.
客户端向Spring Cloud Gateway 发起请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到 Gateway Web Handler,Gateway Web Handler再通过指定的过滤器链来将请求发送到实际的服务执行业务逻辑,然后返回。过滤器直接使用虚线分开,原因是过滤器可能会在发送代理请求之前(“pre”)或者之后 (“post”)执行业务逻辑。Filter在(“pre”)类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等等。在 (“post”)类型的过滤器中可以做响应内容,响应头的修改、日志的输出、流量监控等有着非常重要的作用。Spring Cloud Gateway的核心逻辑是路由转发+执行过滤器链。
入门配置
建module
cloud-gateway-gateway9527
改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zjt-cloud-api</artifactId>
<groupId>com.zjt.cloud-api</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.zjt.cloud-api</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: http://127.0.0.1:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payments/payment/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
主启动类
package cloud.zjt.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import java.util.TimeZone;
/**
* @author zjt
* @date 2020-09-14
*/
@SpringBootApplication
@EnableEurekaClient
public class Gateway9527Application {
public static void main(String[] args) {
// 时区设置
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
SpringApplication.run(Gateway9527Application.class, args);
}
}
以上代码就完成了最简单的网关Gateway配置方式一,通过配置文件的配置。映射到8001微服务提供者。测试
方式二:通过代码做网关映射。
package cloud.zjt.cloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author zjt
* @date 2020-09-14
*/
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
// 需要注意访问路径匹配时不要乱写
routes.route("zjt_gateway_baidu_news_h2", item -> item.path("/guonei").uri("http://news.baidu.com/guonei"))
.route("zjt-gateway-baidu-news-h3", item -> item.path("/guoji").uri("http://news.baidu.com/guoji"))
.route("zjt-gateway-baidu-news-h4", item -> item.path("/mil").uri("http://news.baidu.com/mil"))
.route("zjt-gateway-baidu-news-h5", item -> item.path("/finance").uri("http://news.baidu.com/finance"))
.route("zjt-gateway-baidu-news-h6", item -> item.path("/ent").uri("http://news.baidu.com/ent"))
.route("zjt-gateway-baidu-news-h7", item -> item.path("/sports").uri("http://news.baidu.com/sports"))
.route("zjt-gateway-baidu-news-h8", item -> item.path("/internet").uri("http://news.baidu.com/internet"));
return routes.build();
}
}
将百度新闻的一部分内容通过9527映射。
动态路由和扩展
现在的问题,现在调用的时8001写死的,动态扩容无法做到。
默认情况下,Gateway会根据注册中心注册的服务列表,以注册中心上的微服务名称为路径创建动态路由进行转发。从而实现动态路由的功能。
application.yml 修改
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从服务注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payments/payment/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
测试 启动 eureka7001 payment 8001、8002两个服务提供者。
predicate 断言
在使用的时候,gateway9527启动时 自动加载的有 RoutePredicateFactory,官网介绍https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factoriesPredicate是一组匹配规则,让请求根据规则找到对应的Route进行处理。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
# 在一个时间之后可用 获取这个时间的方法在下面
#- After=2020-09-15T09:35:59.574+08:00[Asia/Shanghai]
#- Before=2020-09-15T09:35:59.574+08:00[Asia/Shanghai]
- Between=2020-09-15T09:35:59.574+08:00[Asia/Shanghai], 2021-09-15T09:35:59.574+08:00[Asia/Shanghai]
- Cookie=chocolate, ch.p
- Query=red, gree.
- Header=X-Request-Id, \d+ #请求头中必须包含Request-Id 并且属性值为整数的正则表达式
- Host=**.somehost.org,**.anotherhost.org
- Method=GET,POST
- RemoteAddr=192.168.1.1/24
- Weight=group1, 2
public static void main(String[] args) {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
}
Filter 过滤器
GatewayFilter Factories 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway 内置了多种过滤器,它们都由 Gateway Filter 的工厂类来产生。 自定义过滤器
package cloud.zjt.cloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author zjt
* @date 2020-09-14
*/
@Slf4j
@Component
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 请求头 可以做权鉴
HttpHeaders headers = exchange.getRequest().getHeaders();
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
String userName = params.getFirst("userName");
// 简单校验一下userName 为空不被接受请求
if (StringUtils.isBlank(userName)) {
log.info("用户名为空");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
log.info("---------进入全局过滤器------------");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
测试 userName 不为空
测试 userName 为空