【基于spring-cloud-gateway实现自己的网关过滤器】

news2024/9/28 7:25:35

基于spring-cloud-gateway实现自己的网关过滤器

spring cloud gateway custom starter

自定义非阻塞式反应网关服务,集成鉴权、限流、响应的增强处理等等
  • 环境要求
    <properties>
          <java.version>17</java.version>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          <maven.compiler.target>17</maven.compiler.target>
          <maven.compiler.source>17</maven.compiler.source>
          <java.source.version>17</java.source.version>
          <java.target.version>17</java.target.version>
          <spring-boot.version>3.1.12</spring-boot.version>
          <spring-cloud.version>2022.0.5</spring-cloud.version>
          <commons.pool2.version>2.12.0</commons.pool2.version>
          <redisson.version>3.34.1</redisson.version>
          <com.fastjson.jackson.version>2.17.2</com.fastjson.jackson.version>
          <commons.lang3.version>3.16.0</commons.lang3.version>
          <lombok.version>1.18.32</lombok.version>
      </properties>
    
  • GatewayFilter 路由过滤器
    • TokenFilterGatewayFilterFactory,鉴权处理
    • 配置示例:
    spring:
      cloud:
        gateway:
          routes:
            - id: testRoute
              uri: http://127.0.0.1:8080
              predicates:
                - Path=/test/**
              filters:
                - name: TokenFilter
                  args:
                    requestHeaderKey: auth
    
    • RlimterGatewayFilterFactory,自定义key,比如:我们想根据不同用户去做自定义限流,那么我们可以在自己的网关过滤工厂里面将limterKey设置为根据请求头自定义的用户标识,来进行自定义的配置。相比重写spring-cloud-gateway里面自定义的keyResolver和RedisLimiter相对容易一些。
      • 配置示例:
      spring:
          cloud:
              gateway:
                routes:
                  - id: route_test
                    uri: http://192.168.1.1:8091
                    predicates:
                      - Path=/from/requestIds/to/appNames
                    filters:
                      - name: RLimter
                        args:
                          limterKey: v1_10 #限流所需要的key
                          rate: 5  #每秒允许的请求数
                          crust: 0 #每秒令牌桶的填充数
      
      • 更多路由过滤器扩展中。。。
    自定义网关过滤工厂实现,TokenFilterGatewayFilterFactory和RlimterGatewayFilterFactory,以自定义限流器RlimterGatewayFilterFactory为例
  • 首先定义网关过滤工厂功能接口,限流的key,速率和桶的大小,我们是按照spring-cloud-gateway内部限流的实现改编而来的,通过调用lua脚本,采用redis的令牌桶算法做限流
public interface RLimter {

    Mono<Response> isAllowed(String limitKey, String rate, String crust);

    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    class Response {
        private boolean allowed;
    }
}
  • 限流功能组件的注入和声明,注入我们需要的Bean,注入RedisScript,调用自定义的lua脚本,以及StringRedisTemplate,因为下面这段代码我是将整个网关作为的启动器,所以限流的实现类也一并交给spring管理了,RRedisRateLimiter
@Configuration
public class RLimterAutoConfiguration {

    @Bean(name = "rredisRequestRateLimiterScript")
    public RedisScript<?> redisRequestRateLimiterScript() {
        DefaultRedisScript redisScript = new DefaultRedisScript();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/request_rate_limiter.lua")));
        redisScript.setResultType(List.class);
        return redisScript;
    }

    @Bean(name = "rredisRateLimiter")
    public RRedisRateLimiter redisRateLimiter(@Qualifier("rredisRequestRateLimiterScript") RedisScript<List<Long>> redisRequestRateLimiterScript, StringRedisTemplate redisTemplate) {
        return new RRedisRateLimiter(redisTemplate, redisRequestRateLimiterScript);
    }

    @Bean
    public RLimterGatewayFilterFactory rLimterGatewayFilterFactory(@Qualifier("rredisRateLimiter") RRedisRateLimiter redisRateLimiter) {
        return new RLimterGatewayFilterFactory(redisRateLimiter);
    }
}
  • 上面代码中提到的new ClassPathResource(“script/request_rate_limiter.lua”),工程resources目录下即可,也是摘抄自spring-cloud-gateway内部的限流脚本,原封不动
redis.replicate_commands()

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
-- redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = capacity / rate
local ttl = math.floor(fill_time * 2)

-- for testing, it should use redis system time in production
if now == nil then
  now = redis.call('TIME')[1]
end

--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. now)
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)

local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
  last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
  last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
  new_tokens = filled_tokens - requested
  allowed_num = 1
end

--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)

if ttl > 0 then
  redis.call("setex", tokens_key, ttl, new_tokens)
  redis.call("setex", timestamp_key, ttl, now)
end

-- return { allowed_num, new_tokens, capacity, filled_tokens, requested, new_tokens }
return { allowed_num, new_tokens }

  • 然后是限流实现类,通过传入上面lua脚本需要的四个参数即可,lua脚本中requested为每次从桶里面取出的令牌数,这个默认为1,此处不关注这个,默认值即可。
public class RRedisRateLimiter implements RLimter {

    private static final Logger logger = LoggerFactory.getLogger(RedisRateLimiter.class);
    private final StringRedisTemplate redisTemplate;
    private final RedisScript<List<Long>> script;

    public RRedisRateLimiter(StringRedisTemplate redisTemplate, RedisScript<List<Long>> script) {
        this.redisTemplate = redisTemplate;
        this.script = script;
    }

    static List<String> getKeys(String id) {
        String prefix = "request_rate_limiter.{" + id;
        String tokenKey = prefix + "}.tokens";
        String timestampKey = prefix + "}.timestamp";
        return Arrays.asList(tokenKey, timestampKey);
    }

    @Override
    public Mono<Response> isAllowed(String key, String rate, String crust) {
        List<String> keys = getKeys(key);
        List<Long> exec;
        try {
            exec = this.redisTemplate.execute(this.script, keys, rate, crust, "", "1");
        } catch (Throwable throwable) {
            logger.error("Error calling rate limiter lua", throwable);
            exec = Arrays.asList(1L, -1L);
        }
        assert exec != null;
        boolean allowed = exec.get(0) == 1L;
        return Mono.just(new Response(allowed));
    }
}
  • 最后就是定义我们自己的自定义限流网关工厂了,通过继承spring-cloud-gateway的一个父类,帮助我们加载自定义的网关过滤工厂,AbstractGatewayFilterFactory,父类支持传入我们的自定义配置参数,Config,通过泛型参数定义自己的配置类,并在构造中传入。
public class RLimterGatewayFilterFactory extends AbstractGatewayFilterFactory<RLimterGatewayFilterFactory.Config2> {

    private final RRedisRateLimiter redisRateLimiter;

    public RLimterGatewayFilterFactory(RRedisRateLimiter redisRateLimiter) {
        super(Config2.class);
        this.redisRateLimiter = redisRateLimiter;
    }

    @Override
    public GatewayFilter apply(Config2 config) {
        return (exchange, chain) -> redisRateLimiter.isAllowed(config.limterKey, config.rate, config.crust).flatMap(response -> {
            if (response.isAllowed()) {
                return chain.filter(exchange);
            } else {
                ServerWebExchangeUtils.setResponseStatus(exchange, config.getStatusCode());
                return exchange.getResponse().setComplete();
            }
        });
    }

    @Setter
    @Getter
    public static class Config2 implements HasRouteId {
        private String routeId;
        private String limterKey = "default";
        private String rate = "1";
        private String crust = "1";
        private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS;

        @Override
        public String getRouteId() {
            return routeId;
        }

        @Override
        public void setRouteId(String routeId) {
            this.routeId = routeId;
        }
    }
}
  • 这里我只是写了个简单的实例,如果实现自定义的限流,还需要在网关工厂里面,通过exchange拿到请求体来进行相应的key的解析,结合业务实现自定义的限流。配置可以参考一开始的yaml配置示例。可以测试当把令牌桶设置为0时,会给出TOO MANY REQUEST的429状态码。
全局过滤器案例
  • 首先定义自己的上下文对象
@Setter
@Getter
public class RequestContext {
    private String requestId;
    private long requestStartTime;
    private String requestIp;
}

  • 全局过滤器实现
@Component
public class RequestContextFilter implements WebFilter, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(RequestContextFilter.class);

    @Override
    public int getOrder() {
        return OrderConstant.REQUEST_CONTEXT_ORDER;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        long requestStartTime = SystemClock.now();
        String requestId = generateRequestId();
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getRawPath();
        String requestIp = IpUtils.tryGetRealIp(request);
        exchange.getResponse().getHeaders().add("requestId", requestId);
        logger.info("request start,requestId:{}, requestUri:{},ip:{},requestStartTime:{}", requestId, uri, requestIp, requestStartTime);
        RequestContext context = new RequestContext();
        context.setRequestId(requestId);
        context.setRequestStartTime(requestStartTime);
        context.setRequestIp(requestIp);
        return chain.filter(exchange).contextWrite(Context.of(RequestContext.class, context))
                .doOnEach(signal -> {
                    long requestEndTime = SystemClock.now();
                    if (signal.isOnComplete()) {
                        logger.info("request end,requestId:{},response:{},requestEndTime:{},耗时ms:{}", requestId, exchange.getResponse().getStatusCode(), requestEndTime, (requestEndTime - requestStartTime));
                    }
                    if (signal.isOnError()) {
                        logger.info("request end,requestId:{},error:{},requestEndTime:{},耗时ms:{}", requestId, signal.getThrowable(), requestEndTime, (requestEndTime - requestStartTime));
                    }
                });
    }

    private String generateRequestId() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2172812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TypeScript入门 (五)异步编程与前后端交互

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的TypeScript学习总结文档。本文旨在全面介绍 TypeScript 中的异步编程与网络请求&#xff0c;帮助读者深入理解 TypeScript 中的 asyn…

Colorful/七彩虹将星X17 XS 22 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

Redis 五大基本数据类型及其应用场景进阶(缓存预热、雪崩 、穿透 、击穿)

Redis 数据类型及其应用场景 Redis 是什么? Redis是一个使用C语言编写的高性能的基于内存的非关系型数据库&#xff0c;基于Key/Value结构存储数据&#xff0c;通常用来 缓解高并发场景下对某一资源的频繁请求 &#xff0c;减轻数据库的压力。它支持多种数据类型,如字符串、…

Linux安装JDK及配置环境变量超详细教程

微服务Linux解析部署使用全流程 linux系统的常用命令 Linux安装vim超详细教程 Linux安装tomcat及配置环境变量超详细教程 1、上传压缩包 统一创建目录&#xff1a;/usr/local/jdk&#xff0c;将压缩包上传到这个目录下。拖动文件到这个目录下即可。 2、执行解压命令 先进…

ELMO理论

目录 1 优点 2 缺点 3.知识点个人笔记 2018年3月份&#xff0c;ELMo出世&#xff0c;该paper是NAACL18 Best Paper。在之前2013年的word2vec及2014年的GloVe的工作中&#xff0c;每个词对应一个vector&#xff0c;对于多义词无能为力。ELMo的工作对于此&#xff0c;提出了一…

在 Gitlab 中使用 ChatGPT 进行 CodeReview

ChatGPT集成Gitlab&#xff0c;实现自动代码审计并进行评论&#xff0c;为软件开发团队提供高效、智能的代码审查解决方案。支持其他模型如通义千问等 自动触发与及时响应&#xff1a;利用Gitlab的Webhook功能&#xff0c;实现代码提交、合并请求和标签创建等事件的自动触发。一…

安全帽检测系统丨OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…

国产化低功耗低延时广覆盖物联网无线通讯方案_LAKI模组

01 物联网系统中为什么要使用LAKI模组。 物联网系统中使用LAKI模组的原因可以归结为以下几个方面&#xff1a; 技术先进性 广覆盖能力&#xff1a;LAKI模组具有卓越的广覆盖能力&#xff0c;其射频SoC芯片接收灵敏度小于-120dBm125kbps&#xff0c;系统通讯距离可达5千米以上…

一款好用的多种格式电子书制作软件

在数字化阅读日益普及的今天&#xff0c;电子书已经成为人们日常生活中不可或缺的一部分。而一款功能强大、操作简便的电子书制作软件&#xff0c;无疑是满足广大用户需求的最佳选择。 这款软件名为“FLBOOK在线制作电子杂志平台”&#xff0c;它支持多种格式输入&#xff0c;如…

设计模式、系统设计 record part02

软件设计模式&#xff1a; 1.应对重复发生的问题 2.解决方案 3.可以反复使用 1.本质是面向对象 2.优点很多 1.创建型-创建和使用分离 2.结构型-组合 3.行为型-协作 571123种模式 UML-统一建模语言-Unified Modeling Language 1.可视化&#xff0c;图形化 2.各种图&#xff08;9…

Python编程:08- pycharm使用技巧

新建文件时,自动填充代码 设置方法&#xff1a; settings→editor→file and code templates,选择python script #${NAME} 文件名 #${DATE} 日期自动补齐 if name ‘main’: # 先输入main,然后按tab键自动补齐自定义的段落 settings→editor→live templates,在右侧点击号…

C语言进阶版第12课—字符函数和字符串函数1

文章目录 1. 字符分类函数1.1 库函数iscntrl1.2 库函数isspace1.3 库函数islower和isupper 2. 字符转换函数3. strlen函数的使用和模拟实现3.1 strlen函数的使用3.2 strlen函数的模拟实现 4. strcpy函数的使用和模拟实现4.1 strcpy函数的使用4.2 strcpy函数的模拟实现 5. strca…

manim页面中不规则分割整个人页面。

界面中的分割方式 在信息设计中&#xff0c;我们常常需要通过分割设计的方式来对信息进行分组&#xff0c;界面中的分割方式大致分为三种&#xff1a;卡片、线条、留白。 界面中的分割方式主要可以分为以下几种&#xff1a; 一、根据规则进行分割&#xff1a; 规则网格分割&…

Starrocks with 嵌套

在某些场景下需要进行 with 嵌套 需要以下进行处理&#xff0c;报如图错误 with abc as (select * from .. ) insert into xxx select * from abc尝试创建物化视图 CREATE MATERIALIZED VIEW IF NOT EXISTS ads_test.xxx_mv REFRESH DEFERRED MANUAL AS with abc as (select…

C#基于SkiaSharp实现印章管理(9)

将印章设计模块设计的印章保存为图片并集中存放在指定文件夹内。新建印章应用项目&#xff0c;主要实现对图片及PDF文件加盖印章功能。本文实现给图片加盖印章功能。   给图片加盖印章的逻辑比较简单&#xff0c;就是将印章图片绘制到图片指定位置&#xff0c;使用SKControl控…

如何选择数据库架构

选择合适的数据库架构是一个复杂的过程&#xff0c;它取决于多种因素&#xff0c;包括应用程序的需求、数据量的大小、并发访问量、数据一致性要求、预算以及技术团队的熟悉程度等。以下是一些关键的步骤和考虑因素&#xff0c;帮助你选择合适的数据库架构&#xff1a; 1. 分析…

JITWatch安装使用方法

JITWatch 版本1.4.2 JDK 版本 11以上 1.下载JITWatch&#xff1a; https://github.com/AdoptOpenJDK/jitwatch/releases/download/1.4.2/jitwatch-ui-1.4.2-shaded-win.jar 2.启动 bat脚本执行&#xff1a;通过启动jar包方式启动JITWatch echo off start cmd /c "ti…

人工智能 实验1 Python语法

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 实验1 Python语言基础一 【实验目的】掌握Python及其集成开发环境的下载安装及其简单应用 【实验内容…

结合了LLM(大语言模型)的编辑器,不仅能理解人类语言,还能与用户互动,仿佛有了自己的思想。...

从前有一个神奇的编辑器王国&#xff0c;那里住着各种编辑器&#xff1a;开源的、AI代码编辑器、视频编辑器&#xff0c;还有专门处理邮件和音频的编辑器。一天&#xff0c;国王Markdown决定举办一场盛会&#xff0c;邀请所有编辑器展示各自的才华。 开源编辑器们自豪地展示了他…

解决hbase和hadoop的log4j依赖冲突的警告

一、运行hbase的发现依赖冲突的警告 这警告不影响使用 二、重命名log4j文件 进入HBase的lib包下&#xff0c;将HBase的log4j文件重命名&#xff0c;改成备份&#xff0c;这样再次运行hbase的时候&#xff0c;就没有依赖冲突了。 三、冲突成功解决