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
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.
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.
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);
}