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