使用Okhttp和SPDY时,“java.io.IOException:stream was reset:CANCEL”的原因是什么?

13
我正在尝试使用OKHttp(版本2.0.0-RC2)和SPDY进行实验,并经常遇到"IOException: stream was reset: CANCEL"的问题, 在一些初步测试中,可能有10%或更多的请求出现此问题。当使用Apache HttpClient和普通https时,据我所知,我们没有看到任何等效的问题。我相当确定在禁用SPDY时,也不会像OkHttp那样看到任何等效的问题(client.setProtocols(ImmutableList.of(Protocol.HTTP_1_1))),但我还没有进行足够的测试以百分之百地确认。
这个之前的问题中,这些异常是其中之一,并且建议忽略它们,但这似乎很疯狂:我们在从服务器读取数据时收到异常,因此我们中止处理数据的代码(使用Jackson)。在这种情况下,我们需要做一些事情。当然,我们可以重试请求,但有时它是不可重试的POST请求,如果我们已经开始从服务器接收数据,那么可以肯定服务器已经采取了请求的操作。
理想情况下,我们可以通过一些客户端和/或服务器的配置来减少这些异常的发生率,但我对SPDY的了解还不足以知道从哪里开始查找或建议我们的服务器管理员团队开始查找。
如果有帮助,以下是堆栈跟踪:
java.io.IOException: stream was reset: CANCEL
  at com.squareup.okhttp.internal.spdy.SpdyStream$SpdyDataSource.checkNotClosed(SpdyStream.java:442)
  at com.squareup.okhttp.internal.spdy.SpdyStream$SpdyDataSource.read(SpdyStream.java:344)
  at com.squareup.okhttp.internal.http.SpdyTransport$SpdySource.read(SpdyTransport.java:273)
  at okio.RealBufferedSource.exhausted(RealBufferedSource.java:60)
  at okio.InflaterSource.refill(InflaterSource.java:96)
  at okio.InflaterSource.read(InflaterSource.java:62)
  at okio.GzipSource.read(GzipSource.java:80)
  at okio.RealBufferedSource$1.read(RealBufferedSource.java:227)
  at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.loadMore(UTF8StreamJsonParser.java:174)
  at com.fasterxml.jackson.core.base.ParserBase.loadMoreGuaranteed(ParserBase.java:431)
  at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._finishString2(UTF8StreamJsonParser.java:2111)
  at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._finishString(UTF8StreamJsonParser.java:2092)
  at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.getText(UTF8StreamJsonParser.java:275)
  at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:205)
  at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer.deserializeArray(JsonNodeDeserializer.java:230)
  at com.fasterxml.jackson.databind.deser.std.BaseNodeDeserializer.deserializeObject(JsonNodeDeserializer.java:202)
  at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:58)
  at com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer.deserialize(JsonNodeDeserializer.java:15)
  at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:2765)
  at com.fasterxml.jackson.databind.ObjectMapper.readTree(ObjectMapper.java:1546)
  at com.fasterxml.jackson.core.JsonParser.readValueAsTree(JsonParser.java:1363)
  at (application-level code...)

你知道你正在与哪个Web服务器交互吗?可能是我们这边或者服务器端存在协议错误。 - Jesse Wilson
你的请求超时设置是多少? - Jesse Wilson
@JesseWilson 我们正在与使用一些nginx版本的CloudFlare通信,请求超时时间如下: cli.setConnectTimeout(20, TimeUnit.SECONDS); cli.setReadTimeout(15, TimeUnit.SECONDS); - mlc
3个回答

3
您最好在CANCELL错误代码被分配的两个地方设置断点:即SpdyStream#closeInternal(第246行)和SpdyStream#receiveRstStream(第304行)。如果您能在这里设置断点,就可以捕获取消流的who,从而解决问题。
如果由于某种原因您无法附加调试器,则可以在达到那些行时将代码标记为仪表板以打印堆栈跟踪:
new Exception("SETTING ERROR CODE TO " + errorCode).printStackTrace();

无论哪种情况,我都是这段代码的作者,很乐意帮助您解决这个问题。

1
现在我想让“CANCEL”错误代码出现,但我无法重现它。我会在接下来的几天里再仔细看看,感谢你到目前为止的帮助! - mlc
我在即将进行的Android项目中使用OkHttp 2.4.0-RC1,但在使用Android M预览版模拟器(x86)时遇到了许多CANCEL错误。有趣的是,如果我激活调试器,大多数网络请求突然可以正确地执行 - 可能是竞态条件?以下是异常的样子:http://pastebin.com/AwGssiZg(目前我正在使用Volley,但以后可能会切换到Retrofit) - mreichelt
@mreichelt 适用相同的建议。在赋值 errorCode 的代码处设置断点。 - Jesse Wilson
大家好,我很好奇你们在这里是否找到了解决方案。我处于同样的情况下,Cloudflare 在我的服务器前面添加了 SPDY 项目(我们的后端不提供它,因为使用较旧版本的 NGINX)。 - shortstuffsushi

2

我曾经遇到过同样的问题,这是由于网络连接超时导致的。原因是从Web服务下载了一个大文件。我的超时设置为2分钟,所以我把它改成了5分钟,这解决了我的问题。

val  okkHttpclient = OkHttpClient.Builder()
   .connectTimeout(5, TimeUnit.MINUTES)
    .writeTimeout(5, TimeUnit.MINUTES) // write timeout
    .readTimeout(5, TimeUnit.MINUTES) // read timeout
    .addInterceptor(networkConnectionInterceptor)
    .build()

这对于OkHttpClient有所帮助,但仍无法使用RestTemplate - Frankie Drake

1
我们遇到了由于损坏的http头而引起的问题。Android Base64编码器默认添加换行符,这破坏了我们的授权头部。

我在使用okhttp v3.11.0时遇到了错误,因为我没有指定我们服务器所需的正确API密钥。 - Nick Wright

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接