2013-10-08 14 views
33

Vorrei sapere se ci sono problemi noti su Android con HttpURLConnection e richieste POST. Stiamo riscontrando intermittenti EOFExceptions quando si effettuano richieste POST da un client Android. Ritentare la stessa richiesta funzionerà alla fine. Ecco una traccia dello stack di esempio:Android HttpURLConnection EOFException

java.io.EOFException 
at libcore.io.Streams.readAsciiLine(Streams.java:203) 
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:579) 
at libcore.net.http.HttpEngine.readResponse(HttpEngine.java:827) 
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:283) 
at libcore.net.http.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:497) 
at libcore.net.http.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:134) 

Ci sono molte segnalazioni di errori simili e post di overflow dello stack, ma non riesco a capire se c'è davvero un problema e se sì, quali versioni di Android sono interessati e ciò che la correzione proposta/lavorare è.

Ecco alcuni dei rapporti simili mi riferisco a:

Ecco un potenziale quadro Android fissare

so c'era un problema con i collegamenti avvelenati nel pool di connessioni in pre-Froyo, ma questi problemi si verificano esclusivamente sui nuovi dispositivi ICS +. Se ci fosse un problema su dispositivi successivi mi aspetterei una sorta di documentazione ufficiale di Android del problema.

+0

Che dire di questo lavoro? C'è qualcosa di sbagliato in questo? http://stackoverflow.com/a/17638671/609782 – Darpan

+0

@Darpan si potrebbe semplicemente provarlo, anche se sembra non correlato basato sulla traccia dello stack .. – Kevin

risposta

12

La nostra conclusione è che c'è un problema nella piattaforma Android. La nostra soluzione era prendere il EOFException e riprovare la richiesta N numero di volte. Di seguito è riportato lo pseudo codice:

private static final int MAX_RETRIES = 3; 

private ResponseType fetchResult(RequestType request) { 
    return fetchResult(request, 0); 
} 

private ResponseType fetchResult(RequestType request, int reentryCount) { 
    try { 
     // attempt to execute request 
    } catch (EOFException e) { 
     if (reentryCount < MAX_RETRIES) { 
      fetchResult(request, reentryCount + 1); 
     } 
    } 
    // continue processing response 
} 
+2

Ciao Matt. Ci sto provando ma non funziona. Voglio verificare che il tuo pseudo codice sia il seguente: 1. apri la connessione tramite url.openConnection(). 2. Se ottengo un EOF quando chiami getResponseCode(), quindi disconnetti, imposta HttpURLConnection su null e riconnetti. 3. Provare questo fino al numero massimo di tentativi. E 'questo che hai fatto? Grazie. –

+1

Sì, ogni volta che si tenta la richiesta, si consiglia di ricreare HttpURLConnection, non tentare di riutilizzare la stessa istanza. Quando dici che non funziona, cosa intendi? –

+1

Grazie per aver risposto. Volevo dire che ho provato a fare come hai detto tu e sto ancora avendo problemi con EOFException. –

6

La libreria HttpURLConnection mantiene un pool di connessioni. Quindi, ogni volta che viene inviata una richiesta, controlla innanzitutto se esiste già una connessione esistente nel pool, in base alla quale decide di crearne una nuova.

Queste connessioni non sono altro che i socket, e questa libreria di default non si chiude queste prese. A volte può capitare che una connessione (socket) che non è attualmente in uso e sia presente nel pool non sia più utilizzabile poiché il Server può scegliere di interrompere la connessione dopo un po 'di tempo. Ora, dal momento che la connessione è chiusa dal server, la libreria non ne sa nulla e presuppone che la connessione/socket sia ancora connessa. Quindi invia la nuova richiesta usando questa connessione obsoleta e quindi otteniamo EOFException.

Il modo migliore per gestire questa situazione è quello di controllare le intestazioni di risposta dopo ogni richiesta che si invia. Il server invia sempre una "Connessione: Chiudi" prima di terminare una connessione (HTTP 1.1). Quindi, è possibile utilizzare getHeaderField() e verificare il campo "Connessione". Un'altra cosa da notare è che il server invia SOLO questo campo di connessione quando sta per terminare la connessione.Quindi, è necessario il codice intorno a questo con la possibilità di ottenere un "null" nel caso normale (quando il server non chiude la connessione)

+0

Grazie per aver spiegato cosa sta succedendo, piuttosto che dare un comando inspiegabile per qualcuno da eseguire! Molto apprezzato. [+1] – naki

4

Questa soluzione tende ad essere affidabile e performante:

static final int MAX_CONNECTIONS = 5; 

T send(..., int failures) throws IOException { 
    HttpURLConnection connection = null; 

    try { 
     // initialize connection... 

     if (failures > 0 && failures <= MAX_CONNECTIONS) { 
      connection.setRequestProperty("Connection", "close"); 
     } 

     // return response (T) from connection... 
    } catch (EOFException e) { 
     if (failures <= MAX_CONNECTIONS) { 
      disconnect(connection); 
      connection = null; 

      return send(..., failures + 1); 
     } 

     throw e; 
    } finally { 
     disconnect(connection); 
    } 
} 

void disconnect(HttpURLConnection connection) { 
    if (connection != null) { 
     connection.disconnect(); 
    } 
} 

Questa implementazione si basa sul fatto che il numero predefinito di connessioni che possono essere aperte con un server è 5 (Froyo - KitKat). Ciò significa che possono esistere fino a 5 connessioni stantie, ognuna delle quali dovrà essere chiusa.

Dopo ogni tentativo non riuscito, la proprietà di richiesta Connection:close causerà il motore HTTP sottostante per chiudere il socket quando viene chiamato connection.disconnect(). Ritentando fino a 6 volte (connessioni max + 1), ci assicuriamo che all'ultimo tentativo venga sempre assegnato un nuovo socket.

La richiesta può verificarsi latenza aggiuntiva se nessuna connessione è attiva, ma è sicuramente meglio di un EOFException. In tal caso, il tentativo di invio finale non chiuderà immediatamente la connessione appena aperta. Questa è l'unica ottimizzazione pratica che può essere fatta.

Invece di fare affidamento sul valore predefinito magico 5, è possibile configurare autonomamente la proprietà di sistema. Tieni presente che questa proprietà è accessibile da un blocco di inizializzazione statico in KitKat ConnectionPool.java e funziona così anche nelle versioni precedenti di Android. Di conseguenza, la proprietà può essere utilizzata prima che tu abbia la possibilità di impostarla.

static final int MAX_CONNECTIONS = 5; 

static { 
    System.setProperty("http.maxConnections", String.valueOf(MAX_CONNECTIONS)); 
} 
+1

Grazie amico! Questo dovrebbe essere controllato come risposta giusta! – Andranik

-5

connection.addRequestProperty ("Accept-Encoding", "gzip");

è la risposta

1

ho il sospetto che potrebbe essere il server che è colpa qui, e il HttpURLConnection non è così clemente come altre implementazioni. Questa è stata la causa della mia EOFException. Sospetto nel mio caso che ciò non sia intermittente (risolto prima di testare la soluzione alternativa di retry), quindi le risposte sopra riportate riguardano altri problemi e rappresentano una soluzione corretta in questi casi.

Il mio server stava usando python SimpleHTTPServer e mi è stato ingiustamente ammesso tutto quello che dovevo fare per indicare il successo è stata la seguente:

self.send_response(200) 

che invia la riga di intestazione risposta iniziale, un server e un colpo di testa data, ma lascia il flusso nello stato in cui è possibile inviare anche intestazioni aggiuntive. HTTP richiede una nuova riga aggiuntiva dopo le intestazioni per indicare che sono terminate. Appare se questa nuova linea non è presente quando si tenta di ottenere l'InputStream del corpo del risultato o il codice di risposta ecc con HttpURLConnection, quindi lancia l'EOFException (che è effettivamente ragionevole, pensandoci). Alcuni client HTTP accettano la risposta breve e riportano il codice del risultato di successo che mi porta forse a puntare ingiustamente il dito su HttpURLConnection.

ho cambiato il mio server per fare questo, invece:

self.send_response(200) 
self.send_header("Content-Length", "0") 
self.end_headers() 

Non più EOFException con quel codice.

0

Questo ha funzionato per me.

public ResponseObject sendPOST(String urlPrefix, JSONObject payload) throws JSONException { 
    String line; 
    StringBuffer jsonString = new StringBuffer(); 
    ResponseObject response = new ResponseObject(); 
    try { 

     URL url = new URL(POST_URL); 
     HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 
     connection.setDoInput(true); 
     connection.setDoOutput(true); 
     connection.setReadTimeout(10000); 
     connection.setConnectTimeout(15000); 
     connection.setRequestMethod("POST"); 
     connection.setRequestProperty("Accept", "application/json"); 
     connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); 

     OutputStream os = connection.getOutputStream(); 
     os.write(payload.toString().getBytes("UTF-8")); 
     os.close(); 
     BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream())); 
     while ((line = br.readLine()) != null) { 
      jsonString.append(line); 
     } 
     response.setResponseMessage(connection.getResponseMessage()); 
     response.setResponseReturnCode(connection.getResponseCode()); 
     br.close(); 
     connection.disconnect(); 
    } catch (Exception e) { 
     Log.w("Exception ",e); 
     return response; 
    } 
    String json = jsonString.toString(); 
    response.setResponseJsonString(json); 
    return response; 
} 
4

Sì. C'è un problema nella piattaforma Android, in particolare, nella libcore di Android con la versione 4.1-4.3.

Il problema è stato introdotto in questo impegno: https://android.googlesource.com/platform/libcore/+/b2b02ac6cd42a69463fd172531aa1f9b9bb887a8

Android 4.4 commutato http lib a "okhttp", che non ha questo problema.

Problema spiegato come segue:

su Android 4.1-4.3, quando si utilizza URLConnection/HttpURLConnection al post con "ChunkedStreamingMode" o "FixedLengthStreamingMode" impostato, URLConnection/HttpURLConnection non si fare silenzioso riprovare se la connessione riutilizzata è obsoleta. Riprova POST al massimo "http.maxConnections + 1" volte nel tuo codice, proprio come suggeriscono le risposte precedenti.

Problemi correlati