笔记--Spring Cloud(十一):Spring Cloud Gateway(过滤器)_spring cloud gateway流量清洗

                }
            })
    );
}

@Override
public int getOrder() {
    return Ordered.LOWEST_PRECEDENCE;
}

}


我们在请求刚刚到达时,往`ServerWebExchange`中放入了一个属性`elapsedTimeBegin`,属性值为当时的毫秒级时间戳。然后在请求执行结束后,又从中取出我们之前放进去的那个时间戳,与当前时间的差值即为该请求的耗时。因为这是与业务无关的日志所以将`Ordered`设为`Integer.MAX_VALUE`以降低优先级。


现在再来看我们之前的问题:怎么来区分是“pre”还是“post”呢?其实就是`chain.filter(exchange)`之前的就是“pre”部分,之后的也就是`then`里边的是“post”部分。


创建好 Filter 之后我们将它添加到我们的 Filter Chain 里边



@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
// @formatter:off
return builder.routes()
.route(r -> r.path(“/fluent/customer/**”)
.filters(f -> f.stripPrefix(2)
.filter(new ElapsedFilter())
.addResponseHeader(“X-Response-Default-Foo”, “Default-Bar”))
.uri(“lb://EUREKA-CONSUMER”)
.order(0)
.id(“fluent_customer_service”)
)
.build();
// @formatter:on
}


现在再尝试访问 http://localhost:20000/fluent/customer/hello/cloud 即可在控制台里看到请求路径与对应的耗时



2020-08-23 17:17:43.584 INFO 9856 — [ctor-http-nio-1] o.s.cloud.gateway.filter.GatewayFilter : /hello/cloud: 1ms


### 自定义全局过滤器


前边讲了自定义的过滤器,那个过滤器只是局部的,如果我们有多个路由就需要一个一个来配置,


这在我们要全局统一处理某些业务的时候就显得比较麻烦,比如像最开始我们说的要做身份校验,有没有简单的方法呢?这时候就该**全局过滤器**出场了。



> 
> 针对所有的路由规则生效
> 
> 
> 


有了前边的基础,我们创建全局过滤器就简单多了。只需要把实现的接口`GatewayFilter`换成`GlobalFilter`,就完事大吉了。比如下面的 Demo 就是从请求头中获取`token`字段,如果能获取到就 pass,获取不到就直接返回`401`错误,虽然简单,但足以说明问题了。



import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class TokenFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String token = exchange.getRequest().getHeaders().getFirst("token");
    if (token == null || token.isEmpty()) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
    return chain.filter(exchange);
}

@Override
public int getOrder() {
    return -100;
}

}


然后在 Spring Config 中配置这个 Bean



@Bean
public TokenFilter tokenFilter(){
return new TokenFilter();
}


重启应用就能看到效果了



2020-08-23 17:54:10.574 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping : Route matched: service_customer
2020-08-23 17:54:10.579 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping : Mapping [Exchange: GET http://localhost:20000/customer/hello/cloud] to Route{id=‘service_customer’, uri=lb://EUREKA-CONSUMER, order=0, predicate=Paths: [/customer/**], match trailing slash: true, gatewayFilters=[[[StripPrefix parts = 1], order = 1], [[AddResponseHeader X-Response-Default-Foo = ‘Default-Bar’], order = 2]], metadata={}}
2020-08-23 17:54:10.579 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.h.RoutePredicateHandlerMapping : [b6c683f3-7] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@28fcb031
2020-08-23 17:54:10.579 DEBUG 20452 — [ctor-http-nio-4] o.s.c.g.handler.FilteringWebHandler : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@15ccd9e2}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@3dc6b2a1}, order = -2147482648], [GatewayFilterAdapter{delegate=com.hjj.spring.cloud.filter.TokenFilter@633d9a32}, order = -100], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@1aaf3c8f}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@56dc29b5}, order = 0], [[StripPrefix parts = 1], order = 1], [[AddResponseHeader X-Response-Default-Foo = ‘Default-Bar’], order = 2], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@687c8e69}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerClientFilter@1b8cd83a}, order = 10100], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@252ce945}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@4263f6d0}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@35f420c0}, order = 2147483647]]


### 自定义过滤器工厂


如果你还对上一篇关于路由的文章有印象,你应该还得我们在配置中有这么一段



filters:

  • StripPrefix=1
  • AddResponseHeader=X-Response-Default-Foo, Default-Bar

`StripPrefix`、`AddResponseHeader`这两个实际上是两个过滤器工厂(GatewayFilterFactory),用这种配置的方式更灵活方便。


我们就将之前的那个`ElapsedFilter`改造一下,让它能接收一个`boolean`类型的参数,来决定是否将请求参数也打印出来。



import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;

public class ElapsedGatewayFilterFactory extends AbstractGatewayFilterFactory<ElapsedGatewayFilterFactory.Config> {

private static final Log log = LogFactory.getLog(GatewayFilter.class);
private static final String ELAPSED_TIME_BEGIN = "elapsedTimeBegin";
private static final String KEY = "withParams";

@Override
public List<String> shortcutFieldOrder() {
    return Arrays.asList(KEY);
}

public ElapsedGatewayFilterFactory() {
    super(Config.class);
}

@Override
public GatewayFilter apply(Config config) {
    return (exchange, chain) -> {
        exchange.getAttributes().put(ELAPSED_TIME_BEGIN, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(ELAPSED_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;
    }

}

}


过滤器工厂的顶级接口是`GatewayFilterFactory`,我们可以直接继承它的两个抽象类来简化开发`AbstractGatewayFilterFactory`和`AbstractNameValueGatewayFilterFactory`,这两个抽象类的区别就是前者接收一个参数(像`StripPrefix`和我们创建的这种),后者接收两个参数(像`AddResponseHeader`)。


![在这里插入图片描述](https://img-blog.csdnimg.cn/20201024164503836.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NTM0NTQx,size_16,color_FFFFFF,t_70#pic_center)


`GatewayFilter apply(Config config)`方法内部实际上是创建了一个`GatewayFilter`的匿名类,具体实现和之前的几乎一样,就不解释了。


静态内部类`Config`就是为了接收那个`boolean`类型的参数服务的,里边的变量名可以随意写,但是要重写`List<String> shortcutFieldOrder()`这个方法。


这里注意一下,一定要调用一下父类的构造器把`Config`类型传过去,否则会报`ClassCastException`



public ElapsedGatewayFilterFactory() {
super(Config.class);
}


工厂类我们有了,再把它注册到 Spring 当中



@Bean
public ElapsedGatewayFilterFactory elapsedGatewayFilterFactory() {
return new ElapsedGatewayFilterFactory();
}


然后添加配置(主要改动在第 8 行)



spring:
cloud:
gateway:
discovery:
locator:
enabled: true
default-filters:
- Elapsed=true
routes:
- id: service_customer
uri: lb://CONSUMER
order: 0
predicates:
- Path=/customer/**
filters:
- StripPrefix=1
- AddResponseHeader=X-Response-Default-Foo, Default-Bar


然后我们再次访问 http://localhost:20000/customer/hello/yujian?token=1000 即可在控制台看到以下内容



2018-05-08 16:53:02.030 INFO 84423 — [ctor-http-nio-1] o.s.cloud.gateway.filter.GatewayFilter : /hello/yujian: 656ms params:{token=[1000]}



**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Linux运维工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/680fb872d98d4069fbb022b844cd4186.png)
![img](https://img-blog.csdnimg.cn/img_convert/4b7977ec13b796229ecf721a1565f2dd.png)
![img](https://img-blog.csdnimg.cn/img_convert/cf6b0f27ea40d9dcfb9f86115cd2f06e.png)
![img](https://img-blog.csdnimg.cn/img_convert/658b45f3f85fe5bdb275cddd799a2a3d.png)
![img](https://img-blog.csdnimg.cn/img_convert/a624227c43a91444ce061771b1bff825.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)**
![img](https://img-blog.csdnimg.cn/img_convert/d436d5f46cdd82de6e5a1d715240f72f.jpeg)

25008)]
[外链图片转存中...(img-p1EqjcfC-1712915325008)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)**
[外链图片转存中...(img-4gFMZSKD-1712915325009)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值