View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net;
19  
20  import java.io.Closeable;
21  import java.io.IOException;
22  import java.net.InetAddress;
23  import java.net.ServerSocket;
24  import java.net.Socket;
25  
26  /**
27   * The MockTcpServer class is a simple TCP implementation of a server for the different TCP related protocols.
28   * <p>
29   * To use this class simply create a new class that will extend {@link MockTcpServer} and implement
30   * {@link MockTcpServer#processClientSocket(Socket)} method.
31   * <p>
32   * Example usage:
33   * <code>
34   * final class HelloWorldTCPServer extends MockTcpServer {
35   *      // other fields and constructors are omitted for brevity
36   *     {@literal @Override}
37   *     protected void processClientSocket(final Socket clientSocket) throws Exception {
38   *         try (PrintWriter pw = new PrintWriter(clientSocket.getOutputStream())) {
39   *             pw.write("Hello, World!");
40   *             pw.flush();
41   *         }
42   *     }
43   * }
44   * </code>
45   * <p>NOTE: this is for <B>debugging and testing purposes only</B> and not meant to be run as a reliable server.</p>
46   * @see org.apache.commons.net.daytime.MockDaytimeTCPServer MockDaytimeTCPServer (for example Daytime Protocol implementation)
47   */
48  public abstract class MockTcpServer implements Runnable, Closeable {
49  
50      /** Flag that indicates whether server is running */
51      protected volatile boolean running;
52  
53      /** {@link InetAddress} on which server is bound to */
54      protected final InetAddress serverAddress;
55  
56      /** {@link ServerSocket} on which server listens */
57      protected final ServerSocket serverSocket;
58  
59      /** Port number on which {@link #serverSocket} is listening */
60      protected final int port;
61  
62      /** {@link Thread} that server is running on */
63      protected final Thread serverThread;
64  
65      /**
66       * Creates new {@link MockTcpServer} that will bind to {@link InetAddress#getLocalHost()}
67       * on random port.
68       *
69       * @throws IOException if an I/O error occurs when opening the socket.
70       */
71      protected MockTcpServer() throws IOException {
72          this(0);
73      }
74  
75      /**
76       * Creates new {@link MockTcpServer} that will bind to {@link InetAddress#getLocalHost()}
77       * on specified port.
78       *
79       * @param port the port number the server will bind to, or 0 to use a port number that is automatically allocated
80       * @throws IOException if an I/O error occurs when opening the socket.
81       */
82      protected MockTcpServer(final int port) throws IOException {
83          this(port, InetAddress.getLocalHost());
84      }
85  
86      /**
87       * Creates new {@link MockTcpServer} that will bind to specified {@link InetAddress} and on specified port.
88       *
89       * @param port the port number the server will bind to, or 0 to use a port number that is automatically allocated
90       * @param serverAddress the InetAddress the server will bind to
91       * @throws IOException if an I/O error occurs when opening the socket.
92       */
93      protected MockTcpServer(final int port, final InetAddress serverAddress) throws IOException {
94          this.serverAddress = serverAddress;
95          this.serverSocket = new ServerSocket(port, 50, serverAddress);
96          this.port = serverSocket.getLocalPort();
97          this.serverThread = new Thread(this);
98      }
99  
100     /**
101      * Closes server socket and stop listening.
102      * Calling this method will have the same effect as {@link MockTcpServer#stop()}
103      *
104      * @throws IOException If an I/O error occurs while closing the {@link #serverSocket}.
105      * @see MockTcpServer#stop()
106      */
107     @Override
108     public void close() throws IOException {
109         stop();
110     }
111 
112     /**
113      * Gets the port number on which {@link #serverSocket} is listening
114      *
115      * @return the port number to which this socket is listening or -1
116      *         if the socket is not bound yet
117      */
118     public int getPort() {
119         return port;
120     }
121 
122     /**
123      * Processes next client {@link Socket} from this server.
124      *
125      * @implNote there is no need to manually close {@code clientSocket}, it will be closed for you
126      * @param clientSocket next accepted {@link Socket} you can use. Never {@code null}
127      * @throws Exception in case of any error
128      */
129     protected abstract void processClientSocket(Socket clientSocket) throws Exception;
130 
131     @Override
132     public final void run() {
133         while (running) {
134             try (Socket clientSocket = serverSocket.accept()) {
135                 processClientSocket(clientSocket);
136             } catch (final IOException e) {
137                 System.err.println("IOException on MockWebServer serverSocket.accept(): " + e);
138             } catch (final Exception e) {
139                 System.err.println("MockWebServer serverSocket.accept() error: " + e);
140             }
141         }
142     }
143 
144     /**
145      * Starts the server and begins to listen on {@link #serverSocket}
146      */
147     public synchronized void start() {
148         System.out.println("Starting MockWebServer...");
149         if (!running) {
150             running = true;
151             serverThread.start();
152             System.out.println("Successfully started MockWebServer on address " + serverAddress.getHostAddress() + " and port " + port);
153         }
154     }
155 
156     /**
157      * Closes server socket and stop listening.
158      *
159      * @throws IOException If an I/O error occurs while closing the {@link #serverSocket}.
160      * @see MockTcpServer#close()
161      */
162     public synchronized void stop() throws IOException {
163         System.out.println("Closing MockWebServer...");
164         if (!running) {
165             return;
166         }
167         running = false;
168         serverThread.interrupt();
169         try {
170             serverSocket.close();
171             System.out.println("Successfully closed MockWebServer!");
172         } catch (final IOException ioException) {
173             System.err.println("Could not stop MockWebServer, cause: " + ioException.getMessage());
174             throw ioException;
175         }
176     }
177 
178 }
179