HttpURLConnection.getResponseCode() 第二次调用返回 -1

29

我似乎在Android 1.5上遇到了一个奇怪的问题,当我使用一个库(signpost 1.1-SNAPSHOT)对远程服务器进行两次连续连接时,第二个连接总是失败,并返回HttpURLConnection.getResponseCode()-1

这里是一个测试用例,可以展示该问题:

// BROKEN
public void testDefaultOAuthConsumerAndroidBug() throws Exception {
    for (int i = 0; i < 2; ++i) {
        final HttpURLConnection c = (HttpURLConnection) new URL("https://api.tripit.com/oauth/request_token").openConnection();
        final DefaultOAuthConsumer consumer = new DefaultOAuthConsumer(api_key, api_secret, SignatureMethod.HMAC_SHA1);
        consumer.sign(c);                             // This line...
        final InputStream is = c.getInputStream();
        while( is.read() >= 0 ) ;                     // ... in combination with this line causes responseCode -1 for i==1 when using api.tripit.com but not mail.google.com
        assertTrue(c.getResponseCode() > 0);
    }
}

如果我对请求进行签名,然后消耗整个输入流,那么下一次请求将以结果代码-1失败。但如果我只从输入流中读取一个字符,则不会出现此问题。

请注意,这并不会发生在任何URL上 - 只会在特定的URL上发生,比如上面提到的URL。

另外,如果我改用HttpClient而不是HttpURLConnection,一切都正常:

// WORKS
public void testCommonsHttpOAuthConsumerAndroidBug() throws Exception {
    for (int i = 0; i < 2; ++i) {
        final HttpGet c = new HttpGet("https://api.tripit.com/oauth/request_token");
        final CommonsHttpOAuthConsumer consumer = new CommonsHttpOAuthConsumer(api_key, api_secret, SignatureMethod.HMAC_SHA1);
        consumer.sign(c);
        final HttpResponse response = new DefaultHttpClient().execute(c);
        final InputStream is = response.getEntity().getContent();
        while( is.read() >= 0 ) ;
        assertTrue( response.getStatusLine().getStatusCode() == 200);
    }
}

我在其他地方找到了似乎类似的问题参考文献,但目前还没有解决方案。如果它们真的是相同的问题,那么问题可能不在于signpost,因为其他参考文献没有提及它。

有什么想法吗?

5个回答

29

尝试设置此属性,看看是否有帮助:

http.keepAlive=false

当UrlConnection无法理解服务器响应并导致客户端/服务器出现不同步问题时,我看到类似的问题。

如果这可以解决您的问题,则需要获取HTTP跟踪以精确定位响应中有何特殊之处。

编辑:此更改仅确认了我的怀疑。它不能解决您的问题,只是隐藏了症状。

如果第一次请求的响应是200,则我们需要进行跟踪。我通常使用Ethereal/Wireshark来获取TCP跟踪。

如果第一个响应不是200,则我在您的代码中看到了问题。使用OAuth时,错误响应(401)实际上会返回数据,其中包括ProblemAdvice、Signature Base String等帮助您进行调试的信息。您需要从错误流中读取所有内容。否则,它将混淆下一个连接,这是-1的原因。以下示例显示了如何正确处理错误:

public static String get(String url) throws IOException {

    ByteArrayOutputStream os = new ByteArrayOutputStream();
    URLConnection conn=null;
    byte[] buf = new byte[4096];

    try {
        URL a = new URL(url);
        conn = a.openConnection();
        InputStream is = conn.getInputStream();
        int ret = 0;
        while ((ret = is.read(buf)) > 0) {
            os.write(buf, 0, ret);
        }
        // close the inputstream
        is.close();
        return new String(os.toByteArray());
    } catch (IOException e) {
        try {
            int respCode = ((HttpURLConnection)conn).getResponseCode();
            InputStream es = ((HttpURLConnection)conn).getErrorStream();
            int ret = 0;
            // read the response body
            while ((ret = es.read(buf)) > 0) {
                os.write(buf, 0, ret);
            }
            // close the errorstream
            es.close();
            return "Error response " + respCode + ": " + 
               new String(os.toByteArray());
        } catch(IOException ex) {
            throw ex;
        }
    }
}

4
有意思。在测试用例的开头添加System.setProperty("http.keepAlive", "false")完全解决了这个问题。 如何进行HTTP跟踪?我需要使用日志代理吗?还是有什么方法可以直接在客户端上完成? - emmby
1
确认这是一个Android的bug,我们正在此处跟踪它: http://code.google.com/p/android/issues/detail?id=7786 - Jesse Wilson
谢谢。这让我们在过去的一天里发疯了。 - Artem Russakovskii
顺便提一下,设置“Connection: Close”头是不起作用的 - 必须像上面emmby指定的那样使用System.setProperty("http.keepAlive", "false")。 - Artem Russakovskii

10

在我关闭InputStream并打开第二个连接之前,如果没有将所有数据从InputStream中读入,则会遇到相同的问题。要解决这个问题,可以使用System.setProperty("http.keepAlive", "false");或者只需循环读取直到读完整个InputStream即可。

虽然这与您的问题不完全相关,但希望这能帮助其他遇到类似问题的人。


5

由于这只发生在Froyo之前,因此Google提供了一个优雅的解决方案:

private void disableConnectionReuseIfNecessary() {
    // HTTP connection reuse which was buggy pre-froyo
    if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
        System.setProperty("http.keepAlive", "false");
    }
}

参见http://android-developers.blogspot.ca/2011/09/androids-http-clients.html

,该文章涉及到Android的HTTP客户端。


完美地工作。对我来说,在使用Stripe时,这些事情发生在API 4.1.1中。 - Kishan Vaghela

2
或者,您可以在连接(HttpUrlConnection)中设置HTTP标头:
conn.setRequestProperty("Connection", "close");

0

你能验证在读取响应完成之前连接没有被关闭吗?也许 HttpClient 会立即解析响应代码并将其保存以供未来查询,但是 HttpURLConnection 在连接关闭后可能会返回 -1?


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