/* * Copyright (C) 2018 BARBOTIN Nicolas */ package net.montoyo.wd.miniserv.server; import net.montoyo.wd.WebDisplays; import net.montoyo.wd.utilities.Log; import net.montoyo.wd.utilities.serialization.Util; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.HashMap; public class Server implements Runnable { private static Server instance; public static Server getInstance() { if(instance == null) instance = new Server(); return instance; } private ServerSocketChannel server; private Selector selector; private int port = 25566; private final ArrayList clientList = new ArrayList<>(); private final HashMap clientMap = new HashMap<>(); private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(8192); private final ClientManager clientMgr = new ClientManager(); private File directory; private volatile boolean running; private volatile Thread thread; public int getPort() {return port;} public void setPort(int p) { port = p; } public void start() { thread = new Thread(this); thread.setName("MiniServServer"); thread.setDaemon(true); try { server = ServerSocketChannel.open(); server.bind(new InetSocketAddress(port)); server.configureBlocking(false); } catch(Throwable t) { t.printStackTrace(); Util.silentClose(server); server = null; return; } try { selector = Selector.open(); server.register(selector, SelectionKey.OP_ACCEPT); } catch(Throwable t) { t.printStackTrace(); Util.silentClose(selector); Util.silentClose(server); selector = null; server = null; return; } synchronized(this) { running = true; } thread.start(); } public void stopServer() throws IOException { if(getRunning()) { Thread thread = this.thread; synchronized(this) { running = false; selector.wakeup(); server.close(); } while(thread.isAlive()) { try { thread.join(); } catch(InterruptedException ex) { } } Log.info("Miniserv server stopped"); } } private boolean getRunning() { boolean ret; synchronized(this) { ret = running; } return ret; } @Override public void run() { while(getRunning()) { try { loopUnsafe(); } catch(Throwable t) { Log.errorEx("Miniserv Server crashed", t); break; } } synchronized(this) { running = false; } for(ServerClient cli: clientList) Util.silentClose(cli.getChannel()); clientList.clear(); clientMap.clear(); Util.silentClose(selector); Util.silentClose(server); selector = null; server = null; thread = null; } private void loopUnsafe() throws Throwable { selector.select(1000); //Allow the server to kick timed-out clients for(SelectionKey key: selector.selectedKeys()) { if (key == null) continue; try { // this can throw an exception // there is no getting around this fact other than try/catch if(key.isAcceptable()) { SocketChannel chan; try { chan = server.accept(); } catch(Throwable t) { Log.warningEx("Could not accept client", t); chan = null; } if(chan != null) { chan.configureBlocking(false); ServerClient toAdd = new ServerClient(chan, selector); clientMap.put(chan, toAdd); clientList.add(toAdd); } } if (key.isReadable()) { ServerClient cli = clientMap.get(key.channel()); if (cli == null) Log.warning("Received read info from unknown client"); else { try { readBuffer.clear(); int read = cli.getChannel().read(readBuffer); if (read < 0) cli.setShouldRemove(); //End of stream else if (read > 0) { readBuffer.position(0); readBuffer.limit(read); cli.readyRead(readBuffer); } } catch (Throwable t) { Log.warningEx("Could not read data from client", t); cli.setShouldRemove(); } } } if (key.isWritable()) { ServerClient cli = clientMap.get(key.channel()); if (cli == null) Log.warning("Received write info from unknown client"); else { try { cli.readyWrite(); } catch (Throwable t) { Log.warningEx("Could not write data to client", t); cli.setShouldRemove(); } } } } catch (Throwable err) { continue; } } long ctime = System.currentTimeMillis(); for(int i = clientList.size() - 1; i >= 0; i--) { ServerClient cli = clientList.get(i); if(cli.shouldRemove()) removeClient(cli); else if(cli.hasTimedOut(ctime)) { Log.info("Client %s has timed out!", cli.getUUIDString()); removeClient(cli); } } } private void removeClient(ServerClient cli) { clientMap.remove(cli.getChannel()); clientList.remove(cli); Util.silentClose(cli.getChannel()); } public ClientManager getClientManager() { return clientMgr; } public void setDirectory(File dir) { if(!dir.exists()) { if(!dir.mkdir()) Log.warning("Could not create miniserv storage directory %s", dir.getAbsolutePath()); } directory = dir; } public File getDirectory() { return directory; } public long getMaxQuota() { return WebDisplays.INSTANCE.miniservQuota; } }