Server.java

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 → KILLED
            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 → KILLED
            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.logWarn(() -> "Exception caught in Server.doHttpWork: " + StacktraceUtils.stackTraceToString(ex));
122
        }
123
    }
124
125
    static void handleServerException(IOException ex, ILogger logger) {
126
        // we do expect to see an exception bubbling up to here if we
127
        // are closing our server, so if the message includes certain values,
128
        // we will skip logging an error.
129 2 1. handleServerException : negated conditional → KILLED
2. handleServerException : negated conditional → KILLED
        if (!(ex.getMessage().contains("Socket closed") || ex.getMessage().contains("Socket is closed"))) {
130
            logger.logWarn(() -> StacktraceUtils.stackTraceToString(ex));
131
        }
132
    }
133
134
    @Override
135
    public void close() throws IOException {
136
        // close all the running sockets
137 1 1. close : removed call to com/renomad/minum/web/SetOfSws::stopAllServers → KILLED
        setOfSWs.stopAllServers();
138
        logger.logTrace(() -> "close called on " + this);
139
        // close the primary server socket
140 1 1. close : removed call to java/net/ServerSocket::close → KILLED
        serverSocket.close();
141
    }
142
143
    @Override
144
    public String getHost() {
145 1 1. getHost : replaced return value with "" for com/renomad/minum/web/Server::getHost → KILLED
        return serverSocket.getInetAddress().getHostAddress();
146
    }
147
148
    @Override
149
    public int getPort() {
150 1 1. getPort : replaced int return with 0 for com/renomad/minum/web/Server::getPort → KILLED
        return serverSocket.getLocalPort();
151
    }
152
153
    @Override
154
    public void removeMyRecord(ISocketWrapper socketWrapper) {
155 1 1. removeMyRecord : removed call to com/renomad/minum/web/SetOfSws::remove → KILLED
        setOfSWs.remove(socketWrapper);
156
    }
157
158
    @Override
159
    public void addToSetOfSws(ISocketWrapper sw) {
160 1 1. addToSetOfSws : removed call to com/renomad/minum/web/SetOfSws::add → KILLED
        this.setOfSWs.add(sw);
161
    }
162
163
    /**
164
     * Returns the name of this server, which is set
165
     * when the server is instantiated.
166
     */
167
    @Override
168
    public String toString() {
169 1 1. toString : replaced return value with "" for com/renomad/minum/web/Server::toString → KILLED
        return this.serverName;
170
    }
171
172
    @Override
173
    public Future<?> getCentralLoopFuture() {
174 1 1. getCentralLoopFuture : replaced return value with null for com/renomad/minum/web/Server::getCentralLoopFuture → KILLED
        return centralLoopFuture;
175
    }
176
177
    @Override
178
    public HttpServerType getServerType() {
179 1 1. getServerType : replaced return value with null for com/renomad/minum/web/Server::getServerType → KILLED
        return serverType;
180
    }
181
}

Mutations

87

1.1
Location : outermostLoop
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/web/Server::handleServerException → KILLED

102

1.1
Location : lambda$takeOffDequeForProcessing$0
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to com/renomad/minum/web/Server::doHttpWork → KILLED

117

1.1
Location : doHttpWork
Killed by : com.renomad.minum.web.ServerTests
removed call to com/renomad/minum/web/Server::addToSetOfSws → KILLED

119

1.1
Location : doHttpWork
Killed by : com.renomad.minum.web.ServerTests
removed call to com/renomad/minum/web/WebFramework::httpProcessing → KILLED

129

1.1
Location : handleServerException
Killed by : com.renomad.minum.web.ServerTests
negated conditional → KILLED

2.2
Location : handleServerException
Killed by : com.renomad.minum.web.ServerTests
negated conditional → KILLED

137

1.1
Location : close
Killed by : com.renomad.minum.web.ServerTests
removed call to com/renomad/minum/web/SetOfSws::stopAllServers → KILLED

140

1.1
Location : close
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
removed call to java/net/ServerSocket::close → KILLED

145

1.1
Location : getHost
Killed by : com.renomad.minum.web.WebTests
replaced return value with "" for com/renomad/minum/web/Server::getHost → KILLED

150

1.1
Location : getPort
Killed by : com.renomad.minum.web.WebPerformanceTests.test3(com.renomad.minum.web.WebPerformanceTests)
replaced int return with 0 for com/renomad/minum/web/Server::getPort → KILLED

155

1.1
Location : removeMyRecord
Killed by : com.renomad.minum.web.ServerTests
removed call to com/renomad/minum/web/SetOfSws::remove → KILLED

160

1.1
Location : addToSetOfSws
Killed by : com.renomad.minum.web.ServerTests
removed call to com/renomad/minum/web/SetOfSws::add → KILLED

169

1.1
Location : toString
Killed by : com.renomad.minum.web.ServerTests
replaced return value with "" for com/renomad/minum/web/Server::toString → KILLED

174

1.1
Location : getCentralLoopFuture
Killed by : com.renomad.minum.web.FullSystemTests
replaced return value with null for com/renomad/minum/web/Server::getCentralLoopFuture → KILLED

179

1.1
Location : getServerType
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
replaced return value with null for com/renomad/minum/web/Server::getServerType → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0