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

背景

我们使用了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断言,可以根据如下配置

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 的源码

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参数读入到缓存,配置如下

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

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

@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断言,配置如下:

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参数路由转发了,如下监控:
在这里插入图片描述

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring Cloud Gateway 中获取参数可以使用 Predicate 和 Filter 这两个组件来实现。下面给出两种常见的获取参数的方法: 1. 使用 Predicate 获取参数: 在路由配置中,可以使用 Predicate 来匹配请求,并从请求中获取参数。例如,可以使用 QueryRoutePredicateFactory 来匹配 URL 中的参数,并进行路由: ```yaml spring: cloud: gateway: routes: - id: example_route uri: http://example.com predicates: - Query=param=value ``` 上述配置将匹配具有 `param=value` 查询参数的请求,并将其路由到 `http://example.com`。 2. 使用 Filter 获取参数: 可以创建一个自定义的过滤器来获取请求中的参数。首先,实现 GlobalFilter 接口并重写过滤方法: ```java import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class CustomFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求中的参数 String paramValue = exchange.getRequest().getQueryParams().getFirst("param"); // 进行相应的处理 // ... return chain.filter(exchange); } @Override public int getOrder() { return 0; } } ``` 上述自定义过滤器可以获取请求中名为 `param` 的查询参数,并进行相应的处理。 通过 Predicate 或 Filter,你可以根据自己的需求从请求中获取参数,并进行相应的处理。请根据你的具体场景选择适合的方法来获取参数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值