Правильный способ отключения HTTP-компонентов Apache, блокирующих HTTP-сервер

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

org.apache.http.impl.bootstrap.HttpServer server;
....

public void stopServer(){
    server.shutdown(42, TimeUnit.MICROSECONDS);
}

У меня следующая проблема в Linux: Если открыты соединения Keep-alive (нет обработки запросов), эти сокеты не закрываются. Только ServerSocket закрыт:

netstat -aon | grep 58276
TCP    127.0.0.1:50658        127.0.0.1:58276        ESTABLISHED     18012
TCP    127.0.0.1:58276        127.0.0.1:50658        ESTABLISHED     18012

При попытке снова запустить HTTP-сервер на том же порту выдается исключение BindingException:

Caused by: java.net.BindException: Address already in use
at java.net.PlainSocketImpl.socketBind(Native Method)
at java.net.PlainSocketImpl.socketBind(PlainSocketImpl.java:521)
at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:414)
at java.net.ServerSocket.bind(ServerSocket.java:326)
at java.net.ServerSocket.<init>(ServerSocket.java:192)
at javax.net.DefaultServerSocketFactory.createServerSocket(ServerSocketFactory.java:170)
at org.apache.http.impl.bootstrap.HttpServer.start(HttpServer.java:116)

В Windows поведение такое же, однако BindingException отсутствует, и сервер без проблем запускает обработку запросов.


person Anton Krosnev    schedule 24.03.2016    source источник
comment
Не могли бы вы собрать тестовый пример, воспроизводящий проблему, и запустить JIRA по адресу issues.apache.org/jira?   -  person ok2c    schedule 24.03.2016


Ответы (2)


Оказывается, это ошибка: https://issues.apache.org/jira/browse/HTTPCORE-420 Олег уже предложил исправление. Я обновлю это, когда он будет выпущен официально.

person Anton Krosnev    schedule 12.04.2016

Ваша проблема в том, что соединение не закрыто. Спецификация HTTP не указывает, как долго должно поддерживаться постоянное соединение.

Из документации:

Некоторые HTTP-серверы используют нестандартный заголовок Keep-Alive, чтобы сообщить клиенту период времени в секундах, в течение которого они намерены поддерживать соединение на стороне сервера. HttpClient использует эту информацию, если она доступна. Если заголовок Keep-Alive отсутствует в ответе, HttpClient предполагает, что соединение можно поддерживать неопределенно долго.

Поэтому они советуют создать свой KeepAliveStrategy, чтобы разрывать эти соединения через определенное время:

ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {

    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        // Honor 'keep-alive' header
        HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(NumberFormatException ignore) {
                }
            }
        }
        HttpHost target = (HttpHost) context.getAttribute(
                HttpClientContext.HTTP_TARGET_HOST);
        if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
            // Keep alive for 5 seconds only
            return 5 * 1000;
        } else {
            // otherwise keep alive for 30 seconds
            return 30 * 1000;
        }
    }

};
CloseableHttpClient client = HttpClients.custom()
        .setKeepAliveStrategy(myStrategy)
        .build();
person user987339    schedule 24.03.2016
comment
Я использую http-компоненты сервера. Мой код работает на стороне сервера. Я не знаю, какие клиенты будут подключаться к моему серверу. - person Anton Krosnev; 24.03.2016