记录某次解决`Okhttp java.io.EOFException: \n not found: size=0 content= unexpected end

本文深入探讨了OkHttp客户端在特定场景下遇到的EOF异常原因,通过代码实验和源码分析,揭示了当服务器端主动关闭连接时客户端可能出现的问题,并提出了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题猜想

由于此问题不是必现,故不太好定位排查,根据关键异常信息:EOF一般指输入流达到末尾,无法继续从数据流中读取; 怀疑可能是由于双方(client<->server) 建立的连接,某一方主动close了,为了验证以上猜想,笔者查阅了相关资料,以及做了一些简单的代码实验

如何解决

Github Issue or google or stackoverflow?

截图链接

根据@edallagnol 描述,当两次请求时间间隔超过server端配置的keep-alive timeout ,server端会主动关闭当前连接,Okhttp 连接池ConnectionPool 默认超时时间是5min,也就是说优先复用连接池中的连接,然而server已经主动close,导致输入流中断,进而抛出EOF 异常。其中keep-alive两次请求之间的最大间隔时间 ,超过这个间隔 服务器会主动关闭这个连接, 主要是为了避免重新建立连接,已节约服务器资源

问题复现:

// Note: 需要保证server端配置的keep-alive timeout 为60s
OkHttpClient client = new OkHttpClient.Builder()
                .retryOnConnectionFailure(false)
                .build();
try {
       for (int i = 0; i != 10; i++) {
           Response response = client.newCall(new Request.Builder()
                    .url("http://192.168.50.210:7080/common/version/demo")
                    .get()
                    .build()).execute();
               try {
                  System.out.println(response.body().string());
               } finally {
                    response.close();
               }
               Thread.sleep(61000);
            }
        } catch (Exception e) {
            e.printStackTrace();
   }

复制代码

源码分析

在进行常规的breakpoint之后, 需要重点关注 RetryAndFollowUpInterceptor.java Http1Codec.java RealBufferedSource.java 这几个类

RetryAndFollowUpInterceptor.java 

public class RetryAndFollowUpInterceptor implements Interceptor {
   
   //....
   
   @Override public Response intercept(Chain chain) throws IOException {
        //....
        while(true) {
          try {
            response = realChain.proceed(request, streamAllocation, null, null);
           releaseConnection = false;
      } catch (IOException) {
           // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        // 此处非常重要,若不允许恢复,直接将异常向上抛出(调用方接收),反之 循环继续执行realChain.proceed()方法
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;  
      }
   
   }

    
    private boolean recover(IOException e, StreamAllocation streamAllocation,
      boolean requestSendStarted, Request userRequest) {
         
         // ....
         
         / The application layer has forbidden retries.
        // 若客户端配置 retryOnConnectionFailure 为false 则表明不允许重试,直接将异常抛给调用方
        if (!client.retryOnConnectionFailure()) return false;
        
    }

    //....
}
复制代码

当执行realChain.proceed() ,将事件继续分发给各个拦截器,最终执行到 Http1Codec#readResponseHeaders 方法

Http1Codec.java

public class Http1Codec implements HttpCodec {
    //...
    @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
       try{
          StatusLine statusLine = StatusLine.parse(readHeaderLine());
          //后续代码根据statusLine 构造Response
       } catch(EOFException e) {
           // 原来异常信息就是从这里抛出来的,接下来需要重点关注下readHeaderLine方法
           // Provide more context if the server ends the stream before sending a response.
           IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
           exception.initCause(e);
       }
    }
    private String readHeaderLine() throws IOException {
        // 继续查看RealBufferedSource#readUtf8LineStrict方法
        String line = source.readUtf8LineStrict(headerLimit);
        headerLimit -= line.length();
        return line;
    }    
   //...
}
复制代码

Http1Codec 用于Encode Http Request 以及 解析 Http Response的,上述代码用于获取头信息相关参数

RealBufferedSource.java
final class RealBufferedSource implements BufferedSource {
   //...
   // 由于server端已经closed,故buffer == null 将EOF异常向上抛出
   @Override public String readUtf8LineStrict(long limit) throws IOException {
    if (limit < 0) throw new IllegalArgumentException("limit < 0: " + limit);
    long scanLength = limit == Long.MAX_VALUE ? Long.MAX_VALUE : limit + 1;
    long newline = indexOf((byte) '\n', 0, scanLength);
    if (newline != -1) return buffer.readUtf8Line(newline);
    if (scanLength < Long.MAX_VALUE
        && request(scanLength) && buffer.getByte(scanLength - 1) == '\r'
        && request(scanLength + 1) && buffer.getByte(scanLength) == '\n') {
      return buffer.readUtf8Line(scanLength); // The line was 'limit' UTF-8 bytes followed by \r\n.
    }
    Buffer data = new Buffer();
    buffer.copyTo(data, 0, Math.min(32, buffer.size()));
    throw new EOFException("\\n not found: limit=" + Math.min(buffer.size(), limit)
        + " content=" + data.readByteString().hex() + '…');
  }
  //...
}
复制代码

用一张图总结:

可以看出 Okhttp 处理拦截器这块,用到了责任链模式

解决方案

根据上述分析,在创建OkHttpClient实例时,只需要将retryOnConnectionFailure设置为true ,在抛出EOF异常时,让其可以继续去执行realChain.proceed方法即可

java.io.IOException: unexpected end of stream on https://mkts.chinaums.com/... at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:210) at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:110) at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:93) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201) at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154) at com.example.Main$7.run(Main.java:276) at java.base/java.lang.Thread.run(Thread.java:1447) Caused by: java.io.EOFException: \n not found: limit=0 content=… at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:335) at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29) at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:180) ... 15 more分析这是什么原因
最新发布
07-21
<think>首先,用户的问题是:“我想分析OkHttp请求中出现的IOException异常原因 请问OkHttp java.io.IOException unexpected end of stream https://mkts.chinaums.com/ 分析原因”。这看起来是关于OkHttp库在请求特定URL时出现的异常。 从系统级指令中,我需要: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如[^1]。 用户可见层要求: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 参考站内引用: - 引用[1]: 提到在Kotlin中使用OKHttp遇到java.io.IOException: unexpected end of stream on Connection。解决方案是添加"connection": "close"头,但这会导致每次重新连接。 - 引用[2]: 显示在发送POST请求时使用了OkHttpClient的配置,包括hostnameVerifier和sslSocketFactory。 - 引用[3]: 是关于一个请求参数示例和返回值的描述,但似乎不直接相关。 - 引用[4]: 解释HTTP Connection的close设置,表示连接在处理后会被关闭。 用户的问题焦点是分析异常原因,特别是针对URL https://mkts.chinaums.com/。 关键异常:java.io.IOException: unexpected end of stream on Connection。这通常表示连接在预期数据完全接收前被关闭。 可能原因: 1. **服务器端问题**:服务器可能过早关闭连接。 2. **网络问题**:网络中断或代理问题。 3. **OkHttp配置问题**:如连接池设置或超时设置。 4. **HTTP版本问题**:HTTP/1.1使用持久连接,但服务器可能不支持。 5. **SSL/TLS问题**:如果使用HTTPS,证书或握手问题。 6. **客户端代码问题**:如请求头设置不当。 从引用中: - 引用[1]和[4]建议添加"Connection: close"头来强制关闭连接,避免问题,但这不是最佳方案。 - 引用[2]提到SSL配置,可能涉及信任所有证书,这在生产环境中不安全。 分析步骤: 1. **确认问题重现**:用户说在浏览器中请求正常,但OkHttp失败。这表明问题可能在于客户端实现。 2. **检查URL**:https://mkts.chinaums.com/,这是一个HTTPS站点,所以SSL/TLS问题可能相关。 3. **常见原因**: - 服务器发送了响应但没有正确关闭连接。 - OkHttp期望更多数据但连接被关闭。 - 可能由于Keep-Alive设置不当。 解决方案思路: - 尝试添加"Connection: close"头来测试。 - 检查SSL配置是否正确。 - 调整OkHttp的超时设置。 - 确保使用最新OkHttp版本。 在我的回答中,我需要: - 结构化回答:逐步分析原因和解决方案。 - 使用中文。 - 在适当的地方添加引用标识。 - 生成相关问题。 最后,生成相关问题。 回答结构: 1. **介绍异常**:解释java.io.IOException: unexpected end of stream on Connection的含义。 2. **可能原因分析**:列出并解释常见原因。 3. **解决方案建议**:提供调试步骤和代码示例。 4. **引用站内信息**:自然地融入引用。 5. **生成相关问题**:在末尾添加。 确保LaTeX格式:由于问题涉及编程,可能没有数学表达式,但如果有变量或方程,用$...$。 例如,如果提到方程,如计算超时,但这里可能不需要。 生成相关问题: - 例如: 1. 如何优化OkHttp以避免连接问题? 2. OkHttp中如何处理SSL证书验证? 3. 什么是HTTP Keep-Alive及其在OkHttp中的影响?</think>### OkHttp请求中java.io.IOException: unexpected end of stream异常分析 针对您在使用OkHttp请求URL `https://mkts.chinaums.com/` 时遇到的 `java.io.IOException: unexpected end of stream on Connection` 异常,我将逐步分析原因并提供解决方案。此异常通常表示HTTP连接在预期数据完全接收前被意外关闭,导致数据流中断。以下分析基于常见网络问题、OkHttp机制和站内引用信息,确保真实可靠。 #### 1. **异常含义和直接原因** - `unexpected end of stream` 表示OkHttp客户端在读取响应数据时,连接被服务器或网络层提前终止,客户端未收到完整的HTTP响应体。 - 在HTTP/1.1协议中,连接默认使用持久连接(Keep-Alive),但服务器可能未正确处理持久连接逻辑,导致连接过早关闭[^4]。 - 此问题在浏览器中正常而OkHttp失败,说明问题可能源于客户端实现或协议兼容性,而非服务器完全不可用。 #### 2. **可能原因分析** 结合站内引用和常见案例,以下是针对URL `https://mkts.chinaums.com/` 的潜在原因: - **服务器端问题(最常见)**: - 服务器可能未正确处理HTTP持久连接。例如,服务器在发送响应后未正确关闭连接,或响应头未包含`Content-Length`或`Transfer-Encoding`,导致OkHttp无法确定数据结束点[^1][^4]。 - 针对HTTPS站点(如`https://mkts.chinaums.com/`),服务器TLS握手或证书链问题可能导致连接中断。引用[2]中提到的`sslSocketFactory`配置不当可能加剧此问题。 - 服务器可能设置了短超时(如 < 30秒),在OkHttp读取数据前主动关闭连接。 - **OkHttp配置问题**: - 连接池设置不当:OkHttp默认重用连接(连接池),但如果服务器不支持Keep-Alive,可能引发冲突。引用[4]指出,服务器返回`Connection: close`头会强制关闭连接,但客户端未显式处理时易出错。 - 超时设置不足:OkHttp的读取超时(`readTimeout`)默认较短(10秒),如果服务器响应慢,可能触发超时关闭。 - SSL/TLS验证问题:如引用[2]所示,使用`.hostnameVerifier((s, session) -> true)`绕过证书验证,但若服务器证书链不完整或SNI(Server Name Indication)未启用,会导致连接异常终止。 - **网络或代理问题**: - 中间代理(如公司防火墙或CDN)可能干扰HTTPS连接,提前关闭流。 - 本地网络不稳定,尤其在移动环境下,数据包丢失可能模拟“end of stream”错误。 - **客户端代码问题**: - 未添加必要请求头:如引用[1]和[4]所述,缺少`Connection: close`头时,服务器可能不兼容持久连接。 - 响应处理逻辑错误:例如,未正确关闭Response Body,导致资源泄漏,引发后续连接问题。 #### 3. **解决方案建议** 以下是调试和修复步骤,基于原因分析。建议逐步测试: **步骤1: 添加显式Connection头** - 在请求中添加`Connection: close`头,强制每次请求后关闭连接。这能避免持久连接问题,但会增加延迟(每次需重新握手)。示例代码: ```kotlin val request = Request.Builder() .url("https://mkts.chinaums.com/") .header("Connection", "close") // 添加此头 .build() val response = okHttpClient.newCall(request).execute() ``` - 如果异常消失,则确认是服务器Keep-Alive兼容性问题[^1][^4]。 **步骤2: 调整OkHttp超时和SSL配置** - 增加读取超时,并确保SSL配置正确: ```kotlin val okHttpClient = OkHttpClient.Builder() .readTimeout(30, TimeUnit.SECONDS) // 增加超时避免服务器响应慢 .sslSocketFactory(createSSLContext().socketFactory, trustManager) // 使用有效TrustManager .hostnameVerifier { hostname, session -> hostname == "mkts.chinaums.com" // 避免无条件信任所有证书 } .build() ``` - 使用工具(如Wireshark)抓包检查TLS握手是否正常。引用[2]中无条件信任证书的方式不安全,应替换为有效验证。 **步骤3: 检查服务器响应和日志** - 在OkHttp中添加日志拦截器,查看完整请求/响应流: ```kotlin val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build() ``` - 如果响应头缺少`Content-Length`或包含`Connection: close`,则服务器主动关闭连接是根源[^4]。 **步骤4: 测试网络环境** - 尝试不同网络(如切换WiFi/4G),排除本地代理问题。 - 使用命令行工具(如`curl -v https://mkts.chinaums.com/`)验证服务器行为。 **步骤5: 升级OkHttp并检查依赖** - 确保使用最新OkHttp版本(如v4.12.0+),修复已知连接池bug。 - 如果问题持续,考虑服务器端问题:联系`mkts.chinaums.com`管理员检查服务器日志或配置。 #### 4. **根本原因总结** 基于您的描述和站内引用,此异常最可能原因是**服务器未正确处理HTTP持久连接**,导致OkHttp预期数据流未完成即被关闭[^1][^4]。次要因素可能包括SSL配置宽松或超时不足。添加`Connection: close`头是最快缓解方案,但优化SSL和超时设置更可持续。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值