Okhttp java.net.ProtocolException:意外的流结束

4

我目前正在开发Android应用程序,使用okttp3进行简单的GET请求。 我正在将我的flask应用程序用作API端点。 Android代码:

 OkHttpClient client = new OkHttpClient();

        final Request request = new Request.Builder()
                .url(url)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

                    String s = response.body().string();
                    System.out.println(s);

            }

            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {

                System.out.println(e.getMessage());
            }

        });

Flask框架代码

@app.route('/abc', methods=["GET"])
def abc():
    return jsonify({"test": "abc"})

当我使用POSTMAN发出请求时,没有任何问题: postman的get请求 然而,当我使用上面的Java代码时,有时它可以工作,有时我会得到以下错误:

D/OkHttp: java.net.ProtocolException: unexpected end of stream
        at okhttp3.internal.http1.Http1ExchangeCodec$FixedLengthSource.read(Http1ExchangeCodec.kt:389)
        at okhttp3.internal.connection.Exchange$ResponseBodySource.read(Exchange.kt:276)
        at okio.Buffer.writeAll(Buffer.kt:1655)
        at okio.RealBufferedSource.readString(RealBufferedSource.kt:95)
        at okhttp3.ResponseBody.string(ResponseBody.kt:187)
        at com.example.teste.MainActivity$2.onResponse(MainActivity.java:83)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:504)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
D/OkHttp:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

我尝试过其他帖子中提出的多种解决方案,例如:

OkHttpClient client = new OkHttpClient.Builder()
    .retryOnConnectionFailure(true)
    .build();


或者
Request request = new Request.Builder()
                    .addHeader("Authorization", "Bearer " + apiToken)
                    .addHeader("content-type", "application/json")
                    .addHeader("Connection","close")
                    .url(uri)
                    .build();

但是它们都没有起作用。这里的问题是什么?我已经试过使用相同的代码向其他虚拟 API 发出请求,它总是有效的。我的 Flask API 缺少了什么?
错误发生在这里:
val read = super.read(sink, minOf(bytesRemaining, byteCount))
      if (read == -1L) {
        connection.noNewExchanges() // The server didn't supply the promised content length.
        val e = ProtocolException("unexpected end of stream")
        responseBodyComplete()
        throw e
      }

这与服务器提供的内容长度有关。我该如何解决?

4个回答

4
如何为每个请求添加一个拦截器并添加Connection-Close标头?如下所示。
okHttpClient = new OkHttpClient.Builder()
        .addNetworkInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request().newBuilder().addHeader("Connection", "close").build();
                return chain.proceed(request);
            }
        })
        .build();

HTTP/1.1定义了“关闭”连接选项,用于发送方在响应完成后发出信号表示连接将被关闭。例如, 请求或响应头字段中的 Connection: close 表示在当前请求/响应完成后不应将连接视为“持久连接”(第8.1节)。 不支持持久连接的HTTP/1.1应用程序必须在每个消息中包含“close”连接选项。

2

只有在使用模拟器时才出现异常,设备上没有问题。


7
这不是一个清晰的回答。OKHttp库有些问题。实际上,情况是模拟器上的Java请求速度比预期快得多,一旦它读取了一些数据到缓冲区中,该库就会关闭连接,使其余的传入数据悬挂在空中,而服务器需要更多时间将整个数据写入输出缓冲区。现在,在模拟器上,这有时会像一个错误/正面的结果一样工作。那些熟悉C套接字编程的人对这种行为非常了解。 - Hamza AVvan

1
我正在编写一个代理服务器,它可以对请求体进行调整。所以我改变了请求体,但忘记调整 Content-Length=123 头部,导致 HTTPClient 出现错误。这可能意味着头部和实际请求体长度之间存在不一致。

1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

0
在我的使用场景中,我从一个由Nginx驱动的服务器下载了一个大文件,并即时处理数据。为了做到这一点,我从输入流中读取与当前正在处理的内容相对应的小块数据,并在消耗更多数据之前花费时间进行处理。不知何故,系统(也可能是OkHttp)提前缓存了文件的大部分内容,并且只有在我消耗了此缓存后才会联系服务器获取更多数据。这很明显,因为即使关闭服务器,程序仍能在几分钟内继续读取和处理数据,直到达到意外的流结束。
结果发现,Nginx默认配置为如果客户端60秒未与服务器联系,则断开连接
Syntax:   send_timeout time;  
Default:  send_timeout 60s;  
Context:  http, server, location

Sets a timeout for transmitting a response to the client. The timeout is set only between two successive write operations, not for the transmission of the whole response. If the client does not receive anything within this time, the connection is closed.

所以,当消耗数据时犹豫不决导致了问题。我通过增加服务器配置中的超时时间来解决了这个问题:
location /MyLargeDownloadsFolder/ {
   send_timeout 600s;
}

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