1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.net;
15  
16  import java.io.IOException;
17  import java.net.ServerSocket;
18  import java.net.Socket;
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.concurrent.CountDownLatch;
22  
23  import javax.net.ServerSocketFactory;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  import ch.qos.logback.classic.LoggerContext;
29  import ch.qos.logback.classic.joran.JoranConfigurator;
30  import ch.qos.logback.core.joran.spi.JoranException;
31  
32  /**
33   * A simple {@link SocketNode} based server.
34   * 
35   * <pre>
36   *      &lt;b&gt;Usage:&lt;/b&gt; java ch.qos.logback.classic.net.SimpleSocketServer port configFile
37   * </pre>
38   * 
39   * where <em>port</em> is a port number where the server listens and
40   * <em>configFile</em> is an XML configuration file fed to
41   * {@link JoranConfigurator}.
42   * 
43   * </pre>
44   * 
45   * @author Ceki G&uuml;lc&uuml;
46   * @author S&eacute;bastien Pennec
47   * 
48   * @since 0.8.4
49   */
50  public class SimpleSocketServer extends Thread {
51  
52      Logger logger = LoggerFactory.getLogger(SimpleSocketServer.class);
53  
54      private final int port;
55      private final LoggerContext lc;
56      private boolean closed = false;
57      private ServerSocket serverSocket;
58      private List<SocketNode> socketNodeList = new ArrayList<SocketNode>();
59  
60      // used for testing purposes
61      private CountDownLatch latch;
62  
63      public static void main(String argv[]) throws Exception {
64          doMain(SimpleSocketServer.class, argv);
65      }
66  
67      protected static void doMain(Class<? extends SimpleSocketServer> serverClass, String argv[]) throws Exception {
68          int port = -1;
69          if (argv.length == 2) {
70              port = parsePortNumber(argv[0]);
71          } else {
72              usage("Wrong number of arguments.");
73          }
74  
75          String configFile = argv[1];
76          LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
77          configureLC(lc, configFile);
78  
79          SimpleSocketServer sss = new SimpleSocketServer(lc, port);
80  
81          // start the server in a separate thread
82          sss.start();
83      }
84  
85      public SimpleSocketServer(LoggerContext lc, int port) {
86          this.lc = lc;
87          this.port = port;
88      }
89  
90      public void run() {
91  
92          final String oldThreadName = Thread.currentThread().getName();
93  
94          try {
95  
96              final String newThreadName = getServerThreadName();
97              Thread.currentThread().setName(newThreadName);
98  
99              logger.info("Listening on port " + port);
100             serverSocket = getServerSocketFactory().createServerSocket(port);
101             while (!closed) {
102                 logger.info("Waiting to accept a new client.");
103                 signalAlmostReadiness();
104                 Socket socket = serverSocket.accept();
105                 logger.info("Connected to client at " + socket.getInetAddress());
106                 logger.info("Starting new socket node.");
107                 SocketNode newSocketNode = new SocketNode(this, socket, lc);
108                 synchronized (socketNodeList) {
109                     socketNodeList.add(newSocketNode);
110                 }
111                 final String clientThreadName = getClientThreadName(socket);
112                 new Thread(newSocketNode, clientThreadName).start();
113             }
114         } catch (Exception e) {
115             if (closed) {
116                 logger.info("Exception in run method for a closed server. This is normal.");
117             } else {
118                 logger.error("Unexpected failure in run method", e);
119             }
120         }
121 
122         finally {
123             Thread.currentThread().setName(oldThreadName);
124         }
125     }
126 
127     /**
128      * Returns the name given to the server thread.
129      */
130     protected String getServerThreadName() {
131         return String.format("Logback %s (port %d)", getClass().getSimpleName(), port);
132     }
133 
134     /**
135      * Returns a name to identify each client thread.
136      */
137     protected String getClientThreadName(Socket socket) {
138         return String.format("Logback SocketNode (client: %s)", socket.getRemoteSocketAddress());
139     }
140 
141     /**
142      * Gets the platform default {@link ServerSocketFactory}.
143      * <p>
144      * Subclasses may override to provide a custom server socket factory.
145      */
146     protected ServerSocketFactory getServerSocketFactory() {
147         return ServerSocketFactory.getDefault();
148     }
149 
150     /**
151      * Signal another thread that we have established a connection This is useful
152      * for testing purposes.
153      */
154     void signalAlmostReadiness() {
155         if (latch != null && latch.getCount() != 0) {
156             // System.out.println("signalAlmostReadiness() with latch "+latch);
157             latch.countDown();
158         }
159     }
160 
161     /**
162      * Used for testing purposes
163      * 
164      * @param latch
165      */
166     void setLatch(CountDownLatch latch) {
167         this.latch = latch;
168     }
169 
170     /**
171      * Used for testing purposes
172      */
173     public CountDownLatch getLatch() {
174         return latch;
175     }
176 
177     public boolean isClosed() {
178         return closed;
179     }
180 
181     public void close() {
182         closed = true;
183         if (serverSocket != null) {
184             try {
185                 serverSocket.close();
186             } catch (IOException e) {
187                 logger.error("Failed to close serverSocket", e);
188             } finally {
189                 serverSocket = null;
190             }
191         }
192 
193         logger.info("closing this server");
194         synchronized (socketNodeList) {
195             for (SocketNode sn : socketNodeList) {
196                 sn.close();
197             }
198         }
199         if (socketNodeList.size() != 0) {
200             logger.warn("Was expecting a 0-sized socketNodeList after server shutdown");
201         }
202 
203     }
204 
205     public void socketNodeClosing(SocketNode sn) {
206         logger.debug("Removing {}", sn);
207 
208         // don't allow simultaneous access to the socketNodeList
209         // (e.g. removal whole iterating on the list causes
210         // java.util.ConcurrentModificationException)
211         synchronized (socketNodeList) {
212             socketNodeList.remove(sn);
213         }
214     }
215 
216     static void usage(String msg) {
217         System.err.println(msg);
218         System.err.println("Usage: java " + SimpleSocketServer.class.getName() + " port configFile");
219         System.exit(1);
220     }
221 
222     static int parsePortNumber(String portStr) {
223         try {
224             return Integer.parseInt(portStr);
225         } catch (java.lang.NumberFormatException e) {
226             e.printStackTrace();
227             usage("Could not interpret port number [" + portStr + "].");
228             // we won't get here
229             return -1;
230         }
231     }
232 
233     static public void configureLC(LoggerContext lc, String configFile) throws JoranException {
234         JoranConfigurator configurator = new JoranConfigurator();
235         lc.reset();
236         configurator.setContext(lc);
237         configurator.doConfigure(configFile);
238     }
239 }