一次现网问题定位-com.google.gson.JsonSyntaxExceptio on: java.io.EOFException: End of input

发布时间:2026/9/1 16:22:05
一次现网问题定位-com.google.gson.JsonSyntaxExceptio on: java.io.EOFException: End of input 背景收到隔壁组请求支援定位调用接口报错报错日志如下2026-08-28 16:07:22.462 [http-nio-8003-exec-14] ERROR [UnknownExceptionHandler:28]com.google.gson.JsonSyntaxException on: java.io.EOFException: End of input at line 1 column 1479 path $.notification[5].lastUpdateDateat com.google.gson.Gson.fromJson(Gson.java:1370)at com.google.gson.Gson.fromJson(Gson.java:1262)at com.google.gson.Gson.fromJson(Gson.java:1171)at com.google.gson.Gson.fromJson(Gson.java:1107)…从报错日志初步来看像是json不完整解析过程中碰到结束符了。他们提供了代码从代码逻辑看非常简单他们先通过RestTemplate转成String然后在手工序列化。报错就发生在序列化那一行。StringresponserestTemplate.doGet(url,headers,String.class);TkbHotResponsetkbHotResponseGSON.fromJson(response,TkbHotResponse.class);系统组网如下日志告警的系统ALB上游系统定位思路先定界-是不是json不完整从报错来看像是json不完整首先得把这个方向确认了才好继续往下定位因此通过堆栈稍微看了下Gson的源码确认后着手定位为什么输入的字符串不是完整的json。初步想法是不是上游因为啥原因导致传输的json就不完整。隔壁组提供了使用postman调用的响应里面内容是完整的。该场景排除。为什么restTemplate返回的String不是完整的json通过上一步的分析上游传的数据是没问题的那初步认为是不是偶发网络提前中断了导致数据没传输完如果是上述场景的话那按说RestTemplate会抛异常吧看了前后日志并没有异常难道RestTemplate接受到200的响应如果读取响应过程中连接中断不会有异常遂初步翻翻RestTemplate的源码看看反序列化网络字节流转String的过程是怎么样的。protectedStringreadInternal(Class?extendsStringclazz,HttpInputMessageinputMessage)throwsIOException{CharsetcharsetgetContentTypeCharset(inputMessage.getHeaders().getContentType());longlengthinputMessage.getHeaders().getContentLength();byte[]bytes(length0lengthInteger.MAX_VALUE?inputMessage.getBody().readNBytes((int)length):inputMessage.getBody().readAllBytes());returnnewString(bytes,charset);}通过阅读StringHttpMessageConverter代码发现以下可疑点如果传来Content-Length头会根据这个头的长度来读取响应。我感觉像是传了错误的Content-Length头因为如果是网络问题报错概率应该很小但是我看日志还挺多的。为此我找他们用postman调用了一下看看是不是有传这个头。结果还真有从他们上面截图可以看到除了传Content-Length头还传了Content-Encoding:gzip。我感觉应该是启用了压缩然后Content-Length头传的是压缩后的值。如果像猜想一样的话那这个问题应该是必现找隔壁项目组求证后得到了肯定的答复。为此坚定了这个定位方向。接下来就是找上游确认Content-Length是怎么写进去的压缩到底是谁做的。和上游定位Content-Length是怎么传的里面的值怎么来的先是找他们确认Content-Length头是他们传的还是ALB传的通过访问了单边地址防止ALB的干扰发现这个头确实是他们自己传的。然后就联合上游分析他们代码咋写的这头是他们自己代码写的还是tomcat写的通过分析代码发现确实是他们自己写的这个头。publicvoiddoWrite(byte[]content,HttpServletResponsehttpResponse)throwsIOException{httpResponse.setContentType(text/html; charsetUTF-8);httpResponse.addHeader(Content-Length,Integer.toString(content.length));httpResponse.addHeader(Content-Encoding,gzip);StreamUtil.transfer(newBufferedInputStream(newByteArrayInputStream(content)),httpResponse.getOutputStream());}# doWrite中的content参数从pullHtml函数中获取publicbyte[]pullHtml(HttpServletRequestrequest,HttpServletResponseresponse,WebRequestVOwebRequest,XhtmBeanxhtmBean)throwsIOException{returntoGzipByteArray(createHtml(request,response,webRequest,xhtmBean),UTF_8);}从他们代码中得到一下信息他们会自己对数据做压缩压缩完后同时传了Content-Length和gzip的压缩头。所以Content-Length头的值是没问题的代表的是HTTP响应中body的真实字节数。至此问题根因初步明确了RestTemplate拿到的Content-Length的值是压缩后的导致反序列化时字节数读少了。接下来的问题就是为什么测试环境没这个问题而生产有这个问题。生产和测试的差异通过对比生产和测试环境的响应发现响应头有些差异测试环境没有Content-Length头变成了Transfer-Encoding:chunked生产环境测试环境Content-Length: 1542Transfer-Encoding:chunkedContent-Encoding:gzipContent-Encoding:gzip从结果来看像是生产环境ALB完全透传后台的数据而测试环境像是先解压然后重新压缩这样有Transfer-Encoding:chunked。为了确认是ALB的问题我们绕过ALB调用了单边地址直接调用微服务发现测试/生产都一样返回的头与代码逻辑一致包含Content-Length和Content-Encoding:gzip。以上得出结论相同响应经过不同环境的ALB后产生了差异。ALB其实也就是ningx在找他们之前自己也做了功课搜了一下nginx什么情况下会解压后端数据后重新压缩AI告知场景一Nginx 开启了内容替换或修改模块最常见如果你在 Nginx 中配置了对网页内容的实时修改例如修改域名、注入脚本、替换文本Nginx 必须拿到明文内容才能进行字符串匹配和替换。我们并看不到ALB的配置因此找他们求证得到的答复是我们测试环境的网页上面会显示BETA的样式这个是他们拦截了请求并注入了特定的内容而生产没有到此疑问得以消除AI的答案还是很有参考性的。目前还剩最后一个疑问为什么之前没有而现在才有双方都检查一下包上游没有任何修改而隔壁组最近升级了开源软件。HttpClient5由5.5升级到5.6.x了通过调试发现在内容压缩的情况下两者对Content-Length头的处理由差异前者会吞掉它而后者原封不动的给调用方因此RestTemplate拿到了错误的值。此致所有疑问得以解决。小插曲实际上前面我并不知道上游应用自己做了压缩没看到对应代码为此我还看了tomcat的源码看应用层传Content-Encoding:gzip头tomcat会做何处理tomcat何种情况下会进行压缩压缩后会不会覆盖应用层写的Content-Length。毕竟初步看他们代码时没看仔细没发现他们自己有做压缩。既然他们应用层传的值没问题那很有可能是tomcat进行了压缩并覆盖了该值。通过源码确认以下事实tomcat靠全局配置启用压缩应用层传了Content-Encoding:gzip头后tomcat认为应用层已经压缩过了就算tomcat启用了压缩也不会再次做压缩。经此分析后出现了矛盾的地方应用层传了正确的值tomcat又不做压缩那为什么通过访问单边地址看到的Content-Length头的值不正确为此和他们一起仔细调试了代码然后就发现他们自己代码做了压缩该值是压缩后的值。