问题发现
在一次与前端联调中,发现一个新增接口一直报错:
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)
开始的我以为请求参数序列化异常,或者有特殊字符,通过格式后检查,并不是!
问题排查
通过对请求参数(参数格式为:application/json)的格式化进行检查,发现并没有特殊字符,最主要的是:我本地请求接口也报了这个错 (! - !)!后面一次偶然的尝试,我发现将请求的json列表参数去掉几个,就....成功了~,初步段定:参数过大!
这里需要提一点:我们的项目是使用的微服务架构,采取的DDD模式开发,所有的前端请求必须先通过路由到聚合BFF服务,然后才是将需要的参数请求到后端核心CORE服务。本次的问题:由前端触发,聚合层转发,核心层报错。
问题解决
通过之前的分析,知道了Illegal character ((CTRL-CHAR, code 31))
的问题是因为聚合层往核心层进行过feign调用时参数过大,经过网络传输到达后端服务时数据丢失导致解析失败。所以接下来调整聚合层的feign的压缩配置:
# 请求参数压缩配置
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
我们项目使用的openfeign,网上有他的官方配置文档:https://docs.spring.io/spring-cloud-openfeign/docs/3.0.3/reference/html/#feign-requestresponse-compression ,如果使用的其他feignClient,可以参考对应的配置文档。
到这里,简单的调整已经结束,按理说应该是OK的,但是并没有。。调用接口仍然报相同的错!!!
随后,进入了茫茫的找解决方案的步骤,这里记录一下我做过的一些尝试:
调整feign的默认请求客户端为OkHTTP(原生的有bug) -- 未生效
编写feign请求拦截器透传所有客户端请求(如:"acceptEncoding: gzip") -- 未生效
编写gzip工具类使得请求前压缩,后端接收解压缩(因其他安排未实现)
经过很多尝试,以及网上可靠方案,还是一样报错...此时有点怀疑自己了。。。再之后我突然想到:参数配置没问题,参数配置值是不是有问题?feign请求参数过小导致没有压缩?
之后调整feign压缩的最小阈值:feign.compression.request.min-request-size = 4096
然后就成功!!!真是可喜可贺@@
问题复盘
说实话,这个问题解决起来很简单,相信很多人应该在配置好请求参数压缩配置,应该就happy ending了;我这因为对参数min-request-size
太信任了所以才有了后续的探索过程。
回到正题,对于feign调用,可以有很多的其他优化参数,这里只针对参数压缩做了一些分享,有兴趣的可以尝试在网关层尝试做手工压缩的方式。这里贴一下完整的配置:
# 启用okhttp
feign.okhttp.enabled = true
feign.httpclient.enabled = false
feign.httpclient.max-connections-per-route = 100
feign.httpclient.max-connections = 1000
# 请求参数压缩配置
feign.compression.request.enabled = true
feign.compression.response.enabled = true
#配置请求参数压缩阈值(开发估算最大值7k,在此配置为8k)
feign.compression.request.min-request-size = 8192
feign.compression.request.mime-types = text/xml,application/xml,application/json