Разнообразие ошибок 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();
  }
  1. Обновлена ​​конфигурация nginx для приема данных размером 5M по умолчанию: 1Mb, а некоторые клиенты отправляют более 1 МБ, а сервер отделяет соединение с ошибкой 413.
client_max_body_size 5M;
  1. Также увеличился тайм-аут прокси-сервера 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, если рукопожатие не работает.

Реализация будет в основном заменой замены.