29

当我正在使用的库(signpost 1.1-SNAPSHOT)与远程服务器建立两个连续连接时,我似乎在 Android 1.5 上遇到了一个特殊问题。第二个连接总是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,例如上面的那个。

此外,如果我切换到使用 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);
    }
}

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

有任何想法吗?

4

5 回答 5

29

Try set this property to see if it helps,

http.keepAlive=false

I saw similar problems when server response is not understood by UrlConnection and client/server gets out of sync.

If this solves your problem, you have to get a HTTP trace to see exactly what's special about the response.

EDIT: This change just confirms my suspicion. It doesn't solve your problem. It just hides the symptom.

If the response from first request is 200, we need a trace. I normally use Ethereal/Wireshark to get the TCP trace.

If your first response is not 200, I do see a problem in your code. With OAuth, the error response (401) actually returns data, which includes ProblemAdvice, Signature Base String etc to help you debug. You need to read everything from error stream. Otherwise, it's going to confuse next connection and that's the cause of -1. Following example shows you how to handle errors correctly,

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;
        }
    }
}
于 2009-09-17T21:42:45.103 回答
10

当我在关闭 InputStream 并打开第二个连接之前没有从 InputStream 中读取所有数据时,我遇到了同样的问题。在我读完 InputStream 的其余部分之前,它也可以通过System.setProperty("http.keepAlive", "false");或只是循环修复。

与您的问题不完全相关,但希望这可以帮助其他有类似问题的人。

于 2009-10-27T00:19:05.880 回答
5

谷歌提供了一个优雅的解决方法,因为它只发生在 Froyo 之前:

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

于 2012-03-27T17:32:26.703 回答
2

或者,您可以在连接中设置 HTTP 标头(HttpUrlConnection):

conn.setRequestProperty("Connection", "close");
于 2014-04-24T07:18:32.050 回答
0

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

于 2009-09-17T20:29:45.813 回答