webdisplays/src/main/java/net/montoyo/wd/miniserv/server/Server.java
2023-11-11 00:06:56 -05:00

252 lines
7.2 KiB
Java

/*
* 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<ServerClient> clientList = new ArrayList<>();
private final HashMap<SocketChannel, ServerClient> 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;
}
}