欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

解决spring cloud gateway 获取body内容并修改的问题

程序员文章站 2022-03-09 19:53:08
之前写过一篇文章,如何获取body的内容。spring cloud gateway获取body内容,不影响get请求确实能够获取所有body的内容了,不过今天终端同学调试接口的时候和我说,遇到了400...

之前写过一篇文章,如何获取body的内容。

spring cloud gateway获取body内容,不影响get请求

确实能够获取所有body的内容了,不过今天终端同学调试接口的时候和我说,遇到了400的问题,报错是这样的http method names must be tokens,搜了一下,都是说https引起的。可我的项目还没用https,排除了。

想到是不是因为修改了body内容导致的问题,试着不修改body的内容,直接传给微服务,果然没有报错了。

问题找到,那就好办了,肯定是我新构建的request对象缺胳膊少腿了,搜索一通之后发现一篇大牛写的文章:

spring cloud gateway(读取、修改 request body)

这里要再次表扬一下古哥,同样是中文文章,度娘却搜不到

不过文章中的spring cloud版本是

spring cloud: greenwich.rc2

我本地是最新的release版本rs3,并不能完全照搬过来,不过算是给了很大的启发(如何获取body以及重构)

下面给出我的代码

网关中对body内容进行解密然后验签

/**
 * @author tengdj
 * @date 2019/8/13 11:08
 * 设备接口验签,解密
 **/
@slf4j
public class terminalsignfilter implements gatewayfilter, ordered {

 private static final string aes_securty = "xxx";
 private static final string md5_salt = "xxx";

 @override
 public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
 exchange.getattributes().put("starttime", system.currenttimemillis());
 if (exchange.getrequest().getmethod().equals(httpmethod.post)) {
  //重新构造request,参考modifyrequestbodygatewayfilterfactory
  serverrequest serverrequest = serverrequest.create(exchange, handlerstrategies.withdefaults().messagereaders());
  mediatype mediatype = exchange.getrequest().getheaders().getcontenttype();
  //重点
  mono<string> modifiedbody = serverrequest.bodytomono(string.class).flatmap(body -> {
  //因为约定了终端传参的格式,所以只考虑json的情况,如果是表单传参,请自行发挥
  if (mediatype.application_json.iscompatiblewith(mediatype) || mediatype.application_json_utf8.iscompatiblewith(mediatype)) {
   jsonobject jsonobject = jsonutil.tojo(body);
   string paramstr = jsonobject.getstring("param");
   string newbody;
   try{
   newbody = verifysignature(paramstr);
   }catch (exception e){
   return processerror(e.getmessage());
   }
   return mono.just(newbody);
  }
  return mono.empty();
  });
  bodyinserter bodyinserter = bodyinserters.frompublisher(modifiedbody, string.class);
  httpheaders headers = new httpheaders();
  headers.putall(exchange.getrequest().getheaders());
  //猜测这个就是之前报400错误的元凶,之前修改了body但是没有重新写content length
  headers.remove("content-length");
  //mycachedbodyoutputmessage 这个类完全就是cachedbodyoutputmessage,只不过cachedbodyoutputmessage不是公共的
  mycachedbodyoutputmessage outputmessage = new mycachedbodyoutputmessage(exchange, headers);
  return bodyinserter.insert(outputmessage, new bodyinsertercontext()).then(mono.defer(() -> {
  serverhttprequest decorator = this.decorate(exchange, headers, outputmessage);
  return returnmono(chain, exchange.mutate().request(decorator).build());
  }));
 } else {
  //get 验签 
  multivaluemap<string, string> map = exchange.getrequest().getqueryparams();
  if (!collectionutils.isempty(map)) {
  string paramstr = map.getfirst("param");
  try{
   verifysignature(paramstr);
  }catch (exception e){
   return processerror(e.getmessage());
  }
  }
  return returnmono(chain, exchange);
 }
 }

 @override
 public int getorder() {
 return 1;
 }

 private mono<void> returnmono(gatewayfilterchain chain,serverwebexchange exchange){
 return chain.filter(exchange).then(mono.fromrunnable(()->{
  long starttime = exchange.getattribute("starttime");
  if (starttime != null){
  long executetime = (system.currenttimemillis() - starttime);
  log.info("耗时:{}ms" , executetime);
  log.info("状态码:{}" , objects.requirenonnull(exchange.getresponse().getstatuscode()).value());
  }
 }));
 }

 private string verifysignature(string paramstr) throws exception{
 log.info("密文{}", paramstr);
 string dparamstr;
 try{
  dparamstr = aesutil.decrypt(paramstr, aes_securty);
 }catch (exception e){
  throw new exception("解密失败!");
 }
 log.info("解密得到字符串{}", dparamstr);
 string signature = signutil.sign(dparamstr, md5_salt);
 log.info("重新加密得到签名{}", signature);
 jsonobject jsonobject1 = jsonutil.tojo(dparamstr);
 if (!jsonobject1.getstring("signature").equals(signature)) {
  throw new exception("签名不匹配!");
 }
 return jsonobject1.tojsonstring();
 }

 private mono processerror(string message) {
  /*exchange.getresponse().setstatuscode(httpstatus.unauthorized);
  return exchange.getresponse().setcomplete();*/
 log.error(message);
 return mono.error(new exception(message));
 }

 serverhttprequestdecorator decorate(serverwebexchange exchange, httpheaders headers, mycachedbodyoutputmessage outputmessage) {
 return new serverhttprequestdecorator(exchange.getrequest()) {
  public httpheaders getheaders() {
  long contentlength = headers.getcontentlength();
  httpheaders httpheaders = new httpheaders();
  httpheaders.putall(super.getheaders());
  if (contentlength > 0l) {
   httpheaders.setcontentlength(contentlength);
  } else {
   httpheaders.set("transfer-encoding", "chunked");
  }
  return httpheaders;
  }
  public flux<databuffer> getbody() {
  return outputmessage.getbody();
  }
 };
 }
}

代码到这里就结束了,希望看到的朋友可以少走点弯路,少踩点坑。

补充知识:springcloud gateway之addrequestparameter详细使用及踩坑注意

springcloud的网关gateway提供了多个内置filter,其中addrequestheader是添加header的,这个无坑,比较简单。还有一个添加参数的,addrequestparameter,这个就有点问题了。具体往下看。

版本如下,请注意springboot版本,这是本篇post请求异常的关键。

解决spring cloud gateway 获取body内容并修改的问题

1 对应的uri只能是get请求

解决spring cloud gateway 获取body内容并修改的问题

看一个简单的示例,addrequestparameter,我们匹配/addparam请求,并将请求转发至http://localhost:8888/header

这个是8888端口的服务

解决spring cloud gateway 获取body内容并修改的问题

如果发起get请求到网关,那么可以正常请求,一切ok。此时,调用发起方和最终的服务提供方都是get请求,没有问题。

如果发起的请求是get,但是服务提供方是如下的post。

解决spring cloud gateway 获取body内容并修改的问题

注意,这里我用了postmapping,然后分别启动两个工程,再访问localhost:8080/addparam,而后会报错,这个也可以理解。

解决spring cloud gateway 获取body内容并修改的问题

但是,如果调用发起方和服务提供方都是post请求,理论上应该也是ok的。

但是事实上不是的

解决spring cloud gateway 获取body内容并修改的问题

网关程序会报错如下:

解决spring cloud gateway 获取body内容并修改的问题

这个就很尴尬了,作为一个网关,居然在代理非get请求时出现异常,必然是不能容忍的。

经过一番探索,发现这是springboot不同版本的原因导致,在springboot2.0.5之前,不存在该问题,之后就有这种问题了。需要加以注意,解决方案会在下一篇写。

2 添加的参数value值必须合法(不能含有空格)

解决spring cloud gateway 获取body内容并修改的问题

上面已经知道了,addrequestparameter对应的后端请求是get型,那么明显添加的parameter只能是get请求支持的,能在浏览器地址栏直接敲上去合法的。

这里,我将value的值变成带空格的,然后去访问后端的服务。

解决spring cloud gateway 获取body内容并修改的问题

然后会发现控制台报错,invalid uri query。这是因为get请求的value值不能含有非法字符.

解决spring cloud gateway 获取body内容并修改的问题

同理

解决spring cloud gateway 获取body内容并修改的问题

像这样的,后台接收的是

解决spring cloud gateway 获取body内容并修改的问题

如果是这样的参数

解决spring cloud gateway 获取body内容并修改的问题

后台这样

解决spring cloud gateway 获取body内容并修改的问题

结果是:

解决spring cloud gateway 获取body内容并修改的问题

这样就可以添加多个parameter了。

同时添加header和parameter

结束了addrequestparameter的说明,我们可以来看看,假如某个path,既想addheader,又想addparameter,而系统的这两个方法,都是一个path只能搭配一个add的filter,即便写了两个也不生效,如

解决spring cloud gateway 获取body内容并修改的问题

解决spring cloud gateway 获取body内容并修改的问题

结果就只有header被打印了

解决spring cloud gateway 获取body内容并修改的问题

那么就是想同时添加header和parameter该怎么办呢。

貌似通过java代码是无法实现了,好在可以通过yml配置来实现。

spring:
 cloud:
  gateway:
   routes:
   - id: header
    uri: http://localhost:8888/header
    filters:
    - addrequestheader=newheader, bar
    - addrequestparameter=newparam, param
    predicates:
    - path=/header

在yml就可以在filters里,添加多个filter了,注意不要写错了filter的名字。

可以看到结果

解决spring cloud gateway 获取body内容并修改的问题

解决spring cloud gateway 获取body内容并修改的问题

发现header和param都传过来了。

以上这篇解决spring cloud gateway 获取body内容并修改的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。