背景
我们使用了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断言,可以根据如下配置
1spring: 2 cloud: 3 gateway: 4 routes: 5 - id: blog 6 uri: http://blog.yuqiyu.com 7 predicates: 8 - Path=/api/demo 9 - Query=xxx, zzz
根据上面配置,我们限定了参数xxx必须为zzz时才会被成功转发,否则会出现404抓发失败,根据上面配置就可以根据get参数转发
POST请求转发
post参数转发,没有现成的转发断言,这里我们需要参考readbody断言来实现,下面是ReadBodyPredicateFactory 的源码
1public class ReadBodyPredicateFactory extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> { 2 protected static final Log log = LogFactory.getLog(ReadBodyPredicateFactory.class); 3 private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute"; 4 private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; 5 private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); 6 7 public ReadBodyPredicateFactory() { 8 super(ReadBodyPredicateFactory.Config.class); 9 } 10 11 public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) { 12 return (exchange) -> { 13 Class inClass = config.getInClass(); 14 Object cachedBody = exchange.getAttribute("cachedRequestBodyObject"); 15 if (cachedBody != null) { 16 try { 17 boolean test = config.predicate.test(cachedBody); 18 exchange.getAttributes().put("read_body_predicate_test_attribute", test); 19 return Mono.just(test); 20 } catch (ClassCastException var6) { 21 if (log.isDebugEnabled()) { 22 log.debug("Predicate test failed because class in predicate does not match the cached body object", var6); 23 } 24 25 return Mono.just(false); 26 } 27 } else { 28 return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> { 29 return ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> { 30 exchange.getAttributes().put("cachedRequestBodyObject", objectValue); 31 }).map((objectValue) -> { 32 return config.getPredicate().test(objectValue); 33 }); 34 }); 35 } 36 }; 37 } 38 39 public Predicate<ServerWebExchange> apply(ReadBodyPredicateFactory.Config config) { 40 throw new UnsupportedOperationException("ReadBodyPredicateFactory is only async."); 41 } 42 43 public static class Config { 44 private Class inClass; 45 private Predicate predicate; 46 private Map<String, Object> hints; 47 48 public Config() { 49 } 50 51 public Class getInClass() { 52 return this.inClass; 53 } 54 55 public ReadBodyPredicateFactory.Config setInClass(Class inClass) { 56 this.inClass = inClass; 57 return this; 58 } 59 60 public Predicate getPredicate() { 61 return this.predicate; 62 } 63 64 public ReadBodyPredicateFactory.Config setPredicate(Predicate predicate) { 65 this.predicate = predicate; 66 return this; 67 } 68 69 public <T> ReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) { 70 this.setInClass(inClass); 71 this.predicate = predicate; 72 return this; 73 } 74 75 public Map<String, Object> getHints() { 76 return this.hints; 77 } 78 79 public ReadBodyPredicateFactory.Config setHints(Map<String, Object> hints) { 80 this.hints = hints; 81 return this; 82 } 83 } 84}
这个只是把post参数读入到缓存,配置如下
1predicates: 2 - Path=/card/api/** 3 - name: ReadBodyPredicateFactory #使用ReadBodyPredicateFactory断言,将body读入缓存 4 args: 5 inClass: '#{T(String)}' 6 predicate: '#{@bodyPredicate}' #注入实现predicate接口类
但是这个暂时不能满足要求,我们需要参考ReadBodyPredicateFactory自定义一个predicatefactory来实现我们的需求
1@Component() 2@Slf4j 3public class MyReadBodyPredicateFactory extends AbstractRoutePredicateFactory<MyReadBodyPredicateFactory.Config> { 4 5 6 private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies 7 .withDefaults().messageReaders(); 8 9 public MyReadBodyPredicateFactory() { 10 super(MyReadBodyPredicateFactory.Config.class); 11 } 12 13 public MyReadBodyPredicateFactory(Class<MyReadBodyPredicateFactory.Config> configClass) { 14 super(configClass); 15 } 16 17 @Override 18 @SuppressWarnings("unchecked") 19 public AsyncPredicate<ServerWebExchange> applyAsync(MyReadBodyPredicateFactory.Config config) { 20 return new AsyncPredicate<ServerWebExchange>() { 21 @Override 22 public Publisher<Boolean> apply(ServerWebExchange exchange) { 23 Object cachedBody = exchange.getAttribute(MyFacadeConstants.CACHE_REQUEST_BODY_OBJECT_KEY); 24 if (cachedBody != null) { 25 try { 26 boolean test = match(config.sceneIds, (MycRequest) cachedBody); 27 return Mono.just(test); 28 } catch (ClassCastException e) { 29 if (log.isDebugEnabled()) { 30 log.debug("Predicate test failed because class in predicate " 31 + "does not match the cached body object", e); 32 } 33 } 34 return Mono.just(false); 35 } else { 36 return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, 37 (serverHttpRequest) -> ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), messageReaders) 38 .bodyToMono(MycRequest.class) 39 .doOnNext(objectValue -> exchange.getAttributes().put(MyFacadeConstants.CACHE_REQUEST_BODY_OBJECT_KEY,objectValue)) 40 .map(objectValue -> { return match(config.sceneIds, objectValue);})); 41 } 42 } 43 }; 44 } 45 46 private boolean match(String params, MycRequest mycRequest) { 47 if("others".equals(params)){ 48 return true; 49 } 50 String[] paramArray = params.split(","); 51 if (ArrayUtils.contains(paramArray, mycRequest.getRouteId)) { 52 return true; 53 } else { 54 return false; 55 } 56 } 57 58 @Override 59 @SuppressWarnings("unchecked") 60 public Predicate<ServerWebExchange> apply(MyReadBodyPredicateFactory.Config config) { 61 throw new UnsupportedOperationException( 62 "MyReadBodyPredicateFactory is only async."); 63 } 64 65 public static class Config { 66 67 private String params; 68 69 public MyReadBodyPredicateFactory.Config setParams(params) { 70 this.params = params; 71 return this; 72 } 73 74 public String getParams() { 75 return params; 76 } 77 } 78}
这里我们可以根据将参数转为MyRequest,然后再进行判断是否路由,当然这里我们同样也需要使用到path断言,配置如下:
1spring: 2 cloud: 3 gateway: 4 routes: 5 - id: route1 6 uri: http://host1:8080 7 predicates: 8 - Path=/api/demo 9 - name: MyReadBodyPredicateFactory 10 args: 11 params: "23,22" 12 - id: route2 13 uri: http://host2:8080 14 predicates: 15 - Path=/api/demo 16 - name: RecommendReadBodyPredicateFactory 17 args: 18 params: "44,56"
这样就可以根据post参数路由转发了,如下监控:
