WSO2 MI 转发 base64 文档时偶发 Could not write JSON stream
从现有堆栈可以看出,MI 在向调用方写回响应时发生报错,底层异常是 Consumer error or stale connection has occurred.。仅凭这些日志,无法判断 base64 内容是否有误,也无法确定哪个环节关闭了连接。应先关联同一次请求的完整日志,再检查首次异常、连接超时情况和故障响应路径。
后端收到请求和 MI 返回响应失败可能同时发生。即使后端已经处理成功,调用方也可能因响应传输失败而收不到结果。
堆栈说明了什么
关键调用链是:
RespondMediator.mediate
-> Axis2Sender.sendBack
-> PassThroughHttpSender.submitResponse
-> JsonStreamFormatter.writeTo
-> JsonUtil.writeJsonStream
-> Pipe$ByteBufferOutputStream.write
-> IOException: Consumer error or stale connection has occurred.
Could not write JSON stream 是上层包装的错误。最深层异常表明传输管道写入失败,没有直接给出 JSON 语法错误或 base64 解码失败的证据。
堆栈中还出现了 MediatorFaultHandler.onFault,说明这次响应写入发生在故障处理路径中。当前的发送失败可能是处理先前异常时产生的后续错误,因此需要继续查找进入故障处理之前的第一条异常。
日志中的 [ENDED] 位于 <respond /> 之前,只能说明执行到了这条日志,不能证明响应已经完整送达调用方。
先把同一次请求的日志串起来
现有日志中,写入失败和稍后的默认故障日志带有相同的 MessageID,可以关联。最后两条 Http code = 201 和业务成功日志没有显示请求标识,不能仅凭时间相近就认定它们属于同一次调用。
建议分别在进入接口、调用后端之前、后端返回之后和故障处理入口记录同一个请求标识。例如:
<log level="custom">
<property name="stage" value="after-backend-call" />
<property name="MessageID" expression="get-property('MessageID')" />
<property name="HTTP_SC" expression="$axis2:HTTP_SC" />
</log>
在故障处理入口、调用 defaultFaultSequence 之前,记录当时的错误属性:
<log level="custom">
<property name="stage" value="fault-entry" />
<property name="MessageID" expression="get-property('MessageID')" />
<property name="ERROR_CODE" expression="get-property('ERROR_CODE')" />
<property name="ERROR_MESSAGE" expression="get-property('ERROR_MESSAGE')" />
</log>
结合 MI、后端和入口代理的日志,确认请求开始时间、后端实际完成时间、首次异常时间,以及调用方何时结束等待。050 和对应文案的设置位置也需要查明。现有材料无法判断它们来自哪个自定义序列,不能直接将其解释为这次传输异常的标准错误码。
检查故障处理是否只有一个响应出口
当前故障流程先执行:
<sequence key="defaultFaultSequence" />
随后构造 JSON 并执行 <respond />。需要查看 defaultFaultSequence 的实际内容,确认它是否已经发送响应、终止处理,或者改写了错误属性及消息体。
如果被调用序列已经负责发送响应,应统一响应出口;如果它只记录日志、设置属性,外层继续构造响应可以有合理用途。仅凭序列名称无法判断是否发生了重复响应。
另一个需要修正的配置是:
<property name="HTTP_SC" expression="get-property('HTTP_SC')" />
这里没有指定 scope="axis2",不能据此认为它设置了日志中读取的 $axis2:HTTP_SC。若要明确返回某个错误状态,应显式设置作用域,并根据实际错误选择状态码。例如,对于未分类的内部处理异常:
<property name="HTTP_SC" value="500" scope="axis2" />
这样可以明确错误响应的状态,但无法恢复已经失效的连接。
用简单的故障响应缩小范围
在测试环境中,可以暂时使用固定 JSON 响应来隔离故障处理逻辑。下面的示例用于排查,接入前需确认原故障序列中是否还有必须保留的处理:
<faultSequence>
<log level="custom">
<property name="stage" value="fault-entry" />
<property name="MessageID" expression="get-property('MessageID')" />
<property name="ERROR_CODE" expression="get-property('ERROR_CODE')" />
<property name="ERROR_MESSAGE" expression="get-property('ERROR_MESSAGE')" />
</log>
<payloadFactory media-type="json">
<format>{"returnCode":"INTERNAL_ERROR","returnMessage":"Request processing failed"}</format>
<args />
</payloadFactory>
<property name="HTTP_SC" value="500" scope="axis2" />
<property name="messageType" value="application/json" scope="axis2" />
<respond />
</faultSequence>
固定响应可以排除动态错误文案引起的 JSON 转义问题,并减少故障路径中的处理步骤。如果仍出现相同的管道写入异常,应进一步检查连接生命周期。对于偶发问题,需要在相近的负载、请求大小和耗时条件下比较,单次成功不能证明问题已经解决。
检查超时和 base64 请求大小的关联
重点核对调用方、入口代理或负载均衡器、MI 和后端之间的超时关系。例如,如果调用方或代理已经结束等待,而后端稍后才返回,MI 写回响应时就可能遇到失效连接。这是需要通过日志验证的可能原因,当前材料尚不能确认。
建议用同一份有效请求做以下对照:
- 分别测试较小文档和容易出错的文档,记录请求体字节数、总耗时及失败情况。
- 若链路中存在入口代理,在受控环境中比较经代理与直接访问 MI 的结果。
- 检查失败是否集中在某个超时边界附近,并与后端处理耗时对应。
base64 会增加传输体积,可能间接影响耗时和资源占用,但这不等于 base64 编码本身有问题。先检查 JSON 是否有效、后端是否按预期接收文档;不要仅凭错误名称修改编码方式。
排查期间,可将完整消息体日志改为请求标识、状态码、耗时和大小等元数据。json-eval($) 会访问消息内容,可能影响流式处理的观察结果;完整文档也不适合持续写入日志。这项调整可以减少干扰,不能直接当作根因修复。
如何判断修复方向
若确认调用方或代理先断开连接,应调整相关超时关系,或缩短后端处理时间;若确认故障序列存在重复响应,应统一响应出口;若发现更早的业务或消息处理异常,应先修复该异常,再复测返回流程。
目前缺少 ApiKeySequence、defaultFaultSequence、stagingPatchService 的配置,以及客户端和代理超时信息,因此还不能给出唯一根因,也没有依据将问题认定为 MI 4.3.0 的特定缺陷。
响应失败时,上传操作仍可能已经在后端完成。重试之前应通过业务标识确认处理结果,并结合接口的幂等设计避免重复上传。
备注:内容仅供参考。
