解決spring cloud gateway 獲取body內(nèi)容并修改的問(wèn)題
之前寫過(guò)一篇文章,如何獲取body的內(nèi)容。
Spring Cloud Gateway獲取body內(nèi)容,不影響GET請(qǐng)求
確實(shí)能夠獲取所有body的內(nèi)容了,不過(guò)今天終端同學(xué)調(diào)試接口的時(shí)候和我說(shuō),遇到了400的問(wèn)題,報(bào)錯(cuò)是這樣的HTTP method names must be tokens,搜了一下,都是說(shuō)https引起的。可我的項(xiàng)目還沒(méi)用https,排除了。
想到是不是因?yàn)樾薷牧薭ody內(nèi)容導(dǎo)致的問(wèn)題,試著不修改body的內(nèi)容,直接傳給微服務(wù),果然沒(méi)有報(bào)錯(cuò)了。
問(wèn)題找到,那就好辦了,肯定是我新構(gòu)建的REQUEST對(duì)象缺胳膊少腿了,搜索一通之后發(fā)現(xiàn)一篇大牛寫的文章:
Spring Cloud Gateway(讀取、修改 Request Body)
這里要再次表?yè)P(yáng)一下古哥,同樣是中文文章,度娘卻搜不到
不過(guò)文章中的spring cloud版本是
Spring Cloud: Greenwich.RC2
我本地是最新的Release版本RS3,并不能完全照搬過(guò)來(lái),不過(guò)算是給了很大的啟發(fā)(如何獲取body以及重構(gòu))
下面給出我的代碼
網(wǎng)關(guān)中對(duì)body內(nèi)容進(jìn)行解密然后驗(yàn)簽
/** * @author tengdj * @date 2019/8/13 11:08 * 設(shè)備接口驗(yàn)簽,解密 **/@Slf4jpublic 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)) { //重新構(gòu)造request,參考ModifyRequestBodyGatewayFilterFactory ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders()); MediaType mediaType = exchange.getRequest().getHeaders().getContentType(); //重點(diǎn) Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> { //因?yàn)榧s定了終端傳參的格式,所以只考慮json的情況,如果是表單傳參,請(qǐng)自行發(fā)揮 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()); //猜測(cè)這個(gè)就是之前報(bào)400錯(cuò)誤的元兇,之前修改了body但是沒(méi)有重新寫content length headers.remove('Content-Length'); //MyCachedBodyOutputMessage 這個(gè)類完全就是CachedBodyOutputMessage,只不過(guò)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 驗(yàn)簽 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('耗時(shí):{}ms' , executeTime); log.info('狀態(tài)碼:{}' , 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(); } }; }}
代碼到這里就結(jié)束了,希望看到的朋友可以少走點(diǎn)彎路,少踩點(diǎn)坑。
補(bǔ)充知識(shí):springcloud gateway之a(chǎn)ddRequestParameter詳細(xì)使用及踩坑注意
SpringCloud的網(wǎng)關(guān)gateway提供了多個(gè)內(nèi)置Filter,其中addRequestHeader是添加header的,這個(gè)無(wú)坑,比較簡(jiǎn)單。還有一個(gè)添加參數(shù)的,addRequestParameter,這個(gè)就有點(diǎn)問(wèn)題了。具體往下看。
版本如下,請(qǐng)注意Springboot版本,這是本篇Post請(qǐng)求異常的關(guān)鍵。
1 對(duì)應(yīng)的uri只能是get請(qǐng)求
看一個(gè)簡(jiǎn)單的示例,addRequestParameter,我們匹配/addParam請(qǐng)求,并將請(qǐng)求轉(zhuǎn)發(fā)至http://localhost:8888/header
這個(gè)是8888端口的服務(wù)
如果發(fā)起Get請(qǐng)求到網(wǎng)關(guān),那么可以正常請(qǐng)求,一切OK。此時(shí),調(diào)用發(fā)起方和最終的服務(wù)提供方都是Get請(qǐng)求,沒(méi)有問(wèn)題。
如果發(fā)起的請(qǐng)求是Get,但是服務(wù)提供方是如下的Post。
注意,這里我用了PostMapping,然后分別啟動(dòng)兩個(gè)工程,再訪問(wèn)localhost:8080/addParam,而后會(huì)報(bào)錯(cuò),這個(gè)也可以理解。
但是,如果調(diào)用發(fā)起方和服務(wù)提供方都是Post請(qǐng)求,理論上應(yīng)該也是OK的。
但是事實(shí)上不是的
網(wǎng)關(guān)程序會(huì)報(bào)錯(cuò)如下:
這個(gè)就很尷尬了,作為一個(gè)網(wǎng)關(guān),居然在代理非Get請(qǐng)求時(shí)出現(xiàn)異常,必然是不能容忍的。
經(jīng)過(guò)一番探索,發(fā)現(xiàn)這是Springboot不同版本的原因?qū)е拢赟pringboot2.0.5之前,不存在該問(wèn)題,之后就有這種問(wèn)題了。需要加以注意,解決方案會(huì)在下一篇寫。
2 添加的參數(shù)value值必須合法(不能含有空格)
上面已經(jīng)知道了,addRequestParameter對(duì)應(yīng)的后端請(qǐng)求是Get型,那么明顯添加的parameter只能是Get請(qǐng)求支持的,能在瀏覽器地址欄直接敲上去合法的。
這里,我將value的值變成帶空格的,然后去訪問(wèn)后端的服務(wù)。
然后會(huì)發(fā)現(xiàn)控制臺(tái)報(bào)錯(cuò),Invalid URI query。這是因?yàn)間et請(qǐng)求的value值不能含有非法字符.
同理
像這樣的,后臺(tái)接收的是
如果是這樣的參數(shù)
后臺(tái)這樣
結(jié)果是:
這樣就可以添加多個(gè)parameter了。
同時(shí)添加header和parameter
結(jié)束了addRequestParameter的說(shuō)明,我們可以來(lái)看看,假如某個(gè)path,既想addHeader,又想addParameter,而系統(tǒng)的這兩個(gè)方法,都是一個(gè)path只能搭配一個(gè)add的filter,即便寫了兩個(gè)也不生效,如
結(jié)果就只有header被打印了
那么就是想同時(shí)添加header和parameter該怎么辦呢。
貌似通過(guò)java代碼是無(wú)法實(shí)現(xiàn)了,好在可以通過(guò)yml配置來(lái)實(shí)現(xiàn)。
spring: cloud: gateway: routes: - id: header uri: http://localhost:8888/header filters: - AddRequestHeader=NewHeader, Bar - AddRequestParameter=NewParam, Param predicates: - Path=/header
在yml就可以在filters里,添加多個(gè)filter了,注意不要寫錯(cuò)了filter的名字。
可以看到結(jié)果
發(fā)現(xiàn)header和param都傳過(guò)來(lái)了。
以上這篇解決spring cloud gateway 獲取body內(nèi)容并修改的問(wèn)題就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. python爬蟲(chóng)實(shí)戰(zhàn)之制作屬于自己的一個(gè)IP代理模塊2. HTML 絕對(duì)路徑與相對(duì)路徑概念詳細(xì)3. .NET6打包部署到Windows Service的全過(guò)程4. 使用FormData進(jìn)行Ajax請(qǐng)求上傳文件的實(shí)例代碼5. 解決ajax請(qǐng)求后臺(tái),有時(shí)收不到返回值的問(wèn)題6. Python編寫nmap掃描工具7. 如何在jsp界面中插入圖片8. 基于javaweb+jsp實(shí)現(xiàn)企業(yè)財(cái)務(wù)記賬管理系統(tǒng)9. Ajax返回值類型與用法實(shí)例分析10. .Net Core和RabbitMQ限制循環(huán)消費(fèi)的方法
