Server.java
package com.renomad.minum.web;
import com.renomad.minum.state.Constants;
import com.renomad.minum.logging.ILogger;
import com.renomad.minum.security.ITheBrig;
import com.renomad.minum.state.Context;
import com.renomad.minum.utils.ConcurrentSet;
import com.renomad.minum.utils.StacktraceUtils;
import com.renomad.minum.utils.ThrowingRunnable;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* The purpose here is to make it marginally easier to
* work with a ServerSocket.
* <p>
* First, instantiate this class using a running serverSocket
* Then, by running the start method, we gain access to
* the server's socket. This way we can easily test / control
* the server side but also tie it in with an ExecutorService
* for controlling lots of server threads.
*/
final class Server implements IServer {
private final ServerSocket serverSocket;
private final SetOfSws setOfSWs;
private final ExecutorService es;
private final HttpServerType serverType;
private final ILogger logger;
private final String serverName;
private final ITheBrig theBrig;
private final Constants constants;
private final WebFramework webFramework;
/**
* This is the future returned when we submitted the
* thread for the central server loop to the ExecutorService
*/
private Future<?> centralLoopFuture;
Server(ServerSocket ss, Context context, String serverName, ITheBrig theBrig, WebFramework webFramework, ExecutorService es, HttpServerType serverType) {
this.serverSocket = ss;
this.logger = context.getLogger();
this.constants = context.getConstants();
this.webFramework = webFramework;
this.serverName = serverName;
this.theBrig = theBrig;
setOfSWs = new SetOfSws(new ConcurrentSet<>(), logger, serverName);
this.es = es;
this.serverType = serverType;
}
@Override
public void start() {
ThrowingRunnable serverCode = buildMainServerLoop(es);
Runnable t = ThrowingRunnable.throwingRunnableWrapper(serverCode, logger);
this.centralLoopFuture = es.submit(t);
}
/**
* This code is the innermost loop of the server, waiting for incoming
* connections and then delegating their handling off to a handler.
*
* @param es The ExecutorService helping us with the threads
*/
private ThrowingRunnable buildMainServerLoop(ExecutorService es) {
return () -> {
Thread.currentThread().setName("Main Server");
try {
// yes, this infinite loop can only exit by an exception. But this is
// the beating heart of a server, and to the best of my current knowledge,
// when a server socket is force-closed it's going to throw an exception, and
// that's just part of its life cycle
//noinspection InfiniteLoopStatement
while (true) {
logger.logTrace(() -> serverName + " waiting to accept connection");
Socket freshSocket = serverSocket.accept();
ISocketWrapper sw = new SocketWrapper(freshSocket, this, logger, constants.socketTimeoutMillis, constants.hostName);
logger.logTrace(() -> String.format("client connected from %s", sw.getRemoteAddrWithPort()));
setOfSWs.add(sw);
ThrowingRunnable innerServerCode = this.webFramework.makePrimaryHttpHandler(sw, theBrig);
Runnable task = ThrowingRunnable.throwingRunnableWrapper(innerServerCode, logger);
es.submit(task);
}
} catch (IOException ex) {
handleServerException(ex, logger);
}
};
}
static void handleServerException(IOException ex, ILogger logger) {
if (!(ex.getMessage().contains("Socket closed") || ex.getMessage().contains("Socket is closed"))) {
logger.logAsyncError(() -> StacktraceUtils.stackTraceToString(ex));
}
}
@Override
public void close() throws IOException {
// close all the running sockets
setOfSWs.stopAllServers();
logger.logTrace(() -> "close called on " + this);
// close the primary server socket
serverSocket.close();
}
@Override
public String getHost() {
return serverSocket.getInetAddress().getHostAddress();
}
@Override
public int getPort() {
return serverSocket.getLocalPort();
}
@Override
public void removeMyRecord(ISocketWrapper socketWrapper) {
setOfSWs.remove(socketWrapper);
}
/**
* Returns the name of this server, which is set
* when the server is instantiated.
*/
@Override
public String toString() {
return this.serverName;
}
@Override
public Future<?> getCentralLoopFuture() {
return centralLoopFuture;
}
@Override
public HttpServerType getServerType() {
return serverType;
}
}