1 /* 2 * Copyright (c) 2015, 2018, 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 com.sun.net.httpserver.HttpServer; 25 26 import java.io.Closeable; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import javax.net.ServerSocketFactory; 31 import javax.net.ssl.SSLServerSocket; 32 import java.net.InetAddress; 33 import java.net.InetSocketAddress; 34 import java.net.ServerSocket; 35 import java.net.Socket; 36 import java.nio.charset.StandardCharsets; 37 import java.util.Collections; 38 import java.util.LinkedList; 39 import java.util.List; 40 import java.util.Iterator; 41 import java.util.concurrent.ArrayBlockingQueue; 42 import java.util.concurrent.TimeUnit; 43 import java.util.concurrent.atomic.AtomicInteger; 44 import static java.nio.charset.StandardCharsets.ISO_8859_1; 45 46 /** 47 * A cut-down Http/1 Server for testing various error situations 48 * 49 * use interrupt() to halt 50 */ 51 public class MockServer extends Thread implements Closeable { 52 53 final ServerSocket ss; 54 private final List<Connection> sockets; 55 private final List<Connection> removals; 56 private final List<Connection> additions; 57 AtomicInteger counter = new AtomicInteger(0); 58 // if specified (not null), only requests which 59 // contain this value in their status line 60 // will be taken into account and returned by activity(). 61 // Other requests will get summarily closed. 62 // When specified, this can prevent answering to rogue 63 // (external) clients that might be lurking 64 // on the test machine instead of answering 65 // to the test client. 66 final String root; 67 68 // waits up to 2000 seconds for something to happen 69 // dont use this unless certain activity coming. activity()70 public Connection activity() { 71 for (int i = 0; i < 80 * 100; i++) { 72 doRemovalsAndAdditions(); 73 for (Connection c : sockets) { 74 if (c.poll()) { 75 if (root != null) { 76 // if a root was specified in MockServer 77 // constructor, rejects (by closing) all 78 // requests whose statusLine does not contain 79 // root. 80 if (!c.statusLine.contains(root)) { 81 System.out.println("Bad statusLine: " 82 + c.statusLine 83 + " closing connection"); 84 c.close(); 85 continue; 86 } 87 } 88 return c; 89 } 90 } 91 try { 92 Thread.sleep(250); 93 } catch (InterruptedException e) { 94 e.printStackTrace(); 95 } 96 } 97 return null; 98 } 99 doRemovalsAndAdditions()100 private void doRemovalsAndAdditions() { 101 synchronized (removals) { 102 Iterator<Connection> i = removals.iterator(); 103 while (i.hasNext()) { 104 Connection c = i.next(); 105 System.out.println("socket removed: " + c); 106 sockets.remove(c); 107 } 108 removals.clear(); 109 } 110 111 synchronized (additions) { 112 Iterator<Connection> i = additions.iterator(); 113 while (i.hasNext()) { 114 Connection c = i.next(); 115 System.out.println("socket added: " + c); 116 sockets.add(c); 117 } 118 additions.clear(); 119 } 120 } 121 122 // clears all current connections on Server. reset()123 public void reset() { 124 for (Connection c : sockets) { 125 c.close(); 126 } 127 } 128 129 /** 130 * Reads data into an ArrayBlockingQueue<String> where each String 131 * is a line of input, that was terminated by CRLF (not included) 132 */ 133 class Connection extends Thread { Connection(Socket s)134 Connection(Socket s) throws IOException { 135 this.socket = s; 136 id = counter.incrementAndGet(); 137 is = s.getInputStream(); 138 os = s.getOutputStream(); 139 incoming = new ArrayBlockingQueue<>(100); 140 setName("Server-Connection"); 141 setDaemon(true); 142 } 143 final Socket socket; 144 final int id; 145 final InputStream is; 146 final OutputStream os; 147 final ArrayBlockingQueue<String> incoming; 148 volatile String statusLine; 149 150 final static String CRLF = "\r\n"; 151 152 // sentinel indicating connection closed 153 final static String CLOSED = "C.L.O.S.E.D"; 154 volatile boolean closed = false; 155 volatile boolean released = false; 156 157 @Override run()158 public void run() { 159 byte[] buf = new byte[256]; 160 String s = ""; 161 try { 162 while (true) { 163 int n = is.read(buf); 164 if (n == -1) { 165 cleanup(); 166 return; 167 } 168 String s0 = new String(buf, 0, n, ISO_8859_1); 169 s = s + s0; 170 int i; 171 while ((i=s.indexOf(CRLF)) != -1) { 172 String s1 = s.substring(0, i+2); 173 System.out.println("Server got: " + s1.substring(0,i)); 174 if (statusLine == null) statusLine = s1.substring(0,i); 175 incoming.put(s1); 176 if (i+2 == s.length()) { 177 s = ""; 178 break; 179 } 180 s = s.substring(i+2); 181 } 182 } 183 } catch (IOException |InterruptedException e1) { 184 cleanup(); 185 } catch (Throwable t) { 186 System.out.println("Exception: " + t); 187 t.printStackTrace(); 188 cleanup(); 189 } 190 } 191 192 @Override toString()193 public String toString() { 194 return "Server.Connection: " + socket.toString(); 195 } 196 sendHttpResponse(int code, String body, String... headers)197 public void sendHttpResponse(int code, String body, String... headers) 198 throws IOException 199 { 200 String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; 201 for (int i=0; i<headers.length; i+=2) { 202 r1 += headers[i] + ": " + headers[i+1] + CRLF; 203 } 204 int clen = body == null ? 0 : body.getBytes(ISO_8859_1).length; 205 r1 += "Content-Length: " + Integer.toString(clen) + CRLF; 206 r1 += CRLF; 207 if (body != null) { 208 r1 += body; 209 } 210 send(r1); 211 } 212 213 // content-length is 10 bytes too many sendIncompleteHttpResponseBody(int code)214 public void sendIncompleteHttpResponseBody(int code) throws IOException { 215 String body = "Hello World Helloworld Goodbye World"; 216 String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; 217 int clen = body.getBytes(ISO_8859_1).length + 10; 218 r1 += "Content-Length: " + Integer.toString(clen) + CRLF; 219 r1 += CRLF; 220 if (body != null) { 221 r1 += body; 222 } 223 send(r1); 224 } 225 sendIncompleteHttpResponseHeaders(int code)226 public void sendIncompleteHttpResponseHeaders(int code) 227 throws IOException 228 { 229 String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; 230 send(r1); 231 } 232 send(String r)233 public void send(String r) throws IOException { 234 try { 235 os.write(r.getBytes(ISO_8859_1)); 236 } catch (IOException x) { 237 IOException suppressed = 238 new IOException("MockServer[" 239 + ss.getLocalPort() 240 +"] Failed while writing bytes: " 241 + x.getMessage()); 242 x.addSuppressed(suppressed); 243 System.err.println("WARNING: " + suppressed); 244 throw x; 245 } 246 } 247 close()248 public synchronized void close() { 249 cleanup(); 250 closed = true; 251 incoming.clear(); 252 } 253 nextInput(long timeout, TimeUnit unit)254 public String nextInput(long timeout, TimeUnit unit) { 255 String result = ""; 256 while (poll()) { 257 try { 258 String s = incoming.poll(timeout, unit); 259 if (s == null && closed) { 260 return CLOSED; 261 } else { 262 result += s; 263 } 264 } catch (InterruptedException e) { 265 return null; 266 } 267 } 268 return result; 269 } 270 nextInput()271 public String nextInput() { 272 return nextInput(0, TimeUnit.SECONDS); 273 } 274 poll()275 public boolean poll() { 276 return incoming.peek() != null; 277 } 278 cleanup()279 private void cleanup() { 280 if (released) return; 281 synchronized(this) { 282 if (released) return; 283 released = true; 284 } 285 try { 286 socket.close(); 287 } catch (Throwable e) {} 288 synchronized (removals) { 289 removals.add(this); 290 } 291 } 292 } 293 MockServer(int port, ServerSocketFactory factory, String root)294 MockServer(int port, ServerSocketFactory factory, String root) throws IOException { 295 ss = factory.createServerSocket(); 296 ss.setReuseAddress(false); 297 ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); 298 this.root = root; // if specified, any request which don't have this value 299 // in their statusLine will be rejected. 300 sockets = Collections.synchronizedList(new LinkedList<>()); 301 removals = new LinkedList<>(); 302 additions = new LinkedList<>(); 303 setName("Test-Server"); 304 setDaemon(true); 305 } 306 MockServer(int port, ServerSocketFactory factory)307 MockServer(int port, ServerSocketFactory factory) throws IOException { 308 this(port, factory, "/foo/"); 309 } 310 MockServer(int port)311 MockServer(int port) throws IOException { 312 this(port, ServerSocketFactory.getDefault()); 313 } 314 MockServer()315 MockServer() throws IOException { 316 this(0); 317 } 318 port()319 int port() { 320 return ss.getLocalPort(); 321 } 322 serverAuthority()323 String serverAuthority() { 324 return InetAddress.getLoopbackAddress().getHostName() + ":" + port(); 325 } 326 getURL()327 public String getURL() { 328 if (ss instanceof SSLServerSocket) { 329 return "https://" + serverAuthority() + "/foo/"; 330 } else { 331 return "http://" + serverAuthority() + "/foo/"; 332 } 333 } 334 335 private volatile boolean closed; 336 337 @Override close()338 public void close() { 339 closed = true; 340 try { 341 ss.close(); 342 } catch (Throwable e) { 343 e.printStackTrace(); 344 } 345 for (Connection c : sockets) { 346 c.close(); 347 } 348 } 349 350 @Override run()351 public void run() { 352 try { 353 while (!closed) { 354 try { 355 System.out.println("Server waiting for connection"); 356 Socket s = ss.accept(); 357 Connection c = new Connection(s); 358 c.start(); 359 System.out.println("Server got new connection: " + c); 360 synchronized (additions) { 361 additions.add(c); 362 } 363 } catch (IOException e) { 364 if (closed) 365 return; 366 e.printStackTrace(System.out); 367 } 368 } 369 } catch (Throwable t) { 370 System.out.println("Unexpected exception in accept loop: " + t); 371 t.printStackTrace(System.out); 372 } finally { 373 if (closed) { 374 System.out.println("Server closed: exiting accept loop"); 375 } else { 376 System.out.println("Server not closed: exiting accept loop and closing"); 377 close(); 378 } 379 } 380 } 381 382 } 383