前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springcloud gateway高级功能之根据参数自定义路由Predicate

springcloud gateway高级功能之根据参数自定义路由Predicate

作者头像
一笠风雨任生平
发布2022-01-06 14:20:42
2.3K0
发布2022-01-06 14:20:42
举报
文章被收录于专栏:服务化进程服务化进程

背景

我们使用了springcloud gateway作为也给路由转发功能,由于历史遗留问题,不仅仅需要根据path转发,还需要根据get或者post中的参数进行转发

解决方案

这里我们使用自定义的Predicate进行转发

简介

这里简单介绍下相关术语 (1)Filter(过滤器):

和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。

(2)Route(路由):

网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。

(3)Predicate(断言):

这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。断言的输入类型是一个 ServerWebExchange。

这里我们会使用自定义的断言来实现,常用的断言有如下几个:

在这里插入图片描述
在这里插入图片描述

详细信息可以参考下面链接:https://www.jianshu.com/p/d2c3b6851e1d?utm_source=desktop&utm_medium=timeline

GET请求转发

在常用断言中就有支持根据get参数转发,所以这里需要同时使用path以及query断言,可以根据如下配置

代码语言:javascript
复制
spring:
  cloud:
    gateway:
      routes:
        - id: blog
          uri: http://blog.yuqiyu.com
          predicates:
            - Path=/api/demo
            - Query=xxx, zzz

根据上面配置,我们限定了参数xxx必须为zzz时才会被成功转发,否则会出现404抓发失败,根据上面配置就可以根据get参数转发

POST请求转发

post参数转发,没有现成的转发断言,这里我们需要参考readbody断言来实现,下面是ReadBodyPredicateFactory 的源码

代码语言:javascript
复制
public class ReadBodyPredicateFactory extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> {
    protected static final Log log = LogFactory.getLog(ReadBodyPredicateFactory.class);
    private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
    private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

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

    public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) {
        return (exchange) -> {
            Class inClass = config.getInClass();
            Object cachedBody = exchange.getAttribute("cachedRequestBodyObject");
            if (cachedBody != null) {
                try {
                    boolean test = config.predicate.test(cachedBody);
                    exchange.getAttributes().put("read_body_predicate_test_attribute", test);
                    return Mono.just(test);
                } catch (ClassCastException var6) {
                    if (log.isDebugEnabled()) {
                        log.debug("Predicate test failed because class in predicate does not match the cached body object", var6);
                    }

                    return Mono.just(false);
                }
            } else {
                return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
                    return ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
                        exchange.getAttributes().put("cachedRequestBodyObject", objectValue);
                    }).map((objectValue) -> {
                        return config.getPredicate().test(objectValue);
                    });
                });
            }
        };
    }

    public Predicate<ServerWebExchange> apply(ReadBodyPredicateFactory.Config config) {
        throw new UnsupportedOperationException("ReadBodyPredicateFactory is only async.");
    }

    public static class Config {
        private Class inClass;
        private Predicate predicate;
        private Map<String, Object> hints;

        public Config() {
        }

        public Class getInClass() {
            return this.inClass;
        }

        public ReadBodyPredicateFactory.Config setInClass(Class inClass) {
            this.inClass = inClass;
            return this;
        }

        public Predicate getPredicate() {
            return this.predicate;
        }

        public ReadBodyPredicateFactory.Config setPredicate(Predicate predicate) {
            this.predicate = predicate;
            return this;
        }

        public <T> ReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
            this.setInClass(inClass);
            this.predicate = predicate;
            return this;
        }

        public Map<String, Object> getHints() {
            return this.hints;
        }

        public ReadBodyPredicateFactory.Config setHints(Map<String, Object> hints) {
            this.hints = hints;
            return this;
        }
    }
}

这个只是把post参数读入到缓存,配置如下

代码语言:javascript
复制
predicates:
        - Path=/card/api/**
        - name: ReadBodyPredicateFactory #使用ReadBodyPredicateFactory断言,将body读入缓存
          args:
            inClass: '#{T(String)}'
            predicate: '#{@bodyPredicate}' #注入实现predicate接口类

但是这个暂时不能满足要求,我们需要参考ReadBodyPredicateFactory自定义一个predicatefactory来实现我们的需求

代码语言:javascript
复制
@Component()
@Slf4j
public class MyReadBodyPredicateFactory extends AbstractRoutePredicateFactory<MyReadBodyPredicateFactory.Config> {


    private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies
            .withDefaults().messageReaders();

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

    public MyReadBodyPredicateFactory(Class<MyReadBodyPredicateFactory.Config> configClass) {
        super(configClass);
    }

    @Override
    @SuppressWarnings("unchecked")
    public AsyncPredicate<ServerWebExchange> applyAsync(MyReadBodyPredicateFactory.Config config) {
        return new AsyncPredicate<ServerWebExchange>() {
            @Override
            public Publisher<Boolean> apply(ServerWebExchange exchange) {
                Object cachedBody = exchange.getAttribute(MyFacadeConstants.CACHE_REQUEST_BODY_OBJECT_KEY);
                if (cachedBody != null) {
                    try {
                        boolean test = match(config.sceneIds, (MycRequest) cachedBody);
                        return Mono.just(test);
                    } catch (ClassCastException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Predicate test failed because class in predicate "
                                    + "does not match the cached body object", e);
                        }
                    }
                    return Mono.just(false);
                } else {
                    return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
                            (serverHttpRequest) -> ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders)
                                    .bodyToMono(MycRequest.class)
                                    .doOnNext(objectValue -> exchange.getAttributes().put(MyFacadeConstants.CACHE_REQUEST_BODY_OBJECT_KEY,objectValue))
                                    .map(objectValue -> { return match(config.sceneIds, objectValue);}));
                }
            }
        };
    }

    private boolean match(String params, MycRequest mycRequest) {
        if("others".equals(params)){
            return true;
        }
        String[] paramArray = params.split(",");
        if (ArrayUtils.contains(paramArray, mycRequest.getRouteId)) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Predicate<ServerWebExchange> apply(MyReadBodyPredicateFactory.Config config) {
        throw new UnsupportedOperationException(
                "MyReadBodyPredicateFactory is only async.");
    }

    public static class Config {

        private String params;

        public MyReadBodyPredicateFactory.Config setParams(params) {
            this.params = params;
            return this;
        }

        public String getParams() {
            return params;
        }
    }
}

这里我们可以根据将参数转为MyRequest,然后再进行判断是否路由,当然这里我们同样也需要使用到path断言,配置如下:

代码语言:javascript
复制
spring:
  cloud:
    gateway:
      routes:
        - id: route1
          uri: http://host1:8080
          predicates:
            - Path=/api/demo
            - name: MyReadBodyPredicateFactory 
              args:
                params: "23,22" 
        - id: route2
          uri: http://host2:8080
          predicates:
            - Path=/api/demo
            - name: RecommendReadBodyPredicateFactory 
              args:
                params: "44,56" 

这样就可以根据post参数路由转发了,如下监控:

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/01/04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 解决方案
    • 简介
      • GET请求转发
        • POST请求转发
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档