X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fserial%2FServer.java;h=934be33786e27b1bbe159e39afce42cdbc373845;hb=d463d2663337bb11aaeab48a06d2b3091c3b7830;hp=bc4e637a7d65d0e8bbdc154c6b45c32b0197a80f;hpb=cd0c27d2e457ea19fcd9def879e1534a528292c2;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/serial/Server.java b/src/be/nikiroo/utils/serial/Server.java index bc4e637..934be33 100644 --- a/src/be/nikiroo/utils/serial/Server.java +++ b/src/be/nikiroo/utils/serial/Server.java @@ -11,19 +11,34 @@ import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; +import be.nikiroo.utils.TraceHandler; import be.nikiroo.utils.Version; +/** + * This class implements a simple server that can listen for connections and + * send/receive objects. + *

+ * Note: this {@link Server} has to be discarded after use (cannot be started + * twice). + * + * @author niki + */ abstract public class Server implements Runnable { static private final String[] ANON_CIPHERS = getAnonCiphers(); - private int port; - private boolean ssl; + private final String name; + private final boolean ssl; + private final Object lock = new Object(); + private final Object counterLock = new Object(); + private ServerSocket ss; + private int port; + private boolean started; private boolean exiting = false; private int counter; - private Object lock = new Object(); - private Object counterLock = new Object(); + + private TraceHandler tracer = new TraceHandler(); @Deprecated public Server(@SuppressWarnings("unused") Version notUsed, int port, @@ -31,90 +46,136 @@ abstract public class Server implements Runnable { this(port, ssl); } + /** + * Create a new server that will start listening on the network when + * {@link Server#start()} is called. + * + * @param port + * the port to listen on, or 0 to assign any unallocated port + * found (which can later on be queried via + * {@link Server#getPort()} + * @param ssl + * use a SSL connection (or not) + * + * @throws IOException + * in case of I/O error + */ public Server(int port, boolean ssl) throws IOException { + this((String) null, port, ssl); + } + + /** + * Create a new server that will start listening on the network when + * {@link Server#start()} is called. + * + * @param name + * the server name (only used for debug info and traces) + * @param port + * the port to listen on + * @param ssl + * use a SSL connection (or not) + * + * @throws IOException + * in case of I/O error + */ + public Server(String name, int port, boolean ssl) throws IOException { + this.name = name; this.port = port; this.ssl = ssl; this.ss = createSocketServer(port, ssl); - } - public void start() { - synchronized (lock) { - if (!started) { - started = true; - new Thread(this).start(); - } + if (this.port == 0) { + this.port = this.ss.getLocalPort(); } } - public void stop() { - stop(0, true); + /** + * The traces handler for this {@link Server}. + * + * @return the traces handler + */ + public TraceHandler getTraceHandler() { + return tracer; } - // wait = wait before returning (sync VS async) timeout in ms, 0 or -1 for - // never - public void stop(final long timeout, final boolean wait) { - if (wait) { - stop(timeout); - } else { - new Thread(new Runnable() { - @Override - public void run() { - stop(timeout); - } - }).start(); + /** + * The traces handler for this {@link Server}. + * + * @param tracer + * the new traces handler + */ + public void setTraceHandler(TraceHandler tracer) { + if (tracer == null) { + tracer = new TraceHandler(false, false, false); } + + this.tracer = tracer; } - // timeout in ms, 0 or -1 or never - private void stop(long timeout) { - synchronized (lock) { - if (started && !exiting) { - exiting = true; + /** + * Return the assigned port. + * + * @return the assigned port + */ + public int getPort() { + return port; + } - try { - new ConnectActionClient(createSocket(null, port, ssl)) { - @Override - public void action(Version serverVersion) - throws Exception { - } - }.connect(); + /** + * Start the server (listen on the network for new connections). + *

+ * Can only be called once. + *

+ * This call is asynchronous, and will just start a new {@link Thread} on + * itself (see {@link Server#run()}). + */ + public void start() { + new Thread(this).start(); + } - long time = 0; - while (ss != null && timeout > 0 && timeout > time) { - Thread.sleep(10); - time += 10; - } - } catch (Exception e) { - if (ss != null) { - counter = 0; // will stop the main thread - onError(e); - } - } + /** + * Start the server (listen on the network for new connections). + *

+ * Can only be called once. + *

+ * You may call it via {@link Server#start()} for an asynchronous call, too. + */ + @Override + public void run() { + ServerSocket ss = null; + boolean alreadyStarted = false; + synchronized (lock) { + ss = this.ss; + if (!started && ss != null) { + started = true; + } else { + alreadyStarted = started; } + } - // only return when stopped - while (started || exiting) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - } - } + if (alreadyStarted) { + tracer.error(name + ": cannot start server on port " + port + + ", it is already started"); + return; + } + + if (ss == null) { + tracer.error(name + ": cannot start server on port " + port + + ", it has already been used"); + return; } - } - @Override - public void run() { try { + tracer.trace(name + ": server starting on port " + port); + while (started && !exiting) { count(1); Socket s = ss.accept(); new ConnectActionServer(s) { - private Version clientVersion = new Version(); - @Override - public void action(Version dummy) throws Exception { + public void action(Version clientVersion) throws Exception { try { - for (Object data = flush(); true; data = flush()) { + for (Object data = rec(); true; data = rec()) { Object rep = null; try { rep = onRequest(this, clientVersion, data); @@ -125,6 +186,8 @@ abstract public class Server implements Runnable { } } catch (NullPointerException e) { // Client has no data any more, we quit + tracer.trace(name + + ": client has data no more, stopping connection"); } } @@ -136,11 +199,6 @@ abstract public class Server implements Runnable { count(-1); } } - - @Override - protected void onClientVersionReceived(Version clientVersion) { - this.clientVersion = clientVersion; - } }.connectAsync(); } @@ -159,25 +217,127 @@ abstract public class Server implements Runnable { onError(e); } - ss = null; + this.ss = null; started = false; exiting = false; counter = 0; + + tracer.trace(name + ": client terminated on port " + port); + } + } + + /** + * Will stop the server, synchronously and without a timeout. + */ + public void stop() { + tracer.trace(name + ": stopping server"); + stop(0, true); + } + + /** + * Stop the server. + * + * @param timeout + * the maximum timeout to wait for existing actions to complete, + * or 0 for "no timeout" + * @param wait + * wait for the server to be stopped before returning + * (synchronous) or not (asynchronous) + */ + public void stop(final long timeout, final boolean wait) { + if (wait) { + stop(timeout); + } else { + new Thread(new Runnable() { + @Override + public void run() { + stop(timeout); + } + }).start(); + } + } + + /** + * Stop the server (synchronous). + * + * @param timeout + * the maximum timeout to wait for existing actions to complete, + * or 0 for "no timeout" + */ + private void stop(long timeout) { + tracer.trace(name + ": server stopping on port " + port); + synchronized (lock) { + if (started && !exiting) { + exiting = true; + + try { + new ConnectActionClient(createSocket(null, port, ssl)) + .connect(); + long time = 0; + while (ss != null && timeout > 0 && timeout > time) { + Thread.sleep(10); + time += 10; + } + } catch (Exception e) { + if (ss != null) { + counter = 0; // will stop the main thread + onError(e); + } + } + } + + // only return when stopped + while (started || exiting) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } } } + /** + * This is the method that is called on each client request. + *

+ * You are expected to react to it and return an answer (which can be NULL). + * + * @param action + * the client action + * @param clientVersion + * the client version + * @param data + * the data sent by the client (which can be NULL) + * + * @return the answer to return to the client (which can be NULL) + * + * @throws Exception + * in case of an exception, the error will only be logged + */ abstract protected Object onRequest(ConnectActionServer action, Version clientVersion, Object data) throws Exception; + /** + * This method will be called on errors. + *

+ * By default, it will only call the trace handler (so you may want to call + * super {@link Server#onError} if you override it). + * + * @param e + * the error + */ protected void onError(Exception e) { - if (e == null) { - e = new Exception("Unknown error"); - } - - e.printStackTrace(); + tracer.error(e); } + /** + * Change the number of currently serviced actions. + * + * @param change + * the number to increase or decrease + * + * @return the current number after this operation + */ private int count(int change) { synchronized (counterLock) { counter += change; @@ -185,6 +345,21 @@ abstract public class Server implements Runnable { } } + /** + * Create a {@link Socket}. + * + * @param host + * the host to connect to + * @param port + * the port to connect to + * @param ssl + * TRUE for SSL mode (or FALSE for plain text mode) + * + * @return the {@link Socket} + * + * @throws IOException + * in case of I/O error + */ static Socket createSocket(String host, int port, boolean ssl) throws IOException { Socket s; @@ -198,6 +373,19 @@ abstract public class Server implements Runnable { return s; } + /** + * Create a {@link ServerSocket}. + * + * @param port + * the port to accept connections on + * @param ssl + * TRUE for SSL mode (or FALSE for plain text mode) + * + * @return the {@link ServerSocket} + * + * @throws IOException + * in case of I/O error + */ static ServerSocket createSocketServer(int port, boolean ssl) throws IOException { ServerSocket ss; @@ -211,6 +399,11 @@ abstract public class Server implements Runnable { return ss; } + /** + * Return all the supported ciphers that do not use authentication. + * + * @return the list of such supported ciphers + */ private static String[] getAnonCiphers() { List anonCiphers = new ArrayList(); for (String cipher : ((SSLSocketFactory) SSLSocketFactory.getDefault())