Рубрики
Без рубрики

HttpClient не может подключиться к прокси-серверу TLS

Это будет короткий и по существу пост. Главным образом потому, что я уже порядком устал от… Помеченный java, прокси, tls, ssl.

Это будет короткий и по существу пост. Главным образом потому, что я уже порядком устал от попыток разобраться в этом 😅 .

HttpClient Java ( java.net.http. HttpClient ) позволяет указать прокси-сервер для маршрутизации всех запросов таким образом:

final HttpClient client = 
  HttpClient.newBuilder()
    .proxy(ProxySelector.of(new InetSocketAddress(proxyHost, proxyPort)))
     // other builder config goes here
    .build();

То, что я обнаружил, пытаясь создать некоторый код для связи с прокси-сервером, который мы используем в моей компании, заключается в том, что если прокси-хост (который является просто адресом хоста без какого-либо протокола) и прокси-порт указывают на конечную точку, зашифрованную с помощью TLS, текущая реализация Java не будет работать. Это связано с тем, что реализация предполагает, что соединение (через запрос CONNECT ) устанавливается без TLS.

Код

Мои выводы были обнаружены во время отладки небольшого образца приложения и ссылки на базу кода openjdk/jdk .

Во-первых, вы можете обратиться к getConnection в Http - соединение , которое вызывается при попытке выполнить Http - запрос .

    /**
     * Factory for retrieving HttpConnections. A connection can be retrieved
     * from the connection pool, or a new one created if none available.
     *
     * The given {@code addr} is the ultimate destination. Any proxies,
     * etc, are determined from the request. Returns a concrete instance which
     * is one of the following:
     *      {@link PlainHttpConnection}
     *      {@link PlainTunnelingConnection}
     *
     * The returned connection, if not from the connection pool, must have its,
     * connect() or connectAsync() method invoked, which ( when it completes
     * successfully ) renders the connection usable for requests.
     */
    public static HttpConnection getConnection(InetSocketAddress addr,
                                               HttpClientImpl client,
                                               HttpRequestImpl request,
                                               Version version) {
        // The default proxy selector may select a proxy whose  address is
        // unresolved. We must resolve the address before connecting to it.
        InetSocketAddress proxy = Utils.resolveAddress(request.proxy());
        HttpConnection c = null;
        boolean secure = request.secure();
        ConnectionPool pool = client.connectionPool();


        if (!secure) {
            c = pool.getConnection(false, addr, proxy);
            if (c != null && c.checkOpen() /* may have been eof/closed when in the pool */) {
                final HttpConnection conn = c;
                if (DEBUG_LOGGER.on())
                    DEBUG_LOGGER.log(conn.getConnectionFlow()
                                     + ": plain connection retrieved from HTTP/1.1 pool");
                return c;
            } else {
                return getPlainConnection(addr, proxy, request, client);
            }
        } else {  // secure
            if (version != HTTP_2) { // only HTTP/1.1 connections are in the pool
                c = pool.getConnection(true, addr, proxy);
            }
            if (c != null && c.isOpen()) {
                final HttpConnection conn = c;
                if (DEBUG_LOGGER.on())
                    DEBUG_LOGGER.log(conn.getConnectionFlow()
                                     + ": SSL connection retrieved from HTTP/1.1 pool");
                return c;
            } else {
                String[] alpn = null;
                if (version == HTTP_2 && hasRequiredHTTP2TLSVersion(client)) {
                    alpn = new String[] { "h2", "http/1.1" };
                }
                return getSSLConnection(addr, proxy, alpn, request, client);
            }
        }
    }


    private static HttpConnection getSSLConnection(InetSocketAddress addr,
                                                   InetSocketAddress proxy,
                                                   String[] alpn,
                                                   HttpRequestImpl request,
                                                   HttpClientImpl client) {
        if (proxy != null)
            return new AsyncSSLTunnelConnection(addr, client, alpn, proxy,
                                                proxyTunnelHeaders(request));
        else
            return new AsyncSSLConnection(addr, client, alpn);
    }

Просматривая его самостоятельно, вы обнаружите, независимо от пункта назначения/целевого адреса, который вы пытаетесь ввести в запрос (http://или https://), необходимо установить соединение с указанным прокси-сервером , который, опять же, ожидает передачи через TLS.

Если ваша цель – http://, то вызовите получить Простое Соединение сделано. Это приводит к установлению соединения с прокси-сервером через Простое прокси-соединение , которое, насколько я вижу, установит соединение с сокетом, но не согласует рукопожатие TLS при отправке запроса на подключение.

Если ваша цель – https://, то выполняется вызов получить SSL-соединение . Это приводит к установлению соединения с прокси-сервером через Асинхронное SSL-туннельное соединение , которое, согласно его собственной документации, является “SSL-туннелем, построенным на обычном (ПОДКЛЮЧАЕМОМ) TCP-туннеле” .

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

Связанный

Оригинал: “https://dev.to/kdrakon/httpclient-can-t-connect-to-a-tls-proxy-118a”