2. gateway 调用链截断及filter重复执行

1.背景

      开放平台项目,开发者接入开放平台后,调用开平接口,开平网关需要获取开发者的信息,进而做判断开发者权限、 记录流量信息等操作。

2.问题

      开平网关获取开发者信息后,经判断开发者没有权限,想做两个操作 1.返回开发者无权限信息;2.截断之后的filter及向业务服务的调用,如下图所示:
在这里插入图片描述

3.互联网解决方案

      百度之后,发现网上流行的解决方案类似这样:

public static Mono<Void> badReturn(ServerWebExchange exchange, Status statusCode) {
   if(statusCode == null) {
      statusCode = Status.ERROR_PARAMETERS;
   }
   //exchange中获取response
   ServerHttpResponse response = exchange.getResponse();
   //设置response code及header信息
   response.setStatusCode(HttpStatus.OK);
   HttpHeaders headers = response.getHeaders();
   headers= HttpHeaders.writableHttpHeaders(headers);
   headers.setContentType(MediaType.APPLICATION_JSON);
   //生成返回开发者的信息
   String fastResult = returnString(statusCode, "F");
   //将string信息写入dataBuffer中
   DataBuffer dataBuffer = response.bufferFactory().allocateBuffer().write(fastResult.getBytes(StandardCharsets.UTF_8));
   //response写回
   return response.writeWith(Mono.just(dataBuffer));
}

      或者是此种方法的变体,使用ServerHttpResponseDecorator进行封装,类似这样:

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   List<String> authorization = exchange.getRequest().getHeaders().get("Authorization");
   if (CollectionUtils.isEmpty(authorization) &&
      !PatternMatchUtils.simpleMatch(URL_WITHOUT_AUTH, exchange.getRequest().getURI().toString())) {
      exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
      ServerHttpResponse originalResponse = exchange.getResponse();
      DataBufferFactory bufferFactory = originalResponse.bufferFactory();
      ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
         @Override
         public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            if (body instanceof Flux) {
               Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
               return super.writeWith(fluxBody.map(dataBuffer -> {
                  // probably should reuse buffers 
                  byte[] content = new byte[dataBuffer.readableByteCount()];
                  dataBuffer.read(content);
                  byte[] uppedContent = new String(content, Charset.forName("UTF-8")).toUpperCase().getBytes();
                  return bufferFactory.wrap(uppedContent);
               }));
            }
            return super.writeWith(body); // if body is not a flux. never got there.
         }
      };
      return chain.filter(exchange.mutate().response(decoratedResponse).build()); // replace response with decorator
   }
   String token = authorization.get(0).split(" ")[1];
   // token validation
   return chain.filter(exchange);
}

3.1互联网方案的局限

      我借用了第一种方式,并在项目中实测,确实可以正确的返回开发者信息。虽然可以使用,但在我的项目(gateway版本2.2.8.RELEASE)中产生了两个严重的副作用:1. filter重复执行;2. 第二次执行filter时报错。虽然在重复实行和报错时,因为response已经返回,所以并不会影响调用者,但我觉得此种方式不够完美。

3.2复现说明

      我项目中filter链是这样的,其中0和3两个filter是我添加的,其它filter一部分是项目遗留的,用来记录日志,判断内部调用时token的合法性等的filter,另一部分就是gateway自己的filter了。
在这里插入图片描述
      当执行到index=3时,执行到我自定义的filter
在这里插入图片描述
      filter首次执行时的情况如下图,可以看到body中是有数据的,经过判断后判定没权限,返回调用者无权限。
在这里插入图片描述
      调用者确实可以正常收到结果
在这里插入图片描述
      但继续看后台代码执行过程,诡异的现象出现了。就算此时filter没有截断,也要执行index=4的filter了,但此时,index=1,filter重复执行了,更诡异的是,filter是从index=1执行的,不是index=0的filter。
在这里插入图片描述
      执行到index=3时,再次执行到我定义的filter。
在这里插入图片描述
      此时body数据为空,当然也就无权限,再次返回调用者无权限信息
在这里插入图片描述
      后台发生错误,推测可能是因为过滤器链第一次执行时,response已经返回,链接关闭导致。
在这里插入图片描述

4. 思考

      这个问题可以分成两部分来看,一部分是response返回后filter没有截断,后续filter和业务服务调用依然要发生,只是在发生后续操作的时候,出现了另一个问题,就是filter重复执行,并且从index=1的filter开始执行,不是index=0的filter。
      仔细思考一下,网上的第二种形式应该也是有问题的,它应该也不能完成filter的截断,至于会不会重复执行index=1及之后的filter的情况,我没有实验。
      那如何完成filter的截断呢?
      抛开gateway,从更高的层面想一下,Spring FrameWork 5使用的响应式编程框架是Project Reactor3(以下简称Reactor),Reactor是怎么处理的呢?它是形如这样的代码:

private void reactorExample() {
   Flux.fromIterable(Arrays.asList("a,b,c".split(",")))
      .subscribe(result->{
         //成功时执行此处
      }, error->{
         //失败时执行此处
      });
}

      数据发出后经过Flux或者Mono操作符的处理,处理成功执行result代表的consumer代码块,处理失败执行error代表的errorConsumer代码块。
      gateway执行filter时,实际上是在执行consumer,如果想让filter截断,需要让它执行到其它的分支上去,比如errorConsumer代码分支。

5. 最终方案

      顺着以上思路,我做了如下三点改动:
      1.权限验证失败时,不在使用response回写body数据,而是返回了Mono.error。
在这里插入图片描述
      2.自定义了GatewayException封装返回给调用方的信息。
在这里插入图片描述
      3.定义全局异常处理,在异常里返回调用方信息。
在这里插入图片描述
      亲测问题解决。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值