1 /* 2 * Copyright (c) 2015, 2016, 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 javax.net.ServerSocketFactory; 32 import javax.net.ssl.SSLContext; 33 import javax.net.ssl.SSLParameters; 34 import javax.net.ssl.SSLServerSocket; 35 import javax.net.ssl.SSLServerSocketFactory; 36 import javax.net.ssl.SNIServerName; 37 38 /** 39 * Waits for incoming TCP connections from a client and establishes 40 * a HTTP2 connection. Two threads are created per connection. One for reading 41 * and one for writing. Incoming requests are dispatched to the supplied 42 * Http2Handler on additional threads. All threads 43 * obtained from the supplied ExecutorService. 44 */ 45 public class Http2TestServer implements AutoCloseable { 46 final ServerSocket server; 47 volatile boolean secure; 48 final ExecutorService exec; 49 volatile boolean stopping = false; 50 final Map<String,Http2Handler> handlers; 51 final SSLContext sslContext; 52 final String serverName; 53 final HashMap<InetSocketAddress,Http2TestServerConnection> connections; 54 55 private static ThreadFactory defaultThreadFac = 56 (Runnable r) -> { 57 Thread t = new Thread(r); 58 t.setName("Test-server-pool"); 59 return t; 60 }; 61 62 63 private static ExecutorService getDefaultExecutor() { 64 return Executors.newCachedThreadPool(defaultThreadFac); 65 } 66 67 public Http2TestServer(String serverName, boolean secure, int port) throws Exception { 68 this(serverName, secure, port, getDefaultExecutor(), null); 69 } 70 71 public Http2TestServer(boolean secure, int port) throws Exception { 72 this(null, secure, port, getDefaultExecutor(), null); 73 } 74 75 public InetSocketAddress getAddress() { 76 return (InetSocketAddress)server.getLocalSocketAddress(); 77 } 78 79 public Http2TestServer(boolean secure, 80 SSLContext context) throws Exception { 81 this(null, secure, 0, null, context); 82 } 83 84 public Http2TestServer(String serverName, boolean secure, 85 SSLContext context) throws Exception { 86 this(serverName, secure, 0, null, context); 87 } 88 89 public Http2TestServer(boolean secure, 90 int port, 91 ExecutorService exec, 92 SSLContext context) throws Exception { 93 this(null, secure, port, exec, context); 94 } 95 96 /** 97 * Create a Http2Server listening on the given port. Currently needs 98 * to know in advance whether incoming connections are plain TCP "h2c" 99 * or TLS "h2"/ 100 * 101 * @param serverName SNI servername 102 * @param secure https or http 103 * @param port listen port 104 * @param exec executor service (cached thread pool is used if null) 105 * @param context the SSLContext used when secure is true 106 */ 107 public Http2TestServer(String serverName, 108 boolean secure, 109 int port, 110 ExecutorService exec, 111 SSLContext context) 112 throws Exception 113 { 114 this.serverName = serverName; 115 if (secure) { 116 server = initSecure(port); 117 } else { 118 server = initPlaintext(port); 119 } 120 this.secure = secure; 121 this.exec = exec == null ? getDefaultExecutor() : exec; 122 this.handlers = Collections.synchronizedMap(new HashMap<>()); 123 this.sslContext = context; 124 this.connections = new HashMap<>(); 125 } 126 127 /** 128 * Adds the given handler for the given path 129 */ 130 public void addHandler(Http2Handler handler, String path) { 131 handlers.put(path, handler); 132 } 133 134 Http2Handler getHandlerFor(String path) { 135 if (path == null || path.equals("")) 136 path = "/"; 137 138 final String fpath = path; 139 AtomicReference<String> bestMatch = new AtomicReference<>(""); 140 AtomicReference<Http2Handler> href = new AtomicReference<>(); 141 142 handlers.forEach((key, value) -> { 143 if (fpath.startsWith(key) && key.length() > bestMatch.get().length()) { 144 bestMatch.set(key); 145 href.set(value); 146 } 147 }); 148 Http2Handler handler = href.get(); 149 if (handler == null) 150 throw new RuntimeException("No handler found for path " + path); 151 System.err.println("Using handler for: " + bestMatch.get()); 152 return handler; 153 } 154 155 final ServerSocket initPlaintext(int port) throws Exception { 156 return new ServerSocket(port); 157 } 158 159 public void stop() { 160 // TODO: clean shutdown GoAway 161 stopping = true; 162 for (Http2TestServerConnection connection : connections.values()) { 163 connection.close(); 164 } 165 try { 166 server.close(); 167 } catch (IOException e) {} 168 exec.shutdownNow(); 169 } 170 171 172 final ServerSocket initSecure(int port) throws Exception { 173 ServerSocketFactory fac; 174 if (sslContext != null) { 175 fac = sslContext.getServerSocketFactory(); 176 } else { 177 fac = SSLServerSocketFactory.getDefault(); 178 } 179 SSLServerSocket se = (SSLServerSocket) fac.createServerSocket(port); 180 SSLParameters sslp = se.getSSLParameters(); 181 sslp.setApplicationProtocols(new String[]{"h2"}); 182 se.setSSLParameters(sslp); 183 se.setEnabledCipherSuites(se.getSupportedCipherSuites()); 184 se.setEnabledProtocols(se.getSupportedProtocols()); 185 // other initialisation here 186 return se; 187 } 188 189 public String serverName() { 190 return serverName; 191 } 192 193 /** 194 * Starts a thread which waits for incoming connections. 195 */ 196 public void start() { 197 exec.submit(() -> { 198 try { 199 while (!stopping) { 200 Socket socket = server.accept(); 201 InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress(); 202 Http2TestServerConnection c = new Http2TestServerConnection(this, socket); 203 connections.put(addr, c); 204 try { 205 c.run(); 206 } catch(Throwable e) { 207 // we should not reach here, but if we do 208 // the connection might not have been closed 209 // and if so then the client might wait 210 // forever. 211 connections.remove(addr, c); 212 c.close(); 213 throw e; 214 } 215 } 216 } catch (Throwable e) { 217 if (!stopping) { 218 System.err.println("TestServer: start exception: " + e); 219 e.printStackTrace(); 220 } 221 } 222 }); 223 } 224 225 @Override 226 public void close() throws Exception { 227 stop(); 228 } 229 }