今天遇到一個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;
}