| 1 | package com.renomad.minum.web; | |
| 2 | ||
| 3 | import com.renomad.minum.logging.ILogger; | |
| 4 | import com.renomad.minum.state.Constants; | |
| 5 | import com.renomad.minum.state.Context; | |
| 6 | import com.renomad.minum.utils.ConcurrentSet; | |
| 7 | import com.renomad.minum.utils.StacktraceUtils; | |
| 8 | import com.renomad.minum.utils.ThrowingRunnable; | |
| 9 | ||
| 10 | import java.io.IOException; | |
| 11 | import java.net.ServerSocket; | |
| 12 | import java.net.Socket; | |
| 13 | import java.util.concurrent.BlockingQueue; | |
| 14 | import java.util.concurrent.ExecutorService; | |
| 15 | import java.util.concurrent.Future; | |
| 16 | import java.util.concurrent.LinkedBlockingQueue; | |
| 17 | ||
| 18 | import static com.renomad.minum.utils.ThrowingRunnable.throwingRunnableWrapper; | |
| 19 | ||
| 20 | /** | |
| 21 | * The purpose here is to make it marginally easier to | |
| 22 | * work with a ServerSocket. | |
| 23 | * <p> | |
| 24 | * First, instantiate this class using a running serverSocket | |
| 25 | * Then, by running the start method, we gain access to | |
| 26 | * the server's socket. This way we can easily test / control | |
| 27 | * the server side but also tie it in with an ExecutorService | |
| 28 | * for controlling lots of server threads. | |
| 29 | */ | |
| 30 | final class Server implements IServer { | |
| 31 | private final ServerSocket serverSocket; | |
| 32 | private final SetOfSws setOfSWs; | |
| 33 | private final ExecutorService es; | |
| 34 | private final HttpServerType serverType; | |
| 35 | private final ILogger logger; | |
| 36 | private final String serverName; | |
| 37 | private final WebFramework webFramework; | |
| 38 | private final BlockingQueue<Socket> socketQueue; | |
| 39 | private final Constants constants; | |
| 40 | ||
| 41 | /** | |
| 42 | * This is the future returned when we submitted the | |
| 43 | * thread for the central server loop to the ExecutorService | |
| 44 | */ | |
| 45 | private Future<?> centralLoopFuture; | |
| 46 | ||
| 47 | Server(ServerSocket ss, Context context, String serverName, WebFramework webFramework, ExecutorService es, HttpServerType serverType) { | |
| 48 | this.serverSocket = ss; | |
| 49 | this.logger = context.getLogger(); | |
| 50 | this.constants = context.getConstants(); | |
| 51 | this.webFramework = webFramework; | |
| 52 | this.serverName = serverName; | |
| 53 | setOfSWs = new SetOfSws(new ConcurrentSet<>(), logger, serverName); | |
| 54 | this.es = es; | |
| 55 | this.serverType = serverType; | |
| 56 | this.socketQueue = new LinkedBlockingQueue<>(); | |
| 57 | } | |
| 58 | ||
| 59 | @Override | |
| 60 | public void start() { | |
| 61 | ThrowingRunnable serverCode = this::outermostLoop; | |
| 62 | this.centralLoopFuture = es.submit(throwingRunnableWrapper(serverCode, logger)); | |
| 63 | ||
| 64 | ThrowingRunnable socketHandler = this::takeOffDequeForProcessing; | |
| 65 | es.submit(throwingRunnableWrapper(socketHandler, logger)); | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * This code is the outermost loop of the server, waiting for incoming | |
| 70 | * connections and then delegating their handling off to a handler. | |
| 71 | */ | |
| 72 | private void outermostLoop() { | |
| 73 | Thread.currentThread().setName("Main Server"); | |
| 74 | try { | |
| 75 | // yes, this infinite loop can only exit by an exception. But this is | |
| 76 | // the beating heart of a server, and to the best of my current knowledge, | |
| 77 | // when a server socket is force-closed it's going to throw an exception, and | |
| 78 | // that's just part of its life cycle | |
| 79 | //noinspection InfiniteLoopStatement | |
| 80 | while (true) { | |
| 81 | Socket freshSocket = serverSocket.accept(); | |
| 82 | // see takeOffDeque for the code that pulls sockets out of this queue | |
| 83 | // and sends them for processing | |
| 84 | socketQueue.add(freshSocket); | |
| 85 | } | |
| 86 | } catch (IOException ex) { | |
| 87 |
1
1. outermostLoop : removed call to com/renomad/minum/web/Server::handleServerException → TIMED_OUT |
handleServerException(ex, logger); |
| 88 | } | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * An infinite loop that pulls connected sockets out of the | |
| 93 | * deque for processing | |
| 94 | */ | |
| 95 | private void takeOffDequeForProcessing() throws InterruptedException { | |
| 96 | Thread.currentThread().setName("socket queue handler"); | |
| 97 | ||
| 98 | // this is a known infinite loop, meant to keep running all during the runtime | |
| 99 | //noinspection InfiniteLoopStatement | |
| 100 | while(true) { | |
| 101 | Socket socket = socketQueue.take(); | |
| 102 |
1
1. lambda$takeOffDequeForProcessing$0 : removed call to com/renomad/minum/web/Server::doHttpWork → TIMED_OUT |
es.submit(() -> this.doHttpWork(socket)); |
| 103 | } | |
| 104 | } | |
| 105 | ||
| 106 | ||
| 107 | void doHttpWork(Socket freshSocket) { | |
| 108 | // provide a name for this thread for easier debugging | |
| 109 | Thread.currentThread().setName("SocketWrapper thread for " + freshSocket.getInetAddress().getHostAddress()); | |
| 110 | ||
| 111 | try { | |
| 112 | // prepare the socket for later processing | |
| 113 | ISocketWrapper socketWrapper = new SocketWrapper(freshSocket, this, logger, constants.socketTimeoutMillis, constants.hostName); | |
| 114 | logger.logTrace(() -> String.format("client connected from %s", socketWrapper.getRemoteAddrWithPort())); | |
| 115 | ||
| 116 | // add to a set of wrapped sockets so we can precisely close them all at shutdown | |
| 117 |
1
1. doHttpWork : removed call to com/renomad/minum/web/Server::addToSetOfSws → KILLED |
addToSetOfSws(socketWrapper); |
| 118 | ||
| 119 |
1
1. doHttpWork : removed call to com/renomad/minum/web/WebFramework::httpProcessing → KILLED |
webFramework.httpProcessing(socketWrapper); |
| 120 | } catch (Exception ex) { | |
| 121 | logger.logAsyncError(() -> StacktraceUtils.stackTraceToString(ex)); | |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | static void handleServerException(IOException ex, ILogger logger) { | |
| 126 |
2
1. handleServerException : negated conditional → TIMED_OUT 2. handleServerException : negated conditional → KILLED |
if (!(ex.getMessage().contains("Socket closed") || ex.getMessage().contains("Socket is closed"))) { |
| 127 | logger.logAsyncError(() -> StacktraceUtils.stackTraceToString(ex)); | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | @Override | |
| 132 | public void close() throws IOException { | |
| 133 | // close all the running sockets | |
| 134 |
1
1. close : removed call to com/renomad/minum/web/SetOfSws::stopAllServers → TIMED_OUT |
setOfSWs.stopAllServers(); |
| 135 | logger.logTrace(() -> "close called on " + this); | |
| 136 | // close the primary server socket | |
| 137 |
1
1. close : removed call to java/net/ServerSocket::close → TIMED_OUT |
serverSocket.close(); |
| 138 | } | |
| 139 | ||
| 140 | @Override | |
| 141 | public String getHost() { | |
| 142 |
1
1. getHost : replaced return value with "" for com/renomad/minum/web/Server::getHost → TIMED_OUT |
return serverSocket.getInetAddress().getHostAddress(); |
| 143 | } | |
| 144 | ||
| 145 | @Override | |
| 146 | public int getPort() { | |
| 147 |
1
1. getPort : replaced int return with 0 for com/renomad/minum/web/Server::getPort → TIMED_OUT |
return serverSocket.getLocalPort(); |
| 148 | } | |
| 149 | ||
| 150 | @Override | |
| 151 | public void removeMyRecord(ISocketWrapper socketWrapper) { | |
| 152 |
1
1. removeMyRecord : removed call to com/renomad/minum/web/SetOfSws::remove → KILLED |
setOfSWs.remove(socketWrapper); |
| 153 | } | |
| 154 | ||
| 155 | @Override | |
| 156 | public void addToSetOfSws(ISocketWrapper sw) { | |
| 157 |
1
1. addToSetOfSws : removed call to com/renomad/minum/web/SetOfSws::add → TIMED_OUT |
this.setOfSWs.add(sw); |
| 158 | } | |
| 159 | ||
| 160 | /** | |
| 161 | * Returns the name of this server, which is set | |
| 162 | * when the server is instantiated. | |
| 163 | */ | |
| 164 | @Override | |
| 165 | public String toString() { | |
| 166 |
1
1. toString : replaced return value with "" for com/renomad/minum/web/Server::toString → TIMED_OUT |
return this.serverName; |
| 167 | } | |
| 168 | ||
| 169 | @Override | |
| 170 | public Future<?> getCentralLoopFuture() { | |
| 171 |
1
1. getCentralLoopFuture : replaced return value with null for com/renomad/minum/web/Server::getCentralLoopFuture → KILLED |
return centralLoopFuture; |
| 172 | } | |
| 173 | ||
| 174 | @Override | |
| 175 | public HttpServerType getServerType() { | |
| 176 |
1
1. getServerType : replaced return value with null for com/renomad/minum/web/Server::getServerType → KILLED |
return serverType; |
| 177 | } | |
| 178 | } | |
Mutations | ||
| 87 |
1.1 |
|
| 102 |
1.1 |
|
| 117 |
1.1 |
|
| 119 |
1.1 |
|
| 126 |
1.1 2.2 |
|
| 134 |
1.1 |
|
| 137 |
1.1 |
|
| 142 |
1.1 |
|
| 147 |
1.1 |
|
| 152 |
1.1 |
|
| 157 |
1.1 |
|
| 166 |
1.1 |
|
| 171 |
1.1 |
|
| 176 |
1.1 |