2013-10-08 3 views
33

Я хотел бы знать, есть ли известные проблемы на Android с запросами HttpUrlConnection и POST. Мы испытываем intermittent EOFExceptions при выполнении запросов POST от клиента Android. Повторная попытка повторного запроса будет в конечном итоге работать. Вот стопка образца след: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) 

Есть много подобных сообщений об ошибках и сообщений к переполнению стека, но я не могу понять, если действительно есть проблема, и если да, то какие версии Android влияют и то, что предлагаемое исправление/работа вокруг есть.

Вот некоторые из подобных отчетов я имею в виду:

Вот потенциал Android рамки исправить

Я знаю, что была проблема с отравленными соединений в пуле соединений в пре-Froyo, но эти вопросы происходят исключительно на новых устройствах ICS +. Если на более поздних устройствах возникла проблема, я бы ожидал какой-то официальной документации по этому вопросу в Android.

+0

Как обстоит дело? Что-то не так с этим? http://stackoverflow.com/a/17638671/609782 – Darpan

+0

@Darpan вы могли бы просто попробовать, хотя он кажется несвязанным на основе трассировки стека. – Kevin

ответ

12

Наш вывод о том, что на платформе Android есть проблема. Наше решение заключалось в том, чтобы поймать EOFException и повторить запрос N раз. Ниже приведен псевдокод:

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

Hi Matt. Я пытаюсь это сделать, но он не работает. Я хочу проверить, что ваш псевдокод выглядит следующим образом: 1. открыть соединение через url.openConnection(). 2. Если я получаю EOF при вызове метода getResponseCode(), отключите его, установите HttpURLConnection на null и снова подключитесь. 3. Попробуйте это до максимального количества попыток. Это вы сделали? Спасибо. –

+1

Да, каждый раз, когда вы пытаетесь выполнить запрос, я бы посоветовал вам воссоздать HttpURLConnection, не пытайтесь повторно использовать один и тот же экземпляр. Когда вы говорите, что это не работает, что вы имеете в виду? –

+1

Спасибо, что ответили. Я имел в виду, что пытался сделать так, как вы сказали, и у меня все еще возникают проблемы с EOFException. –

6

библиотека HttpURLConnection внутренне поддерживает пул соединений. Таким образом, всякий раз, когда запрос отправляется, он сначала проверяет, существует ли уже существующее соединение в пуле, на основе которого оно решает создать новый.

Эти соединения представляют собой ничего, кроме сокетов, и эта библиотека по умолчанию не закрывает эти сокеты. Иногда может случиться так, что соединение (сокет), которое в настоящее время не используется и присутствует в пуле, больше не может использоваться, поскольку сервер может решить завершить соединение через некоторое время. Теперь, поскольку соединение, даже если оно закрыто сервером, библиотека не знает об этом и предполагает, что соединение/сокет все еще подключено. Таким образом, он отправляет новый запрос, используя это устаревшее соединение, и, следовательно, мы получаем EOFException.

Лучший способ справиться с этим - проверить заголовки ответов после каждого отправленного вами запроса. Сервер завершает соединение (HTTP 1.1). Таким образом, вы можете использовать getHeaderField() и проверить поле «Соединение». Другое дело, что сервер ТОЛЬКО отправляет это поле подключения, когда он собирается завершить соединение.Итак, вам нужно закодировать вокруг этого с возможностью получения «нулевой» в обычном случае (когда сервер не закрывает соединение)

+0

Спасибо, что объяснили, что происходит, вместо того, чтобы просто дать необъяснимую команду кому-то выполнить! Очень признателен. [+1] – naki

4

Этот способ, как правило, быть надежным и производительным:

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

Эта реализация основана на том факте, что число подключений по умолчанию, которое может быть открыто с сервером, равно 5 (Froyo - KitKat). Это означает, что может существовать до 5 устаревших соединений, каждый из которых должен быть закрыт.

После каждой неудачной попытки свойство запроса Connection:close приведет к тому, что основной HTTP-сервер закроет сокет, когда вызывается connection.disconnect(). Повторяя до 6 раз (максимальное количество соединений + 1), мы гарантируем, что последней попытке всегда будет присвоен новый сокет.

Запрос может иметь дополнительную задержку, если соединения отсутствуют, но это, безусловно, лучше, чем EOFException. В этом случае окончательная попытка отправки не будет немедленно закрывать только что открытое соединение. Это единственная практическая оптимизация, которую можно сделать.

Вместо того, чтобы полагаться на магическое значение по умолчанию 5, вы можете самостоятельно настроить системное свойство. Имейте в виду, что к этому свойству обращается статический блок инициализатора в KitKat, и он также работает в старых версиях Android. В результате свойство может быть использовано до того, как вы сможете его установить.

static final int MAX_CONNECTIONS = 5; 

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

Спасибо, человек! Это должно быть проверено как правильный ответ! – Andranik

-5

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

ответ

1

Я подозреваю, что это может быть сервер, виноват здесь, а HttpURLConnection не прощает других реализаций. Это было причиной моего исключения EOF. Я подозреваю, что в моем случае это не будет прерывистым (исправлено его перед тестированием обходного пути N), поэтому приведенные выше ответы касаются других проблем и будут правильным решением в этих случаях.

Мой сервер использует питон SimpleHTTPServer, и я был ошибочно предполагая, все, что мне нужно сделать, чтобы указать успех был следующий:

self.send_response(200) 

Это посылает начальную строку заголовка ответа, сервер и заголовок даты, но оставляет поток в состоянии, в котором вы также можете отправлять дополнительные заголовки. HTTP требует дополнительной новой строки после заголовков, чтобы указать, что они завершены. Если эта новая строка отсутствует, когда вы пытаетесь получить тело ввода InputStream или код ответа и т. Д. С HttpURLConnection, тогда он выдает исключение EOFException (что на самом деле разумно, думая об этом). Некоторые HTTP-клиенты действительно принимали короткий ответ и сообщали код результата успеха, который привел ко мне, возможно, несправедливо указывая пальцем на HttpURLConnection.

Я изменил мой сервер, чтобы сделать это вместо того, чтобы:

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

Нет больше EOFException с этим кодом.

0

Это сработало для меня.

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

Да. В Android-платформе есть проблема, в частности, в Android libcore с версией 4.1-4.3.

Проблема вводится в этой фиксации: https://android.googlesource.com/platform/libcore/+/b2b02ac6cd42a69463fd172531aa1f9b9bb887a8

Android 4.4 переключился HTTP LIB к "okhttp", который не имеет этой проблемы.

Проблема объясняется следующим образом:

на андроид 4.1-4.3, когда вы используете URLConnection/HttpURLConnection для POST с "ChunkedStreamingMode" или "FixedLengthStreamingMode" набор, URLConnection/HttpURLConnection не делать если повторное использование устарело. Вы должны повторить POST не более «http.maxConnections + 1» раз в вашем коде, как и предыдущие ответы.

Смежные вопросы