0

我正在编写一个使用 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。

你能帮忙吗?谢谢。

4

1 回答 1

0

我认为您尝试连接的服务器未启用 http2,您需要将 webapp 配置为接受 http2 请求。如果您使用的是apache tomcat,则需要tomcat 9.x 和JDK9 或更高版本才能支持http2。检查here为apache tomcat配置http2。您还需要安装支持 http2 的 openssl 和 apr 库。

按照此链接安装 apr 和 openssl 库

于 2019-01-28T09:16:27.357 回答