解决spring cloud gateway 获取body内容并修改的问题
之前写过一篇文章,如何获取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请求异常的关键。
1 对应的uri只能是get请求
看一个简单的示例,addrequestparameter,我们匹配/addparam请求,并将请求转发至http://localhost:8888/header
这个是8888端口的服务
如果发起get请求到网关,那么可以正常请求,一切ok。此时,调用发起方和最终的服务提供方都是get请求,没有问题。
如果发起的请求是get,但是服务提供方是如下的post。
注意,这里我用了postmapping,然后分别启动两个工程,再访问localhost:8080/addparam,而后会报错,这个也可以理解。
但是,如果调用发起方和服务提供方都是post请求,理论上应该也是ok的。
但是事实上不是的
网关程序会报错如下:
这个就很尴尬了,作为一个网关,居然在代理非get请求时出现异常,必然是不能容忍的。
经过一番探索,发现这是springboot不同版本的原因导致,在springboot2.0.5之前,不存在该问题,之后就有这种问题了。需要加以注意,解决方案会在下一篇写。
2 添加的参数value值必须合法(不能含有空格)
上面已经知道了,addrequestparameter对应的后端请求是get型,那么明显添加的parameter只能是get请求支持的,能在浏览器地址栏直接敲上去合法的。
这里,我将value的值变成带空格的,然后去访问后端的服务。
然后会发现控制台报错,invalid uri query。这是因为get请求的value值不能含有非法字符.
同理
像这样的,后台接收的是
如果是这样的参数
后台这样
结果是:
这样就可以添加多个parameter了。
同时添加header和parameter
结束了addrequestparameter的说明,我们可以来看看,假如某个path,既想addheader,又想addparameter,而系统的这两个方法,都是一个path只能搭配一个add的filter,即便写了两个也不生效,如
结果就只有header被打印了
那么就是想同时添加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的名字。
可以看到结果
发现header和param都传过来了。
以上这篇解决spring cloud gateway 获取body内容并修改的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。