Существует два типа проблем, вызванных длительным http-соединением (keep-alive).Конечно, эти два вида проблем вызваны разными причинами.Здесь анализируются только исключения, связанные с keep-alive.
1 SocketException: Connection reset || SocketException: Connection reset reset by peer
Обычный журнал стека ошибок выглядит следующим образом:
Caused by: java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:209)
...
или
java.net.SocketException: Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method)
О SocketException описано в официальном документе:java8 connection_release
Существенной причиной являетсяСервер возвращает сообщение RST клиенту по протоколу TCP., указывая на то, что отправка и получение завершены.Если в это время клиент читает данные из потока, произойдет сброс соединения, а при записи данных в поток произойдет сброс соединения.Сброс соединения по пиру. То есть, если сначала возвращается RST, то произойдет исключение SocketException, если вы будете читать или писать в это время.
Что касается того, почему сервер вернетсяRST-сообщение, это может быть проблема, вызванная http keep-alive.
Если это сервер springboot, то по умолчаниютайм-аут проверки активности составляет 60 сек., но по умолчанию он не возвращает [Keep-Alive] в заголовке ответа.
Если клиент использует версию apache httpclient 4.x, по умолчанию keep-alive читает поле Keep-Alive в заголовке ответа, если нет, то оно бесконечно (в коде возвращается -1)."If the Keep-Alive header is not present in the response, HttpClient assumes the connection can be kept alive indefinitely", подробный код задается классом DefaultConnectionKeepAliveStrategy.class, код выглядит следующим образом:
public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
Args.notNull(response, "HTTP response");
final HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
final HeaderElement he = it.nextElement();
final String param = he.getName();
final String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch(final NumberFormatException ignore) {
}
}
}
return -1;
}
Таким образом, проблема относительно ясна.Хотя у Springboot есть keep-alive 60, он не возвращает заголовок Keep-Alive в ответе. Если клиент использует apache httpclient 4.x и вручную не устанавливает Keep-Alive, тайм-аут сервера будет 60 с, а клиент будет неограничен.В некоторых особых случаях сервер закрывает соединение, а клиент также установить это соединение, чтобы продолжить отправку и получение данных, что вызовет вышеуказанные проблемы.
Решение тоже очень простое.В документе httpclient 4.x также сказано установить таймаут по умолчанию, то есть дать пользовательскую реализацию. В частности, фиксированная продолжительность может быть передана непосредственно через setKeepAliveStrategy при его создании.
HttpClients.custom().setMaxConnPerRoute(xxx)
.setMaxConnTotal(xxx)
.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse httpResponse,
HttpContext httpContext) {
return keepalive;
}
}).build();
Сегодня я обнаружил, что httpclient выпустил новую версию 5.x. Я посмотрел на код и обнаружил, что в реализации keep-alive произошли небольшие изменения. Значение по умолчанию, возвращаемое без заголовка keep-alive, больше не - 1, но из In RequestConfiggetConnectionKeepAlive:
@Override
public TimeValue getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
Args.notNull(response, "HTTP response");
final Iterator<HeaderElement> it = MessageSupport.iterate(response, HeaderElements.KEEP_ALIVE);
while (it.hasNext()) {
final HeaderElement he = it.next();
final String param = he.getName();
final String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return TimeValue.ofSeconds(Long.parseLong(value));
} catch(final NumberFormatException ignore) {
}
}
}
final HttpClientContext clientContext = HttpClientContext.adapt(context);
final RequestConfig requestConfig = clientContext.getRequestConfig();
return requestConfig.getConnectionKeepAlive();
}
Давайте еще раз посмотрим на RequestConfig.Если у вас нет setConnectionKeepAlive, по умолчанию используется DEFAULT_CONN_KEEP_ALIVE = TimeValue.ofMinutes(3); 1 мин, но он настроен как Это то же самое, что и requestTimeout, который равен 3 мин. В любом случае, вы можете знать, каково конечное время выполнения), что составляет три минуты. Итак, когда мы создаем объект HttpClients, мы можем установить время ConnectionKeepAlive через RequestConfig, без необходимости setKeepAliveStrategy, конечно, мы также можем установитьKeepAliveStrategy. Обратите внимание, что свойство ConnectionKeepAlive отсутствует в RequestConfig в httpclient4.x.
2 NoHttpResponseException: xxx.xxx.xxx.xxx failed to respond
Caused by: org.apache.http.NoHttpResponseException: xxx.xxx.xxx.xxx failed to respond
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:141)
Давайте рассмотрим второй случай, когда возникает исключение при анализе заголовка ответа. Это означает, что запрос был отправлен на сервер, но возвращается NoHttpResponse. Это немного отличается от исключения в 1. Исключение в 1 заключается в том, что исключение выдается, когда прикладной уровень взаимодействует с локальным сетевым уровнем, и запрос не отправляется. Но на самом деле это также один из случаев, когда указанная выше поддержка активности вызывает аварийное завершение работы сокета и отправку RST. Функция socket.close() приложения имеет семантическую асимметрию с FIN TCP. Это можно понимать как два шага: после того, как запрос достигает сервера, socket.close() сначала закрывается, то есть приложение возвращает -1, чтобы указать конец потока, поэтому сервер не может обработать запрос, и клиент сообщает об ошибке.
Краткое резюме:
1 Сервер возвращает RST клиенту, но клиент готов продолжать использовать это соединение для чтения и записи, что вызовет SocketException: Connection reset.
2 Сначала клиент использовал соединение для отправки http-запроса на сервер, но в это время служба по некоторым причинам вернула RST, в результате чего httpclient клиента выдал исключение NoHttpResponseException.
Чтобы решить эту проблему, вызванную закрытием сервера перед клиентом, позвольте клиенту активно закрывать соединение перед сервером, то есть установите значение проверки активности на клиенте меньше, чем на сервере. Как и в приведенном выше примере, мы можем уменьшить указанное выше исключение, установив продолжительность поддержания активности в клиенте менее 60 секунд.
Есть много причин для этих двух типов исключений.Здесь я проведу некоторый анализ и обработку на основе того, с чем столкнулся.
Дополнительные очки знаний:
-
Заголовок «Connection: Keep-Alive» используется только в HTTP 1.0, а в HTTP 1.1 по умолчанию это Keep-Alive, поэтому нет необходимости добавлять заголовок «Connection: Keep-Alive». Таким образом, в springboot версии 1.5 и выше, даже если вы добавите заголовок «Connection: Keep-Alive» в заголовок запроса, в возвращаемом заголовке нет соединения. Ссылки по теме:GitHub.com/spring-pro — это…
-
Команда curl может использовать --http1.0 для принудительного использования протокола http1.0.