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