WebEngine.java

1
package com.renomad.minum.web;
2
3
import com.renomad.minum.state.Constants;
4
import com.renomad.minum.logging.ILogger;
5
import com.renomad.minum.state.Context;
6
7
import javax.net.ssl.KeyManagerFactory;
8
import javax.net.ssl.SSLContext;
9
import java.io.IOException;
10
import java.io.InputStream;
11
import java.net.ServerSocket;
12
import java.net.Socket;
13
import java.net.URL;
14
import java.nio.file.Path;
15
import java.security.*;
16
import java.util.concurrent.ExecutorService;
17
18
import static com.renomad.minum.web.HttpServerType.ENCRYPTED_HTTP;
19
import static com.renomad.minum.web.HttpServerType.PLAIN_TEXT_HTTP;
20
21
/**
22
 * This class contains the basic internet capabilities.
23
 * <br><br>
24
 * Think of this class as managing some of the lowest-level internet
25
 * communications we need to handle to support a web application. Sockets,
26
 * Servers, Threads, that kind of stuff.
27
 */
28
final class WebEngine {
29
30
  private final Constants constants;
31
  private final Context context;
32
  private final ExecutorService executorService;
33
  private final WebFramework webFramework;
34
35
  WebEngine(Context context, WebFramework webFramework) {
36
    this.logger = context.getLogger();
37
    this.logger.logDebug(() -> "Using a supplied logger in WebEngine");
38
    this.constants = context.getConstants();
39
    this.context = context;
40
    this.executorService = context.getExecutorService();
41
    this.webFramework = webFramework;
42
  }
43
44
  private final ILogger logger;
45
  static final String HTTP_CRLF = "\r\n";
46
47
  /**
48
   * Start a plain-text unencrypted server, listening on the port specified by the user
49
   */
50
  IServer startServer() {
51
    int port = constants.serverPort;
52
      ServerSocket ss;
53
      try {
54
          ss = new ServerSocket(port);
55
      } catch (Exception e) {
56
          throw new WebServerException("Failed to create serversocket on port " + port, e);
57
      }
58
      logger.logDebug(() -> String.format("Just created a new ServerSocket: %s", ss));
59
    IServer server = new Server(ss, context, "http server", webFramework, executorService, PLAIN_TEXT_HTTP);
60
    logger.logDebug(() -> String.format("Just created a new Server: %s", server));
61 1 1. startServer : removed call to com/renomad/minum/web/IServer::start → KILLED
    server.start();
62
    String hostname = constants.hostName;
63
    logger.logDebug(() -> String.format("%s started at http://%s:%s", server, hostname, port));
64 1 1. startServer : replaced return value with null for com/renomad/minum/web/WebEngine::startServer → KILLED
    return server;
65
  }
66
67
  /**
68
   * Start an encrypted server, using TLS 1.3
69
   */
70
  IServer startSslServer() {
71
72
    /*
73
     * If we are provided details for a keystore (its location and password), use it
74
     */
75
    final var useExternalKeystore = isProvidedKeystoreProperties(constants.keystorePath, constants.keystorePassword, logger);
76
    KeyStoreResult keystoreResult = getKeyStoreResult(useExternalKeystore, constants.keystorePath, constants.keystorePassword, logger);
77
78
    int port = constants.secureServerPort;
79
    ServerSocket ss = createSslSocketWithSpecificKeystore(port, keystoreResult.keystoreUrl(), keystoreResult.keystorePassword());
80
    logger.logDebug(() -> String.format("Just created a new ServerSocket: %s", ss));
81
    IServer server = new Server(ss, context, "https server", webFramework, executorService, ENCRYPTED_HTTP);
82
    logger.logDebug(() -> String.format("Just created a new SSL Server: %s", server));
83 1 1. startSslServer : removed call to com/renomad/minum/web/IServer::start → KILLED
    server.start();
84
    String hostname = constants.hostName;
85
    logger.logDebug(() -> String.format("%s started at https://%s:%s", server, hostname, port));
86 1 1. startSslServer : replaced return value with null for com/renomad/minum/web/WebEngine::startSslServer → KILLED
    return server;
87
  }
88
89
  static KeyStoreResult getKeyStoreResult(
90
          boolean useExternalKeystore,
91
          String keystorePath,
92
          String keystorePassword,
93
          ILogger logger) {
94 1 1. getKeyStoreResult : negated conditional → KILLED
    if (useExternalKeystore) {
95
      logger.logDebug(() -> "Using keystore and password referenced in minum.config");
96
    } else {
97
      logger.logDebug(() -> "Using the default (self-signed / testing-only) certificate");
98
    }
99
100
    final URL keystoreUrl;
101
    try {
102 1 1. getKeyStoreResult : negated conditional → KILLED
        keystoreUrl = useExternalKeystore ?
103
                Path.of(keystorePath).toUri().toURL() :
104
                WebEngine.class.getResource("/certs/keystore");
105
    } catch (Exception e) {
106
        throw new WebServerException("Error while building keystoreUrl: " + e);
107
    }
108 1 1. getKeyStoreResult : negated conditional → KILLED
    final String keystorePasswordFinal = useExternalKeystore ?
109
          keystorePassword :
110
          "passphrase";
111 1 1. getKeyStoreResult : replaced return value with null for com/renomad/minum/web/WebEngine::getKeyStoreResult → KILLED
    return new KeyStoreResult(keystoreUrl, keystorePasswordFinal);
112
  }
113
114
  record KeyStoreResult(URL keystoreUrl, String keystorePassword) { }
115
116
117
  /**
118
   * Look into the system properties to see whether values have been
119
   * set for the keystore and keystorePassword keys.
120
   * <p>
121
   * the key for keystore is:  javax.net.ssl.keyStore
122
   * the key for keystorePassword is: javax.net.ssl.keyStorePassword
123
   * <p>
124
   * It's smart, if you are creating a server that will run
125
   * with a genuine signed certificate, to have those files
126
   * stored somewhere and then set these system properties.  That
127
   * way, it's a characteristic of a particular server - it's not
128
   * needed to bundle the certificate with the actual server in
129
   * any way.
130
   * <p>
131
   * We *do* bundle a cert, but it's for testing and is self-signed.
132
   */
133
  static Boolean isProvidedKeystoreProperties(String keystorePath, String keystorePassword, ILogger logger) {
134
135
    // get the directory to the keystore from a system property
136 2 1. isProvidedKeystoreProperties : negated conditional → KILLED
2. isProvidedKeystoreProperties : negated conditional → KILLED
    boolean hasKeystore = ! (keystorePath == null || keystorePath.isBlank());
137 1 1. isProvidedKeystoreProperties : negated conditional → KILLED
    if (! hasKeystore) {
138
      logger.logDebug(() -> "Keystore system property was not set");
139
    }
140
141
    // get the password to that keystore from a system property
142 2 1. isProvidedKeystoreProperties : negated conditional → KILLED
2. isProvidedKeystoreProperties : negated conditional → KILLED
    boolean hasKeystorePassword = ! (keystorePassword == null || keystorePassword.isBlank());
143 1 1. isProvidedKeystoreProperties : negated conditional → KILLED
    if (! hasKeystorePassword) {
144
      logger.logDebug(() -> "keystorePassword system property was not set");
145
    }
146
147 3 1. isProvidedKeystoreProperties : replaced Boolean return with True for com/renomad/minum/web/WebEngine::isProvidedKeystoreProperties → KILLED
2. isProvidedKeystoreProperties : negated conditional → KILLED
3. isProvidedKeystoreProperties : negated conditional → KILLED
    return hasKeystore && hasKeystorePassword;
148
  }
149
150
151
  /**
152
   * Create an SSL Socket using a specified keystore
153
   */
154
  ServerSocket createSslSocketWithSpecificKeystore(int sslPort, URL keystoreUrl, String keystorePassword) {
155
    try (InputStream keystoreInputStream = keystoreUrl.openStream()) {
156
      final var keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
157
      char[] passwordCharArray = keystorePassword.toCharArray();
158 1 1. createSslSocketWithSpecificKeystore : removed call to java/security/KeyStore::load → KILLED
      keyStore.load(keystoreInputStream, passwordCharArray);
159
160
      final var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
161 1 1. createSslSocketWithSpecificKeystore : removed call to javax/net/ssl/KeyManagerFactory::init → KILLED
      keyManagerFactory.init(keyStore, passwordCharArray);
162
163
      final var keyManagers = keyManagerFactory.getKeyManagers();
164
165
      final var sslContext = SSLContext.getInstance("TLSv1.3");
166 1 1. createSslSocketWithSpecificKeystore : removed call to javax/net/ssl/SSLContext::init → KILLED
      sslContext.init(keyManagers, null, new SecureRandom());
167
168
      final var socketFactory = sslContext.getServerSocketFactory();
169
      return socketFactory.createServerSocket(sslPort);
170
    } catch (Exception ex) {
171
      logger.logDebug(ex::getMessage);
172
      throw new WebServerException(ex);
173
    }
174
  }
175
176
  /**
177
   * Create a client {@link ISocketWrapper} connected to the running host server
178
   */
179
  ISocketWrapper startClient(Socket socket) throws IOException {
180
    logger.logDebug(() -> String.format("Just created new client socket: %s", socket));
181 1 1. startClient : replaced return value with null for com/renomad/minum/web/WebEngine::startClient → KILLED
    return new SocketWrapper(socket, null, logger, constants.socketTimeoutMillis, constants.hostName);
182
  }
183
184
}

Mutations

61

1.1
Location : startServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/IServer::start → KILLED

64

1.1
Location : startServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::startServer → KILLED

83

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/IServer::start → KILLED

86

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::startSslServer → KILLED

94

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

102

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

108

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

111

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::getKeyStoreResult → KILLED

136

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

137

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

142

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

143

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

147

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced Boolean return with True for com/renomad/minum/web/WebEngine::isProvidedKeystoreProperties → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.WebEngineTests
negated conditional → KILLED

3.3
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

158

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to java/security/KeyStore::load → KILLED

161

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to javax/net/ssl/KeyManagerFactory::init → KILLED

166

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to javax/net/ssl/SSLContext::init → KILLED

181

1.1
Location : startClient
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/WebEngine::startClient → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0