web-dev-qa-db-fra.com

HttpURLConnection.getResponseCode () renvoie -1 à la deuxième invocation

Il semble que je rencontre un problème particulier sur Android 1.5 lorsqu'une bibliothèque que j'utilise (panneau 1.1-SNAPSHOT) établit deux connexions consécutives sur un serveur distant. La deuxième connexion échoue toujours avec une HttpURLConnection.getResponseCode() de -1

Voici un cas de test qui expose le problème:

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

Fondamentalement, si je signe la demande puis que je consomme l’ensemble du flux d’entrée, la demande suivante échouera avec un code de résultat de -1. L'échec ne semble pas se produire si je ne lis qu'un seul caractère du flux d'entrée.

Notez que cela ne se produit pas pour toutes les URL - juste des URL spécifiques telles que celle ci-dessus.

De plus, si je passe à l’utilisation de HttpClient à la place de HttpURLConnection, tout fonctionne correctement:

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

J'ai trouvé des références à ce qui semble être un problème similaire ailleurs, mais jusqu'à présent aucune solution. S'il s'agit vraiment du même problème, le problème ne vient probablement pas de poteau indicateur, car les autres références ne le mentionnent pas.

Des idées?

28
emmby

Essayez de définir cette propriété pour voir si cela peut aider,

http.keepAlive=false

J'ai rencontré des problèmes similaires lorsque la réponse du serveur n'est pas comprise par UrlConnection et que le client/serveur se désynchronise.

Si cela résout votre problème, vous devez obtenir une trace HTTP pour voir exactement ce qui est spécial dans la réponse.

EDIT: Ce changement ne fait que confirmer mes soupçons. Cela ne résout pas votre problème. Cela cache juste le symptôme.

Si la réponse à la première demande est 200, nous avons besoin d'une trace. J'utilise normalement Ethereal/Wireshark pour obtenir la trace TCP.

Si votre première réponse n'est pas 200, je vois un problème dans votre code. Avec OAuth, la réponse d'erreur (401) renvoie en fait des données, notamment ProblemAdvice, Signature Base String, etc. pour vous aider à déboguer. Vous devez tout lire depuis le flux d'erreur. Sinon, la prochaine connexion sera confuse et c'est la cause de -1. L'exemple suivant vous montre comment gérer les erreurs correctement,

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

J'ai rencontré le même problème lorsque je ne lisais pas toutes les données du InputStream avant de le fermer et d'ouvrir une deuxième connexion. Il a également été corrigé avec System.setProperty("http.keepAlive", "false"); ou simplement en boucle jusqu'à ce que j'ai lu le reste du InputStream.

Pas complètement lié à votre problème, mais espérons que cela aidera tout le monde avec un problème similaire.

10
kwogger

Google a fourni une solution de contournement élégante puisqu'elle ne se produit qu'avant 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");
    }
}

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

5
Murphy

Ou, vous pouvez définir un en-tête HTTP dans la connexion (HttpUrlConnection):

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

Pouvez-vous vérifier que la connexion n'est pas fermée avant d'avoir fini de lire la réponse? Peut-être que HttpClient analyse immédiatement le code de réponse et l'enregistre pour les futures requêtes, mais HttpURLConnection pourrait renvoyer -1 une fois la connexion fermée.

0
esac