0

If I send the following command to the server for HTTP/2 connection testing, it works well.

 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%                                                  

However, when running the following code, the following error occurs.

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>

The development dependencies are as follows

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

The reason for using CloseableHttpClient is that when receiving large amounts of data, CloseableAsyncHttpClient returns an InputStream, making stream processing impossible. Therefore, I need to use CloseableHttpClient, but the problem is that HTTP/2 requests result in errors. While Java 11 HttpClient could be an alternative, it cannot be used due to internal circumstances. I need help.

Handling HTTP/2 requests using CloseableHttpClient

4
  • your code is correct. If I ran your test agains httpbin.org/get - works well. I see differenct in curl/and java code that you ran curl without https (-k flag). Commented Dec 2, 2024 at 7:40
  • I set HttpGet httpGet = new HttpGet("https://httpbin.org/get");in the code and tested it again, but the same 505 error occurred. Are you sure it works fine? Could you check my code to see if I made a mistake somewhere? Commented Dec 2, 2024 at 8:03
  • 1
    yes, my bad. I tried with client=5.4.1 and it worked fine, but with 5.3.1 I got the same error as you. Commented Dec 2, 2024 at 12:02
  • I tested it with version 5.4.1. Even after configuring it for HTTP/2, I confirmed that the communication is actually happening over HTTP/1.1. It seems that CloseableHttpClient does not support HTTP/2. Nonetheless, I want to express my gratitude for your help. Commented Dec 3, 2024 at 5:24

1 Answer 1

2
  1. HTTP protocol level gets negotiated on the per connection basis. The protocol version at the message level for outgoing messages is merely a hint, nothing more. A message with a HTTP/1.0 hint can be transmitted over HTTP/1.1 using HTTP/1.0 compatible semantics. HTTP/2.0 hint over HTTP/1.1 connection makes no sense.

  2. Classic HttpClient supports HTTP/1.1 protocol only. One must use async version of HttpClient in order to get HTTP/2.0 support. Usually HTTP/2 will be automatically negotiated whenever possible. One, however, can force HttpCclient to use HTTP/2 only.

  3. HttpClient presently does not support h2c upgrade over HTTP/1.1. One must either use TLS and have HTTP/2 negotated or force HTTP/2 over plain connections

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);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.