OkHttp解析无CRLF分隔的响应超时问题解决方案咨询
问题背景
Android端基于OkHttp实现HTTP 1.1请求能力,绝大多数服务对接正常,仅对接某台未遵循HTTP规范的服务器时固定出现请求超时。
故障详情
- 根因定位:目标服务器未按HTTP 1.1规范使用CRLF换行符分隔响应头与消息体,OkHttp持续将消息体内容识别为响应头解析,最终触发超时。
- 响应特征:该类异常响应的消息体固定以ASCII字符
D3开头。 - 修复目标:尽可能复用OkHttp现有Call组件能力,避免重复实现网络请求逻辑,让OkHttp识别到以
D3开头的行时直接判定为响应体起始位置,终止响应头解析。
已尝试方案的问题
- 曾尝试通过Interceptor直接操作Socket发送请求,手动读取Socket流,读到CRLF或
D3字符时就停止状态行、响应头收集。但OkHttp要求Interceptor必须调用Chain.proceed()才能返回响应,调用该方法会重复发起请求,方案不可行。 - 备选方案是自定义Socket工厂,手动获取Socket实现全链路请求发送、响应解析,但会完全重复OkHttp已实现的成熟逻辑,维护成本过高。
可行解决方案
方案1:自定义ExchangeCodec(最小侵入,复用全量OkHttp能力)
OkHttp的响应解析逻辑封装在对应协议的HttpExchangeCodec实现中,不需要在Interceptor层拦截Socket,直接扩展解析逻辑即可:
- 自定义一个专属协议常量,比如
HTTP_1_1_SPECIAL - 实现自定义的
ExchangeCodec.Factory,替换默认的HTTP 1.1编解码工厂,重写Http1ExchangeCodec中的readResponseHeaders()方法:- 保留原有逐行读取响应头的逻辑
- 新增判断:每次读取新的一行前,先检查首字节是否为
0xD3,如果是,直接将该字节回退到读缓冲区,立刻终止响应头解析,后续内容交给响应体读取逻辑处理
- 初始化OkHttpClient时,将自定义协议加入支持的协议列表,替换默认的编解码工厂即可
该方案完全复用OkHttp的连接池、请求调度、缓存、超时控制、重试等所有已有能力,不会触发重复请求,因为解析逻辑是在请求发送完成、读取响应的阶段执行,不会二次触发请求发送流程。
方案2:Socket层透明流包装(版本兼容性更强)
如果不想依赖OkHttp内部编解码相关的API,可以在Socket工厂层做透明的流处理,对上层OkHttp逻辑完全无侵入:
- 自定义明文
SocketFactory和HTTPS场景的SSLSocketFactory,在Socket创建完成、返回给OkHttp之前,包装Socket的InputStream - 实现自定义的
FilterInputStream,在OkHttp首次读取响应数据时做预读处理:- 逐字节预读数据,缓存状态行和响应头内容,当读到连续CRLF分隔符,或者读到字节
0xD3时停止预读 - 如果是读到
0xD3触发的预读终止,手动在预读缓存末尾补一个CRLF空行,再把0xD3和后续读到的内容拼接到缓存中 - 后续OkHttp读取流时,优先返回预读缓存中的内容,缓存读完后再直接读取Socket原始流
- 逐字节预读数据,缓存状态行和响应头内容,当读到连续CRLF分隔符,或者读到字节
- 该方案下OkHttp会认为自己读到了完全符合HTTP规范的响应,不会出现把消息体识别为响应头的问题
这个方案不依赖OkHttp的内部实现API,即使后续OkHttp升级版本,只要Socket输入流的交互逻辑不变,代码就可以正常运行,兼容性更好。
注意事项
不要在应用拦截器(Application Interceptor)中直接操作Socket,应用拦截器执行时请求还未发送,必须调用proceed()才能走到网络请求链路,必然会出现重复发请求的问题。网络拦截器(Network Interceptor)虽然可以拿到连接对象,但直接操作底层流很容易破坏OkHttp内部的流读写状态,稳定性不如上述两个方案。
内容的提问来源于stack exchange,提问作者cj-
相关产品推荐
相关产品推荐

