Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到api,并为它们提供交叉关注点,例如:安全性、监视/度量和弹性。
Spring Cloud Gateway功能:
•基于Spring Framework 5、Project Reactor和Spring Boot 2.0构建•能够匹配任何请求属性上的路由。•谓词和过滤器是特定于路由的。•Hystrix断路器集成。•Spring Cloud DiscoveryClient集成•易于编写Predicates and Filters•请求速率限制•路径重写
•Route:路由是网关的基本构件。它由ID、目标URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由。•Predicate:参照Java8的新特性Predicate。这允许开发人员匹配HTTP请求中的任何内容,比如头或参数。•Filter:在这里,可以在发送下游请求之前或之后修改请求和响应。
客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送给网关Web处理程序。此处理程序运行通过特定于请求的过滤器链发送请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前或之后执行逻辑。执行所有“预”筛选器逻辑,然后发出代理请求。发出代理请求后,执行“post”筛选器逻辑。
注:在没有端口的路由中定义的uri将得到HTTP和HTTPS uri的默认端口分别设置为80和443。
Spring Cloud Gateway作为Spring WebFlux HandlerMapping基础设施的一部分匹配路由。Spring Cloud Gateway包含许多内置的路由谓词工厂。所有这些谓词都匹配HTTP请求的不同属性。可以组合多个路由谓词工厂,并通过逻辑和组合它们。
本文使用springcloud2.0.3和springcloud的Finchley版。
1. 依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
2.使用内置的predicates
springcloud Gateway内置了许多的predicates:
注:图片来自互联网
下面application.yml文件配置针对以上的进行了简单的操作。
server:
port: 8081
spring:
profiles:
##指定你要激活的文件配置,对应下面所有配置的profiles的值。
active: host_route
---
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://httpbin.org:80/get
## 断言:在这个时间之后的可以访问。也就说,下面这个时间设置,你访问的话是报404的。
predicates:
- After=2020-01-20T17:42:47.789-07:00[America/Denver]
profiles: after_route
---
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://httpbin.org:80/get
## 断言:加头部信息
predicates:
- Header=X-Request-Id, \d+
profiles: header_route
---
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://httpbin.org:80/get
## 断言:加Cookie信息。
predicates:
- Cookie=name, forezp
profiles: cookie_route
---
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://httpbin.org:80/get
## 断言:按域名拦截
predicates:
- Host=**.xbmchina.cn
profiles: host_route
---
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://httpbin.org:80/get
## 断言:按请求方式拦截
predicates:
- Method=GET
profiles: method_route
---
spring:
cloud:
gateway:
routes:
- id: path_route
uri: http://httpbin.org:80/get
## 断言:按地址路由拦截
predicates:
- Path=/foo/{segment}
profiles: path_route
---
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://httpbin.org:80/get
## 断言:按查询参数拦截
predicates:
- Query=zero, hehe.
profiles: query_route
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
Spring Cloud Gateway内置有很多网关的工厂类。其中可分为两大类:
GatewayFilter 和 GlobalFilters。
下面有些常用栗子,更多可参考官网[1]。
SpringCloud内置的过滤器工厂类有如下:
通过application.yml进行配置即可使用
server:
port: 8082
spring:
profiles:
active: elapse_route
---
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://httpbin.org:80/get
## 添加请求头
filters:
- AddRequestHeader=X-Request-Foo, Bar
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
profiles: add_request_header_route
---
## 重写路由,跟Nginx的反向代理很像。
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: https://blog.csdn.net
predicates:
- Path=/foo/**
filters:
- RewritePath=/foo/(?<segment>.*), /$\{segment}
profiles: rewritepath_route
•1.一个拦截请求,打印请求时间的过滤器RequestTimeFilter 。
public class RequestTimeFilter implements GatewayFilter, Ordered {
private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
if (startTime != null) {
log.info(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms");
}
})
);
}
@Override
public int getOrder() {
return 0;
}
}
•
1.在启动类Application中注入路由拦截的规则。
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
// @formatter:off
return builder.routes()
.route(r -> r.path("/customer/**")
.filters(f -> f.filter(new RequestTimeFilter())
.addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
.uri("http://httpbin.org:80/get")
.order(0)
.id("customer_filter_router")
)
.build();
// @formatter:on
}
•3.同样是上面的需求,下面是通过写一个Filter工厂来实现。
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
private static final String KEY = "withParams";
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY);
}
public RequestTimeGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
if (startTime != null) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
if (config.isWithParams()) {
sb.append(" params:").append(exchange.getRequest().getQueryParams());
}
log.info(sb.toString());
}
})
);
};
}
public static class Config {
private boolean withParams;
public boolean isWithParams() {
return withParams;
}
public void setWithParams(boolean withParams) {
this.withParams = withParams;
}
}
}
•
4.同样要注入到Spring容器中,交由Spring管理实例。
@Bean
public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
return new RequestTimeGatewayFilterFactory();
}
•
5.最后一步,在application.yml中应用上面的工厂类进行配置
server:
port: 8082
spring:
profiles:
active: elapse_route
---
spring:
cloud:
gateway:
routes:
- id: elapse_route
uri: http://httpbin.org:80/get
## 自定义工厂类日志打印出访问时间
filters:
- RequestTime=false
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
profiles: elapse_route
下面例子主要是全局拦截进行token校验。
public class TokenFilter implements GlobalFilter, Ordered {
Logger logger= LoggerFactory.getLogger( TokenFilter.class );
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (token == null || token.isEmpty()) {
logger.info( "token is empty..." );
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100;
}
}
在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。
更详细的教程参考【方志朋大神博客[2]】
1.在上面的依赖基础上需要添加redis的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2.application.yml 主要是配置redis和gateway整合。
server:
port: 8084
spring:
cloud:
gateway:
routes:
- id: limit_route
uri: http://httpbin.org:80/get
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@hostAddrKeyResolver}'
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
application:
name: gateway-limiter
redis:
host: localhost
port: 6379
database: 0
3.以上工作仅仅是完成配置工作。如果你需要对某个主机进行限流,或者对于某个接口进行限流,还是说对于某位用户进行限流都是可以的。
比如下面的代码
public class HostAddrKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//地址限流
return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
// return Mono.just(exchange.getRequest().getURI().getPath());//uri拦截限流
// return Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));//用户限流
}
}
跟以往的文章一样,都是加入Eureka的客户端依赖,配置文件连接到Eureka服务端即可。
application.yml配置如下:
server:
port: 8081
spring:
application:
name: sc-gateway-server
cloud:
gateway:
discovery:
locator:
enabled: false
lowerCaseServiceId: true
routes:
- id: cloud-eureka-client
uri: lb://CLOUD-EUREKA-CLIENT
predicates:
- Path=/demo/**
filters:
- StripPrefix=1
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
与服务调用一样的操作,添加hystrix的依赖,配置错误处理的方法。
样例如下:
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
String httpUri = "http://httpbin.org:80";
return builder.routes()
.route(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri(httpUri))
.route(p -> p
.host("*.hystrix.com")
.filters(f -> f
.hystrix(config -> config
.setName("mycmd")
.setFallbackUri("forward:/fallback")))
.uri(httpUri))
.build();
}
@RequestMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("fallback");
}
Gateway完全可以替代zuul,并且有很多的功能zuul都没有的。本文过长下次再比较一下两者的区别。
[1]
官网: http://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories
[2]
方志朋大神博客: https://blog.csdn.net/forezp/article/details/85081162