web-dev-qa-db-ja.com

HttpURLConnection.getResponseCode()は2回目の呼び出しで-1を返します

Android 1.5で特有の問題が発生しているようです(道標1.1-SNAPSHOT)を使用しているライブラリは、リモートサーバーに2つの連続した接続を確立します。2番目の接続は常に失敗しますa 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の結果コードで失敗します。入力ストリームから1文字を読み取っただけでは、エラーは発生しないようです。

これは、どのURLでも発生しないことに注意してください。上記のような特定のURLのみです。

また、HttpURLConnectionの代わりにHttpClientを使用するように切り替えると、すべてが正常に機能します。

// 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);
    }
}

私は references を他の場所で同様の問題と思われるものに見つけましたが、これまでのところ解決策はありません。それらが本当に同じ問題である場合、他の参照がそれを参照していないため、問題はおそらく標識にはありません。

何か案は?

29
emmby

このプロパティを設定して、効果があるかどうかを確認してください。

http.keepAlive=false

サーバーの応答がUrlConnectionによって理解されず、クライアント/サーバーが同期しなくなったときに、同様の問題が発生しました。

これで問題が解決した場合は、HTTPトレースを取得して、応答の何が特別なのかを正確に確認する必要があります。

編集:この変更は私の疑いを確認するだけです。それはあなたの問題を解決しません。症状を隠すだけです。

最初の要求からの応答が200の場合、トレースが必要です。私は通常、Ethereal/Wiresharkを使用してTCPトレースを取得します。

最初の応答が200でない場合、コードに問題があります。 OAuthを使用すると、エラー応答(401)は実際に、デバッグに役立つProblemAdvice、署名ベース文字列などを含むデータを返します。エラーストリームからすべてを読み取る必要があります。そうしないと、次の接続が混乱し、それが-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;
        }
    }
}
28
ZZ Coder

それを閉じて2番目の接続を開く前に、InputStreamからすべてのデータを読み取らなかったときにも、同じ問題が発生しました。また、System.setProperty("http.keepAlive", "false");を使用して修正されるか、残りのInputStreamを読み取るまで単にループするだけです。

問題に完全に関連しているわけではありませんが、これが同様の問題を持つ他の人に役立つことを願っています。

10
kwogger

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");
    }
}

Cf. http://Android-developers.blogspot.ca/2011/09/androids-http-clients.html

5
Murphy

または、接続(HttpUrlConnection)にHTTPヘッダーを設定できます。

conn.setRequestProperty("Connection", "close");
2
Yar

応答を読み終える前に、接続が閉じられていないことを確認できますか?たぶんHttpClientはすぐに応答コードを解析し、将来のクエリのためにそれを保存しますが、接続が閉じられるとHttpURLConnectionが-1を返す可能性がありますか?

0
esac