? ? ? ?基于公司的業(yè)務(wù)需求,在SpringCloud Gateway組件的基礎(chǔ)上,寫了一個(gè)轉(zhuǎn)發(fā)服務(wù),測(cè)試開發(fā)階段運(yùn)行正常,并實(shí)現(xiàn)初步使用。但三個(gè)月后,PostMan請(qǐng)求接口,返回異常,經(jīng)排查,從日志中獲取到轉(zhuǎn)發(fā)響應(yīng)的結(jié)果為亂碼:
? ? ??
跟蹤日志:
轉(zhuǎn)發(fā)到目標(biāo)接口,響應(yīng)結(jié)果已亂碼。一般排查的思路是,查看請(qǐng)求方和響應(yīng)方的編碼格式是否一致,打印請(qǐng)求方的編碼格式為UTF-8,響應(yīng)服務(wù)的編碼格式也是UTF-8。
以上說明編碼格式?jīng)]有問題。上網(wǎng)去找“gateway響應(yīng)結(jié)果亂碼”的相關(guān)文章,大多數(shù)會(huì)提供解決方案:
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 釋放掉內(nèi)存
DataBufferUtils.release(join);
String str = new String(content, Charset.forName("UTF-8"));
originalResponse.getHeaders().setContentLength(str.getBytes().length);
System.out.println(str);
return bufferFactory.wrap(str.getBytes());
這段關(guān)鍵代碼,在我的響應(yīng)結(jié)果包裝過濾器是有的,如下:
/**
* 獲取到解碼方的response,驗(yàn)簽--->重新封裝--->加簽
* 通過 DataBufferFactory 解決響應(yīng)體分段傳輸問題。
*/
private ServerHttpResponseDecorator verifyRePackageSignatureResponse(ServerWebExchange exchange, String jmf_decode_url, String route_privateKey, String jmf_publicKey) {
ServerHttpResponse response = exchange.getResponse();
log.debug("R:給響應(yīng)結(jié)果response設(shè)置編碼格式----START----");
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
log.debug("R:給響應(yīng)結(jié)果response設(shè)置編碼格式-----END-----");
DataBufferFactory bufferFactory = response.bufferFactory();
return new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
// 獲取響應(yīng)類型,如果是 json 就打印
String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
log.debug("響應(yīng)類型為originalResponseContentType:{}",originalResponseContentType);
if (RequestResponseUtil.isJson(originalResponseContentType)) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// 合并多個(gè)流集合,解決返回體分段傳輸
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 釋放掉內(nèi)存
DataBufferUtils.release(join);
// 正常返回的數(shù)據(jù)
String rootData = new String(content, StandardCharsets.UTF_8);
log.debug("R:正常返回的數(shù)據(jù)rootData為:{}", rootData);
//使用枚舉 + 工廠 + 策略模式(第二版)
String newQqtBodyJson = null;
try {
newQqtBodyJson = DecoderSignStrategyContext.zwmVerifySignResponse(rootData, jmf_decode_url, route_privateKey, jmf_publicKey);
log.debug("R:【碼路由服務(wù)】經(jīng)具體策略處理,得到結(jié)果newQqtBodyJson為:{}", newQqtBodyJson);
} catch (Exception e) {
log.error("R:【碼路由服務(wù)】解碼方返回結(jié)果驗(yàn)簽異常:{}", e);
throw new VerifySignException(StaticVar.FAIL_10020015, "R:【碼路由服務(wù)】解碼方返回結(jié)果驗(yàn)簽異常");
}
byte[] respData = newQqtBodyJson.getBytes();
//byte[] respData = newQqtBodyJson.getBytes(StandardCharsets.UTF_8);
byte[] uppedContent = new String(respData, Charset.forName("UTF-8")).getBytes();
return bufferFactory.wrap(uppedContent);
}));
} else {
log.error("響應(yīng)結(jié)果異常");
throw new ProcessHandleException(StaticVar.FAIL_10020015, "R:【碼路由服務(wù)】解碼方返回結(jié)果異常,非法JSON");
}
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
}
因此不是代碼的問題。又找到了一篇文章,解決了PostMan請(qǐng)求的問題。
https://bbs.csdn.net/topics/399102026/close文章來源:http://www.zghlxwxcb.cn/news/detail-758743.html
如上所述,在PostMan請(qǐng)求的headers中去掉Accept-Encoding,請(qǐng)求成功:
? ? ? ?至此,PostMan請(qǐng)求亂碼的問題已解決。但事情似乎沒有那么簡(jiǎn)單,接下來,使用手機(jī)模擬掃碼,請(qǐng)求碼路由服務(wù),又出現(xiàn)了亂碼,掃碼結(jié)果頁面,返回結(jié)果:
?查看日志,亂碼如下:
? ? ? 排查問題進(jìn)入瓶頸期,有些煩躁了,必須冷靜下來,重新捋一下代碼,同時(shí)也在想,既然和PostMan請(qǐng)求存在同樣的問題,是不是在請(qǐng)求頭中默認(rèn)會(huì)有一個(gè)Accept-Encoding屬性,從而導(dǎo)致了亂碼,根據(jù)這個(gè)思路,在自定義的請(qǐng)求轉(zhuǎn)發(fā)過濾器中,發(fā)現(xiàn)了以下代碼:
由于在請(qǐng)求轉(zhuǎn)發(fā)的邏輯中,重新構(gòu)建了一個(gè)request請(qǐng)求的同時(shí),重新創(chuàng)建了一個(gè)請(qǐng)求頭headers,它在重新構(gòu)建的時(shí)候默認(rèn)會(huì)有一個(gè)accept-encoding屬性,看到這一行代碼時(shí),正印證了以上我的猜想,問題已經(jīng)找到了,將其置為空字符串即可(注意:不能使其為null,也不能remove這個(gè)屬性,會(huì)報(bào)錯(cuò))。
? 將headers的accept-encoding屬性置為空字符串,并添加注釋如下:
// 定義新的消息頭
HttpHeaders headers = new HttpHeaders();
//System.out.println("headers = " + headers); //查看重新定義消息頭的內(nèi)容
headers.putAll(exchange.getRequest().getHeaders());
// 由于修改了傳遞參數(shù),需要重新設(shè)置CONTENT_LENGTH,長(zhǎng)度是字節(jié)長(zhǎng)度,不是字符串長(zhǎng)度
int length = bodyStr.getBytes().length;
log.debug("bodyStr長(zhǎng)度為:{}",length);
headers.remove(HttpHeaders.CONTENT_LENGTH);
// 設(shè)置CONTENT_TYPE
if (StringUtils.isNotBlank(contentType)) {
headers.set(HttpHeaders.CONTENT_TYPE, contentType);
}
String jmf_decode_url = request.getURI().toString();
String qqtBodyJsonStr = JSONUtils.toString(bizPackage);
//request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(bodyStr.length())); //報(bào)錯(cuò)JSON parse error JsonEOFException,長(zhǎng)度必須為字節(jié)的長(zhǎng)度
request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(length));//成功,解決JSON parse error JsonEOFException
/**
* 亂碼現(xiàn)象:請(qǐng)求響應(yīng)結(jié)果亂碼
* 解決過程分為PostMan請(qǐng)求和手機(jī)端App掃碼請(qǐng)求:
* (1)、PostMan請(qǐng)求響應(yīng)亂碼解決:
* PostMan請(qǐng)求的headers中默認(rèn)會(huì)有"Accept-Encoding"屬性,值為"gzip, deflate, br",導(dǎo)致響應(yīng)結(jié)果亂碼
* 去掉Accept-Encoding后請(qǐng)求正常。
*
* (2)、手機(jī)端App掃碼請(qǐng)求亂碼解決:
* 在HttpRequestSignForwardGatewayFilter中定義新的消息頭,headers中默認(rèn)會(huì)有"accept-encoding"屬性,
* 值為"gzip, deflate, br", 添加代碼"request.mutate().header("accept-encoding",""); "解決亂碼
*
* 注意: 創(chuàng)建headers對(duì)象默認(rèn)會(huì)生成“accept-encoding=‘gzip, deflate, br‘ ”屬性,此處必須將accept-encoding
* 置為空字符串(置為null會(huì)報(bào)錯(cuò)),否則使用默認(rèn)值會(huì)導(dǎo)致響應(yīng)結(jié)果亂碼。
* 說明: 測(cè)試開發(fā)階段未發(fā)生此問題,第三方檢測(cè)時(shí)演示出現(xiàn)此問題,這個(gè)可能是gateway內(nèi)部的問題,尚未可知。
*
* 經(jīng)測(cè)試,這種方式已解決了:手機(jī)端APP掃碼和PostMan響應(yīng)結(jié)果亂碼的問題(PostMan請(qǐng)求時(shí)可以不用刻意去掉Accept-Encoding,
* 也可請(qǐng)求成功)
*
*/
request.mutate().header("accept-encoding","");
重啟后,部署后問題得到解決。這個(gè)問題在測(cè)試開發(fā)階段沒有暴露出來,按理說應(yīng)該早暴露了,但現(xiàn)實(shí)情況就是這么詭異,這可能是Gateway內(nèi)部的bug,尚未可知。
? ? ?希望此文對(duì)遇到同樣問題的小伙伴有所幫助和啟發(fā),望了解gateway內(nèi)部原理機(jī)制的大神,參與討論。
? ? 亂碼問題已排查并處理結(jié)束,完結(jié),撒花!
參考文章:
RestTemplate請(qǐng)求頭accept-encoding導(dǎo)致亂碼_resttemplate 亂碼_AE86Jag的博客-CSDN博客
https://bbs.csdn.net/topics/399102026/close
在此感謝文章作者!文章來源地址http://www.zghlxwxcb.cn/news/detail-758743.html
到了這里,關(guān)于記一次線上bug排查-----SpringCloud Gateway組件 請(qǐng)求頭accept-encoding導(dǎo)致響應(yīng)結(jié)果亂碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!