/* * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import com.sun.net.httpserver.HttpServer; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.net.ServerSocketFactory; import javax.net.ssl.SSLServerSocket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static java.nio.charset.StandardCharsets.ISO_8859_1; /** * A cut-down Http/1 Server for testing various error situations * * use interrupt() to halt */ public class MockServer extends Thread implements Closeable { final ServerSocket ss; private final List sockets; private final List removals; private final List additions; AtomicInteger counter = new AtomicInteger(0); // if specified (not null), only requests which // contain this value in their status line // will be taken into account and returned by activity(). // Other requests will get summarily closed. // When specified, this can prevent answering to rogue // (external) clients that might be lurking // on the test machine instead of answering // to the test client. final String root; // waits up to 2000 seconds for something to happen // dont use this unless certain activity coming. public Connection activity() { for (int i = 0; i < 80 * 100; i++) { doRemovalsAndAdditions(); for (Connection c : sockets) { if (c.poll()) { if (root != null) { // if a root was specified in MockServer // constructor, rejects (by closing) all // requests whose statusLine does not contain // root. if (!c.statusLine.contains(root)) { System.out.println("Bad statusLine: " + c.statusLine + " closing connection"); c.close(); continue; } } return c; } } try { Thread.sleep(250); } catch (InterruptedException e) { e.printStackTrace(); } } return null; } private void doRemovalsAndAdditions() { synchronized (removals) { Iterator i = removals.iterator(); while (i.hasNext()) { Connection c = i.next(); System.out.println("socket removed: " + c); sockets.remove(c); } removals.clear(); } synchronized (additions) { Iterator i = additions.iterator(); while (i.hasNext()) { Connection c = i.next(); System.out.println("socket added: " + c); sockets.add(c); } additions.clear(); } } // clears all current connections on Server. public void reset() { for (Connection c : sockets) { c.close(); } } /** * Reads data into an ArrayBlockingQueue where each String * is a line of input, that was terminated by CRLF (not included) */ class Connection extends Thread { Connection(Socket s) throws IOException { this.socket = s; id = counter.incrementAndGet(); is = s.getInputStream(); os = s.getOutputStream(); incoming = new ArrayBlockingQueue<>(100); setName("Server-Connection"); setDaemon(true); } final Socket socket; final int id; final InputStream is; final OutputStream os; final ArrayBlockingQueue incoming; volatile String statusLine; final static String CRLF = "\r\n"; // sentinel indicating connection closed final static String CLOSED = "C.L.O.S.E.D"; volatile boolean closed = false; volatile boolean released = false; @Override public void run() { byte[] buf = new byte[256]; String s = ""; try { while (true) { int n = is.read(buf); if (n == -1) { cleanup(); return; } String s0 = new String(buf, 0, n, ISO_8859_1); s = s + s0; int i; while ((i=s.indexOf(CRLF)) != -1) { String s1 = s.substring(0, i+2); System.out.println("Server got: " + s1.substring(0,i)); if (statusLine == null) statusLine = s1.substring(0,i); incoming.put(s1); if (i+2 == s.length()) { s = ""; break; } s = s.substring(i+2); } } } catch (IOException |InterruptedException e1) { cleanup(); } catch (Throwable t) { System.out.println("Exception: " + t); t.printStackTrace(); cleanup(); } } @Override public String toString() { return "Server.Connection: " + socket.toString(); } public void sendHttpResponse(int code, String body, String... headers) throws IOException { String r1 = "HTTP/1.1 " + Integer.toString(code) + " status" + CRLF; for (int i=0; i()); removals = new LinkedList<>(); additions = new LinkedList<>(); setName("Test-Server"); setDaemon(true); } MockServer(int port, ServerSocketFactory factory) throws IOException { this(port, factory, "/foo/"); } MockServer(int port) throws IOException { this(port, ServerSocketFactory.getDefault()); } MockServer() throws IOException { this(0); } int port() { return ss.getLocalPort(); } String serverAuthority() { return InetAddress.getLoopbackAddress().getHostName() + ":" + port(); } public String getURL() { if (ss instanceof SSLServerSocket) { return "https://" + serverAuthority() + "/foo/"; } else { return "http://" + serverAuthority() + "/foo/"; } } private volatile boolean closed; @Override public void close() { closed = true; try { ss.close(); } catch (Throwable e) { e.printStackTrace(); } for (Connection c : sockets) { c.close(); } } @Override public void run() { try { while (!closed) { try { System.out.println("Server waiting for connection"); Socket s = ss.accept(); Connection c = new Connection(s); c.start(); System.out.println("Server got new connection: " + c); synchronized (additions) { additions.add(c); } } catch (IOException e) { if (closed) return; e.printStackTrace(System.out); } } } catch (Throwable t) { System.out.println("Unexpected exception in accept loop: " + t); t.printStackTrace(System.out); } finally { if (closed) { System.out.println("Server closed: exiting accept loop"); } else { System.out.println("Server not closed: exiting accept loop and closing"); close(); } } } }