1 /*
   2  * Copyright (c) 2018, 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.InputStream;
  26 import java.io.IOException;
  27 import java.io.OutputStream;
  28 import java.net.InetAddress;
  29 import java.net.InetSocketAddress;
  30 import java.net.ServerSocket;
  31 import java.net.Socket;
  32 import java.net.StandardProtocolFamily;
  33 import java.util.ArrayList;
  34 import java.util.Date;
  35 import java.util.List;
  36 import jdk.net.RdmaSockets;
  37 
  38 import jdk.test.lib.Utils;
  39 
  40 
  41 public class TestServers {
  42 
  43     private TestServers() { }
  44 
  45     /**
  46      * An abstract server identifies a server which listens on a port on on a
  47      * given machine.
  48      */
  49     static abstract class AbstractServer {
  50 
  51         private AbstractServer() {
  52         }
  53 
  54         public abstract int getPort();
  55 
  56         public abstract InetAddress getAddress();
  57     }
  58 
  59     /**
  60      * A downgraded type of AbstractServer which will refuse connections. Note:
  61      * use it once and throw it away - this implementation opens an anonymous
  62      * socket and closes it, returning the address of the closed socket. If
  63      * other servers are started afterwards, the address/port might get reused
  64      * and become connectable again - so it's not a good idea to assume that
  65      * connections using this address/port will always be refused. Connections
  66      * will be refused as long as the address/port of the refusing server has
  67      * not been reused.
  68      */
  69     static class RefusingServer extends AbstractServer {
  70 
  71         final InetAddress address;
  72         final int port;
  73 
  74         private RefusingServer(InetAddress address, int port) {
  75             this.address = address;
  76             this.port = port;
  77         }
  78 
  79         @Override
  80         public int getPort() {
  81             return port;
  82         }
  83 
  84         @Override
  85         public InetAddress getAddress() {
  86             return address;
  87         }
  88 
  89         public static RefusingServer newRefusingServer() throws IOException {
  90             return new RefusingServer(InetAddress.getLocalHost(),
  91                        Utils.refusingEndpoint().getPort());
  92         }
  93     }
  94 
  95     /**
  96      * An abstract class for implementing small TCP servers for the nio tests
  97      * purposes. Disclaimer: This is a naive implementation that uses the old
  98      * networking APIs (not those from {@code java.nio.*}) and shamelessly
  99      * extends/creates Threads instead of using an executor service.
 100      */
 101     static abstract class AbstractTcpServer extends AbstractServer
 102             implements Runnable, Closeable {
 103 
 104         protected final long linger; // #of ms to wait before responding
 105         private Thread acceptThread; // thread waiting for accept
 106         // list of opened connections that should be closed on close.
 107         private List<TcpConnectionThread> connections = new ArrayList<>();
 108         private ServerSocket serverSocket; // the server socket
 109         private boolean started = false; // whether the server is started
 110         Throwable error = null;
 111 
 112         /**
 113          * Creates a new abstract TCP server.
 114          *
 115          * @param linger the amount of time the server should wait before
 116          * responding to requests.
 117          */
 118         protected AbstractTcpServer(long linger) {
 119             this.linger = linger;
 120         }
 121 
 122         /**
 123          * The local port to which the server is bound.
 124          *
 125          * @return The local port to which the server is bound.
 126          * @exception IllegalStateException is thrown if the server is not
 127          * started.
 128          */
 129         @Override
 130         public final synchronized int getPort() {
 131             if (!started) {
 132                 throw new IllegalStateException("Not started");
 133             }
 134             return serverSocket.getLocalPort();
 135         }
 136 
 137         /**
 138          * The local address to which the server is bound.
 139          *
 140          * @return The local address to which the server is bound.
 141          * @exception IllegalStateException is thrown if the server is not
 142          * started.
 143          */
 144         @Override
 145         public final synchronized InetAddress getAddress() {
 146             if (!started) {
 147                 throw new IllegalStateException("Not started");
 148             }
 149             return serverSocket.getInetAddress();
 150         }
 151 
 152         /**
 153          * Tells whether the server is started.
 154          *
 155          * @return true if the server is started.
 156          */
 157         public final synchronized boolean isStarted() {
 158             return started;
 159         }
 160 
 161         /**
 162          * Creates a new server socket.
 163          *
 164          * @param port local port to bind to.
 165          * @param backlog requested maximum length of the queue of incoming
 166          * connections.
 167          * @param address local address to bind to.
 168          * @return a new bound server socket ready to accept connections.
 169          * @throws IOException if the socket cannot be created or bound.
 170          */
 171         protected ServerSocket newServerSocket(int port, int backlog,
 172                 InetAddress address)
 173                 throws IOException {
 174             ServerSocket ss = RdmaSockets.openServerSocket(StandardProtocolFamily.INET);
 175             ss.bind(new InetSocketAddress(address, port), backlog);
 176             return ss;
 177         }
 178 
 179         /**
 180          * Starts listening for connections.
 181          *
 182          * @throws IOException if the server socket cannot be created or bound.
 183          */
 184         public final synchronized void start() throws IOException {
 185             if (started) {
 186                 return;
 187             }
 188             final ServerSocket socket =
 189                     newServerSocket(0, 100, InetAddress.getLocalHost());
 190             serverSocket = socket;
 191             acceptThread = new Thread(this);
 192             acceptThread.setDaemon(true);
 193             acceptThread.start();
 194             started = true;
 195         }
 196 
 197         /**
 198          * Calls {@code Thread.sleep(linger);}
 199          */
 200         protected final void lingerIfRequired() {
 201             if (linger > 0) {
 202                 try {
 203                     Thread.sleep(linger);
 204                 } catch (InterruptedException x) {
 205                     Thread.interrupted();
 206                     final ServerSocket socket = serverSocket();
 207                     if (socket != null && !socket.isClosed()) {
 208                         System.err.println("Thread interrupted...");
 209                     }
 210                 }
 211             }
 212         }
 213 
 214         final synchronized ServerSocket serverSocket() {
 215             return this.serverSocket;
 216         }
 217 
 218         /**
 219          * The main accept loop.
 220          */
 221         @Override
 222         public final void run() {
 223             final ServerSocket sSocket = serverSocket();
 224             try {
 225                 Socket s;
 226                 while (isStarted() && !Thread.interrupted()
 227                         && (s = sSocket.accept()) != null) {
 228                     lingerIfRequired();
 229                     listen(s);
 230                 }
 231             } catch (Exception x) {
 232                 error = x;
 233             } finally {
 234                 synchronized (this) {
 235                     if (!sSocket.isClosed()) {
 236                         try {
 237                             sSocket.close();
 238                         } catch (IOException x) {
 239                             System.err.println("Failed to close server socket");
 240                         }
 241                     }
 242                     if (started && this.serverSocket == sSocket) {
 243                         started = false;
 244                         this.serverSocket = null;
 245                         this.acceptThread = null;
 246                     }
 247                 }
 248             }
 249         }
 250 
 251         /**
 252          * Represents a connection accepted by the server.
 253          */
 254         protected abstract class TcpConnectionThread extends Thread {
 255 
 256             protected final Socket socket;
 257 
 258             protected TcpConnectionThread(Socket socket) {
 259                 this.socket = socket;
 260                 this.setDaemon(true);
 261             }
 262 
 263             public void close() throws IOException {
 264                 socket.close();
 265                 interrupt();
 266             }
 267         }
 268 
 269         /**
 270          * Creates a new TcpConnnectionThread to handle the connection through
 271          * an accepted socket.
 272          *
 273          * @param s the socket returned by {@code serverSocket.accept()}.
 274          * @return a new TcpConnnectionThread to handle the connection through
 275          * an accepted socket.
 276          */
 277         protected abstract TcpConnectionThread createConnection(Socket s);
 278 
 279         /**
 280          * Creates and starts a new TcpConnectionThread to handle the accepted
 281          * socket.
 282          *
 283          * @param s the socket returned by {@code serverSocket.accept()}.
 284          */
 285         private synchronized void listen(Socket s) {
 286             TcpConnectionThread c = createConnection(s);
 287             c.start();
 288             addConnection(c);
 289         }
 290 
 291         /**
 292          * Add the connection to the list of accepted connections.
 293          *
 294          * @param connection an accepted connection.
 295          */
 296         protected synchronized void addConnection(
 297                 TcpConnectionThread connection) {
 298             connections.add(connection);
 299         }
 300 
 301         /**
 302          * Remove the connection from the list of accepted connections.
 303          *
 304          * @param connection an accepted connection.
 305          */
 306         protected synchronized void removeConnection(
 307                 TcpConnectionThread connection) {
 308             connections.remove(connection);
 309         }
 310 
 311         /**
 312          * Close the server socket and all the connections present in the list
 313          * of accepted connections.
 314          *
 315          * @throws IOException
 316          */
 317         @Override
 318         public synchronized void close() throws IOException {
 319             if (serverSocket != null && !serverSocket.isClosed()) {
 320                 serverSocket.close();
 321             }
 322             if (acceptThread != null) {
 323                 acceptThread.interrupt();
 324             }
 325             int failed = 0;
 326             for (TcpConnectionThread c : connections) {
 327                 try {
 328                     c.close();
 329                 } catch (IOException x) {
 330                     // no matter - we're closing.
 331                     failed++;
 332                 }
 333             }
 334             connections.clear();
 335             if (failed > 0) {
 336                 throw new IOException("Failed to close some connections");
 337             }
 338         }
 339     }
 340 
 341     /**
 342      * A small TCP Server that emulates the echo service for tests purposes. See
 343      * http://en.wikipedia.org/wiki/Echo_Protocol This server uses an anonymous
 344      * port - NOT the standard port 7. We don't guarantee that its behavior
 345      * exactly matches the RFC - the only purpose of this server is to have
 346      * something that responds to nio tests...
 347      */
 348     static class EchoServer extends AbstractTcpServer {
 349 
 350         public EchoServer() {
 351             this(0L);
 352         }
 353 
 354         public EchoServer(long linger) {
 355             super(linger);
 356         }
 357 
 358         @Override
 359         protected TcpConnectionThread createConnection(Socket s) {
 360             return new EchoConnection(s);
 361         }
 362 
 363         private final class EchoConnection extends TcpConnectionThread {
 364 
 365             public EchoConnection(Socket socket) {
 366                 super(socket);
 367             }
 368 
 369             @Override
 370             public void run() {
 371                 try {
 372                     final InputStream is = socket.getInputStream();
 373                     final OutputStream out = socket.getOutputStream();
 374                     byte[] b = new byte[255];
 375                     int n;
 376                     while ((n = is.read(b)) > 0) {
 377                         lingerIfRequired();
 378                         out.write(b, 0, n);
 379                     }
 380                 } catch (IOException io) {
 381                     // fall through to finally
 382                 } finally {
 383                     if (!socket.isClosed()) {
 384                         try {
 385                             socket.close();
 386                         } catch (IOException x) {
 387                             System.err.println(
 388                                     "Failed to close echo connection socket");
 389                         }
 390                     }
 391                     removeConnection(this);
 392                 }
 393             }
 394         }
 395 
 396         public static EchoServer startNewServer() throws IOException {
 397             return startNewServer(0);
 398         }
 399 
 400         public static EchoServer startNewServer(long linger) throws IOException {
 401             final EchoServer echoServer = new EchoServer(linger);
 402             echoServer.start();
 403             return echoServer;
 404         }
 405     }
 406 
 407 
 408     /**
 409      * A small TCP Server that accept connections but does not response to any input.
 410      */
 411     static final class NoResponseServer extends EchoServer {
 412         public NoResponseServer() {
 413             super(Long.MAX_VALUE);
 414         }
 415 
 416         public static NoResponseServer startNewServer() throws IOException {
 417             final NoResponseServer noResponseServer = new NoResponseServer();
 418             noResponseServer.start();
 419             return noResponseServer;
 420         }
 421     }
 422 
 423 
 424     /**
 425      * A small TCP server that emulates the Day & Time service for tests
 426      * purposes. See http://en.wikipedia.org/wiki/Daytime_Protocol This server
 427      * uses an anonymous port - NOT the standard port 13. We don't guarantee
 428      * that its behavior exactly matches the RFC - the only purpose of this
 429      * server is to have something that responds to nio tests...
 430      */
 431     static final class DayTimeServer extends AbstractTcpServer {
 432 
 433         public DayTimeServer() {
 434             this(0L);
 435         }
 436 
 437         public DayTimeServer(long linger) {
 438             super(linger);
 439         }
 440 
 441         @Override
 442         protected TcpConnectionThread createConnection(Socket s) {
 443             return new DayTimeServerConnection(s);
 444         }
 445 
 446         @Override
 447         protected void addConnection(TcpConnectionThread connection) {
 448             // do nothing - the connection just write the date and terminates.
 449         }
 450 
 451         @Override
 452         protected void removeConnection(TcpConnectionThread connection) {
 453             // do nothing - we're not adding connections to the list...
 454         }
 455 
 456         private final class DayTimeServerConnection extends TcpConnectionThread {
 457 
 458             public DayTimeServerConnection(Socket socket) {
 459                 super(socket);
 460             }
 461 
 462             @Override
 463             public void run() {
 464                 try {
 465                     final OutputStream out = socket.getOutputStream();
 466                     lingerIfRequired();
 467                     out.write(new Date(System.currentTimeMillis())
 468                             .toString().getBytes("US-ASCII"));
 469                     out.flush();
 470                 } catch (IOException io) {
 471                     // fall through to finally
 472                 } finally {
 473                     if (!socket.isClosed()) {
 474                         try {
 475                             socket.close();
 476                         } catch (IOException x) {
 477                             System.err.println(
 478                                     "Failed to close echo connection socket");
 479                         }
 480                     }
 481                 }
 482             }
 483         }
 484 
 485         public static DayTimeServer startNewServer()
 486                 throws IOException {
 487             return startNewServer(0);
 488         }
 489 
 490         public static DayTimeServer startNewServer(long linger)
 491                 throws IOException {
 492             final DayTimeServer daytimeServer = new DayTimeServer(linger);
 493             daytimeServer.start();
 494             return daytimeServer;
 495         }
 496     }
 497 }