2013-10-08 18 views
33

Chciałbym wiedzieć, czy znane są problemy z Androidem z żądaniami HttpUrlConnection i POST. Podczas wysyłania żądań POST z klienta systemu Android doświadczamy przerywanych EOFExceptions. Ponowienie tego samego żądania w końcu zadziała. Oto ślad próbki stosu: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) 

Istnieje wiele podobnych raporty o błędach i posty na przepełnienie stosu, ale nie mogę zrozumieć, jeśli naprawdę jest problem, a jeśli tak, to jaka wersja Androida ma wpływ i jakie proponowana poprawka/obejść to.

Oto niektóre z podobnych raportów Mam na myśli:

Oto potencjalne ramy Android naprawić

wiem, że był to problem z zatrutych połączeń w puli połączeń w ampułko-Froyo, ale te kwestie występują wyłącznie na nowych urządzeniach ICS +. Jeśli pojawiłby się problem na późniejszych urządzeniach, oczekiwałbym jakiejś oficjalnej dokumentacji tego problemu na Androida.

+0

Co z tym obejściem? Czy coś jest z tym nie tak? http://stackoverflow.com/a/17638671/609782 – Darpan

+0

@Darpan można po prostu spróbować, chociaż wydaje się niezwiązane ze względu na ślad stosu .. – Kevin

Odpowiedz

12

Nasz wniosek jest taki, że na platformie Android występuje problem. Nasze obejście polegało na wychwyceniu wyjątku EOFException i ponownej próbie podania numeru N razy. Poniżej znajduje się pseudo kod:

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

Cześć Matt. Próbuję tego, ale to nie działa. Chcę sprawdzić, czy Twój pseudo kod jest następujący: 1. Otwórz połączenie przez url.openConnection(). 2. Jeśli otrzymam EOF podczas wywoływania getResponseCode(), a następnie rozłącz, ustaw HttpURLConnection na wartość null i połącz ponownie. 3. Wypróbuj tę funkcję, aż do maksymalnej liczby ponownych prób. Czy to zrobiłeś? Dziękuję Ci. –

+1

Tak, za każdym razem, gdy próbujesz wykonać żądanie, radziłbym odtworzyć połączenie HttpURLConnection, nie próbuj ponownie użyć tego samego wystąpienia. Kiedy mówisz, że to nie działa, co masz na myśli? –

+1

Dzięki za odpowiedź. Chodzi mi o to, że próbowałem robić tak jak powiedziałeś i nadal mam problemy z EOFException. –

6

Biblioteka HttpURLConnection wewnętrznie utrzymuje pulę połączeń. Tak więc zawsze, gdy wysyłane jest żądanie, najpierw sprawdza, czy istnieje istniejące połączenie w puli, na podstawie którego zdecyduje się utworzyć nowe.

Połączenia te są niczym innym jak gniazdami, a ta biblioteka domyślnie nie zamyka tych gniazd. Czasami może się zdarzyć, że połączenie (gniazdo), które nie jest aktualnie używane i jest obecne w puli, nie jest już użyteczne, ponieważ serwer może zdecydować o zakończeniu połączenia po pewnym czasie. Teraz, ponieważ połączenie zostaje zamknięte przez serwer, biblioteka nie wie o tym i zakłada, że ​​połączenie/gniazdo jest nadal połączone. W ten sposób wysyła nowe żądanie za pomocą tego nieaktualnego połączenia, a zatem otrzymujemy EOFException.

Najlepszym sposobem na sprawdzenie tego jest sprawdzenie nagłówków odpowiedzi po każdym wysłanym żądaniu. Serwer zawsze wysyła "Połączenie: zamknij" przed zakończeniem połączenia (HTTP 1.1). Możesz więc użyć getHeaderField() i sprawdzić pole "Connection". Inną rzeczą, na którą należy zwrócić uwagę, jest to, że serwer TYLKO wysyła to pole połączenia, gdy ma zamiar zakończyć połączenie.Więc trzeba kodować wokół to z możliwością dostaję „null” w normalnym przypadku (gdy serwer nie jest zamykanie połączenia)

+0

Dziękuję za wyjaśnienie, co się dzieje, a nie za udzielenie niewyjaśnionego polecenia komuś do wykonania! Bardzo doceniane. [+1] – naki

4

To rozwiązanie wydaje się być solidny i wydajnych:

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

Ta implementacja polega na tym, że domyślna liczba połączeń, które można otworzyć za pomocą serwera, to 5 (Froyo - KitKat). Oznacza to, że może istnieć do 5 nieaktualnych połączeń, z których każdy będzie musiał zostać zamknięty.

Po każdej nieudanej próbie, właściwość żądania Connection:close spowoduje, że bazowy silnik HTTP zamknie gniazdo po wywołaniu connection.disconnect(). Ponawiając próbę do 6 razy (maks. Liczba połączeń + 1), zapewniamy, że ostatnia próba zawsze otrzyma nowe gniazdo.

Żądanie może powodować dodatkowe opóźnienia, jeśli połączenia nie są żywe, ale jest to z pewnością lepsze niż EOFException. W takim przypadku ostateczna próba wysłania nie spowoduje natychmiastowego zamknięcia świeżo otwartego połączenia. To jedyna praktyczna optymalizacja, którą można wykonać.

Zamiast polegać na magicznej wartości domyślnej wynoszącej 5, użytkownik może samodzielnie skonfigurować własność systemu. Należy pamiętać, że ta właściwość jest dostępna przez statyczny blok inicjalizatora w katarze KitKat o numerze ConnectionPool.java i działa tak samo w starszych wersjach systemu Android. W wyniku tego właściwość może zostać użyta, zanim będzie można ją ustawić.

static final int MAX_CONNECTIONS = 5; 

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

Dziękuję człowieku! To powinno być sprawdzone jako właściwa odpowiedź! – Andranik

-5

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

jest odpowiedzią

1

Podejrzewam, że może to być serwer, który jest tu winy, a HttpURLConnection nie jest tak wyrozumiały jak innych implementacjach. To było przyczyną mojego EOFException. Podejrzewam, że w moim przypadku nie byłoby to sporadyczne (naprawiono to przed testowaniem obejścia problemu N ponownie), więc powyższe odpowiedzi odnoszą się do innych problemów i są poprawnym rozwiązaniem w tych przypadkach.

Mój serwer używał Pythona SimpleHTTPServer a ja błędnie zakładając, że wszystko, co potrzebne do zrobienia, aby wskazać sukces był następujący:

self.send_response(200) 

który wysyła początkowy wiersz nagłówka odpowiedzi serwera i datę nagłówek, ale pozostawia strumień w stanie, w którym możesz wysłać również dodatkowe nagłówki. HTTP wymaga dodatkowego nowego wiersza po nagłówkach, aby wskazać, że zostały zakończone. Pojawia się, jeśli ta nowa linia nie jest obecna, gdy próbujesz uzyskać wynikowy obiekt InputStream lub kod odpowiedzi itp. Za pomocą HttpURLConnection, wówczas generuje EOFException (co jest w rzeczywistości uzasadnione, o tym myśląc). Niektórzy klienci HTTP zaakceptowali krótką odpowiedź i zgłosili kod wyniku sukcesu, który doprowadził mnie do niesłusznego wskazania palcem na HttpURLConnection.

zmieniłem serwer to zrobić w zamian:

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

Nie więcej EOFException z tym kodem.

0

To zadziałało dla mnie.

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

Tak. Występuje problem na platformie Android, w szczególności w Androidzie libcore w wersji 4.1-4.3.

Problemem jest wprowadzany w ten popełnić: https://android.googlesource.com/platform/libcore/+/b2b02ac6cd42a69463fd172531aa1f9b9bb887a8

Android 4.4 włączony http lib "okhttp", które nie mają tego problemu.

Problem wyjaśniony następująco:

na Androidzie 4,1-4,3, gdy używasz URLConnection/HttpURLConnection POST z "ChunkedStreamingMode" lub "FixedLengthStreamingMode" ustawiony, URLConnection/HttpURLConnection nie zrobić cichy ponów próbę, jeśli ponownie użyte połączenie jest nieaktualne. Powinieneś powtórzyć POST co najwyżej "http.maxConnections + 1" razy w swoim kodzie, tak jak sugerują poprzednie odpowiedzi.

Powiązane problemy