Android (Java) Тихая повторная попытка HttpURLConnection по тайм-ауту чтения

Поэтому я использую Google Volley для HTTP-запроса, который в основном использует HttpURLConnection Java.

Согласно моим тестам, проблема заключается в следующем:
Когда время ожидания "чтения" на HttpURLConnection достигает, выполняется повторная попытка без вывода сообщений до закрытия соединения и создания соответствующего исключения (SocketTimeoutException).

Обратите внимание на следующее:
– Я заметил эту ошибку при использовании запроса HTTP POST.
– Тайм-аут «чтения» отличается от тайм-аута «подключения».
– Если тайм-аут «чтения» ( задано вызовом connection.setReadTimeout(int)) НЕ установлено (0) или установлено большее значение, чем connection.setConnectTimeout(int), эта ошибка не возникает.
- Эта проблема обсуждалась, здесь, например, но я не нашел удовлетворительного решения.
 — Несколько связанную проблему можно найти здесь, но я не уверен, что это актуально (не так ли?)

Дополнительная информация
Мое приложение используется для оплаты денег, поэтому крайне важно не повторять запрос (да, я знаю, что сервер может обработать его, но я все равно хочу, чтобы мой клиент был "правильным" ).

Когда установлен тайм-аут «чтения», в случае, если соединение с сервером установлено, но сервер ждет/спит/задерживает ответ на это время «тайм-аута» перед ответом (таким образом, возникает исключение «чтение», а не исключение «подключение») , другой (тихий) запрос отправляется непосредственно перед тем, как возникнет это исключение, что приводит к 2 похожим запросам, что недопустимо.

Какое решение я ищу?
Хорошо, решение, которое хорошо решит эту проблему/ошибку, точно так же, как описано в исправлении здесь (но я опять же думаю, что в данном случае это не имеет значения).
Кроме того, я хотел бы сохранить исходный поток как есть, то есть не принудительно закрывать соединение или что-то в этом роде.

Что я собираюсь сделать сейчас, так это установить время ожидания «чтения» в два раза больше времени ожидания «соединения» (они начинают отсчет в одно и то же время), чтобы убедиться, что сначала возникает исключение «соединение». Я также попытаюсь преодолеть эту проблему на стороне сервера. Проблема в том, что этот тайм-аут «чтения» существует по какой-то причине, и моя текущая реализация практически просто игнорирует его и обрабатывает только тайм-ауты «соединения».

EDIT RetryPolicy библиотеки Volley не влияет на эту проблему, так как это повторная попытка без вывода сообщений. Я заглянул как можно глубже в библиотеку. Логи/брейкпойнты везде, отменил вызовы на повтор. Вот откуда я знаю, что это на 99,99% проблема HttpURLConnection.


person guy_m    schedule 23.11.2014    source источник
comment
ты смог ее решить?   -  person Aamir Abro    schedule 14.05.2015
comment
Не идеальное решение, но, как я уже сказал, когда я делаю это сейчас, время ожидания «чтения» устанавливается равным или вдвое больше времени ожидания «соединения». Я отвечу себе, чтобы другие люди могли легко увидеть это решение.   -  person guy_m    schedule 14.05.2015


Ответы (4)


Это плохое решение было принято разработчиком в 2006 году. Вот хорошая цитата от кого-то, которая объясняет всю ситуацию с точки зрения Java:

«Как вы уже, наверное, догадались, это ошибка (http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6382788). Не механизм повтора, конечно, это просто дерьмо. Ошибка в том, что это также происходит для POST (который по умолчанию не является идемпотентным для HTTP RFC) ... Но не волнуйтесь, Билл уже давно исправил эту ошибку. Билл исправил ее, введя переключатель. Билл узнал об обратной совместимости. Билл решил, что лучше оставить переключатель включенным по умолчанию, потому что это приведет к ошибкам. обратно совместим. Билл улыбается. Он уже может видеть лица удивленных разработчиков по всему миру, которые столкнулись с этим. Пожалуйста, не уподобляйтесь Биллу?"
Источник

Итак, предлагаемое решение:

System.setProperty("sun.net.http.retryPost", "false")

Но мы не можем сделать это на Android! Тогда единственное оставшееся решение:

httpURLConnection.setChunkedStreamingMode(0);

Похоже, что это работает, но не очень эффективно из-за времени запроса перспектива.

РЕДАКТИРОВАТЬ: я не мог использовать эту реализацию, поэтому искал альтернативную библиотеку. Я обнаружил, что реализация HttpUrlConnection использует OkHttp, начиная с Android 4.4. Поскольку OkHttp является открытым исходным кодом, я мог бы поискать, есть ли у них проблемы с тихими повторными попытками. И да, у них были с этим проблемы, и они исправили их в апреле 2016 года. CommonsWare (настоящий эксперт по Android) объясняет, что каждый производитель может решить, какую реализацию он может использовать. Как следствие этого, должно быть много устройств, которые выполняют повторные попытки POST-запросов без вывода сообщений, и как разработчик мы можем попробовать только некоторые обходные пути.

Моим решением будет изменить библиотеку

РЕДАКТИРОВАТЬ 2: Чтобы дать вам окончательный ответ: теперь вы можете использовать OkHttp в качестве транспортного уровня для Volley с минимальным кодом.

Еще одно полезное решение

person oli    schedule 07.06.2016
comment
Итак... httpURLConnection.setChunkedStreamingMode(0) не приведет к повторным попыткам? Пожалуйста, объясните эффект в ответе :) И да, именно такой ответ я хотел бы услышать... - person guy_m; 07.06.2016
comment
У меня были проблемы с предложенным решением, поэтому я отредактировал ответ. - person oli; 08.06.2016
comment
Пожалуйста, добавьте ссылку на мой ответ ниже. Я полагаю, это все еще может помочь другим... спасибо. - person guy_m; 08.06.2016
comment
другое решение - использовать setFixedLengthStreamingMode(contentLength), который решает вашу запрашиваемую временную перспективу. прочитайте это issuetracker.google.com/issues/37036206 - person steveen zoleko; 29.08.2017

Что ж, прошло много времени, поэтому я решил, что отвечу своим текущим (не идеальным) решением, чтобы другие люди могли его легко увидеть (также написано внутри вопроса).

Просто установите для readTimeout значение 0 (без тайм-аута) или, по крайней мере, значение тайм-аута соединения. Это гарантирует, что тайм-аут соединения будет увеличен до тайм-аута чтения.

int timeoutMs = request.getTimeoutMs();    //  or whatever
connection.setConnectTimeout(timeoutMs);   //  actual timeout desired
connection.setReadTimeout(timeoutMs);      //  >= timeoutMs or 0

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

Как уже было сказано, на самом деле это не «решение», а очень хорошее и на 90% полезное исправление.

person guy_m    schedule 14.05.2015

Вы можете проверить класс DefaultRetryPolicy Volley.

person Hadi Tok    schedule 23.11.2014
comment
Спасибо, но эта проблема глубже. Первым делом я проверил политику повторных попыток и отменил ее. Эта повторная попытка бесшумна, поэтому Волей об этом не знает. - person guy_m; 24.11.2014

Как сказано в одной из приведенных вами ссылок, вам нужно установить sun.net.http.retryPost на false.

Или не используйте режим передачи с фиксированной длиной или фрагментами: см. этот отчет об ошибке< /а>.

person user207421    schedule 23.11.2014
comment
Я не могу найти солнце как зависимость, есть идеи? - person guy_m; 24.11.2014