1 /* 2 * Copyright (c) 2015, 2017, 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.Closeable; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import javax.net.ServerSocketFactory; 29 import javax.net.ssl.SSLServerSocket; 30 import java.net.ServerSocket; 31 import java.net.Socket; 32 import java.nio.charset.StandardCharsets; 33 import java.util.Collections; 34 import java.util.LinkedList; 35 import java.util.List; 36 import java.util.Iterator; 37 import java.util.concurrent.ArrayBlockingQueue; 38 import java.util.concurrent.TimeUnit; 39 import java.util.concurrent.atomic.AtomicInteger; 40 41 /** 42 * A cut-down Http/1 Server for testing various error situations 43 * 44 * use interrupt() to halt 45 */ 46 public class MockServer extends Thread implements Closeable { 47 48 ServerSocket ss; 49 private final List<Connection> sockets; 50 private final List<Connection> removals; 51 private final List<Connection> additions; 52 AtomicInteger counter = new AtomicInteger(0); 53 54 // waits up to 20 seconds for something to happen 55 // dont use this unless certain activity coming. 56 public Connection activity() { 57 for (int i = 0; i < 80 * 100; i++) { 58 doRemovalsAndAdditions(); 59 for (Connection c : sockets) { 60 if (c.poll()) { 61 return c; 62 } 63 } 64 try { 65 Thread.sleep(250); 66 } catch (InterruptedException e) { 67 e.printStackTrace(); 68 } 69 } 70 return null; 71 } 72 73 private void doRemovalsAndAdditions() { 74 synchronized (removals) { 75 Iterator<Connection> i = removals.iterator(); 76 while (i.hasNext()) { 77 Connection c = i.next(); 78 System.out.println("socket removed: " + c); 79 sockets.remove(c); 80 } 81 removals.clear(); 82 } 83 84 synchronized (additions) { 85 Iterator<Connection> i = additions.iterator(); 86 while (i.hasNext()) { 87 Connection c = i.next(); 88 System.out.println("socket added: " + c); 89 sockets.add(c); 90 } 91 additions.clear(); 92 } 93 } 94 95 // clears all current connections on Server. 96 public void reset() { 97 for (Connection c : sockets) { 98 c.close(); 99 } 100 } 101 102 /** 103 * Reads data into an ArrayBlockingQueue<String> where each String 104 * is a line of input, that was terminated by CRLF (not included) 105 */ 106 class Connection extends Thread { 107 Connection(Socket s) throws IOException { 108 this.socket = s; 109 id = counter.incrementAndGet(); 110 is = s.getInputStream(); 111 os = s.getOutputStream(); 112 incoming = new ArrayBlockingQueue<>(100); 113 setName("Server-Connection"); 114 setDaemon(true); 115 } 116 final Socket socket; 117 final int id; 118 final InputStream is; 119 final OutputStream os; 120 final ArrayBlockingQueue<String> incoming; 121 122 final static String CRLF = "\r\n"; 123 124 // sentinel indicating connection closed 125 final static String CLOSED = "C.L.O.S.E.D"; 126 volatile boolean closed = false; 127 volatile boolean released = false; 128 129 @Override 130 public void run() { 131 byte[] buf = new byte[256]; 132 String s = ""; 133 try { 134 while (true) { 135 int n = is.read(buf); 136 if (n == -1) { 137 cleanup(); 138 return; 139 } 140 String s0 = new String(buf, 0, n, StandardCharsets.ISO_8859_1); 141 s = s + s0; 142 int i; 143 while ((i=s.indexOf(CRLF)) != -1) { 144 String s1 = s.substring(0, i+2); 145 System.out.println("Server got: " + s1.substring(0,i)); 146 incoming.put(s1); 147 if (i+2 == s.length()) { 148 s = ""; 149 break; 150 } 151 s = s.substring(i+2); 152 } 153 } 154 } catch (IOException |InterruptedException e1) { 155 cleanup(); 156 } catch (Throwable t) { 157 System.out.println("X: " + t); 158 cleanup(); 159 } 160 } 161 162 @Override 163 public String toString() { 164 return "Server.Connection: " + socket.toString(); 165 } 166 167 public void sendHttpResponse(int code, String body, String... headers) 168 throws IOException 169 { 170 String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; 171 for (int i=0; i<headers.length; i+=2) { 172 r1 += headers[i] + ": " + headers[i+1] + CRLF; 173 } 174 int clen = body == null ? 0 : body.length(); 175 r1 += "Content-Length: " + Integer.toString(clen) + CRLF; 176 r1 += CRLF; 177 if (body != null) { 178 r1 += body; 179 } 180 send(r1); 181 } 182 183 // content-length is 10 bytes too many 184 public void sendIncompleteHttpResponseBody(int code) throws IOException { 185 String body = "Hello World Helloworld Goodbye World"; 186 String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; 187 int clen = body.length() + 10; 188 r1 += "Content-Length: " + Integer.toString(clen) + CRLF; 189 r1 += CRLF; 190 if (body != null) { 191 r1 += body; 192 } 193 send(r1); 194 } 195 196 public void sendIncompleteHttpResponseHeaders(int code) 197 throws IOException 198 { 199 String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; 200 send(r1); 201 } 202 203 public void send(String r) throws IOException { 204 os.write(r.getBytes(StandardCharsets.ISO_8859_1)); 205 } 206 207 public synchronized void close() { 208 cleanup(); 209 closed = true; 210 incoming.clear(); 211 } 212 213 public String nextInput(long timeout, TimeUnit unit) { 214 String result = ""; 215 while (poll()) { 216 try { 217 String s = incoming.poll(timeout, unit); 218 if (s == null && closed) { 219 return CLOSED; 220 } else { 221 result += s; 222 } 223 } catch (InterruptedException e) { 224 return null; 225 } 226 } 227 return result; 228 } 229 230 public String nextInput() { 231 return nextInput(0, TimeUnit.SECONDS); 232 } 233 234 public boolean poll() { 235 return incoming.peek() != null; 236 } 237 238 private void cleanup() { 239 if (released) return; 240 synchronized(this) { 241 if (released) return; 242 released = true; 243 } 244 try { 245 socket.close(); 246 } catch (IOException e) {} 247 synchronized (removals) { 248 removals.add(this); 249 } 250 } 251 } 252 253 MockServer(int port, ServerSocketFactory factory) throws IOException { 254 ss = factory.createServerSocket(port); 255 sockets = Collections.synchronizedList(new LinkedList<>()); 256 removals = new LinkedList<>(); 257 additions = new LinkedList<>(); 258 setName("Test-Server"); 259 setDaemon(true); 260 } 261 262 MockServer(int port) throws IOException { 263 this(port, ServerSocketFactory.getDefault()); 264 } 265 266 MockServer() throws IOException { 267 this(0); 268 } 269 270 int port() { 271 return ss.getLocalPort(); 272 } 273 274 public String getURL() { 275 if (ss instanceof SSLServerSocket) { 276 return "https://127.0.0.1:" + port() + "/foo/"; 277 } else { 278 return "http://127.0.0.1:" + port() + "/foo/"; 279 } 280 } 281 282 private volatile boolean closed; 283 284 @Override 285 public void close() { 286 closed = true; 287 try { 288 ss.close(); 289 } catch (IOException e) { 290 e.printStackTrace(); 291 } 292 for (Connection c : sockets) { 293 c.close(); 294 } 295 } 296 297 @Override 298 public void run() { 299 while (!closed) { 300 try { 301 System.out.println("Server waiting for connection"); 302 Socket s = ss.accept(); 303 Connection c = new Connection(s); 304 c.start(); 305 System.out.println("Server got new connection: " + c); 306 synchronized (additions) { 307 additions.add(c); 308 } 309 } catch (IOException e) { 310 if (closed) 311 return; 312 e.printStackTrace(); 313 } 314 } 315 } 316 317 }