Разнообразие ошибок HTTP при общении с сервером из приложения Android
ОБНОВЛЕНИЕ: 04 января 2015 г.
У меня все еще есть эти проблемы. Пользователи нашего приложения увеличились, и я вижу все виды сетевых ошибок. Наше приложение отправляет электронные письма каждый раз там является связанной с сетью ошибкой в приложении.
Наше приложение выполняет финансовые транзакции - так что повторные подачи не действительно idempotent - так очень боятся возможности повторной попытки HttpClient. мы сделали какое-то кэширование ответов на сервере для обработки повторное предоставление, выполненное явно пользователем. Однако до сих пор нет решения, которое работает без плохой работы с пользователем.
Оригинальный вопрос
У меня есть приложение для Android, которое публикует данные как часть пользовательской операции. Данные включают в себя несколько изображений, и я их упаковываю как сообщение Protobuf (фактически массив байтов) и отправляю его на сервер через соединение HTTPS.
Хотя приложение работает отлично для большей части, но мы иногда видим ошибки подключения. Теперь проблема стала более выраженной, когда у нас есть некоторые пользователи в относительно медленных сетевых зонах (соединения 2G). Однако проблема не ограничивается медленными областями соединений, проблема наблюдается с клиентами, использующими соединения WiFi и 3G.
Вот несколько исключений, которые мы замечаем в наших журналах приложений
Ниже происходит через 5 минут, так как я установил тайм-аут Socket до 5 минут. Приложение пыталось отправить 145kb данных в этом случае
Трассировка стека java.net.SocketTimeoutException: время ожидания чтения на org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read (родной Метод) на org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $SSLInputStream.read(OpenSSLSocketImpl.java:662) at org.apache.http.impl.io.AbstractSessionInputBuffer.fillBuffer(AbstractSessionInputBuffer.java:103) на org.apache.http.impl.io.AbstractSessionInputBuffer.readLine(AbstractSessionInputBuffer.java:191)
Ниже одного случившегося 2,5 минуты (тайм-аут сокета был установлен на 5 минут), клиент отправил 144kb данных
javax.net.ssl.SSLException: Ошибка записи: ssl = 0x5e4f4640: ошибка ввода-вывода во время системного вызова, Сломанная труба на org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_write (родной Метод) на org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $SSLOutputStream.write(OpenSSLSocketImpl.java:704) at org.apache.http.impl.io.AbstractSessionOutputBuffer.write(AbstractSessionOutputBuffer.java:109) на org.apache.http.impl.io.ContentLengthOutputStream.write(ContentLengthOutputStream.java:113)
Ниже произошло 1 минуту.
Трассировка стека javax.net.ssl.SSLException: соединение закрыто одноранговым узлом at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake (родной Метод) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $SSLInputStream. (OpenSSLSocketImpl.java:634) на org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605)
Ниже произошло через 77 секунд
Трассировка стека javax.net.ssl.SSLException: SSL-квитирование отменено: ssl = 0x5e2baf00: ошибка ввода-вывода во время системного вызова, подключение reset от пользователя at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake (родной Метод) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:378) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl $SSLInputStream. (OpenSSLSocketImpl.java:634) на org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:605) at org.apache.http.impl.io.SocketInputBuffer. (SocketInputBuffer.java:70)
Ниже произошло 15 секунд (время ожидания подключения установлено на 15 секунд)
Время: 15081 Трассировка стека org.apache.http.conn.ConnectTimeoutException: подключиться к /103.xx.xx.xx:443 время ожидания на org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:144) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:365)
Вот фрагменты исходного кода, которые я использую для публикации reqeust
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 15000); //15 seconds
HttpConnectionParams.setSoTimeout(params, 300000); // 5 minutes
HttpClient client = getHttpClient(params);
HttpPost post = new HttpPost(uri);
post.setEntity(new ByteArrayEntity(requestByteArray));
HttpResponse httpResponse = client.execute(post);
....
public static HttpClient getHttpClient(HttpParams params) {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new TrustAllCertsSSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
DefaultHttpClient client = new DefaultHttpClient(ccm, params);
// below line of code will disable the retrying of HTTP request when connection is timed
// out.
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
return client;
} catch (Exception e) {
return new DefaultHttpClient();
}
}
Я прочитал несколько форумов, в которых говорится, что мы должны использовать класс HttpUrlConnection. Я сделал изменения кода, чтобы использовать https://code.google.com/p/basic-http-client/ в качестве исправления. Хотя он работал на моем телефоне Samsung, у него, казалось, была проблема с телефоном, который он использовал, он даже не смог подключиться к нашему сайту. Мне пришлось отбросить его назад, хотя я могу переустановить его, если основная причина может быть привязана к DefaultHttpClient.
Веб-сервер OUr - nginx, и наш веб-сервис работает на Apache Tomcat.
Клиенты в основном используют телефоны Android 4.1+. Клиент, чей телефон, который я получил над трассировкой стека, использует телефон Micromax A110Q с Android 4.2.1.
Любые вводные данные по этому вопросу будут высоко оценены. Большое спасибо!
Update:
- Я заметил, что мы не закрывали диспетчер подключений. Так что добавлен ниже код в конце блока кода, где я использую http-клиент.
if (client != null) { client.getConnectionManager().shutdown();
}
- Обновлена конфигурация nginx для приема данных размером 5M по умолчанию: 1Mb, а некоторые клиенты отправляют более 1 МБ, а сервер отделяет соединение с ошибкой 413.
client_max_body_size 5M;
- Также увеличился тайм-аут прокси-сервера nginx, так что он будет ждать больше времени для получения данных от клиента.
proxy_read_timeout 300;
С приведенными выше изменениями ошибки немного уменьшились. За последнюю неделю я вижу следующие два типа эротов:
-
org.apache.http.conn.ConnectTimeoutException: Connect to /103.xx.xx.xxx:443 timed out
- Это происходит через 15 секунд, это мой тайм-аут подключения. Я предполагаю, что это происходит, когда клиент не может связаться с сервером из-за неточности сети или, как отметил @JaySoyer, может быть связано с переключением сети.
-
java.net.SocketTimeoutException: SSL handshake timed out at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)
. Это происходит по истечении тайм-аута сокета. Я использую 1 минуту как тайм-аут сокета для небольших запросов и 3 и 6 минут для пакетов до 75 КБ и выше соответственно.
Однако эти ошибки значительно сократились, и я вижу 1 отказ в 100 запросах, по сравнению с более ранней версией моего кода, где это было 1 из 10 запросов.
Ответы
Ответ 1
Недавно мне пришлось сделать исчерпывающий анализ моего приложения для компании, поскольку мы видели кучу подобных ошибок и не знали почему. Мы закончили раздачу пользовательских приложений, которые буквально регистрировали время подключения, ошибки, качество сигнала и т.д. В файле. Это делалось неделями. Соберите тысячи точек данных. Имейте в виду, мы поддерживаем постоянное соединение, пока приложение открыто.
Оказывается, большинство наших ошибок было связано с коммутационными сетями. Это действительно очень распространено для обычного пользователя. Так что скажем, что пользователь использует сеть сотовых сетей EDGE, а затем идет в диапазоне WIFI или наоборот. Когда это происходит, Android буквально отключает соединение сотовой связи и делает совершенно новое соединение с WIFI. С точки зрения приложений, он похож на включение режима самолета, а затем снова отбрасывает его. Это происходит даже при переключении внутри сотовых сетей. Например, LTE для HSPA+. Каждый раз, когда это происходит, Android отключает сетевое соединение, измененное трансляцию.
Из приведенных вами сообщений это поведение вызывало следующие подобные ошибки:
- javax.net.ssl.SSLException: Ошибка записи: ssl = 0x5e4f4640
- javax.net.ssl.SSLException: SSL-квитирование отменено:
Иногда сетевой коммутатор работал быстро, иногда медленно. Оказывается, мы не своевременно очищали наши ресурсы быстрыми коммутаторами. В результате мы пытались повторно подключиться к нашим серверам со старыми/старыми TCP-соединениями, которые породили еще более странные ошибки.
Таким образом, я думаю, что удержание - если вы поддерживаете соединение в течение длительного периода времени, ожидайте, что телефон будет постоянно переключаться между сетями, особенно когда сигнал слабый. Когда этот сетевой коммутатор произойдет, вы увидите SSLExeptions и полностью нормальный. Просто убедитесь, что вы очищаете свои ресурсы и правильно подключаетесь.
Ответ 2
Поскольку вы имеете дело с тем, что выглядит как плохая сетевая связь, рассмотрите более отказоустойчивый HTTP-клиент. Мне нравится OkHTTP. Из их описания:
OkHttp сохраняется, когда сеть становится хлопотной: она будет молча восстанавливаться из общих проблем с подключением. Если ваша служба имеет несколько IP-адреса OkHttp будет пытаться использовать альтернативные адреса, если первый соединение не выполняется. Это необходимо для IPv4 + IPv6 и для обслуживаемых служб в избыточных центрах обработки данных. OkHttp инициирует новые соединения с современные функции TLS (SNI, ALPN) и возвращаются к SSLv3, если рукопожатие не работает.
Реализация будет в основном заменой замены.