diff -r 89829dd3cc54 -r b1c1b4ef4be2 test/jdk/java/net/httpclient/MockServer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/httpclient/MockServer.java Wed Dec 06 11:11:59 2017 -0800 @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2015, 2017, 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 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.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; + +/** + * 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 20 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, StandardCharsets.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("X: " + t); + 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(); + } + + public String getURL() { + if (ss instanceof SSLServerSocket) { + return "https://127.0.0.1:" + port() + "/foo/"; + } else { + return "http://127.0.0.1:" + port() + "/foo/"; + } + } + + private volatile boolean closed; + + @Override + public void close() { + closed = true; + try { + ss.close(); + } catch (IOException 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(); + } + } + } + +}