1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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
253
254 return java8Build >= 251;
255 }
256 }
257 return javaVersion > 8;
258 }
259
260 }