1 package be
.nikiroo
.utils
.serial
;
3 import java
.io
.IOException
;
4 import java
.net
.ServerSocket
;
5 import java
.net
.Socket
;
6 import java
.util
.ArrayList
;
9 import javax
.net
.ssl
.SSLServerSocket
;
10 import javax
.net
.ssl
.SSLServerSocketFactory
;
11 import javax
.net
.ssl
.SSLSocket
;
12 import javax
.net
.ssl
.SSLSocketFactory
;
14 import be
.nikiroo
.utils
.TraceHandler
;
15 import be
.nikiroo
.utils
.Version
;
18 * This class implements a simple server that can listen for connections and
19 * send/receive objects.
21 * Note: this {@link Server} has to be discarded after use (cannot be started
26 abstract public class Server
implements Runnable
{
27 static private final String
[] ANON_CIPHERS
= getAnonCiphers();
29 private final String name
;
30 private final boolean ssl
;
31 private final Object lock
= new Object();
32 private final Object counterLock
= new Object();
34 private ServerSocket ss
;
37 private boolean started
;
38 private boolean exiting
= false;
41 private TraceHandler tracer
= new TraceHandler();
44 public Server(@SuppressWarnings("unused") Version notUsed
, int port
,
45 boolean ssl
) throws IOException
{
50 * Create a new server that will start listening on the network when
51 * {@link Server#start()} is called.
54 * the port to listen on, or 0 to assign any unallocated port
55 * found (which can later on be queried via
56 * {@link Server#getPort()}
58 * use a SSL connection (or not)
61 * in case of I/O error
63 public Server(int port
, boolean ssl
) throws IOException
{
64 this((String
) null, port
, ssl
);
68 * Create a new server that will start listening on the network when
69 * {@link Server#start()} is called.
72 * the server name (only used for debug info and traces)
74 * the port to listen on
76 * use a SSL connection (or not)
79 * in case of I/O error
81 public Server(String name
, int port
, boolean ssl
) throws IOException
{
85 this.ss
= createSocketServer(port
, ssl
);
88 this.port
= this.ss
.getLocalPort();
93 * The traces handler for this {@link Server}.
95 * @return the traces handler
97 public TraceHandler
getTraceHandler() {
102 * The traces handler for this {@link Server}.
105 * the new traces handler
107 public void setTraceHandler(TraceHandler tracer
) {
108 if (tracer
== null) {
109 tracer
= new TraceHandler(false, false, false);
112 this.tracer
= tracer
;
116 * Return the assigned port.
118 * @return the assigned port
120 public int getPort() {
125 * Start the server (listen on the network for new connections).
127 * Can only be called once.
129 * This call is asynchronous, and will just start a new {@link Thread} on
130 * itself (see {@link Server#run()}).
132 public void start() {
133 new Thread(this).start();
137 * Start the server (listen on the network for new connections).
139 * Can only be called once.
141 * You may call it via {@link Server#start()} for an asynchronous call, too.
145 ServerSocket ss
= null;
146 boolean alreadyStarted
= false;
147 synchronized (lock
) {
149 if (!started
&& ss
!= null) {
152 alreadyStarted
= started
;
156 if (alreadyStarted
) {
157 tracer
.error(name
+ ": cannot start server on port " + port
158 + ", it is already started");
163 tracer
.error(name
+ ": cannot start server on port " + port
164 + ", it has already been used");
169 tracer
.trace(name
+ ": server starting on port " + port
);
171 while (started
&& !exiting
) {
173 Socket s
= ss
.accept();
174 new ConnectActionServer(s
) {
176 public void action(Version clientVersion
) throws Exception
{
178 for (Object data
= rec(); true; data
= rec()) {
181 rep
= onRequest(this, clientVersion
, data
);
182 } catch (Exception e
) {
187 } catch (NullPointerException e
) {
188 // Client has no data any more, we quit
190 + ": client has data no more, stopping connection");
195 public void connect() {
205 // Will be covered by @link{Server#stop(long)} for timeouts
206 while (counter
> 0) {
209 } catch (Exception e
) {
216 } catch (Exception e
) {
226 tracer
.trace(name
+ ": client terminated on port " + port
);
231 * Will stop the server, synchronously and without a timeout.
234 tracer
.trace(name
+ ": stopping server");
242 * the maximum timeout to wait for existing actions to complete,
243 * or 0 for "no timeout"
245 * wait for the server to be stopped before returning
246 * (synchronous) or not (asynchronous)
248 public void stop(final long timeout
, final boolean wait
) {
252 new Thread(new Runnable() {
262 * Stop the server (synchronous).
265 * the maximum timeout to wait for existing actions to complete,
266 * or 0 for "no timeout"
268 private void stop(long timeout
) {
269 tracer
.trace(name
+ ": server stopping on port " + port
);
270 synchronized (lock
) {
271 if (started
&& !exiting
) {
275 new ConnectActionClient(createSocket(null, port
, ssl
))
278 while (ss
!= null && timeout
> 0 && timeout
> time
) {
282 } catch (Exception e
) {
284 counter
= 0; // will stop the main thread
290 // only return when stopped
291 while (started
|| exiting
) {
294 } catch (InterruptedException e
) {
301 * This is the method that is called on each client request.
303 * You are expected to react to it and return an answer (which can be NULL).
307 * @param clientVersion
310 * the data sent by the client (which can be NULL)
312 * @return the answer to return to the client (which can be NULL)
315 * in case of an exception, the error will only be logged
317 abstract protected Object
onRequest(ConnectActionServer action
,
318 Version clientVersion
, Object data
) throws Exception
;
321 * This method will be called on errors.
323 * By default, it will only call the trace handler (so you may want to call
324 * super {@link Server#onError} if you override it).
329 protected void onError(Exception e
) {
334 * Change the number of currently serviced actions.
337 * the number to increase or decrease
339 * @return the current number after this operation
341 private int count(int change
) {
342 synchronized (counterLock
) {
349 * Create a {@link Socket}.
352 * the host to connect to
354 * the port to connect to
356 * TRUE for SSL mode (or FALSE for plain text mode)
358 * @return the {@link Socket}
360 * @throws IOException
361 * in case of I/O error
363 static Socket
createSocket(String host
, int port
, boolean ssl
)
367 s
= SSLSocketFactory
.getDefault().createSocket(host
, port
);
368 ((SSLSocket
) s
).setEnabledCipherSuites(ANON_CIPHERS
);
370 s
= new Socket(host
, port
);
377 * Create a {@link ServerSocket}.
380 * the port to accept connections on
382 * TRUE for SSL mode (or FALSE for plain text mode)
384 * @return the {@link ServerSocket}
386 * @throws IOException
387 * in case of I/O error
389 static ServerSocket
createSocketServer(int port
, boolean ssl
)
393 ss
= SSLServerSocketFactory
.getDefault().createServerSocket(port
);
394 ((SSLServerSocket
) ss
).setEnabledCipherSuites(ANON_CIPHERS
);
396 ss
= new ServerSocket(port
);
403 * Return all the supported ciphers that do not use authentication.
405 * @return the list of such supported ciphers
407 private static String
[] getAnonCiphers() {
408 List
<String
> anonCiphers
= new ArrayList
<String
>();
409 for (String cipher
: ((SSLSocketFactory
) SSLSocketFactory
.getDefault())
410 .getSupportedCipherSuites()) {
411 if (cipher
.contains("_anon_")) {
412 anonCiphers
.add(cipher
);
416 return anonCiphers
.toArray(new String
[] {});