2009-09-17 9 views
28

Je semble être confronté à un problème particulier sur Android 1.5 lorsqu'une bibliothèque que j'utilise (panneau 1.1-SNAPSHOT), effectue deux connexions consécutives à un serveur distant. La deuxième connexion échoue toujours avec un HttpURLConnection.getResponseCode() de -1HttpURLConnection.getResponseCode() renvoie -1 à la deuxième invocation

Voici un testcase 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); 
    } 
} 

En gros, si je signe la demande et consume le flux d'entrée entier, la requête suivante échouera avec un code de résultat de -1. L'échec ne semble pas se produire si je viens de lire un caractère du flux d'entrée.

Notez que cela ne se produit pas pour n'importe quelle URL - juste des URL spécifiques telles que celle ci-dessus.

De plus, si je passe à l'aide HttpClient au lieu de HttpURLConnection, tout fonctionne bien:

// 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é references à ce qui semble être un problème similaire ailleurs, mais jusqu'à présent aucune solution. S'ils sont vraiment le même problème, alors le problème n'est probablement pas avec signpost puisque les autres références n'y font aucune référence.

Des idées?

Répondre

28

Essayez de définir cette propriété pour voir si elle aide,

http.keepAlive=false 

j'ai vu des problèmes similaires lorsque la réponse du serveur ne sont pas compris par URLConnection et client/serveur sort de synchronisation. Si cela résout votre problème, vous devez obtenir une trace HTTP pour voir exactement ce qui est spécial à propos de la réponse.

EDIT: Cette modification confirme simplement ma suspicion. Cela ne résout pas votre problème. Ça cache juste le symptôme.

Si la réponse de la première requête 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 réellement des données, qui incluent ProblemAdvice, Signature Base String etc pour vous aider à déboguer. Vous devez tout lire du flux d'erreurs. Sinon, cela va confondre la prochaine connexion et c'est la cause de -1. L'exemple suivant vous montre comment gérer correctement les erreurs,

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

Intéressant. L'ajout de 'System.setProperty (" http.keepAlive "," false ")' au début du test permet de résoudre complètement le problème. Des suggestions sur la façon de faire la trace http? Ai-je besoin d'utiliser un proxy de journalisation ou y a-t-il quelque chose que je peux faire directement sur le client? – emmby

+0

Voir ma modification .............. –

+1

Confirmé que c'est un bug android, et nous le suivons ici: http://code.google.com/p/android/issues/ detail? id = 7786 –

0

Pouvez-vous vérifier que la connexion ne se ferme pas avant d'avoir fini de lire la réponse? Peut-être HttpClient analyse-t-il le code de réponse tout de suite, et l'enregistre pour de futures requêtes, cependant HttpURLConnection pourrait retourner -1 une fois la connexion fermée?

10

J'ai rencontré le même problème quand je ne l'ai pas lu dans toutes les données de la 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'aie lu le reste de l'InputStream.

Pas complètement lié à votre problème, mais espérons que cela aide quelqu'un d'autre avec un problème similaire.

5

Google a fourni une solution élégante car il est seulement passe 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

+0

Parfaitement travailler. Pour moi, ça s'est passé dans l'API 4.1.1 en utilisant Stripe. –

2

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

conn.setRequestProperty("Connection", "close"); 
Questions connexes