問題發(fā)現(xiàn)
在一次與前端聯(lián)調(diào)中,發(fā)現(xiàn)一個(gè)新增接口一直報(bào)錯(cuò):
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
at [Source: (PushbackInputStream); line: 1, column: 2]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:285)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:243)
....... 省略.....
Caused by: com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens
at [Source: (PushbackInputStream); line: 1, column: 2]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2337)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:710)
at com.fasterxml.jackson.core.base.ParserMinimalBase._throwInvalidSpace(ParserMinimalBase.java:688)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._skipWSOrEnd(UTF8StreamJsonParser.java:3012)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:724)
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4684)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4586)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:274)
開始的我以為請求參數(shù)序列化異常,或者有特殊字符,通過格式后檢查,并不是!
問題排查
通過對請求參數(shù)(參數(shù)格式為:application/json)的格式化進(jìn)行檢查,發(fā)現(xiàn)并沒有特殊字符,最主要的是:我本地請求接口也報(bào)了這個(gè)錯(cuò) (! - !)!后面一次偶然的嘗試,我發(fā)現(xiàn)將請求的json列表參數(shù)去掉幾個(gè),就....成功了~,初步段定:參數(shù)過大!
這里需要提一點(diǎn):我們的項(xiàng)目是使用的微服務(wù)架構(gòu),采取的DDD模式開發(fā),所有的前端請求必須先通過路由到聚合BFF服務(wù),然后才是將需要的參數(shù)請求到后端核心CORE服務(wù)。本次的問題:由前端觸發(fā),聚合層轉(zhuǎn)發(fā),核心層報(bào)錯(cuò)。
問題解決
通過之前的分析,知道了Illegal character ((CTRL-CHAR, code 31))的問題是因?yàn)榫酆蠈油诵膶舆M(jìn)行過feign調(diào)用時(shí)參數(shù)過大,經(jīng)過網(wǎng)絡(luò)傳輸?shù)竭_(dá)后端服務(wù)時(shí)數(shù)據(jù)丟失導(dǎo)致解析失敗。所以接下來調(diào)整聚合層的feign的壓縮配置:
# 請求參數(shù)壓縮配置
feign.compression.request.mime-types = text/xml,application/xml,application/json
feign.compression.request.min-request-size = 2048
feign.compression.request.enabled = true
feign.compression.response.enabled = true
我們項(xiàng)目使用的openfeign,網(wǎng)上有他的官方配置文檔:https://docs.spring.io/spring-cloud-openfeign/docs/3.0.3/reference/html/#feign-requestresponse-compression ,如果使用的其他feignClient,可以參考對應(yīng)的配置文檔。
到這里,簡單的調(diào)整已經(jīng)結(jié)束,按理說應(yīng)該是OK的,但是并沒有。。調(diào)用接口仍然報(bào)相同的錯(cuò)?。?!
隨后,進(jìn)入了茫茫的找解決方案的步驟,這里記錄一下我做過的一些嘗試:
調(diào)整feign的默認(rèn)請求客戶端為OkHTTP(原生的有bug) -- 未生效
編寫feign請求攔截器透傳所有客戶端請求(如:"acceptEncoding: gzip") -- 未生效
編寫gzip工具類使得請求前壓縮,后端接收解壓縮(因其他安排未實(shí)現(xiàn))
經(jīng)過很多嘗試,以及網(wǎng)上可靠方案,還是一樣報(bào)錯(cuò)...此時(shí)有點(diǎn)懷疑自己了。。。再之后我突然想到:參數(shù)配置沒問題,參數(shù)配置值是不是有問題?feign請求參數(shù)過小導(dǎo)致沒有壓縮?
之后調(diào)整feign壓縮的最小閾值:feign.compression.request.min-request-size = 4096 然后就成功?。?!真是可喜可賀@@
問題復(fù)盤
說實(shí)話,這個(gè)問題解決起來很簡單,相信很多人應(yīng)該在配置好請求參數(shù)壓縮配置,應(yīng)該就happy ending了;我這因?yàn)閷?shù)min-request-size太信任了所以才有了后續(xù)的探索過程。
回到正題,對于feign調(diào)用,可以有很多的其他優(yōu)化參數(shù),這里只針對參數(shù)壓縮做了一些分享,有興趣的可以嘗試在網(wǎng)關(guān)層嘗試做手工壓縮的方式。這里貼一下完整的配置:
# 啟用okhttp
feign.okhttp.enabled = true
feign.httpclient.enabled = false
feign.httpclient.max-connections-per-route = 100
feign.httpclient.max-connections = 1000
# 請求參數(shù)壓縮配置
feign.compression.request.enabled = true
feign.compression.response.enabled = true
#配置請求參數(shù)壓縮閾值(開發(fā)估算最大值7k,在此配置為8k)
feign.compression.request.min-request-size = 8192
feign.compression.request.mime-types = text/xml,application/xml,application/json