解決spring cloud gateway 獲取body內(nèi)容并修改的問題
之前寫過一篇文章,如何獲取body的內(nèi)容。
Spring Cloud Gateway獲取body內(nèi)容,不影響GET請求
確實(shí)能夠獲取所有body的內(nèi)容了,不過今天終端同學(xué)調(diào)試接口的時候和我說,遇到了400的問題,報錯是這樣的HTTP method names must be tokens,搜了一下,都是說https引起的??晌业捻?xiàng)目還沒用https,排除了。
想到是不是因?yàn)樾薷牧薭ody內(nèi)容導(dǎo)致的問題,試著不修改body的內(nèi)容,直接傳給微服務(wù),果然沒有報錯了。
問題找到,那就好辦了,肯定是我新構(gòu)建的REQUEST對象缺胳膊少腿了,搜索一通之后發(fā)現(xiàn)一篇大牛寫的文章:
Spring Cloud Gateway(讀取、修改 Request Body)
這里要再次表揚(yáng)一下古哥,同樣是中文文章,度娘卻搜不到
不過文章中的spring cloud版本是
Spring Cloud: Greenwich.RC2
我本地是最新的Release版本RS3,并不能完全照搬過來,不過算是給了很大的啟發(fā)(如何獲取body以及重構(gòu))
下面給出我的代碼
網(wǎng)關(guān)中對body內(nèi)容進(jìn)行解密然后驗(yàn)簽
/**
* @author tengdj
* @date 2019/8/13 11:08
* 設(shè)備接口驗(yàn)簽,解密
**/
@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)) {
//重新構(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的情況,如果是表單傳參,請自行發(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());
//猜測這個就是之前報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 驗(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("耗時:{}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ǔ)充知識:springcloud gateway之a(chǎn)ddRequestParameter詳細(xì)使用及踩坑注意
SpringCloud的網(wǎng)關(guān)gateway提供了多個內(nèi)置Filter,其中addRequestHeader是添加header的,這個無坑,比較簡單。還有一個添加參數(shù)的,addRequestParameter,這個就有點(diǎn)問題了。具體往下看。
版本如下,請注意Springboot版本,這是本篇Post請求異常的關(guān)鍵。

1 對應(yīng)的uri只能是get請求

看一個簡單的示例,addRequestParameter,我們匹配/addParam請求,并將請求轉(zhuǎn)發(fā)至http://localhost:8888/header
這個是8888端口的服務(wù)

如果發(fā)起Get請求到網(wǎng)關(guān),那么可以正常請求,一切OK。此時,調(diào)用發(fā)起方和最終的服務(wù)提供方都是Get請求,沒有問題。
如果發(fā)起的請求是Get,但是服務(wù)提供方是如下的Post。

注意,這里我用了PostMapping,然后分別啟動兩個工程,再訪問localhost:8080/addParam,而后會報錯,這個也可以理解。

但是,如果調(diào)用發(fā)起方和服務(wù)提供方都是Post請求,理論上應(yīng)該也是OK的。
但是事實(shí)上不是的

網(wǎng)關(guān)程序會報錯如下:

這個就很尷尬了,作為一個網(wǎng)關(guān),居然在代理非Get請求時出現(xiàn)異常,必然是不能容忍的。
經(jīng)過一番探索,發(fā)現(xiàn)這是Springboot不同版本的原因?qū)е?,在Springboot2.0.5之前,不存在該問題,之后就有這種問題了。需要加以注意,解決方案會在下一篇寫。
2 添加的參數(shù)value值必須合法(不能含有空格)

上面已經(jīng)知道了,addRequestParameter對應(yīng)的后端請求是Get型,那么明顯添加的parameter只能是Get請求支持的,能在瀏覽器地址欄直接敲上去合法的。
這里,我將value的值變成帶空格的,然后去訪問后端的服務(wù)。

然后會發(fā)現(xiàn)控制臺報錯,Invalid URI query。這是因?yàn)間et請求的value值不能含有非法字符.

同理

像這樣的,后臺接收的是

如果是這樣的參數(shù)

后臺這樣

結(jié)果是:

這樣就可以添加多個parameter了。
同時添加header和parameter
結(jié)束了addRequestParameter的說明,我們可以來看看,假如某個path,既想addHeader,又想addParameter,而系統(tǒng)的這兩個方法,都是一個path只能搭配一個add的filter,即便寫了兩個也不生效,如


結(jié)果就只有header被打印了

那么就是想同時添加header和parameter該怎么辦呢。
貌似通過java代碼是無法實(shí)現(xiàn)了,好在可以通過yml配置來實(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里,添加多個filter了,注意不要寫錯了filter的名字。
可以看到結(jié)果


發(fā)現(xiàn)header和param都傳過來了。
以上這篇解決spring cloud gateway 獲取body內(nèi)容并修改的問題就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
- 詳解Spring Cloud Gateway 限流操作
- 基于Nacos實(shí)現(xiàn)Spring Cloud Gateway實(shí)現(xiàn)動態(tài)路由的方法
- springcloud gateway如何實(shí)現(xiàn)路由和負(fù)載均衡
- Spring Cloud GateWay 路由轉(zhuǎn)發(fā)規(guī)則介紹詳解
- Spring?cloud網(wǎng)關(guān)gateway進(jìn)行websocket路由轉(zhuǎn)發(fā)規(guī)則配置過程
- Spring Cloud Gateway實(shí)現(xiàn)流量染色的示例代碼
相關(guān)文章
SpringMVC實(shí)現(xiàn)通過郵件找回密碼功能
本篇文章主要介紹的是SpringMVC實(shí)現(xiàn)通過郵件找回密碼功能,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧。2016-10-10
springboot實(shí)現(xiàn)分段上傳功能的示例代碼
這篇文章主要介紹了springboot實(shí)現(xiàn)分段上傳,包括文件上傳下載,斷點(diǎn)續(xù)傳,增量上傳功能,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
springboot自動掃描添加的BeanDefinition源碼實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于springboot自動掃描添加的BeanDefinition的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-02-02
springboot 啟動如何修改application.properties的參數(shù)
這篇文章主要介紹了springboot 啟動如何修改application.properties的參數(shù)方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Java的synchronized關(guān)鍵字深入解析
這篇文章主要介紹了Java的synchronized關(guān)鍵字深入解析,在并發(fā)編程中,多線程同時并發(fā)訪問的資源叫做臨界資源,當(dāng)多個線程同時訪問對象并要求操作相同資源時,分割了原子操作就有可能出現(xiàn)數(shù)據(jù)的不一致或數(shù)據(jù)不完整的情況,需要的朋友可以參考下2023-12-12

