13.SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词、断言,Filter 过滤器(十三)
13.SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词、断言,Filter 过滤器(十三)
阅读本文前可先参考
SpringCloud - Spring Cloud根/父项目,开发准备(二)_MinggeQingchun的博客-CSDN博客
SpringCloud - Spring Cloud 之 Gateway网关(十三)_MinggeQingchun的博客-CSDN博客
Web 有三大组件(监听器 过滤器 servlet),Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念
1、Route(路由)
2、Predicate(谓词/断言)
3、Filter(过滤)
一、Routes路由配置
路由断言/谓词工厂有12个
Gateway有两种配置路由方式
1、Java代码配置类路由
参考官网给出demo Spring Cloud Gateway
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
在 springcloud-7-service-eureka-gateway 模块总 自定义一个配置类 GatewayRouteConfig
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;
/**
* 路由配置类
*/
@Configuration
public class GatewayRouteConfig {
/**
* 代码的路由 和 application.yml文件 不冲突,都可以用
* 如果 uri后面给了一个访问地址 和匹配地址相同,就不会再拼接
*/
@Bean
public RouteLocator customRoiteLocator(RouteLocatorBuilder builder){
//以下url从B站中引用
return builder.routes()
.route("movie-id",r->r.path("/movie").uri("https://www.bilibili.com/movie/?spm_id_from=333.1007.0.0"))
.route("douga-id",r->r.path("/v/douga").uri("https://www.bilibili.com/v/douga/?spm_id_from=333.1007.0.0"))
.build();
}
}
启动springboot 类
输入访问 http://localhost:81/movie
即会跳转到相应 url 进行访问资源
2、application.properties 或 application.yml 配置文件
即 SpringCloud - Spring Cloud 之 Gateway网关(十三)_MinggeQingchun的博客-CSDN博客
文中的 三、Gateway应用方式
application.properties
server.port=81
#eureka注册中心首页的Application这一栏
spring.application.name=springcloud-7-service-eureka-gateway
#每间隔5s,向Eureka服务注册中心发送一次心跳,证明服务是否依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2
#告诉服务端,如果10s之内没有发送心跳,就代表故障,将本服务踢出
eureka.instance.lease-expiration-duration-in-seconds=10
#告诉服务端,服务实例以IP作为链接,不是取机器名
eureka.instance.prefer-ip-address=false
#注册服务实例ID,,服务ID必须唯一 springcloud-7-service-eureka-gateway
eureka.instance.instance-id=${spring.application.name}:${server.port}
#注册中心的链接地址 http://eureka8761:8761/eureka,http://eureka8762:8762/eureka,http://eureka8763:8763/eureka
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
#网关路由配置
#开启网关,默认开启
spring.cloud.gateway.enabled=true
#节点 routes 是一个List 对象,其中 routes 集合中中又包含多个对象,每个对象有三个属性(一个 索引[0]代表一个对象)
#路由 id,没有固定规则,但唯一
spring.cloud.gateway.routes[0].id=login-service-route
#匹配后提供服务的路由地址;uri统一资源定位符 url 统一资源标识符
spring.cloud.gateway.routes[0].uri=http://localhost:9001
#以下是断言条件,必选全部符合条件;断言是给某一个路由来设定的一种匹配规则 默认不能作用在动态路由上
#断言,路径匹配,只要Path匹配上了/doLogin 就往 uri 转发 并且将路径带上 注意:Path 中 P 为大写
#也可以全局匹配,如 /service/**
spring.cloud.gateway.routes[0].predicates[0]=Path=/doLogin
#只能是 GET 请求时,才能访问
spring.cloud.gateway.routes[0].predicates[0]=Method=GET,POST
#配置第二个路由规则
spring.cloud.gateway.routes[1].id=admin-service-route
spring.cloud.gateway.routes[1].uri=http://localhost:9001
spring.cloud.gateway.routes[1].predicates[0]=Path=/doAdmin
spring.cloud.gateway.routes[1].predicates[0]=Method=GET,POST
#表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
spring.cloud.gateway.discovery.locator.enabled=true
#是将请求路径上的服务名配置为小写(服务注册的时候,向注册中心注册时将服务名转成大写了),如以/service/*的请求路径被路由转发到服务名为service的服务上
spring.cloud.gateway.discovery.locator.lower-case-service-id=true
application.yml
server:
port: 81
#eureka注册中心首页的Application这一栏
spring:
application:
name: springcloud-7-service-eureka-gateway
#网关路由配置
cloud:
gateway:
#开启网关,默认开启
enabled: true
#节点 routes 是一个List 对象,其中 routes 集合中中又包含多个对象,每个对象有三个属性(一个 索引[0]代表一个对象)
routes:
#路由 id,没有固定规则,但唯一
- id: login-service-route
#匹配后提供服务的路由地址;uri统一资源定位符 url 统一资源标识符
uri: http://localhost:9001
#以下是断言条件,必选全部符合条件;断言是给某一个路由来设定的一种匹配规则 默认不能作用在动态路由上
predicates:
#断言,路径匹配,只要Path匹配上了/doLogin 就往 uri 转发 并且将路径带上 注意:Path 中 P 为大写
#也可以全局匹配,如 /service/**
- Path=/doLogin
#只能是 GET,POST 请求时,才能访问
- Method=GET,POST
#配置第二个路由规则
- id: admin-service-route
uri: http://localhost:9001
predicates:
- Path=/doAdmin
- Method=GET,POST
#表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
discovery:
locator:
enabled: true
#是将请求路径上的服务名配置为小写(服务注册的时候,向注册中心注册时将服务名转成大写了),如以/service/*的请求路径被路由转发到服务名为service的服务上
lower-case-service-id: true
eureka:
instance:
#每间隔5s,向Eureka服务注册中心发送一次心跳,证明服务是否依然"存活"
lease-renewal-interval-in-seconds: 2
#告诉服务端,如果10s之内没有发送心跳,就代表故障,将本服务踢出
lease-expiration-duration-in-seconds: 10
#告诉服务端,服务实例以IP作为链接,不是取机器名
prefer-ip-address: false
#注册服务实例ID,,服务ID必须唯一 springcloud-7-service-eureka-gateway
instance-id: ${spring.application.name}:${server.port}
#注册中心的链接地址 http://eureka8761:8761/eureka,http://eureka8762:8762/eureka,http://eureka8763:8763/eureka
client:
service-url:
defaultZone: http://localhost:8761/eureka
3、Gateway 微服务名动态路由,负载均衡
我们设置的 routes的 uri 都是写死的,这样不符合微服务的要求,微服务是只要知道服务的名字,根据名字去找,且直接写死URI就没有负载均衡的效果
默认情况下 Gateway 会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路
由进行转发,从而实现动态路由的功能
uri 的协议(如 http 协议)为 lb( load Balance),表示启用 Gateway 的负载均衡功能。
lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri
#匹配后提供服务的路由地址;uri统一资源定位符 url 统一资源标识符
#uri 的协议为 lb(load Balance),表示启用 Gateway 的负载均衡功能
#lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri;serviceName要和启动的微服务名保持一致
spring.cloud.gateway.routes[0].uri=lb://springcloud-7-service-eureka-gateway-login
#eureka注册中心首页的Application这一栏
spring:
application:
name: springcloud-7-service-eureka-gateway
#网关路由配置
cloud:
gateway:
#开启网关,默认开启
enabled: true
#节点 routes 是一个List 对象,其中 routes 集合中中又包含多个对象,每个对象有三个属性(一个 索引[0]代表一个对象)
routes:
#路由 id,没有固定规则,但唯一
- id: login-service-route
#匹配后提供服务的路由地址;uri统一资源定位符 url 统一资源标识符
#uri: http://localhost:9001
#uri 的协议为 lb(load Balance),表示启用 Gateway 的负载均衡功能
#lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri;serviceName要和启动的微服务名保持一致
uri: lb://springcloud-7-service-eureka-gateway-login
#以下是断言条件,必选全部符合条件;断言是给某一个路由来设定的一种匹配规则 默认不能作用在动态路由上
predicates:
#断言,路径匹配,只要Path匹配上了/doLogin 就往 uri 转发 并且将路径带上 注意:Path 中 P 为大写
#也可以全局匹配,如 /service/**
- Path=/doLogin
#只能是 GET,POST 请求时,才能访问
- Method=GET,POST
浏览器输入访问 http://localhost:81/springcloud-7-service-eureka-gateway-login/doLogin
二、Predicate 谓词/断言
路由断言/谓词工厂有12个
Gateway 启动时会去加载一些路由断言工厂(一个 boolean 表达式 )
我们可以通过上述官网查看断言表达式
断言就是路由添加一些条件(通俗的说,断言就是一些布尔表达式,满足条件的返回 true,不满足的返回 false)
Spring Cloud Gateway 将路由作为 Spring WebFlux HandlerMapping 基础架构的一部分进行匹配。
Spring Cloud Gateway 包括许多内置的路由断言工厂。所有这些断言都与 HTTP请求的不同属性匹配。可以将多个路由断言可以组合使用
Spring Cloud Gateway 创建对象时,使用 RoutePredicateFactory创建 Predicate 对象,Predicate 对象可以赋值给 Route。
规则 | 实例 | 说明 |
---|---|---|
Path | - Path=/gate/,/rule/ | ## 当请求的路径为gate、rule开头的时,转发到http://localhost:9023服务器上 |
Before | - Before=2020-01-20T17:42:47.789-07:00[America/Denver] | 在某个时间之前的请求才会被转发到 http://localhost:9023服务器上 |
After | - After=2020-01-20T17:42:47.789-07:00[America/Denver] | 在某个时间之后的请求才会被转发 |
Between | - Between=2020-01-20T17:42:47.789-07:00[America/Denver],2020-01-21T17:42:47.789-07:00[America/Denver] | 在某个时间段之间的才会被转发 |
Cookie | - Cookie=chocolate, ch.p | 名为chocolate的表单或者满足正则ch.p的表单才会被匹配到进行请求转发 |
Header | - Header=X-Request-Id, \d+ | 携带参数X-Request-Id或者满足\d+的请求头才会匹配 |
Host | - Host=www.hd123.com | 当主机名为www.hd123.com的时候直接转发到http://localhost:9023服务器上 |
Method | - Method=GET | 只有GET方法才会匹配转发请求,还可以限定POST、PUT等请求方式 |
application.yml配置文件
server:
port: 81
spring:
application:
name: gateway-81
cloud:
gateway:
enabled: true #开启网关,默认是开启的
routes: #设置路由,注意是数组,可以设置多个,按照 id 做隔离
- id: user-service #路由 id,没有要求,保持唯一即可
uri: lb://provider #使用 lb 协议 微服务名称做负均衡
predicates: #断言匹配
- Path=/info/** #和服务中的路径匹配,是正则匹配的模式
- After=2020-01-20T17:42:47.789-07:00[Asia/Shanghai] #此断言匹配发生在指定 日期时间之后的请求,ZonedDateTime dateTime=ZonedDateTime.now()获得
- Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定 日期时间之前的请求
- Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之间的请求
- Cookie=name,xiaobai #Cookie 路由断言工厂接受两个参数,Cookie 名称和 regexp(一 个 Java 正则表达式)。此断言匹配具有给定名称且其值与正则表达式匹配的 cookie
- Header=token,123456 #头路由断言工厂接受两个参数,头名称和 regexp(一个 Java 正 则表达式)。此断言与具有给定名称的头匹配,该头的值与正则表达式匹配。
- Host=**.bai*.com:* #主机路由断言工厂接受一个参数:主机名模式列表。该模式是一 个 ant 样式的模式。作为分隔符。此断言匹配与模式匹配的主机头
- Method=GET,POST #方法路由断言工厂接受一个方法参数,该参数是一个或多个参数: 要匹配的 HTTP 方法
- Query=username,cxs #查询路由断言工厂接受两个参数:一个必需的 param 和一个 可选的 regexp(一个 Java 正则表达式)。
- RemoteAddr=192.168.1.1/24 #RemoteAddr 路由断言工厂接受一个源列表(最小大小 1), 这些源是 cidr 符号(IPv4 或 IPv6)字符串,比如 192.168.1.1/24(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。
1、After路由谓词工厂
After route谓词工厂采用一个参数,即datetime(这是一个Java ZonedDateTime),该谓词匹配在指定日期时间之后发生的请求,以下示例配置了路由后谓词:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
这条路由符合2017年1月20日17:42:47时间([America/Denver])之后的任何请求;
时间通过获取:System.out .println(ZonedDateTime.now ());
2、Before路由谓词工厂
Before路由谓词工厂采用一个参数,即datetime(这是一个Java ZonedDateTime),该谓词匹配在指定日期时间之前发生的请求,下面的示例配置路由之前谓词:
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
这条路由符合2017年1月20日17:42:47时间([America/Denver])之前的任何请求;
3、Between路由谓词工厂
路由谓词之间的工厂使用两个参数datetime1和datetime2,它们是java ZonedDateTime对象,该谓词匹配在datetime1之后和datetime2之前发生的请求,datetime2参数必须在datetime1之后,以下示例配置了路由之间的谓词:
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
该路线与2017年1月20日山区时间(丹佛)之后和2017年1月21日17:42山区时间(丹佛)之前的任何请求相匹配,这对于维护时段可能很有用;
4、Cookie 路由谓词工厂
Cookie路由谓词工厂采用两个参数,即cookie名称和一个regexp(这是Java正则表达式),该谓词匹配具有给定名称且其值与正则表达式匹配的cookie,以下示例配置Cookie路由谓词工厂:
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
此路由匹配具有名为Chocolate的cookie的请求,该cookie的值与ch.p正则表达式匹配;
举例:curl http://192.168.0.104/index --cookie token=123456
5、Header 路由谓词工厂
header 路由谓词工厂使用两个参数,header 名称和一个regexp(这是Java正则表达式),该谓词与具有给定名称的header 匹配,该header 的值与正则表达式匹配,以下示例配置标头路由谓词:
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
如果请求具有名为X-Request-Id的标头,且其值与\ d +正则表达式匹配(即,其值为一个或多个数字),则此路由匹配;
举例:curl http://192.168.0.104/index --header "X-Request-Id:19228"
6、Host 路由谓词工厂
host路由谓词工厂使用一个参数:主机名模式列表,以下示例配置主机路由谓词:
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
还支持URI模板变量(例如{sub} .myhost.org),如果请求的主机标头的值为www.somehost.org或beta.somehost.org或www.anotherhost.org,则此路由匹配;
7、Method 路由谓词工厂
方法路由谓词工厂使用方法参数,该参数是一个或多个参数:要匹配的HTTP方法,以下示例配置方法route谓词:
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
如果请求方法是GET或POST,则此路由匹配;
8、Path路由谓词工厂
路径路由谓词工厂使用两个参数:Spring PathMatcher模式列表和一个称为matchOptionalTrailingSeparator的可选标志,以下示例配置路径路由谓词:
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
如果请求路径为例如/red/1或/red/blue或/blue/green,则此路由匹配;
9、Query路由谓词工厂
查询路由谓词工厂采用两个参数:必需的参数和可选的regexp(这是Java正则表达式),以下示例配置查询路由谓词:
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
如果请求包含green查询参数,则前面的路由匹配;
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
如果请求包含值与gree匹配的red查询参数,则上述路由匹配;
10、RemoteAddr 路由谓词工厂
RemoteAddr路由谓词工厂使用源列表(最小大小为1),这些源是标记(IPv4或IPv6)字符串,例如192.168.0.1/16(其中192.168.0.1是IP地址,而16是子网掩码)),下面的示例配置RemoteAddr路由谓词:
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
如果请求的远程地址是例如192.168.1.10,则此路由匹配;
11、Weight 路由谓词工厂
权重路由谓词工厂采用两个参数:group和weight(一个int),权重是按组计算的,以下示例配置权重路由谓词:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
这条路由会将约80%的流量转发至weighthigh.org,并将约20%的流量转发至weightlow.org;
12、XForwarded 路由谓词工厂
spring:
cloud:
gateway:
routes:
- id: xforwarded_remoteaddr_route
uri: https://example.org
predicates:
- XForwardedRemoteAddr=192.168.1.1/24
如果x - forward - for报头包含,例如192.168.1.10,则此路由匹配
一、自定义谓词/断言
1、首先定义一个配置类,用于承载配置参数
@Data //lombok
public class TokenConfig {
private String token;
}
2、定义一个路由谓词工厂
@Slf4j
@Component
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenConfig> {
public TokenRoutePredicateFactory() {
super(TokenConfig.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("token");
}
@Override
public Predicate<ServerWebExchange> apply(TokenConfig tokenConfig) {
// (T t) -> true
return exchange -> {
MultiValueMap<String, String> valueMap = exchange.getRequest().getQueryParams();
boolean flag = false;
List<String> list = new ArrayList<>();
valueMap.forEach((k, v) -> {
list.addAll(v);
});
for (String s : list) {
log.info("Token -> {} ", s);
if (StringUtils.equalsIgnoreCase(s, tokenConfig.getToken())) {
flag = true;
break;
}
}
return flag;
};
}
}
注:Token RoutePredicateFactory 类, 前面的Token与.yml配置文件里面配置的名字对应,后面的RoutePredicateFactory 名字是固定的,不能随便写,这是Spring Cloud Gateway的约定,类名须为"谓词工厂名**(如:Token)" + RoutePredicateFactory**
3、在配置文件中启用该路由谓词工厂,即配置一个Token=123456
时间格式不是随便配置,而是Spring Cloud Gateway的默认时间格式,采用JDK8里面的格式化:
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
String nowTime = dateTimeFormatter.format(ZonedDateTime.now());
System.out.println(nowTime);
二、谓词/断言不匹配404处理
处理的顶层接口是WebExceptionHandler
默认实现是DefaultErrorWebExceptionHandler
需要覆盖它的默认实现DefaultErrorWebExceptionHandler,覆盖里面的方法,在方法里面编写返回的结果
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.web.reactive.function.server.*;
import java.util.HashMap;
import java.util.Map;
public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ErrorProperties errorProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 获取异常属性
*/
/*@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
int code = 500;
Throwable error = super.getError(request);
if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
code = 404;
}
return response(code, this.buildMessage(request, error));
}*/
/**
* 指定响应处理方法为JSON处理的方法
*
* @param errorAttributes
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 根据code获取对应的HttpStatus
* @param errorAttributes
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("status");
return statusCode;
}
/**
* 构建异常信息
* @param request
* @param ex
* @return
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle request [");
message.append(request.methodName());
message.append(" ");
message.append(request.uri());
message.append("]");
if (ex != null) {
message.append(": ");
message.append(ex.getMessage());
}
return message.toString();
}
/**
* 构建返回的JSON数据格式
*
* @param status 状态码
* @param errorMessage 异常信息
* @return
*/
public static Map<String, Object> response(int status, String errorMessage) {
Map<String, Object> map = new HashMap<>();
map.put("code", status);
map.put("message", errorMessage);
map.put("data", null);
return map;
}
}
三、Filter 过滤器
Filter有33个
Gateway 里面的过滤器和 Servlet 里面的过滤器,功能差不多,路由过滤器可以用于修改进入
Http 请求和返回 Http 响应
Gateway 中过滤器
1、按生命周期
pre 在业务逻辑之前
post 在业务逻辑之后
2、按种类
GatewayFilter 需要配置某个路由,才能过滤。如果需要使用全局路由,需要配置 DefaultFilters
GlobalFilter 全局过滤器,不需要配置路由,系统初始化作用到所有路由上
全局过滤器 统计请求次数;限流;token 的校验;ip 黑名单拦截 ;跨域本质(filter) ;144 开头的电话;限制一些 ip 的访问
1、自定义 GlobalFilter 全局过滤器
GlobalFilter 是一种作用于所有的路由上的全局过滤器,通过它,我们可以实现一些统一化的业务功能,例如权限认证、IP 访问限制等。当某个请求被路由匹配时,那么所有的 GlobalFilter 会和该路由自身配置的 GatewayFilter 组合成一个过滤器链
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.HashMap;
/**
* 自定义全局过滤器
*/
@Component
public class GlobalFilterConfig implements GlobalFilter, Ordered {
/**
* 过滤方法
* 过滤器链模式;责任链模式
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//针对请求的过滤,拿到请求的header、url、参数等
// HttpServletRequest 是web里面的
// ServerHttpRequest 是webFlux里面(响应式)
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println("path====" + path);
HttpHeaders headers = request.getHeaders();
System.out.println("headers====" + headers);
String methodName = request.getMethod().name();
System.out.println("methodName====" + methodName);
//IPV4、IPV6地址
String hostName = request.getRemoteAddress().getHostName();
System.out.println("hostName====" + hostName);
String ip = request.getHeaders().getHost().getHostString();
System.out.println("ip====" + ip);
// 响应相关的数据
ServerHttpResponse response = exchange.getResponse();
//响应头设置编码
response.getHeaders().set("content-type","application/json;charset=utf-8");
HashMap<String ,Object> map = new HashMap<>(4);
map.put("code", HttpStatus.UNAUTHORIZED.value());
map.put("msg","你未授权");
ObjectMapper objectMapper = new ObjectMapper();
//将一个map转成一个字节数组
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(map);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//通过buffer工厂将字节数组包装成一个数据包
DataBuffer wrap = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(wrap));
// 放行 到下一个过滤器
// return chain.filter(exchange);
}
/**
* 指定顺序的方法,越小越先执行
*/
@Override
public int getOrder() {
return 0;
}
}
2、IP、Token认证拦截
1、token认证拦截
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* token校验
*/
@Component
public class TokenCheckFilter implements GlobalFilter, Ordered {
/**
* 指定好放行的路径
*/
public static final List<String> ALLOW_URL = Arrays.asList("/springcloud-7-service-eureka-gateway-login/doLogin", "/myUrl","/doLogin");
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 和前端约定好 一般放在请求头里面 一般 key Authorization value bearer token
* 1、拿到请求url
* 2、判断放行
* 3、拿到请求头
* 4、拿到token
* 5、校验
* 6、放行/拦截
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (ALLOW_URL.contains(path)) {
return chain.filter(exchange);
}
// 检查
HttpHeaders headers = request.getHeaders();
List<String> authorization = headers.get("Authorization");
if (!CollectionUtils.isEmpty(authorization)) {
String token = authorization.get(0);
if (StringUtils.hasText(token)) {
// 约定好的有前缀的 bearer token
String realToken = token.replaceFirst("bearer ", "");
if (StringUtils.hasText(realToken) && redisTemplate.hasKey(realToken)) {
return chain.filter(exchange);
}
}
}
// 拦截
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("content-type","application/json;charset=utf-8");
HashMap<String, Object> map = new HashMap<>(4);
// 返回401
map.put("code", HttpStatus.UNAUTHORIZED.value());
map.put("msg","未授权");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(map);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer wrap = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(wrap));
}
/**
* 这个顺序 最好在0附近 -2 -1 0 1
* @return
*/
@Override
public int getOrder() {
return 1;
}
2、IP认证拦截
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* 网关里面 过滤器
* ip拦截
* 请求都有一个源头
* 电话 如:144 开头
* 请求------->gateway------->service
* 黑名单 black_list
* 白名单 white_list
* 根据数量
* 像具体的业务服务 一般黑名单
* 一般像数据库 用白名单
*/
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {
/**
* 网关的并发比较高,不要在网关里面直接操作mysql
* 后台系统可以查询数据库,用户量并发量不大
* 如果并发量大 可以查redis 或者 在内存中写好
*/
public static final List<String> BLACK_LIST = Arrays.asList("127.0.0.1", "144.128.139.137");
/**
* 1、获取到ip
* 2、校验ip是否符合规范
* 3、放行 OR 拦截
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String ip = request.getHeaders().getHost().getHostString();
// 查询数据库 看这个ip是否存在黑名单里面
if (!BLACK_LIST.contains(ip)){
return chain.filter(exchange);
}
//拦截
//注:此处如果拦截IP,就要放开 GlobalFilter 的拦截
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().set("content-type","application/json;charset=utf-8");
HashMap<String, Object> map = new HashMap<>(4);
map.put("code", 438);
map.put("msg","你是黑名单");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(map);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
DataBuffer wrap = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(wrap));
}
@Override
public int getOrder() {
return 0;
}
}
3、限流
限流就是限制一段时间内,用户访问资源的次数,减轻服务器压力,限流大致分为两种:
1、IP 限流(5s 内同一个 ip 访问超过 3 次,则限制不让访问,过一段时间才可继续访问)
2.、请求量限流(只要在一段时间内(窗口期),请求次数达到阀值,就直接拒绝后面来的访问了,
过一段时间才可以继续访问)(粒度可以细化到一个 api(url),一个服务)
限流模型:漏斗算法 ,令牌桶算法,窗口滑动算法 计数器算法
令牌桶算法
(1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
(2)根据限流大小,设置按照一定的速率往桶里添加令牌
(3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
(4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
(5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令
牌,以此保证足够的限流
Spring Cloud Gateway 已经内置了一个 RequestRateLimiterGatewayFilterFactory,可以直接使用。
目前 RequestRateLimiterGatewayFilterFactory 的实现依赖于 Redis,所以我们还要引入 spring-boot-starter-data-redis-reactive 依赖
<!--限流要引入 Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置类
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;
/**
* 自定义请求限流
*/
@Configuration
public class RequestRateLimiterConfig {
// 针对某一个接口,ip来限流,如/doLogin,每一个ip,10s只能访问3次
@Bean
@Primary // 主候选的
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
}
// 针对这个路径来限制 /doLogin
// api 就是 接口 外面一般把gateway api网关 新一代网关
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
需要添加一个 @Primary // 主候选的 注解,不然报如下错误
启动快速访问
4、CORS跨域配置
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
或者 yml 文件配置
spring:
cloud:
gateway:
globalcors:
corsConfigurations: '[/**]': // 针对哪些路径
allowCredentials: true // 这个是可以携带 cookie
allowedHeaders: '*'
allowedMethods: '*' allowedOrigins: '*'
四、Spring Cloud Gateway集成ribbon负载均衡
实现原理是在全局LoadBalancerClientFilter中进行拦截,然后再该过滤器中依赖LoadBalancerClient loadBalancer,而此负载均衡接口的具体实现是RibbonLoadBalancerClient,即spring cloud gateway已经整合好了ribbon,已经可以实现负载均衡,我们不需要做任何工作,网关对后端微服务的转发就已经具有负载均衡功能;
1、添加依赖
<!-- sentinel-spring-cloud-gateway-adapter -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.7.2</version>
</dependency>
2、添加sentinel控制台配置
#sentinel dashboard管理后台
spring:
cloud:
sentinel:
eager: true
transport:
dashboard: 192.168.133.128:8080
3、在spring容器中配置一个Sentinel的全局过滤器
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
来源:https://blog.csdn.net/MinggeQingchun/article/details/125554886