View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.testing.nio;
29  
30  import java.net.InetSocketAddress;
31  import java.util.concurrent.Future;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import org.apache.hc.core5.function.Supplier;
36  import org.apache.hc.core5.http.ContentType;
37  import org.apache.hc.core5.http.HttpHost;
38  import org.apache.hc.core5.http.HttpResponse;
39  import org.apache.hc.core5.http.HttpStatus;
40  import org.apache.hc.core5.http.HttpVersion;
41  import org.apache.hc.core5.http.Message;
42  import org.apache.hc.core5.http.Method;
43  import org.apache.hc.core5.http.ProtocolVersion;
44  import org.apache.hc.core5.http.URIScheme;
45  import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
46  import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
47  import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
48  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
49  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
50  import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
51  import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
52  import org.apache.hc.core5.http2.HttpVersionPolicy;
53  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester;
54  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
55  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
56  import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
57  import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
58  import org.apache.hc.core5.io.CloseMode;
59  import org.apache.hc.core5.reactor.IOReactorConfig;
60  import org.apache.hc.core5.reactor.ListenerEndpoint;
61  import org.apache.hc.core5.testing.SSLTestContexts;
62  import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
63  import org.apache.hc.core5.util.ReflectionUtils;
64  import org.apache.hc.core5.util.Timeout;
65  import org.hamcrest.CoreMatchers;
66  import org.hamcrest.MatcherAssert;
67  import org.junit.Assume;
68  import org.junit.Before;
69  import org.junit.BeforeClass;
70  import org.junit.Rule;
71  import org.junit.Test;
72  import org.junit.rules.ExternalResource;
73  import org.slf4j.Logger;
74  import org.slf4j.LoggerFactory;
75  
76  public class H2ProtocolNegotiationTest {
77  
78      private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
79  
80      private final Logger log = LoggerFactory.getLogger(getClass());
81  
82      private HttpAsyncServer server;
83  
84      @Rule
85      public ExternalResource serverResource = new ExternalResource() {
86  
87          @Override
88          protected void before() throws Throwable {
89              log.debug("Starting up test server");
90              server = H2ServerBootstrap.bootstrap()
91                      .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
92                      .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
93                      .setIOReactorConfig(
94                              IOReactorConfig.custom()
95                                      .setSoTimeout(TIMEOUT)
96                                      .build())
97                      .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
98                      .register("*", new Supplier<AsyncServerExchangeHandler>() {
99  
100                         @Override
101                         public AsyncServerExchangeHandler get() {
102                             return new EchoHandler(2048);
103                         }
104 
105                     })
106                     .setStreamListener(LoggingH2StreamListener.INSTANCE)
107                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
108                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
109                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
110                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
111                     .create();
112         }
113 
114         @Override
115         protected void after() {
116             log.debug("Shutting down test server");
117             if (server != null) {
118                 server.close(CloseMode.GRACEFUL);
119             }
120         }
121 
122     };
123 
124     private H2AsyncRequester requester;
125 
126     @Rule
127     public ExternalResource clientResource = new ExternalResource() {
128 
129         @Override
130         protected void before() throws Throwable {
131             log.debug("Starting up test client");
132             requester = H2RequesterBootstrap.bootstrap()
133                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
134                     .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
135                     .setIOReactorConfig(IOReactorConfig.custom()
136                             .setSoTimeout(TIMEOUT)
137                             .build())
138                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
139                     .setStreamListener(LoggingH2StreamListener.INSTANCE)
140                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
141                     .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
142                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
143                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
144                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
145                     .create();
146         }
147 
148         @Override
149         protected void after() {
150             log.debug("Shutting down test client");
151             if (requester != null) {
152                 requester.close(CloseMode.GRACEFUL);
153             }
154         }
155 
156     };
157 
158     private static int JAVA_VER;
159 
160     @BeforeClass
161     public static void determineJavaVersion() {
162         JAVA_VER = ReflectionUtils.determineJRELevel();
163     }
164 
165     @Before
166     public void checkVersion() {
167         Assume.assumeTrue("Java version must be 1.8 or greater", JAVA_VER >= 8);
168     }
169 
170     @Test
171     public void testForceHttp1() throws Exception {
172         server.start();
173         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
174         final ListenerEndpoint listener = future.get();
175         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
176         requester.start();
177 
178         final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
179         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_1, null);
180         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
181 
182         final Future<Message<HttpResponse, String>> resultFuture1 = endpoint.execute(
183                 new BasicRequestProducer(Method.POST, target, "/stuff",
184                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
185                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
186         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
187         MatcherAssert.assertThat(message1, CoreMatchers.notNullValue());
188         final HttpResponse response1 = message1.getHead();
189         MatcherAssert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
190         MatcherAssert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_1_1));
191     }
192 
193     @Test
194     public void testForceHttp2() throws Exception {
195         server.start();
196         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
197         final ListenerEndpoint listener = future.get();
198         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
199         requester.start();
200 
201         final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
202         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
203         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
204 
205         final Future<Message<HttpResponse, String>> resultFuture1 = endpoint.execute(
206                 new BasicRequestProducer(Method.POST, target, "/stuff",
207                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
208                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
209         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
210         MatcherAssert.assertThat(message1, CoreMatchers.notNullValue());
211         final HttpResponse response1 = message1.getHead();
212         MatcherAssert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
213         MatcherAssert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_2));
214     }
215 
216     @Test
217     public void testNegotiateProtocol() throws Exception {
218         server.start();
219         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0), URIScheme.HTTPS);
220         final ListenerEndpoint listener = future.get();
221         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
222         requester.start();
223 
224         final HttpHost target = new HttpHost(URIScheme.HTTPS.id, "localhost", address.getPort());
225         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.NEGOTIATE, null);
226         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
227 
228         final Future<Message<HttpResponse, String>> resultFuture1 = endpoint.execute(
229                 new BasicRequestProducer(Method.POST, target, "/stuff",
230                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
231                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
232         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
233         MatcherAssert.assertThat(message1, CoreMatchers.notNullValue());
234         final HttpResponse response1 = message1.getHead();
235         MatcherAssert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
236 
237         if (isAlpnSupported()) {
238             MatcherAssert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_2));
239         } else {
240             MatcherAssert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_1_1));
241         }
242     }
243 
244     private boolean isAlpnSupported() {
245         if (JAVA_VER == 8) {
246             // The 'java.version' property values are structured "1.8.0_[BUILD NUMBER]" in java 8 releases.
247             final Matcher matcher = Pattern.compile("^1\\.8\\.0_(\\d+)$")
248                     .matcher(System.getProperty("java.version"));
249             if (matcher.matches()) {
250                 final int java8Build = Integer.parseInt(matcher.group(1));
251                 // jep244 (alpn) was backported to java 8u251.
252                 // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8230977
253                 return java8Build >= 251;
254             }
255         }
256         return JAVA_VER > 8 && JAVA_VER < 16;
257     }
258 
259 }