我正在编写一个使用 HttpClient 5.0 beta 的客户端来请求/加载支持 HTTP2 的 Tomcat 中的安全 URL/资源。程序如下。它取自 Apache httpclient 5 示例(除了使用 loadTrustMaterial 创建 SSL 上下文的方式外,代码完全相同)
public class Http2TlsAlpnRequestExecutionExample {
public final static void main(final String[] args) throws Exception {
String trustStorePath = "C:\\cert\\keystore.jks";
String trustStorePassword = "password";
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new File(trustStorePath), trustStorePassword.toCharArray()).build();
// Create and start requester
H2Config h2Config = H2Config.custom()
.setPushEnabled(false)
.build();
final HttpAsyncRequester requester = H2RequesterBootstrap.bootstrap().setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
.setH2Config(h2Config)
.setTlsStrategy(new H2ClientTlsStrategy(sslContext, new SSLSessionVerifier() {
public TlsDetails verify(final NamedEndpoint endpoint, final SSLEngine sslEngine) throws SSLException {
return null;
}
}))
.setStreamListener(new Http2StreamListener() {
public void onHeaderInput(final HttpConnection connection, final int streamId, final List<? extends Header> headers) {
for (int i = 0; i < headers.size(); i++) {
System.out.println(connection + " (" + streamId + ") << " + headers.get(i));
}
}
public void onHeaderOutput(final HttpConnection connection, final int streamId, final List<? extends Header> headers) {
for (int i = 0; i < headers.size(); i++) {
System.out.println(connection + " (" + streamId + ") >> " + headers.get(i));
}
}
public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) {
}
public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) {
}
public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) {
}
public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) {
}
})
.create();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("HTTP requester shutting down");
requester.shutdown(ShutdownType.GRACEFUL);
}
});
requester.start();
HttpHost target = new HttpHost("localhost", 1090, "https");
String[] requestUris = new String[] {"/rest/rest/helloWorld"};
final CountDownLatch latch = new CountDownLatch(requestUris.length);
for (final String requestUri: requestUris) {
final Future<AsyncClientEndpoint> future = requester.connect(target, Timeout.ofSeconds(5));
final AsyncClientEndpoint clientEndpoint = future.get();
clientEndpoint.execute(
new BasicRequestProducer("GET", target, requestUri),
new BasicResponseConsumer<String>(new StringAsyncEntityConsumer()),
new FutureCallback<Message<HttpResponse, String>>() {
public void completed(final Message<HttpResponse, String> message) {
clientEndpoint.releaseAndReuse();
HttpResponse response = message.getHead();
String body = message.getBody();
System.out.println(requestUri + "->" + response.getCode() + " " + response.getVersion());
System.out.println(body);
latch.countDown();
}
public void failed(final Exception ex) {
clientEndpoint.releaseAndDiscard();
System.out.println(requestUri + "->" + ex);
ex.printStackTrace();
latch.countDown();
}
public void cancelled() {
clientEndpoint.releaseAndDiscard();
System.out.println(requestUri + " cancelled");
latch.countDown();
}
});
}
latch.await();
System.out.println("Shutting down I/O reactor");
requester.initiateShutdown();
}
}
运行代码时出现以下异常。
java.io.IOException: An existing connection was forcibly closed by the remote host
at sun.nio.ch.SocketDispatcher.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:197)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
at org.apache.hc.core5.reactor.ssl.SSLIOSession.receiveEncryptedData(SSLIOSession.java:443)
at org.apache.hc.core5.reactor.ssl.SSLIOSession.isAppInputReady(SSLIOSession.java:498)
at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:112)
at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:50)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:173)
at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:123)
at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:80)
at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
at java.lang.Thread.run(Thread.java:745)
如果我没有在 H2RequesterBootstrap 上将设置版本策略设置为 HttpVersionPolicy.FORCE_HTTP_2,它可以工作,但在这种情况下它使用 HTTP/1.1。但我只能使用 HTTP2。
只是为了检查一下,我用 Jetty 的 HTTP2 客户端试了一下,它工作正常。但我只能使用 Apache HttpClient 5.0。
你能帮忙吗?谢谢。