• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net;
6 
7 import android.content.Context;
8 import android.os.ConditionVariable;
9 
10 import org.chromium.base.Log;
11 import org.chromium.net.test.util.CertTestUtil;
12 
13 import java.io.File;
14 import java.util.concurrent.CountDownLatch;
15 
16 import io.netty.bootstrap.ServerBootstrap;
17 import io.netty.channel.Channel;
18 import io.netty.channel.ChannelHandlerContext;
19 import io.netty.channel.ChannelInitializer;
20 import io.netty.channel.ChannelOption;
21 import io.netty.channel.EventLoopGroup;
22 import io.netty.channel.nio.NioEventLoopGroup;
23 import io.netty.channel.socket.SocketChannel;
24 import io.netty.channel.socket.nio.NioServerSocketChannel;
25 import io.netty.handler.codec.http2.Http2SecurityUtil;
26 import io.netty.handler.logging.LogLevel;
27 import io.netty.handler.logging.LoggingHandler;
28 import io.netty.handler.ssl.ApplicationProtocolConfig;
29 import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
30 import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
31 import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
32 import io.netty.handler.ssl.ApplicationProtocolNames;
33 import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
34 import io.netty.handler.ssl.OpenSslServerContext;
35 import io.netty.handler.ssl.SslContext;
36 import io.netty.handler.ssl.SupportedCipherSuiteFilter;
37 
38 /**
39  * Wrapper class to start a HTTP/2 test server.
40  */
41 public final class Http2TestServer {
42     private static Channel sServerChannel;
43     private static final String TAG = Http2TestServer.class.getSimpleName();
44 
45     private static final String HOST = "127.0.0.1";
46     // Server port.
47     private static final int PORT = 8443;
48 
49     private static ReportingCollector sReportingCollector;
50 
shutdownHttp2TestServer()51     public static boolean shutdownHttp2TestServer() throws Exception {
52         if (sServerChannel != null) {
53             sServerChannel.close().sync();
54             sServerChannel = null;
55             sReportingCollector = null;
56             return true;
57         }
58         return false;
59     }
60 
getServerHost()61     public static String getServerHost() {
62         return HOST;
63     }
64 
getServerPort()65     public static int getServerPort() {
66         return PORT;
67     }
68 
getServerUrl()69     public static String getServerUrl() {
70         return "https://" + HOST + ":" + PORT;
71     }
72 
getReportingCollector()73     public static ReportingCollector getReportingCollector() {
74         return sReportingCollector;
75     }
76 
getEchoAllHeadersUrl()77     public static String getEchoAllHeadersUrl() {
78         return getServerUrl() + Http2TestHandler.ECHO_ALL_HEADERS_PATH;
79     }
80 
getEchoHeaderUrl(String headerName)81     public static String getEchoHeaderUrl(String headerName) {
82         return getServerUrl() + Http2TestHandler.ECHO_HEADER_PATH + "?" + headerName;
83     }
84 
getEchoMethodUrl()85     public static String getEchoMethodUrl() {
86         return getServerUrl() + Http2TestHandler.ECHO_METHOD_PATH;
87     }
88 
89     /**
90      * When using this you must provide a CountDownLatch in the call to startHttp2TestServer.
91      * The request handler will continue to hang until the provided CountDownLatch reaches 0.
92      *
93      * @return url of the server resource which will hang indefinitely.
94      */
getHangingRequestUrl()95     public static String getHangingRequestUrl() {
96         return getServerUrl() + Http2TestHandler.HANGING_REQUEST_PATH;
97     }
98 
99     /**
100      * @return url of the server resource which will echo every received stream data frame.
101      */
getEchoStreamUrl()102     public static String getEchoStreamUrl() {
103         return getServerUrl() + Http2TestHandler.ECHO_STREAM_PATH;
104     }
105 
106     /**
107      * @return url of the server resource which will echo request headers as response trailers.
108      */
getEchoTrailersUrl()109     public static String getEchoTrailersUrl() {
110         return getServerUrl() + Http2TestHandler.ECHO_TRAILERS_PATH;
111     }
112 
113     /**
114      * @return url of a brotli-encoded server resource.
115      */
getServeSimpleBrotliResponse()116     public static String getServeSimpleBrotliResponse() {
117         return getServerUrl() + Http2TestHandler.SERVE_SIMPLE_BROTLI_RESPONSE;
118     }
119 
120     /**
121      * @return url of the reporting collector
122      */
getReportingCollectorUrl()123     public static String getReportingCollectorUrl() {
124         return getServerUrl() + Http2TestHandler.REPORTING_COLLECTOR_PATH;
125     }
126 
127     /**
128      * @return url of a resource that includes Reporting and NEL policy headers in its response
129      */
getSuccessWithNELHeadersUrl()130     public static String getSuccessWithNELHeadersUrl() {
131         return getServerUrl() + Http2TestHandler.SUCCESS_WITH_NEL_HEADERS_PATH;
132     }
133 
134     /**
135      * @return url of a resource that sends response headers with the same key
136      */
getCombinedHeadersUrl()137     public static String getCombinedHeadersUrl() {
138         return getServerUrl() + Http2TestHandler.COMBINED_HEADERS_PATH;
139     }
140 
startHttp2TestServer( Context context, String certFileName, String keyFileName)141     public static boolean startHttp2TestServer(
142             Context context, String certFileName, String keyFileName) throws Exception {
143         return startHttp2TestServer(context, certFileName, keyFileName, null);
144     }
145 
startHttp2TestServer(Context context, String certFileName, String keyFileName, CountDownLatch hangingUrlLatch)146     public static boolean startHttp2TestServer(Context context, String certFileName,
147             String keyFileName, CountDownLatch hangingUrlLatch) throws Exception {
148         sReportingCollector = new ReportingCollector();
149         Http2TestServerRunnable http2TestServerRunnable =
150                 new Http2TestServerRunnable(new File(CertTestUtil.CERTS_DIRECTORY + certFileName),
151                         new File(CertTestUtil.CERTS_DIRECTORY + keyFileName), hangingUrlLatch);
152         new Thread(http2TestServerRunnable).start();
153         http2TestServerRunnable.blockUntilStarted();
154         return true;
155     }
156 
Http2TestServer()157     private Http2TestServer() {}
158 
159     private static class Http2TestServerRunnable implements Runnable {
160         private final ConditionVariable mBlock = new ConditionVariable();
161         private final SslContext mSslCtx;
162         private final CountDownLatch mHangingUrlLatch;
163 
Http2TestServerRunnable(File certFile, File keyFile, CountDownLatch hangingUrlLatch)164         Http2TestServerRunnable(File certFile, File keyFile, CountDownLatch hangingUrlLatch)
165                 throws Exception {
166             ApplicationProtocolConfig applicationProtocolConfig = new ApplicationProtocolConfig(
167                     Protocol.ALPN, SelectorFailureBehavior.NO_ADVERTISE,
168                     SelectedListenerFailureBehavior.ACCEPT, ApplicationProtocolNames.HTTP_2);
169 
170             // Don't make netty use java.security.KeyStore.getInstance("JKS") as it doesn't
171             // exist.  Just avoid a KeyManagerFactory as it's unnecessary for our testing.
172             System.setProperty("io.netty.handler.ssl.openssl.useKeyManagerFactory", "false");
173 
174             mSslCtx = new OpenSslServerContext(certFile, keyFile, null, null,
175                     Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE,
176                     applicationProtocolConfig, 0, 0);
177 
178             mHangingUrlLatch = hangingUrlLatch;
179         }
180 
blockUntilStarted()181         public void blockUntilStarted() {
182             mBlock.block();
183         }
184 
185         @Override
run()186         public void run() {
187             boolean retry = false;
188             do {
189                 try {
190                     // Configure the server.
191                     EventLoopGroup group = new NioEventLoopGroup();
192                     try {
193                         ServerBootstrap b = new ServerBootstrap();
194                         b.option(ChannelOption.SO_BACKLOG, 1024);
195                         b.group(group)
196                                 .channel(NioServerSocketChannel.class)
197                                 .handler(new LoggingHandler(LogLevel.INFO))
198                                 .childHandler(
199                                         new Http2ServerInitializer(mSslCtx, mHangingUrlLatch));
200 
201                         sServerChannel = b.bind(PORT).sync().channel();
202                         Log.i(TAG, "Netty HTTP/2 server started on " + getServerUrl());
203                         mBlock.open();
204                         sServerChannel.closeFuture().sync();
205                     } finally {
206                         group.shutdownGracefully();
207                     }
208                     Log.i(TAG, "Stopped Http2TestServerRunnable!");
209                     retry = false;
210                 } catch (Exception e) {
211                     Log.e(TAG, "Netty server failed to start", e);
212                     // Retry once if we hit https://github.com/netty/netty/issues/2616 before the
213                     // server starts.
214                     retry = !retry && sServerChannel == null
215                             && e.toString().contains("java.nio.channels.ClosedChannelException");
216                 }
217             } while (retry);
218         }
219     }
220 
221     /**
222      * Sets up the Netty pipeline for the test server.
223      */
224     private static class Http2ServerInitializer extends ChannelInitializer<SocketChannel> {
225         private final SslContext mSslCtx;
226         private final CountDownLatch mHangingUrlLatch;
227 
Http2ServerInitializer(SslContext sslCtx, CountDownLatch hangingUrlLatch)228         public Http2ServerInitializer(SslContext sslCtx, CountDownLatch hangingUrlLatch) {
229             mSslCtx = sslCtx;
230             mHangingUrlLatch = hangingUrlLatch;
231         }
232 
233         @Override
initChannel(SocketChannel ch)234         public void initChannel(SocketChannel ch) {
235             ch.pipeline().addLast(
236                     mSslCtx.newHandler(ch.alloc()), new Http2NegotiationHandler(mHangingUrlLatch));
237         }
238     }
239 
240     private static class Http2NegotiationHandler extends ApplicationProtocolNegotiationHandler {
241         private final CountDownLatch mHangingUrlLatch;
242 
Http2NegotiationHandler(CountDownLatch hangingUrlLatch)243         protected Http2NegotiationHandler(CountDownLatch hangingUrlLatch) {
244             super(ApplicationProtocolNames.HTTP_1_1);
245             mHangingUrlLatch = hangingUrlLatch;
246         }
247 
248         @Override
configurePipeline(ChannelHandlerContext ctx, String protocol)249         protected void configurePipeline(ChannelHandlerContext ctx, String protocol)
250                 throws Exception {
251             if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
252                 ctx.pipeline().addLast(new Http2TestHandler.Builder()
253                                                .setReportingCollector(sReportingCollector)
254                                                .setServerUrl(getServerUrl())
255                                                .setHangingUrlLatch(mHangingUrlLatch)
256                                                .build());
257                 return;
258             }
259 
260             throw new IllegalStateException("unknown protocol: " + protocol);
261         }
262     }
263 }
264