标签:release final 框架 short cal ctr header attribute monitor
官网文档: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/
微服务架构里面还有一个非常重要的组件,就是网关,
在Spring Cloud 全家桶里面也有这个角色, 在 1.x 版本中 采用的是 Zuul 网关,
但是因为 zuul的升级一直跳票,一直放鸽子, Spring Cloud 在2.x 中研发了一个自己的网关 替代了 Zuul, 那就是 Gateway
网关常见的功能有路由转发、权限校验、限流控制等作用。
例如在微服务架构中, 如果可以使用网关对 请求进行转发, 前端只需访问一个地址,并携带需要调用的目标地址,由网关进行统一管理. 并且在请求过程中 对请求进行过滤,鉴权,使 微服务的 服务地址不直接暴露,保护了 微服务节点的安全
微服务架构中网关的位置:
源码架构:
什么是WebFlux
这里做基本介绍,详细请自行官网学习
基本核心组件
Gateway 三个组件
需要搭建 Gateway 网关的微服务, 并注册到注册中心.
pom依赖:
<dependencies>
<!-- gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- <!– web Gateway不需要web依赖 –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
yml配置:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址,即路由跳转地址
predicates:
- Path=/payment/get/** #路径类型的断言,路径相匹配的则匹配路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
主启动类:
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run( GateWayMain9527.class,args);
}
}
上面的代码中, 将微服务启动, 并注册到7001
微服务,并在配置文件中, 对 路径/payment/get/**
和 /payment/lb/**
进行拦截,这就是断言,若断言为true,则匹配该路由,并跳转到对应的uri
属性下的 地址中
测试结果:
直接访问 8001
微服务的 接口 http://127.0.0.1:8001/payment/lb
返回结果 : "8001" 此接口返回 8001
微服务的端口
访问 9527
Gateway 微服务的 地址: http://127.0.0.1:9527/payment/lb
断言成功, 跳转路由, 返回结果: "8001", 成功调用
若访问的路径,没有任何路由匹配,则报错404:
上面使用 yml 配置文件的方式进行 配置路由规则, 也可以使用编码的方式进行配置
下面我们使用编码的方式配置路由,跳转到百度的国内新闻
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
//路由构建器
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//配置路由规则,对比 yml 文件配置
// id: path_route , predicates: /guonei , uri: http://news.baidu.com/guonei
routes.route("path_route"
, r->r.path("/guonei").uri("http://news.baidu.com/guonei"))
.build();
return routes.build();
}
}
下面进行测试:
http://news.baidu.com/guonei
,成功跳转http://127.0.0.1:9527/guonei
,也可以跳转上面的代码中,我们跳转到某个微服务,都是 直接写对方的ip 地址,
Gateway 会自动 从注册中心中获取服务列表, 可以通过微服务的名称作为路由转发,那么上面的代码就不用写成
http://localhost:8001
而是 lb://cloud-payment-service
lb
为负载均衡,若该微服务有两个实现,则进行负载均衡
代码演示:
首先必须先开启注册中心路由功能: spring.cloud.gateway.discovery.locator.enabled=true
pom修改:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
开启 8001
,8002
微服务,调用9527
地址: http://127.0.0.1:9527/payment/lb
,
轮流返回 "8001","8002" 对应微服务的地址,调用成功,并负载均衡
Gateway网关中 另一个非常重要的组件: 断言, 它决定一个请求是否由匹配此路由. 在前面的案例中使用的就是其中的Path 断言工厂生成的 断言类, 并匹配请求,跳转到指定路径,
GateWay给我们提供了很多不同类型的断言工厂,都是抽象类AbstractRoutePredicateFactory
的子类
详细使用,请查看官网文档 : https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories
分类:
时间相关:
AfterRoutePredicateFactory
: 匹配在指定日期时间之后发生的请求
示例:
# 表示在 2017年1月20日17:42:47 之后
#此时间格式 可以使用 ZonedDateTime 类获取
#ZonedDateTime.now(); // 默认时区
#ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
BeforeRoutePredicateFactory
匹配在指定日期时间之前发生的请求
BetweenRoutePredicateFactory
匹配在datetime1
之后和在datetime2
之前的请求。该datetime2
参数必须datetime1
之后
示例:
#表示在2017年1月20日17:42:47之后 并且 在2017年1月21日17:42:47之前
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie相关:
CookieRoutePredicateFactory
匹配具有指定Cookie,并且值与指定的正则匹配的请求
示例:
# 表示Cookie 中携带 键为chocolate,值为可以匹配正则"ch.p" 的字符串
predicates:
- Cookie=chocolate, ch.p
Header相关:
HeaderRoutePredicateFactory
, 匹配 请求头中有指定的名,并且值匹配指定的正则表达式
示例:
predicates:
- Header=X-Request-Id, \d+
HostRoutePredicateFactory
, 匹配Host 域名列表
示例:
# 匹配路径中host 为 *.baidu.com 的 和 *.souhu.com的
predicates:
- Host=**.baidu.com,**.sohu.com
RemoteAddrRoutePredicateFactory
,匹配请求的Remote(客户端i来源ip)
示例:
# 匹配Remote 为 192.168.1.1 至 192.168.1.254
# 斜杠后面的24 表示最后一位的最大值 即254
#16 表示最后两位 即 255.254
#8 表示最后三位 即 255.255.254
predicates:
- RemoteAddr=192.168.1.1/24
请求相关:
MethodRoutePredicateFactory
匹配指定的请求方式
示例:
#匹配 GET,POST 请求
predicates:
- Method=GET,POST
QueryRoutePredicateFactory
匹配请求有指定的参数key,并且值匹配指定的正则
示例:
# 请求键为 red 值匹配正则 "gree."
predicates:
- Query=red, gree.
PathRoutePredicateFactory
匹配url 路径,也就是我们上面案例中用到的
下面使用MethodRoutePredicateFactory
来进行演示
源码
public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
/** @deprecated */
@Deprecated
public static final String METHOD_KEY = "method";
public static final String METHODS_KEY = "methods";
public MethodRoutePredicateFactory() {
super(MethodRoutePredicateFactory.Config.class);
}
/**
* 封装是 config 类中使用哪个字段接受参数
*/
public List<String> shortcutFieldOrder() {
return Arrays.asList("methods");
}
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
/**
* 实际生产 断言操作类的方法
*/
public Predicate<ServerWebExchange> apply(MethodRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
/**
* 断言操作类的test方法,判断请求是否符合条件
*/
public boolean test(ServerWebExchange exchange) {
HttpMethod requestMethod = exchange.getRequest().getMethod();
return Arrays.stream(config.getMethods()).anyMatch((httpMethod) -> {
return httpMethod == requestMethod;
});
}
public String toString() {
return String.format("Methods: %s", Arrays.toString(config.getMethods()));
}
};
}
/**
* 配置类,接受配置文件中的配置的信息,
*/
@Validated
public static class Config {
// 一个枚举数组, 接受请求方式,例如 [GET,POST]
private HttpMethod[] methods;
public Config() {
}
/** @deprecated */
@Deprecated
public HttpMethod getMethod() {
return this.methods != null && this.methods.length > 0 ? this.methods[0] : null;
}
/** @deprecated */
@Deprecated
public void setMethod(HttpMethod method) {
this.methods = new HttpMethod[]{method};
}
public HttpMethod[] getMethods() {
return this.methods;
}
public void setMethods(HttpMethod... methods) {
this.methods = methods;
}
}
}
上面的代码中, 可以看出其实MethodRoutePredicateFactory
的实现比较简单.生产一个GatewayPredicate
进行断言.主要做了如下两个操作
根据上面的规则,我们可以实现自己的自定义断言工厂
接收参数的Config 类:
public class TulingTimeBetweenConfig {
private LocalTime startTime;
private LocalTime endTime;
public LocalTime getStartTime() {
return startTime;
}
public void setStartTime(LocalTime startTime) {
this.startTime = startTime;
}
public LocalTime getEndTime() {
return endTime;
}
public void setEndTime(LocalTime endTime) {
this.endTime = endTime;
}
}
断言工厂类,注意工厂类的类名必须以"RoutePredicateFactory"开头, "RoutePredicateFactory" 之前的一部分则作为配置文件中的键
@Component
public class TulingTimeBetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<TulingTimeBetweenConfig> {
public TulingTimeBetweenRoutePredicateFactory() {
super(TulingTimeBetweenConfig.class);
}
/**
* 真正的业务判断逻辑
*/
@Override
public Predicate<ServerWebExchange> apply(TulingTimeBetweenConfig config) {
LocalTime startTime = config.getStartTime();
LocalTime endTime = config.getEndTime();
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
LocalTime now = LocalTime.now();
//判断当前时间是否在在配置的时间范围类
return now.isAfter(startTime) && now.isBefore(endTime);
}
};
}
/**
* 用于接受yml中的配置 ‐ TulingTimeBetween=上午7:00,下午11:00
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("startTime", "endTime");
}
}
yaml配置文件,使用逗号分隔
predicates:
- TulingTimeBetween=上午7:00,下午11:00
测试,当请求时间为上午七点到下午十一点前的所有请求,都会被拦截
上面的操作中,我们仅仅只是将 请求拦截,并跳转到某个地址,貌似没做什么操作,作用很小,下面介绍过滤器的使用,将在拦截过程中做一些操作
,SpringCloudGateway 也提供了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等.
下面简单介绍几种常用的:
添加请求头:
给拦截到请求中加入指定的请求头和指定的值
predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ AddRequestHeader=X‐Request‐Company,tuling
添加请求参数
给请求加上指定的 Parameter 参数,和指定的值
predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ AddRequestParameter=company,tuling
为匹配的路由统一添加前缀
给请求加上指定的前缀,比如下面的配置中,请求http://localhost:8888/selectProductInfoById/1
会转发到
http://localhost:8888/product‐api/selectProductInfoById/1
中
predicates:
‐ TulingTimeBetween=上午7:00,下午11:00
filters:
‐ PrefixPath=/product‐api
更多详细用户请参考官网,提供了丰富的过滤器工厂
过滤器工厂的实现思路和断言工厂类似,也可以参考着自定义自己的过滤器工厂,下面我们来实现一个记录整个过滤链执行时间的过滤器工厂类
过滤器操作类:
在查看源码过程中,发现其过滤器工厂返回过滤器操作类代码中,都是使用匿名内部类的方式,但是这样过滤器的执行顺序无法保证,只能按照加载顺序执行,所以这里我们将操作类单独定义,实现Ordered
接口,保证加载顺序优先
public class TimeMonitorGatewayFilter implements GatewayFilter, Ordered {
private static final String COUNT_START_TIME = "countStartTime";
private AbstractNameValueGatewayFilterFactory.NameValueConfig config;
public TimeMonitorGatewayFilter(AbstractNameValueGatewayFilterFactory.NameValueConfig config) {
this.config = config;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain
chain) {
//获取配置文件yml中的
// filters:
// ‐ TimeMonitor=enabled,true
String name = config.getName();
String value = config.getValue();
if (value.equals("false")) {
return null;
}
//在请求中记录开始时间
exchange.getAttributes().put(COUNT_START_TIME,
System.currentTimeMillis());
//then方法相当于aop的后置通知一样,当整个过滤链执行完毕时 ,将调用此方法,
return chain.filter(exchange).then(Mono.fromRunnable(new Runnable() {
@Override
public void run() {
//结束时间
Long startTime = exchange.getAttribute(COUNT_START_TIME);
//获取开始时间 并计算差值
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
sb.append(" params:").append(exchange.getRequest().getQueryParams());
//打印执行时间
System.out.println(sb.toString());
}
}
}));
}
/**
* 数字越小 Spring 加载此类越优先
* @return
*/
@Override
public int getOrder() {
return -100;
}
}
此类在执行链开始时执行,并记录开始时间,并定义了结束过滤链结束时,计算差值
过滤器工厂类:
和断言工厂一样, 也是使用指定的后缀,来确定配置文件中的配置方式,必须为"GatewayFilterFactory" 结尾
并继承了 AbstractNameValueGatewayFilterFactory
类, 可以接受配置文件中的 name,value 形式的参数
但是本例中只使用 value来定义
@Component
public class TimeMonitorGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new TimeMonitorGatewayFilter(config) ;
}
}
yaml配置文件:
接受到的参数 : name为enabled ,value为true,但是上面的代码中 只用到了 true参数,含义为开启此功能
predicates:
‐ Query=company,product
filters:
‐ TimeMonitor=enabled,true
测试: 调用本网关,[127.0.0.1:9527/payment/lb?name=10](http://127.0.0.1:9527/payment/lb?name=10)
打印日志信息: /payment/lb: 8ms params:{name=[10]}
GateWay 框架中,还有一种特殊的过滤器, 为全局过滤器,只要是被拦截的请求,都会被执行,上面的负载均衡功能就是
LoadBalancerClientFilter
全局过滤器起的作用
其他全局过滤器使用,请查看官网: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#global-filters
同样,我们也可以自定义全局过滤器:
@Component
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进来");
//获取 url上第一个 uname param
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 过滤链的排序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
上面的过滤器中,进行了简单的鉴权操作,若请求参数中没有username,则拒绝转发,
标签:release final 框架 short cal ctr header attribute monitor
原文地址:https://www.cnblogs.com/xjwhaha/p/14049372.html