Spring Cloud Gateway 提供一种默认转发的能力,只要将 Spring Cloud Gateway 注册到服务中心,Spring Cloud Gateway 默认就会代理服务中心的所有服务。
Spring Cloud Gateway 的 Filter 的生命周期只有两个:“pre” 和 “post”。
PRE: 这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
为什么需要网关?
有很多个服务时,比如下图中的user-service、goods-service、sales-service等服务,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gatewat服务,然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。
GatewayFilterChain
/*** 网关过滤链表接口
* 用于过滤器的链式调用
* Contract to allow a {@linkWebFilter} to delegate to the next in the chain.*/
public interfaceGatewayFilterChain {/*** 链表启动调用入口方法
* Delegate to the next {@codeWebFilter} in the chain.
*@paramexchange the current server exchange
*@return{@codeMono} to indicate when request handling is complete*/Monofilter(ServerWebExchange exchange);
}
/*** 网关过滤的链表,用于过滤器的链式调用
* 过滤器链表接口的默认实现,
* 包含2个构建函数:
* 1.集合参数构建用于初始化构建链表
* 2. index,parent参数用于构建当前执行过滤对应的下次执行的链表*/
private static class DefaultGatewayFilterChain implementsGatewayFilterChain {/*** 当前过滤执行过滤器在集合中索引*/
private final intindex;/*** 过滤器集合*/
private final Listfilters;public DefaultGatewayFilterChain(Listfilters) {this.filters =filters;this.index = 0;
}/*** 构建
*@paramparent 上一个执行过滤器对应的FilterChain
*@paramindex 当前要执行过滤器的索引*/
private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, intindex) {this.filters =parent.getFilters();this.index =index;
}public ListgetFilters() {returnfilters;
}/***@paramexchange the current server exchange
*@return
*/@Overridepublic Monofilter(ServerWebExchange exchange) {return Mono.defer(() ->{if (this.index
GatewayFilter filter = filters.get(this.index);//构建当前索引的下一个过滤器的FilterChain
DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);//调用过滤器的filter方法执行过滤器
returnfilter.filter(exchange, chain);
}else{//当前索引大于等于过滤集合大小,标识所有链表都已执行完毕,返回空
return Mono.empty(); //complete
}
});
}
}
过滤器的GatewayFilterChain 执行顺序
通过GatewayFilter集合构建顶层的GatewayFilterChain
调用顶层GatewayFilterChain,获取第一个Filter,并创建下一个Filter索引对应的GatewayFilterChain
调用filter的filter方法执行当前filter,并将下次要执行的filter对应GatewayFilterChain传入。
GatewayFilter
public interface GatewayFilter extendsShortcutConfigurable {
String NAME_KEY= "name";
String VALUE_KEY= "value";/*** 过滤器执行方法
* Process the Web request and (optionally) delegate to the next
* {@codeWebFilter} through the given {@linkGatewayFilterChain}.
*@paramexchange the current server exchange
*@paramchain provides a way to delegate to the next filter
*@return{@codeMono} to indicate when request processing is complete*/Monofilter(ServerWebExchange exchange, GatewayFilterChain chain);
}
OrderedGatewayFilter
/*** 排序的网关路由过滤器,用于包装真实的网关过滤器,已达到过滤器可排序
*@authorSpencer Gibb*/
public class OrderedGatewayFilter implementsGatewayFilter, Ordered {//目标过滤器
private finalGatewayFilter delegate;//排序字段
private final intorder;public OrderedGatewayFilter(GatewayFilter delegate, intorder) {this.delegate =delegate;this.order =order;
}
@Overridepublic Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) {return this.delegate.filter(exchange, chain);
}
}
OrderedGatewayFilter实现类主要目的是为了将目标过滤器包装成可排序的对象类型。是目标过滤器的包装类
GatewayFilterAdapter
/*** 全局过滤器的包装类,将全局路由包装成统一的网关过滤器*/
private static class GatewayFilterAdapter implementsGatewayFilter {/*** 全局过滤器*/
private finalGlobalFilter delegate;publicGatewayFilterAdapter(GlobalFilter delegate) {this.delegate =delegate;
}
@Overridepublic Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) {return this.delegate.filter(exchange, chain);
}
}
GatewayFilterAdapter实现类主要目的是为了将GlobalFilter过滤器包装成GatewayFilter类型的对应。是GlobalFilter过滤器的包装类
生命周期
客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,比如上图中的user-service,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。Spring Cloud Gateway 的 Filter 分为两种:GatewayFilter 与 GlobalFilter。GlobalFilter 会应用到所有的路由上,而 GatewayFilter 将应用到单个路由或者一个分组的路由上。
gateway filter
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。
GatewayFilter工厂都是在配置文件application.yml中配置,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。
AddRequestHeader GatewayFilter Factory
movie:
uri: lb://consumer-movie
spring:
application:
name: gateway-client
cloud:
gateway:
routes:-id: route_service_one
uri: ${movie.uri} # uri以lb://开头(lb代表从注册中心获取服务),后面就是需要转发到的服务名称
filters:- AddRequestHeader=X-Request-Foo, Bar
predicates:- Path=/movie/**
AddRequestHeaderGatewayFilterFactory的源码如下:
public class AddRequestHeaderGatewayFilterFactory extendsAbstractNameValueGatewayFilterFactory {
@OverridepublicGatewayFilter apply(NameValueConfig config) {return (exchange, chain) ->{
ServerHttpRequest request=exchange.getRequest().mutate()
.header(config.getName(), config.getValue())
.build();returnchain.filter(exchange.mutate().request(request).build());
};
}
}
根据旧的ServerHttpRequest创建新的 ServerHttpRequest ,在新的ServerHttpRequest加了一个请求头,然后创建新的 ServerWebExchange ,提交过滤器链继续过滤。
RewritePath GatewayFilter Factory
spring:
cloud:
gateway:
routes:-id: rewritepath_route
uri: https://blog.csdn.net
predicates:- Path=/foo/**filters:
- RewritePath=/foo/(?.*), /$\{segment}
profiles: rewritepath_route
所有的/foo/**开始的路径都会命中配置的router,并执行过滤器的逻辑,配置了RewritePath过滤器工厂,此工厂将/foo/(?.*)重写为{segment},然后转发到https://blog.csdn.net。比如在网页上请求localhost:8081/foo/forezp,此时会将请求转发到https://blog.csdn.net/forezp的页面,比如在网页上请求localhost:8081/foo/forezp/1,页面显示404,就是因为不存在https://blog.csdn.net/forezp/1这个页面
自定义过滤器
在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口。写一个RequestTimeFilter,代码如下:
packagecom.smart.filter;importlombok.extern.slf4j.Slf4j;importorg.springframework.cloud.gateway.filter.GatewayFilter;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.core.Ordered;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;
@Slf4jpublic class RequestTimeFilter implementsGatewayFilter,Ordered {private final static String REQUEST_TIME_BEGIN="requestTimeBegin";
@Overridepublic Monofilter(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");
}
}));
}
@Overridepublic intgetOrder() {return 0;
}
}
Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。还有一个filterI(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。然后将该过滤器注册到router中,代码如下:
@BeanpublicRouteLocator customerRouteLocator(RouteLocatorBuilder builder) {returnbuilder.routes()
.route(r-> r.path("/movie/**")
.filters(f->f.hystrix(config -> config.setName("movie.service")
.setFallbackUri("forward:/movie/fallback"))
.filter(newRequestTimeFilter()))
.uri(uri)).build();
}
自定义过滤器工厂
自定义过滤器工厂,可以在配置文件中配置过滤器。GatewayFilterfactory的层级如下:
过滤器工厂的顶级接口是GatewayFilterFactory,有2个两个较接近具体实现的抽象类,分别为AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这2个类前者接收一个参数,比如它的实现类RedirectToGatewayFilterFactory;后者接收2个参数,比如它的实现类AddRequestHeaderGatewayFilterFactory类。需要将请求的日志打印出来,需要使用一个参数,这时可以参照RedirectToGatewayFilterFactory的写法。
global filter
GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
该GlobalFilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:
packagecom.smart.filter;importlombok.extern.slf4j.Slf4j;importorg.springframework.cloud.gateway.filter.GatewayFilterChain;importorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.Ordered;importorg.springframework.http.HttpStatus;importorg.springframework.util.StringUtils;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;
@Slf4jpublic class TokenFilter implementsGlobalFilter,Ordered {
@Overridepublic Monofilter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token= exchange.getRequest().getQueryParams().getFirst("token");if(StringUtils.isEmpty(token)){
log.info("token is empty...");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);returnexchange.getResponse().setComplete();
}returnchain.filter(exchange);
}
@Overridepublic intgetOrder() {return -100;
}
}
TokenFilter需要实现GlobalFilter和Ordered接口,然后根据ServerWebExchange获取ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。
需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中,代码如下:
@BeanpublicTokenFilter tokenFilter(){return newTokenFilter();
}
参考:
https://blog.csdn.net/forezp/article/details/85057268
https://windmt.com/2018/05/08/spring-cloud-14-spring-cloud-gateway-filter/