如何使用 Apache CloseableHttpClient 处理 HTTP/2

问题描述 投票:0回答:1

如果我将以下命令发送到服务器进行 HTTP/2 连接测试,效果很好。

 curl -v --http2  http://192.168.0.171:20002 
*   Trying 192.168.0.171:20002...
* Connected to 192.168.0.171 (192.168.0.171) port 20002
> GET / HTTP/1.1
> Host: 192.168.0.171:20002
> User-Agent: curl/8.7.1
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAoAAAAAIAAAAA
> 
* Request completely sent off
< HTTP/1.1 101 
< Connection: Upgrade
< Upgrade: h2c
< Date: Mon, 02 Dec 2024 07:08:28 GMT
< 
* Received 101, Switching to HTTP/2
< HTTP/2 200 
< content-type: text/plain;charset=UTF-8
< content-length: 13
< date: Mon, 02 Dec 2024 07:08:28 GMT
< 
* Connection #0 to host 192.168.0.171 left intact
OnlineTestApp%                                                  

但是,运行以下代码时,出现以下错误。

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.DefaultClientConnectionReuseStrategy;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.HttpConnectionFactory;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

/**
 * https://stackoverflow.com/questions/50294297/async-response-streaming-with-apache-async-http-client
 * https://stackoverflow.com/questions/77339910/how-do-i-get-inputstream-from-httpclient5-response
 * https://github.com/apache/httpcomponents-client/blob/master/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java
 */
public class ClosableHttpClientTest {

    @Test
    public void test() {

        CloseableHttpClient client = createClient(false);

        RequestConfig config = RequestConfig.custom()
                .setConnectionRequestTimeout(3, TimeUnit.SECONDS)
                .setResponseTimeout(3, TimeUnit.SECONDS)
                .build();


        HttpGet httpGet = new HttpGet("http://192.168.0.171:20002");
        httpGet.setVersion(HttpVersion.HTTP_2_0);
        httpGet.setConfig(config);

        try {
            ClassicHttpResponse resp = client.executeOpen(null, httpGet, null);
            try (InputStream in = resp.getEntity().getContent()) {
                byte[] buf = in.readAllBytes();
                System.err.println(new String(buf));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @BeforeAll
    static void beforeAll() {

    }

    private CloseableHttpClient createClient(boolean http2) {

        SSLConnectionSocketFactory sslsf = getTrustAllConnectionSocketFactory();

        int connTimeout = 3;
        ConnectionConfig connConfig = ConnectionConfig.custom()
                .setConnectTimeout(Timeout.of(connTimeout, TimeUnit.SECONDS))
                .build();

        PoolingHttpClientConnectionManager connManager = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslsf)
                .setMaxConnTotal(200)
                .setMaxConnPerRoute(100)
                .setDefaultConnectionConfig(connConfig)
                .setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX)
                .setConnPoolPolicy(PoolReusePolicy.LIFO)
                .build();
                ;

        CloseableHttpClient client = HttpClients.custom()
                .setUserAgent("TEST_AGENT")
                .setConnectionManager(connManager)
                .setConnectionReuseStrategy(DefaultClientConnectionReuseStrategy.INSTANCE)
                .disableAuthCaching()
                .disableRedirectHandling()
                .disableConnectionState()
                .disableCookieManagement()
                .evictExpiredConnections()
                .evictIdleConnections(TimeValue.of(10, TimeUnit.SECONDS))
                .build();

        int reqTimeout = 30; // 처리 프로세스는 10초 응답 처리.
        return client;
    }


    private static SSLConnectionSocketFactory getTrustAllConnectionSocketFactory() {
        SSLConnectionSocketFactory trustAllConnectionSocketFactory = null;
        try {
            final TrustStrategy acceptingTrustStrategy = (chain, authType) -> {
                // if (LOG.isDebugEnabled()) {
                //     int i = 0;
                //     for (X509Certificate cert : chain) {
                //         LOG.debug("HostnameVerifier.isTrusted chain[{}] {}", i, cert);
                //         i++;
                //     }
                //     LOG.debug("HostnameVerifier.isTrusted authType: {}", authType);
                // }
                return true;
            };

            final SSLContext sslContext = SSLContexts.custom()
                    .loadTrustMaterial(null, acceptingTrustStrategy)
                    .build();

            trustAllConnectionSocketFactory = SSLConnectionSocketFactoryBuilder.create()
                    .setSslContext(sslContext)
                    .setHostnameVerifier((s, sslSession) -> {
                        // if (LOG.isDebugEnabled()) {
                        // LOG.debug("HostnameVerifier.verify {} {}", s, sslSession);
                        // }
                        return true;
                    })
                    .setTlsVersions(TLS.V_1_3, TLS.V_1_2, TLS.V_1_1, TLS.V_1_0)
                    .build();

        } catch (Exception ex) {
            // LOG.error("failed to create trustAll SSLConnectionSocketFactory - {}", ex.getMessage(), ex);
            trustAllConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
            // LOG.error("use default SSLConnectionSocketFactory - {}", trustAllConnectionSocketFactory.getClass().getName());
        }
        return trustAllConnectionSocketFactory;
    }
}

16:05:01.069 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 >> GET / HTTP/2.0
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 >> Accept-Encoding: gzip, x-gzip, deflate
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 >> Host: 192.168.0.171:20002
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 >> Connection: keep-alive
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 >> User-Agent: Herb/3.0
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 >> "GET / HTTP/2.0[\r][\n]"
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 >> "Accept-Encoding: gzip, x-gzip, deflate[\r][\n]"
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 >> "Host: 192.168.0.171:20002[\r][\n]"
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 >> "Connection: keep-alive[\r][\n]"
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 >> "User-Agent: Herb/3.0[\r][\n]"
16:05:01.069 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 >> "[\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "HTTP/1.1 505 [\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "Content-Type: text/html;charset=utf-8[\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "Content-Language: en[\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "Content-Length: 465[\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "Date: Mon, 02 Dec 2024 07:02:05 GMT[\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "[\r][\n]"
16:05:01.083 [main] DEBUG org.apache.hc.client5.http.wire -- http-outgoing-0 << "<!doctype html><html lang="en"><head><title>HTTP Status 505 [0xffffffe2][0xffffff80][0xffffff93] HTTP Version Not Supported</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 505 [0xffffffe2][0xffffff80][0xffffff93] HTTP Version Not Supported</h1></body></html>"
16:05:01.085 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 << HTTP/1.1 505 
16:05:01.085 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 << Content-Type: text/html;charset=utf-8
16:05:01.085 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 << Content-Language: en
16:05:01.085 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 << Content-Length: 465
16:05:01.085 [main] DEBUG org.apache.hc.client5.http.headers -- http-outgoing-0 << Date: Mon, 02 Dec 2024 07:02:05 GMT
16:05:01.087 [main] DEBUG org.apache.hc.client5.http.impl.classic.MainClientExec -- ex-0000000001 connection can be kept alive for 3 MINUTES
16:05:01.088 [main] DEBUG org.apache.hc.client5.http.impl.classic.InternalHttpClient -- ep-0000000001 releasing valid endpoint
16:05:01.088 [main] DEBUG org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager -- ep-0000000001 releasing endpoint
16:05:01.088 [main] DEBUG org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager -- ep-0000000001 connection http-outgoing-0 can be kept alive for 3 MINUTES
16:05:01.088 [main] DEBUG org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager -- ep-0000000001 connection released [route: {}->http://192.168.0.171:20002][total available: 1; route allocated: 1 of 100; total allocated: 1 of 100]
<!doctype html><html lang="en"><head><title>HTTP Status 505 – HTTP Version Not Supported</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 505 – HTTP Version Not Supported</h1></body></html>

开发依赖如下

// apache http client
implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1'

使用CloseableHttpClient的原因是,当接收大量数据时,CloseableAsyncHttpClient返回一个InputStream,导致流处理无法进行。因此,我需要使用CloseableHttpClient,但问题是HTTP/2请求会导致错误。虽然 Java 11 HttpClient 可以作为替代方案,但由于内部情况而无法使用。我需要帮助。

使用 CloseableHttpClient 处理 HTTP/2 请求

http2 apache-httpclient-5.x
1个回答
0
投票
  1. HTTP 协议级别根据每个连接进行协商。传出消息的消息级别的协议版本只是一个提示,仅此而已。带有 HTTP/1.0 提示的消息可以使用 HTTP/1.0 兼容语义通过 HTTP/1.1 进行传输。通过 HTTP/1.1 连接的 HTTP/2.0 提示毫无意义。

  2. Classic HttpClient 仅支持 HTTP/1.1 协议。必须使用异步版本的 HttpClient 才能获得 HTTP/2.0 支持。通常,只要有可能,HTTP/2 就会自动协商。然而,可以强制 HttpCclient 仅使用 HTTP/2。

final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create()
        .setDefaultTlsConfig(TlsConfig.custom()
                .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
                .build())
        .build();
try (final CloseableHttpAsyncClient client = HttpAsyncClients.custom()
        .setConnectionManager(cm)
        .build()) {

    client.start();

    final SimpleHttpRequest request = SimpleRequestBuilder.get()
            .setHttpHost(new HttpHost("httpbin.org"))
            .setPath("/headers")
            .build();

    System.out.println("Executing request " + request);
    final Future<SimpleHttpResponse> future = client.execute(
            SimpleRequestProducer.create(request),
            SimpleResponseConsumer.create(),
            new FutureCallback<SimpleHttpResponse>() {

                @Override
                public void completed(final SimpleHttpResponse response) {
                    System.out.println(request + "->" + new StatusLine(response));
                    System.out.println(response.getBody());
                }

                @Override
                public void failed(final Exception ex) {
                    System.out.println(request + "->" + ex);
                }

                @Override
                public void cancelled() {
                    System.out.println(request + " cancelled");
                }

            });
    future.get();

    System.out.println("Shutting down");
    client.close(CloseMode.GRACEFUL);
}
© www.soinside.com 2019 - 2024. All rights reserved.