• Spring Cloud - 手写 Gateway 源码,实现自定义局部 FilterFactory


    目录

    一、FilterFactory 分析

    1.1、前置知识

    1.2、分析源码

    1.2.1、整体分析

    1.2.2、源码分析

    1.3、手写源码

    1.3.1、基础框架

    1.3.2、实现自定义局部过滤器

    1.3.3、加参数的自定义局部过滤器器


    一、FilterFactory 分析


    1.1、前置知识

    前面的学习我们知道,GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,同时,springcloud 也提供了一些内置的 filter.

    比如:StripPrefix,表示给请求的 url 中去表指定的 n 个前缀路由,例如 - StripPrefix=2 那么如果你原本的请求是路由是 /user/list/get ,那么经过 StripPrefix 处理后,就会变成 /get.

    如果我们需要自己去实现一个像这样的局部过滤器,该怎么实现呢?

    1.2、分析源码

    1.2.1、整体分析

    例如 StripPrefix,他继承了 AbstractGatewayFilterFactory 这个抽象类.

    这里暗含了一层意思:在 application.yml 配置文件中,可以在 filters 配置里写上这个类的前缀 StripPrefix,就表示这个类(后面的 GatewayFilterFactory 是固定写法,就表示他是一个网关过滤器).

    进一步的,如果我们要自定义一个局部过滤器,例如身份认证 Token 过滤器,我们就创建一个类命名为:Token + GatewayFilter,然后继承 AbstractGatewayFilterFactory 抽象类,就表示他是一个局部过滤器.

    1.2.2、源码分析

    源码如下:

    1. public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory {
    2. public static final String PARTS_KEY = "parts";
    3. public StripPrefixGatewayFilterFactory() {
    4. super(Config.class);
    5. }
    6. public List shortcutFieldOrder() {
    7. return Arrays.asList("parts");
    8. }
    9. public GatewayFilter apply(final Config config) {
    10. return new GatewayFilter() {
    11. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    12. ServerHttpRequest request = exchange.getRequest();
    13. ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
    14. String path = request.getURI().getRawPath();
    15. String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
    16. StringBuilder newPath = new StringBuilder("/");
    17. for(int i = 0; i < originalParts.length; ++i) {
    18. if (i >= config.getParts()) {
    19. if (newPath.length() > 1) {
    20. newPath.append('/');
    21. }
    22. newPath.append(originalParts[i]);
    23. }
    24. }
    25. if (newPath.length() > 1 && path.endsWith("/")) {
    26. newPath.append('/');
    27. }
    28. ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();
    29. exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
    30. return chain.filter(exchange.mutate().request(newRequest).build());
    31. }
    32. public String toString() {
    33. return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();
    34. }
    35. };
    36. }
    37. public static class Config {
    38. private int parts = 1;
    39. public Config() {
    40. }
    41. public int getParts() {
    42. return this.parts;
    43. }
    44. public void setParts(int parts) {
    45. this.parts = parts;
    46. }
    47. }
    48. }

    • extends AbstractGatewayFilterFactory :继承 AbstractGatewayFilterFactory 表示他是一个局部过滤器.  传入一个泛型 Config(是一个静态内部类),是因为在配置 filters 的时候,可能需要给参数指定具体的值,例如 - StripPrefix=2,而 Config 就是来处理这里的 2 这个值的.
    • public static final String PARTS_KEY = "parts": 这里就是定义一个常量,后面会用上.
    • StripPrefixGatewayFilterFactory() :构造方法,需要给父类 AbstractGatewayFilterFactory 传递 Config 参数(前面分析过了),将来在 apply 方法中会用上.
    • Config:是一个静态内部类,描述了配置 filters 时,具体要给参数指定的值,并提供了 get 和 set 方法.  这个类就需要传递给父类 AbstractGatewayFilterFactory,最后回传给 apply 方法,在 apply 方法中使用.如果不想给参数指定值,就可以不写 Config 中的内容.
    • shortcutFieldOrder():这个方法是用来指定 filters 配置中参数值的顺序.  也就是说,如果 Config 个中如果有多个参数,那么你在配置 filters 时,指定参数的多个值,顺序是怎样的?他就是用来指定顺序的.
    • apply(Config config):局部过滤器的核心类,用来描述过滤规则的.  这里的参数 Config 就是刚刚讲到的 静态内部类,先传递给父类,然后再回传给了 apply 方法,之后我们就可以直接在 apply 方法中去使用 Config 类中的参数.

    1.3、手写源码

    1.3.1、基础框架

    按照上述分析,不难写出大概模样,例如我们可以模仿源码,创建包 filter.factory ,然后在这个包下定义一个 Token 局部过滤器如下:

    1. @Component // 表示在工厂中创建对象(不能少!)
    2. public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory {
    3. public TokenGatewayFilterFactory() {
    4. super(Config.class);
    5. }
    6. /**
    7. * 核心方法: 处理过滤
    8. * @param config
    9. * @return
    10. */
    11. @Override
    12. public GatewayFilter apply(Config config) {
    13. return new GatewayFilter() {
    14. @Override
    15. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    16. // 处理过滤逻辑......
    17. return chain.filter(exchange);
    18. }
    19. };
    20. }
    21. @Override
    22. public List shortcutFieldOrder() {
    23. return super.shortcutFieldOrder();
    24. }
    25. public static class Config {
    26. }
    27. }

    那么我们就可以在配置文件中,添加这个自定义的局部过滤器

    1.3.2、实现自定义局部过滤器

    例如,自定义一个 Token 局部过滤器,那么就可以创建一个类 filter.factory.TokenGatewayFilterFactory 

    在 apply 中的过滤逻辑就是,判断前端是否传入 token,如果没有就抛异常,如果有就去 redis 上看看是否存在这个 token,如果存在就放行,不存在就抛异常.

    1. @Slf4j
    2. @Component // 表示在工厂中创建对象
    3. public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory {
    4. @Autowired
    5. private StringRedisTemplate redisTemplate;
    6. public TokenGatewayFilterFactory() {
    7. super(Config.class);
    8. }
    9. /**
    10. * 核心方法: 处理过滤
    11. * @param config
    12. * @return
    13. */
    14. @Override
    15. public GatewayFilter apply(Config config) {
    16. return new GatewayFilter() {
    17. @Override
    18. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    19. //1.获取 token 信息
    20. //由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 List
    21. List tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY);
    22. if(tokens == null) {
    23. throw new RuntimeException("没有 token 令牌!");
    24. }
    25. String tokenValue = tokens.get(0);
    26. log.info("token: {}", tokenValue);
    27. //2.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)
    28. if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {
    29. throw new RuntimeException("token 令牌不合法!");
    30. }
    31. return chain.filter(exchange);
    32. }
    33. };
    34. }
    35. @Override
    36. public List shortcutFieldOrder() {
    37. return super.shortcutFieldOrder();
    38. }
    39. public static class Config {
    40. }
    41. }

    a)例如 redis 存储的数据为

    b)执行结果如下:

    c)如果没有 token 数据,响应如下:

    d)如果有 token,但是 token 值错误,响应如下:

    1.3.3、加参数的自定义局部过滤器器

    如果在配置 filters 的时候,要指定一些参数,例如 isRequire(boolean类型,表示是否传),name(String 类型).

    那么就可以在 Config 静态内部类中描述,然后在 shortcutFieldOrder() 方法中指定顺序,最后就可以在 apply 中拿到对应的参数,如下:

    1. @Slf4j
    2. @Component // 表示在工厂中创建对象
    3. public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory {
    4. @Autowired
    5. private StringRedisTemplate redisTemplate;
    6. public TokenGatewayFilterFactory() {
    7. super(Config.class);
    8. }
    9. /**
    10. * 核心方法: 处理过滤
    11. * @param config
    12. * @return
    13. */
    14. @Override
    15. public GatewayFilter apply(Config config) {
    16. return new GatewayFilter() {
    17. @Override
    18. public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    19. log.info("config isRequire: {}", config.isRequire());
    20. log.info("config name: {}", config.getName());
    21. //1.拿到 Config 中自定义的参数 isRequire,判断是否要进行过滤
    22. if(config.isRequire()) {
    23. //2.获取 token 信息
    24. //由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 List
    25. List tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY);
    26. if(tokens == null) {
    27. throw new RuntimeException("没有 token 令牌!");
    28. }
    29. String tokenValue = tokens.get(0);
    30. log.info("token: {}", tokenValue);
    31. //3.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)
    32. if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {
    33. throw new RuntimeException("token 令牌不合法!");
    34. }
    35. }
    36. return chain.filter(exchange);
    37. }
    38. };
    39. }
    40. /**
    41. * 指定参数值填写的顺序
    42. * @return
    43. */
    44. @Override
    45. public List shortcutFieldOrder() {
    46. return Arrays.asList("require", "name");
    47. }
    48. /**
    49. * 提供 filter 配置中的参数值
    50. */
    51. public static class Config {
    52. private boolean require;
    53. private String name;
    54. public boolean isRequire() {
    55. return require;
    56. }
    57. public void setRequire(boolean require) {
    58. this.require = require;
    59. }
    60. public String getName() {
    61. return name;
    62. }
    63. public void setName(String name) {
    64. this.name = name;
    65. }
    66. }
    67. }

    Ps:这里的不要命名为 isRequire ,会冲突! 

    在配置文件中配置 filters:

    执行结果如下:

  • 相关阅读:
    107、怎样理解:程序员需要严谨(2)
    制造行业数字化运维破局之道
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java教务信息管理系统3rtdg
    【刷题心得】双指针法|HashSet<T>
    Mybatis-Plus【吐血详解 一篇掌握】
    docker-compose 安装 jekins
    消失的“金九银十” 互联网的下一个五年在哪里?
    KubeSphere 3.3.0 发布:全面拥抱 GitOps
    SpringCloudAlibaba系列微服务搭建笔记五_Dubbo
    docker介绍和安装
  • 原文地址:https://blog.csdn.net/CYK_byte/article/details/134261650