Android HttpUrlConnection EOFException

时间:2013-10-08 21:12:39

标签: android httpurlconnection

我想知道Android上是否存在HttpUrlConnection和POST请求的已知问题。从Android客户端发出POST请求时,我们遇到间歇性 EOFExceptions。重试相同的请求最终会起作用。这是一个示例堆栈跟踪:

java.io.EOFException
at libcore.io.Streams.readAsciiLine(Streams.java:203)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:579)
at libcore.net.http.HttpEngine.readResponse(HttpEngine.java:827)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:283)
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497)
at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134)

有许多类似的错误报告和帖子到堆栈溢出但我无法理解是否确实存在问题,如果是,那么Android的哪些版本会受到影响以及建议的修复/解决方法是什么。

以下是我所指的一些类似报道:

这是一个潜在的Android框架修复

我知道在Froyo之前连接池中存在中毒连接的问题,但这些问题仅在新的ICS +设备上发生。如果以后的设备出现问题,我会期待某种官方Android文档的问题。

7 个答案:

答案 0 :(得分:12)

我们的结论是Android平台存在问题。我们的解决方法是捕获EOFException并重试请求N次。以下是伪代码:

private static final int MAX_RETRIES = 3;

private ResponseType fetchResult(RequestType request) {
    return fetchResult(request, 0);
}

private ResponseType fetchResult(RequestType request, int reentryCount) {
    try {
        // attempt to execute request
    } catch (EOFException e) {
        if (reentryCount < MAX_RETRIES) {
            fetchResult(request, reentryCount + 1);
        }
    }
    // continue processing response
}

答案 1 :(得分:7)

HttpURLConnection库在内部维护一个Connections池。因此,每当发送请求时,它首先检查池中是否存在现有连接,基于该连接它决定创建新连接。

这些连接只是套接字,默认情况下此库不会关闭这些套接字。有时可能会发生当前未使用且存在于池中的连接(套接字)不再可用,因为服务器可能会选择在一段时间后终止连接。现在,由于连接即使由服务器关闭,库也不知道它并假设连接/套接字仍然连接。因此它使用这个陈旧的连接发送新请求,因此我们得到EOFException。

处理此问题的最佳方法是在发送每个请求后检查响应标头。服务器总是发送一个&#34;连接:关闭&#34;在终止连接之前(HTTP 1.1)。因此,您可以使用getHeaderField()并检查&#34; Connection&#34;领域。另一件需要注意的事情是,服务器ONLY在即将终止连接时发送此连接字段。所以,你需要围绕这个编码,有可能得到一个&#34; null&#34;在正常情况下(当服务器没有关闭连接时)

答案 2 :(得分:4)

这种解决方法往往具有可靠性和高性能:

static final int MAX_CONNECTIONS = 5;

T send(..., int failures) throws IOException {
    HttpURLConnection connection = null;

    try {
        // initialize connection...

        if (failures > 0 && failures <= MAX_CONNECTIONS) {
            connection.setRequestProperty("Connection", "close");
        }

        // return response (T) from connection...
    } catch (EOFException e) {
        if (failures <= MAX_CONNECTIONS) {
            disconnect(connection);
            connection = null;

            return send(..., failures + 1);
        }

        throw e;
    } finally {
        disconnect(connection);
    }
}

void disconnect(HttpURLConnection connection) {
    if (connection != null) {
        connection.disconnect();
    }
}

此实现依赖于以下事实:可以使用服务器打开的默认连接数为5(Froyo - KitKat)。这意味着最多可能存在5个过时连接,每个连接都必须关闭。

每次尝试失败后,Connection:close请求属性将导致底层HTTP引擎在调用connection.disconnect()时关闭套接字。通过重试最多6次(最大连接数+ 1),我们确保始终为最后一次尝试提供新的套接字。

如果没有连接存在,请求可能会遇到额外的延迟,但这肯定比EOFException更好。在这种情况下,最终的发送尝试不会立即关闭刚刚打开的连接。这是唯一可以进行的实际优化。

您可以自己配置系统属性,而不是依赖于5的魔术默认值。请记住,这个属性是由KitKat的ConnectionPool.java中的静态初始化程序块访问的,并且它在旧的Android版本中也是如此。因此,在您有机会设置之前,可以使用该属性。

static final int MAX_CONNECTIONS = 5;

static {
    System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS));
}

答案 3 :(得分:4)

是。 Android平台存在问题,特别是在版本为4.1-4.3的Android libcore中。

此提交中引入了此问题:https://android.googlesource.com/platform/libcore/+/b2b02ac6cd42a69463fd172531aa1f9b9bb887a8

Android 4.4将http lib切换为“okhttp”,但没有这个问题。

问题解释如下:

  

在android 4.1-4.3上,当您使用URLConnection / HttpURLConnection以“ChunkedStreamingMode”或“FixedLengthStreamingMode”设置进行POST时,URLConnection / HttpURLConnection 将不会执行静默重试如果重用的连接是陈旧的。您应该在代码中重试最多“http.maxConnections + 1”次,就像之前的答案所示。

答案 4 :(得分:1)

我怀疑这可能是服务器有问题,并且HttpURLConnection不像其他实现那样宽容。这是我的EOFException的原因。我怀疑在我的情况下这不会是间歇性的(在测试N重试变通办法之前修复它),所以上面的答案与其他问题有关,并且在这些情况下是正确的解决方案。

我的服务器正在使用python SimpleHTTPServer,我错误地假设我需要做的就是表明成功如下:

self.send_response(200)

它发送初始响应标题行,服务器和日期标题,但使流处于您能够发送其他标题的状态。 HTTP需要在标题之后添加一个新行以表示它们已完成。当你尝试使用HttpURLConnection获取结果体InputStream或响应代码等时,如果这个新行不存在,那么它会抛出EOFException(这实际上是合理的,考虑它)。一些HTTP客户端确实接受了短响应并报告了成功结果代码,这导致我可能不公平地指责HttpURLConnection。

我改变了我的服务器来改为:

self.send_response(200)
self.send_header("Content-Length", "0")
self.end_headers()

该代码不再有EOFException。

答案 5 :(得分:0)

这对我有用。

public ResponseObject sendPOST(String urlPrefix, JSONObject payload) throws JSONException {
    String line;
    StringBuffer jsonString = new StringBuffer();
    ResponseObject response = new ResponseObject();
    try {

        URL url = new URL(POST_URL);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setReadTimeout(10000);
        connection.setConnectTimeout(15000);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Accept", "application/json");
        connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

        OutputStream os = connection.getOutputStream();
        os.write(payload.toString().getBytes("UTF-8"));
        os.close();
        BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        while ((line = br.readLine()) != null) {
            jsonString.append(line);
        }
        response.setResponseMessage(connection.getResponseMessage());
        response.setResponseReturnCode(connection.getResponseCode());
        br.close();
        connection.disconnect();
    } catch (Exception e) {
        Log.w("Exception ",e);
        return response;
    }
    String json = jsonString.toString();
    response.setResponseJsonString(json);
    return response;
}

答案 6 :(得分:-5)

connection.addRequestProperty(“Accept-Encoding”,“gzip”);

是答案