1 /* 2 * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.IOException; 25 import java.net.*; 26 import java.util.*; 27 import java.util.concurrent.ExecutorService; 28 import java.util.concurrent.Executors; 29 import java.util.concurrent.ThreadFactory; 30 import java.util.concurrent.atomic.AtomicReference; 31 import java.util.function.Consumer; 32 import javax.net.ServerSocketFactory; 33 import javax.net.ssl.SSLContext; 34 import javax.net.ssl.SSLParameters; 35 import javax.net.ssl.SSLServerSocket; 36 import javax.net.ssl.SSLServerSocketFactory; 37 import javax.net.ssl.SNIServerName; 38 import jdk.internal.net.http.frame.ErrorFrame; 39 40 /** 41 * Waits for incoming TCP connections from a client and establishes 42 * a HTTP2 connection. Two threads are created per connection. One for reading 43 * and one for writing. Incoming requests are dispatched to the supplied 44 * Http2Handler on additional threads. All threads 45 * obtained from the supplied ExecutorService. 46 */ 47 public class Http2TestServer implements AutoCloseable { 48 final ServerSocket server; 49 final boolean supportsHTTP11; 50 volatile boolean secure; 51 final ExecutorService exec; 52 volatile boolean stopping = false; 53 final Map<String,Http2Handler> handlers; 54 final SSLContext sslContext; 55 final String serverName; 56 final HashMap<InetSocketAddress,Http2TestServerConnection> connections; 57 final Properties properties; 58 59 private static ThreadFactory defaultThreadFac = 60 (Runnable r) -> { 61 Thread t = new Thread(r); 62 t.setName("Test-server-pool"); 63 return t; 64 }; 65 66 67 private static ExecutorService getDefaultExecutor() { 68 return Executors.newCachedThreadPool(defaultThreadFac); 69 } 70 71 public Http2TestServer(String serverName, boolean secure, int port) throws Exception { 72 this(serverName, secure, port, getDefaultExecutor(), 50, null, null); 73 } 74 75 public Http2TestServer(boolean secure, int port) throws Exception { 76 this(null, secure, port, getDefaultExecutor(), 50, null, null); 77 } 78 79 public InetSocketAddress getAddress() { 80 return (InetSocketAddress)server.getLocalSocketAddress(); 81 } 82 83 public String serverAuthority() { 84 return InetAddress.getLoopbackAddress().getHostName() + ":" 85 + getAddress().getPort(); 86 } 87 88 public Http2TestServer(boolean secure, 89 SSLContext context) throws Exception { 90 this(null, secure, 0, null, 50, null, context); 91 } 92 93 public Http2TestServer(String serverName, boolean secure, 94 SSLContext context) throws Exception { 95 this(serverName, secure, 0, null, 50, null, context); 96 } 97 98 public Http2TestServer(boolean secure, 99 int port, 100 ExecutorService exec, 101 SSLContext context) throws Exception { 102 this(null, secure, port, exec, 50, null, context); 103 } 104 105 public Http2TestServer(String serverName, 106 boolean secure, 107 int port, 108 ExecutorService exec, 109 SSLContext context) 110 throws Exception 111 { 112 this(serverName, secure, port, exec, 50, null, context); 113 } 114 115 public Http2TestServer(String serverName, 116 boolean secure, 117 int port, 118 ExecutorService exec, 119 int backlog, 120 Properties properties, 121 SSLContext context) 122 throws Exception 123 { 124 this(serverName, secure, port, exec, backlog, properties, context, false); 125 } 126 127 /** 128 * Create a Http2Server listening on the given port. Currently needs 129 * to know in advance whether incoming connections are plain TCP "h2c" 130 * or TLS "h2"/ 131 * 132 * @param serverName SNI servername 133 * @param secure https or http 134 * @param port listen port 135 * @param exec executor service (cached thread pool is used if null) 136 * @param backlog the server socket backlog 137 * @param properties additional configuration properties 138 * @param context the SSLContext used when secure is true 139 */ 140 public Http2TestServer(String serverName, 141 boolean secure, 142 int port, 143 ExecutorService exec, 144 int backlog, 145 Properties properties, 146 SSLContext context, 147 boolean supportsHTTP11) 148 throws Exception 149 { 150 this.serverName = serverName; 151 this.supportsHTTP11 = supportsHTTP11; 152 if (secure) { 153 if (context != null) 154 this.sslContext = context; 155 else 156 this.sslContext = SSLContext.getDefault(); 157 server = initSecure(port, backlog); 158 } else { 159 this.sslContext = context; 160 server = initPlaintext(port, backlog); 161 } 162 this.secure = secure; 163 this.exec = exec == null ? getDefaultExecutor() : exec; 164 this.handlers = Collections.synchronizedMap(new HashMap<>()); 165 this.properties = properties; 166 this.connections = new HashMap<>(); 167 } 168 169 /** 170 * Adds the given handler for the given path 171 */ 172 public void addHandler(Http2Handler handler, String path) { 173 handlers.put(path, handler); 174 } 175 176 volatile Http2TestExchangeSupplier exchangeSupplier = Http2TestExchangeSupplier.ofDefault(); 177 178 /** 179 * Sets an explicit exchange handler to be used for all future connections. 180 * Useful for testing scenarios where non-standard or specific server 181 * behaviour is required, either direct control over the frames sent, "bad" 182 * behaviour, or something else. 183 */ 184 public void setExchangeSupplier(Http2TestExchangeSupplier exchangeSupplier) { 185 this.exchangeSupplier = exchangeSupplier; 186 } 187 188 Http2Handler getHandlerFor(String path) { 189 if (path == null || path.equals("")) 190 path = "/"; 191 192 final String fpath = path; 193 AtomicReference<String> bestMatch = new AtomicReference<>(""); 194 AtomicReference<Http2Handler> href = new AtomicReference<>(); 195 196 handlers.forEach((key, value) -> { 197 if (fpath.startsWith(key) && key.length() > bestMatch.get().length()) { 198 bestMatch.set(key); 199 href.set(value); 200 } 201 }); 202 Http2Handler handler = href.get(); 203 if (handler == null) 204 throw new RuntimeException("No handler found for path " + path); 205 System.err.println("Using handler for: " + bestMatch.get()); 206 return handler; 207 } 208 209 final ServerSocket initPlaintext(int port, int backlog) throws Exception { 210 ServerSocket ss = new ServerSocket(); 211 ss.setReuseAddress(false); 212 ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), backlog); 213 return ss; 214 } 215 216 public synchronized void stop() { 217 // TODO: clean shutdown GoAway 218 stopping = true; 219 System.err.printf("Server stopping %d connections\n", connections.size()); 220 for (Http2TestServerConnection connection : connections.values()) { 221 connection.close(ErrorFrame.NO_ERROR); 222 } 223 try { 224 server.close(); 225 } catch (IOException e) {} 226 exec.shutdownNow(); 227 } 228 229 230 final ServerSocket initSecure(int port, int backlog) throws Exception { 231 ServerSocketFactory fac; 232 SSLParameters sslp = null; 233 fac = sslContext.getServerSocketFactory(); 234 sslp = sslContext.getSupportedSSLParameters(); 235 SSLServerSocket se = (SSLServerSocket) fac.createServerSocket(); 236 se.setReuseAddress(false); 237 se.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), backlog); 238 if (supportsHTTP11) { 239 sslp.setApplicationProtocols(new String[]{"h2", "http/1.1"}); 240 } else { 241 sslp.setApplicationProtocols(new String[]{"h2"}); 242 } 243 sslp.setEndpointIdentificationAlgorithm("HTTPS"); 244 se.setSSLParameters(sslp); 245 se.setEnabledCipherSuites(se.getSupportedCipherSuites()); 246 se.setEnabledProtocols(se.getSupportedProtocols()); 247 // other initialisation here 248 return se; 249 } 250 251 public String serverName() { 252 return serverName; 253 } 254 255 private synchronized void putConnection(InetSocketAddress addr, Http2TestServerConnection c) { 256 if (!stopping) 257 connections.put(addr, c); 258 } 259 260 private synchronized void removeConnection(InetSocketAddress addr, Http2TestServerConnection c) { 261 connections.remove(addr, c); 262 } 263 264 /** 265 * Starts a thread which waits for incoming connections. 266 */ 267 public void start() { 268 exec.submit(() -> { 269 try { 270 while (!stopping) { 271 Socket socket = server.accept(); 272 Http2TestServerConnection c = null; 273 InetSocketAddress addr = null; 274 try { 275 addr = (InetSocketAddress) socket.getRemoteSocketAddress(); 276 c = createConnection(this, socket, exchangeSupplier); 277 putConnection(addr, c); 278 c.run(); 279 } catch (Throwable e) { 280 // we should not reach here, but if we do 281 // the connection might not have been closed 282 // and if so then the client might wait 283 // forever. 284 if (c != null) { 285 removeConnection(addr, c); 286 c.close(ErrorFrame.PROTOCOL_ERROR); 287 } else { 288 socket.close(); 289 } 290 System.err.println("TestServer: start exception: " + e); 291 } 292 } 293 } catch (SecurityException se) { 294 System.err.println("TestServer: terminating, caught " + se); 295 se.printStackTrace(); 296 stopping = true; 297 try { server.close(); } catch (IOException ioe) { /* ignore */} 298 } catch (Throwable e) { 299 if (!stopping) { 300 System.err.println("TestServer: terminating, caught " + e); 301 e.printStackTrace(); 302 } 303 } 304 }); 305 } 306 307 protected Http2TestServerConnection createConnection(Http2TestServer http2TestServer, 308 Socket socket, 309 Http2TestExchangeSupplier exchangeSupplier) 310 throws IOException { 311 return new Http2TestServerConnection(http2TestServer, socket, exchangeSupplier, properties); 312 } 313 314 @Override 315 public void close() throws Exception { 316 stop(); 317 } 318 }