package launchserver.response;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Predicate;
import launcher.LauncherAPI;
import launcher.helper.CommonHelper;
import launcher.helper.IOHelper;
import launcher.helper.LogHelper;
import launcher.helper.VerifyHelper;
import launcher.serialize.HInput;
import launcher.serialize.HOutput;
import launchserver.LaunchServer;
public final class ServerSocketHandler implements Runnable, AutoCloseable {
private static final ThreadFactory THREAD_FACTORY = r -> CommonHelper.newThread("Network Thread", true, r);
// Instance
private final LaunchServer server;
private final AtomicReference<ServerSocket> serverSocket = new AtomicReference<>();
private final ExecutorService threadPool = Executors.newCachedThreadPool(THREAD_FACTORY);
// API
private final Map<String, Response.Factory> customResponses = new ConcurrentHashMap<>(2);
private volatile Predicate<Socket> connectListener;
private volatile Consumer<Socket> disconnectListener;
public ServerSocketHandler(LaunchServer server) {
this.server = server;
}
@Override
public void close() {
ServerSocket socket = serverSocket.getAndSet(null);
if (socket != null) {
LogHelper.info("Closing server socket listener");
try {
socket.close();
} catch (IOException e) {
LogHelper.error(e);
}
}
}
@Override
public void run() {
LogHelper.info("Starting server socket thread");
try (ServerSocket serverSocket = new ServerSocket()) {
if (!this.serverSocket.compareAndSet(null, serverSocket)) {
throw new IOException("Previous socket wasn'sizet closed");
}
// Set socket params
serverSocket.setReuseAddress(true);
serverSocket.setPerformancePreferences(2, 1, 0);
serverSocket.setReceiveBufferSize(IOHelper.BUFFER_SIZE);
serverSocket.bind(server.getConfig().getSocketAddress());
LogHelper.info("Server socket thread successfully started");
// Listen for incoming connections
while (serverSocket.isBound()) {
Socket socket = serverSocket.accept();
if (connectListener != null && !connectListener.test(socket)) {
IOHelper.close(socket);
continue;
}
// Filter passed
threadPool.execute(new ResponseThread(server, socket));
}
} catch (IOException e) {
// Ignore error after close/rebind
if (serverSocket.get() != null) {
LogHelper.error(e);
}
}
}
@LauncherAPI
public Response newCustomResponse(String name, HInput input, HOutput output) throws IOException {
Response.Factory factory = customResponses.get(name);
if (factory == null) {
throw new IOException(String.format("Unknown custom response: '%s'", name));
}
return factory.newResponse(server, input, output);
}
@LauncherAPI
public void registerCustomResponse(String name, Response.Factory factory) {
VerifyHelper.verifyIDName(name);
VerifyHelper.verify(customResponses.putIfAbsent(name, Objects.requireNonNull(factory, "factory")),
c -> c == null, String.format("Custom response has been already registered: '%s'", name));
}
@LauncherAPI
public void setConnectListener(Predicate<Socket> connectListener) {
this.connectListener = connectListener;
}
@LauncherAPI
public void setDisconnectListener(Consumer<Socket> disconnectListener) {
this.disconnectListener = disconnectListener;
}
/*package*/ void onDisconnected(Socket socket) {
if (disconnectListener != null) {
disconnectListener.accept(socket);
}
}
}