unexpected end of stream on okhttp3.Address 和 Caused by: java.io.EOFException: \n not found: size=0

今天遇到一個OkHttpClient的坑,報錯信息如下:

[20200320 10:47:10.553] | [ERROR] | [localhost] | [http-nio-28080-exec-22] | [c.u.] | [unexpected end of stream on okhttp3.Address@fe4f10d5] |
`java.io.IOException: unexpected end of stream on okhttp3.Address@fe4f10d5`
	at okhttp3.internal.http.Http1xStream.readResponse(Http1xStream.java:199)
	at okhttp3.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:125)
	at okhttp3.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:775)
	at okhttp3.internal.http.HttpEngine.access$200(HttpEngine.java:86)
	at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:760)
	at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:733)
。。。。。。。。。。。。。。。。。。。。。。。。。。。org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
`Caused by: java.io.EOFException: \n not found: size=0 content=`
	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:215)
	at okhttp3.internal.http.Http1xStream.readResponse(Http1xStream.java:184)
	... 49 common frames omitted

排查如下:

查看了一下源碼:

//RetryAndFollowUpInterceptor.java
   @NotNull
   public Response intercept(@NotNull Chain chain) throws IOException {
           Intrinsics.checkParameterIsNotNull(chain, "chain");
      Request request = chain.request();
      RealInterceptorChain realChain = (RealInterceptorChain)chain;
      Transmitter transmitter = realChain.transmitter();
      int followUpCount = 0;
      Response priorResponse = (Response)null;

      while(true) {
        //....省略
       try {
                     var16 = true;
                     response = realChain.proceed(request, transmitter, (Exchange)null);
                     var8 = true;
                     var16 = false;
                     break label186;
                  } catch (RouteException var17) {
                     if (!this.recover(var17.getLastConnectException(), transmitter, 							false, request)) {
                        throw (Throwable)var17.getFirstConnectException();
                     }
            //....省略
     }
   }       
       
       private final boolean recover(IOException e, Transmitter transmitter, boolean requestSendStarted, Request userRequest) {
          if (!this.client.retryOnConnectionFailure()) {
             return false;
          } else if (requestSendStarted && this.requestIsOneShot(e, userRequest)) {
             return false;
          } else if (!this.isRecoverable(e, requestSendStarted)) {
             return false;
          } else {
             return transmitter.canRetry();
          }
   }

從源碼中可以看出如果retryOnConnectionFailure設置的爲false的話,recover()方法將返回false,intercept中將拋出異常。
如果是true的話,將繼續繼續執行realChain.proceed()方法。

當執行realChain.proceed() ,將事件繼續分發給各個攔截器,最終執行到 Http1ExchangeCodec#readResponseHeaders 方法。

 @Nullable
   public Builder readResponseHeaders(boolean expectContinue) {
      boolean var2 = this.state == 1 || this.state == 3;
      boolean var3 = false;
      boolean var4 = false;
      if (!var2) {
         int var5 = false;
         String var10 = "state: " + this.state;
         throw (Throwable)(new IllegalStateException(var10.toString()));
      } else {
         try {
            StatusLine statusLine = StatusLine.Companion.parse(this.readHeaderLine());
            Builder responseBuilder = (new Builder()).protocol(statusLine.protocol).code(statusLine.code).message(statusLine.message).headers(this.readHeaders());
            //.............省略
         } catch (EOFException var6) {
           //............省略

            String address = var14;
             //此處的異常便是上文出現的unexpected end of stream on okhttp3.Address@fe4f10d5
            throw (Throwable)(new IOException("unexpected end of stream on " + address, (Throwable)var6));
         }
      }
   }

 private final String readHeaderLine() {
      //RealBufferedSource#readUtf8LineStrict方法
      String line = this.source.readUtf8LineStrict(this.headerLimit);
      this.headerLimit -= (long)line.length();
      return line;
   }

繼續查看RealBufferedSource.java文件。

 //RealBufferedSource.java
// 此時如果server端已經closed了,那麼取到的buffer 爲 null 將EOF異常向上拋出  
@Override public String readUtf8LineStrict() throws IOException {
    long newline = indexOf((byte) '\n');
    if (newline == -1L) {
      Buffer data = new Buffer();
      buffer.copyTo(data, 0, Math.min(32, buffer.size()));
        //定位到異常信息j\n not found: size=0 content=…
      throw new EOFException("\\n not found: size=" + buffer.size()
          + " content=" + data.readByteString().hex() + "…");
    }
    return buffer.readUtf8Line(newline);
  }

解決方案

結合源碼分析,在創建OkHttpClient實例的時候,只需要將retryOnConnectionFailure設置爲true ,在拋出EOF異常時,可以讓其繼續去執行realChain.proceed方法。由於OkHttpClient在實例化的時候默認的retryOnConnectionFailure值便是true.如下:

 public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      proxySelector = ProxySelector.getDefault();
      cookieJar = CookieJar.NO_COOKIES;
      socketFactory = SocketFactory.getDefault();
      hostnameVerifier = OkHostnameVerifier.INSTANCE;
      certificatePinner = CertificatePinner.DEFAULT;
      proxyAuthenticator = Authenticator.NONE;
      authenticator = Authenticator.NONE;
      connectionPool = new ConnectionPool();
      dns = Dns.SYSTEM;
      followSslRedirects = true;
      followRedirects = true;
      retryOnConnectionFailure = true;  //默認爲true
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章