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.impl.bootstrap.HttpAsyncServer;
45  import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
46  import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
47  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
48  import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
49  import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
50  import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
51  import org.apache.hc.core5.http2.HttpVersionPolicy;
52  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2AsyncRequester;
53  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
54  import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
55  import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
56  import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
57  import org.apache.hc.core5.io.CloseMode;
58  import org.apache.hc.core5.reactor.IOReactorConfig;
59  import org.apache.hc.core5.reactor.ListenerEndpoint;
60  import org.apache.hc.core5.testing.SSLTestContexts;
61  import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
62  import org.apache.hc.core5.util.ReflectionUtils;
63  import org.apache.hc.core5.util.Timeout;
64  import org.hamcrest.CoreMatchers;
65  import org.junit.Assert;
66  import org.junit.Assume;
67  import org.junit.Before;
68  import org.junit.BeforeClass;
69  import org.junit.Rule;
70  import org.junit.Test;
71  import org.junit.rules.ExternalResource;
72  import org.slf4j.Logger;
73  import org.slf4j.LoggerFactory;
74  
75  public class H2ProtocolNegotiationTest {
76  
77      private static final Timeout TIMEOUT = Timeout.ofSeconds(30);
78  
79      private final Logger log = LoggerFactory.getLogger(getClass());
80  
81      private HttpAsyncServer server;
82  
83      @Rule
84      public ExternalResource serverResource = new ExternalResource() {
85  
86          @Override
87          protected void before() throws Throwable {
88              log.debug("Starting up test server");
89              server = H2ServerBootstrap.bootstrap()
90                      .setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext(), SecureAllPortsStrategy.INSTANCE))
91                      .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
92                      .setIOReactorConfig(
93                              IOReactorConfig.custom()
94                                      .setSoTimeout(TIMEOUT)
95                                      .build())
96                      .setTlsStrategy(new H2ServerTlsStrategy(
97                              SSLTestContexts.createServerSSLContext(),
98                              SecureAllPortsStrategy.INSTANCE))
99                      .register("*", new Supplier<AsyncServerExchangeHandler>() {
100 
101                         @Override
102                         public AsyncServerExchangeHandler get() {
103                             return new EchoHandler(2048);
104                         }
105 
106                     })
107                     .setStreamListener(LoggingH2StreamListener.INSTANCE)
108                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
109                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
110                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
111                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
112                     .create();
113         }
114 
115         @Override
116         protected void after() {
117             log.debug("Shutting down test server");
118             if (server != null) {
119                 server.close(CloseMode.GRACEFUL);
120             }
121         }
122 
123     };
124 
125     private H2AsyncRequester requester;
126 
127     @Rule
128     public ExternalResource clientResource = new ExternalResource() {
129 
130         @Override
131         protected void before() throws Throwable {
132             log.debug("Starting up test client");
133             requester = H2RequesterBootstrap.bootstrap()
134                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
135                     .setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
136                     .setIOReactorConfig(IOReactorConfig.custom()
137                             .setSoTimeout(TIMEOUT)
138                             .build())
139                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
140                     .setStreamListener(LoggingH2StreamListener.INSTANCE)
141                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
142                     .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
143                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
144                     .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
145                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
146                     .create();
147         }
148 
149         @Override
150         protected void after() {
151             log.debug("Shutting down test client");
152             if (requester != null) {
153                 requester.close(CloseMode.GRACEFUL);
154             }
155         }
156 
157     };
158 
159     private static int javaVersion;
160 
161     @BeforeClass
162     public static void determineJavaVersion() {
163         javaVersion = ReflectionUtils.determineJRELevel();
164     }
165 
166     @Before
167     public void checkVersion() {
168         Assume.assumeTrue("Java version must be 1.8 or greater", javaVersion > 7);
169     }
170 
171     @Test
172     public void testForceHttp1() throws Exception {
173         server.start();
174         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
175         final ListenerEndpoint listener = future.get();
176         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
177         requester.start();
178 
179         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
180         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_1, null);
181         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
182 
183         final Future<Message<HttpResponse, String>> resultFuture1 = endpoint.execute(
184                 new BasicRequestProducer(Method.POST, target, "/stuff",
185                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
186                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
187         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
188         Assert.assertThat(message1, CoreMatchers.notNullValue());
189         final HttpResponse response1 = message1.getHead();
190         Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
191         Assert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_1_1));
192     }
193 
194     @Test
195     public void testForceHttp2() throws Exception {
196         server.start();
197         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
198         final ListenerEndpoint listener = future.get();
199         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
200         requester.start();
201 
202         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
203         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.FORCE_HTTP_2, null);
204         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
205 
206         final Future<Message<HttpResponse, String>> resultFuture1 = endpoint.execute(
207                 new BasicRequestProducer(Method.POST, target, "/stuff",
208                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
209                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
210         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
211         Assert.assertThat(message1, CoreMatchers.notNullValue());
212         final HttpResponse response1 = message1.getHead();
213         Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
214         Assert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_2));
215     }
216 
217     @Test
218     public void testNegotiateProtocol() throws Exception {
219         server.start();
220         final Future<ListenerEndpoint> future = server.listen(new InetSocketAddress(0));
221         final ListenerEndpoint listener = future.get();
222         final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
223         requester.start();
224 
225         final HttpHost target = new HttpHost("https", "localhost", address.getPort());
226         final Future<AsyncClientEndpoint> connectFuture = requester.connect(target, TIMEOUT, HttpVersionPolicy.NEGOTIATE, null);
227         final AsyncClientEndpoint endpoint = connectFuture.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
228 
229         final Future<Message<HttpResponse, String>> resultFuture1 = endpoint.execute(
230                 new BasicRequestProducer(Method.POST, target, "/stuff",
231                         new StringAsyncEntityProducer("some stuff", ContentType.TEXT_PLAIN)),
232                 new BasicResponseConsumer<>(new StringAsyncEntityConsumer()), null);
233         final Message<HttpResponse, String> message1 = resultFuture1.get(TIMEOUT.getDuration(), TIMEOUT.getTimeUnit());
234         Assert.assertThat(message1, CoreMatchers.notNullValue());
235         final HttpResponse response1 = message1.getHead();
236         Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(HttpStatus.SC_OK));
237 
238         if (isAlpnSupported()) {
239             Assert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_2));
240         } else {
241             Assert.assertThat(response1.getVersion(), CoreMatchers.<ProtocolVersion>equalTo(HttpVersion.HTTP_1_1));
242         }
243     }
244 
245     private boolean isAlpnSupported() {
246         if (javaVersion == 8) {
247             // The 'java.version' property values are structured "1.8.0_[BUILD NUMBER]" in java 8 releases.
248             final Matcher matcher = Pattern.compile("^1\\.8\\.0_(\\d+)$")
249                     .matcher(System.getProperty("java.version"));
250             if (matcher.matches()) {
251                 final int java8Build = Integer.parseInt(matcher.group(1));
252                 // jep244 (alpn) was backported to java 8u251.
253                 // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8230977
254                 return java8Build >= 251;
255             }
256         }
257         return javaVersion > 8;
258     }
259 
260 }