diff --git a/build.gradle b/build.gradle
index 09927b8..305add0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,14 +9,19 @@ repositories {
}
dependencies {
- implementation 'commons-fileupload:commons-fileupload:1.4'
- implementation 'commons-io:commons-io:2.7'
implementation 'org.dom4j:dom4j:2.1.3'
- implementation 'org.xerial:sqlite-jdbc:3.8.11.2'
compileOnly 'mysql:mysql-connector-java:8.0.16'
+ compileOnly 'org.xerial:sqlite-jdbc:3.8.11.2'
+
compileOnly 'javax.servlet:javax.servlet-api:3.1.0'
+ compileOnly 'org.shredzone.acme4j:acme4j-client:2.12'
+ compileOnly 'org.shredzone.acme4j:acme4j-utils:2.12'
+
+ compileOnly 'commons-fileupload:commons-fileupload:1.4'
+ compileOnly 'commons-io:commons-io:2.7'
+
testImplementation 'junit:junit:4.13.1'
testImplementation 'org.hamcrest:hamcrest-core:1.3'
testImplementation 'com.carrotsearch:junit-benchmarks:0.7.2'
diff --git a/src/zutil/net/acme/AcmeClient.java b/src/zutil/net/acme/AcmeClient.java
new file mode 100644
index 0000000..6c53463
--- /dev/null
+++ b/src/zutil/net/acme/AcmeClient.java
@@ -0,0 +1,227 @@
+package zutil.net.acme;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.shredzone.acme4j.Account;
+import org.shredzone.acme4j.AccountBuilder;
+import org.shredzone.acme4j.Authorization;
+import org.shredzone.acme4j.Certificate;
+import org.shredzone.acme4j.Order;
+import org.shredzone.acme4j.Session;
+import org.shredzone.acme4j.Status;
+import org.shredzone.acme4j.challenge.Http01Challenge;
+import org.shredzone.acme4j.exception.AcmeException;
+import org.shredzone.acme4j.util.CSRBuilder;
+import org.shredzone.acme4j.util.KeyPairUtils;
+import zutil.StringUtil;
+import zutil.log.LogUtil;
+import zutil.net.http.HttpServer;
+import zutil.net.http.page.HttpStaticContentPage;
+
+/**
+ * A class implementing the ACME protocol (Automatic Certificate Management Environment) used by the LetsEncrypt service.
+ *
+ * Code based on the example from the acme4j project:
+ * https://github.com/shred/acme4j/blob/master/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java
+ */
+public class AcmeClient {
+ private static final Logger logger = LogUtil.getLogger();
+
+ public static final String ACME_SERVER_LETSENCRYPT_PRODUCTION = "acme://letsencrypt.org";
+ public static final String ACME_SERVER_LETSENCRYPT_STAGING = "acme://letsencrypt.org/staging";
+
+ private static final int KEY_SIZE = 2048; // RSA key size of generated key pairs
+
+
+ private String acmeServerUrl;
+ private AcmeDataStore dataStore;
+
+ /**
+ * Create a new instance of the ACME Client
+ */
+ public AcmeClient(AcmeDataStore dataStore, String acmeServerUrl) {
+ Security.addProvider(new BouncyCastleProvider());
+
+ this.dataStore = dataStore;
+ this.acmeServerUrl = acmeServerUrl;
+ }
+
+ public AcmeClient(AcmeDataStore dataStore) {
+ this(dataStore, ACME_SERVER_LETSENCRYPT_PRODUCTION);
+ }
+
+
+ /**
+ * Generates a certificate for the given domains. Also takes care for the registration
+ * process.
+ *
+ * @param httpServer the web server where the challenge and response will be performed on and where the certificate will be applied to.
+ * @param domains the domains to get a certificates for
+ * @return a certificate for the given domains.
+ */
+ public X509Certificate fetchCertificate(HttpServer httpServer, String... domains) throws IOException, AcmeException {
+ // ------------------------------------------------
+ // Read in keys
+ // ------------------------------------------------
+
+ KeyPair userKeyPair = dataStore.loadUserKeyPair(); // Load the user key file. If there is no key file, create a new one.
+ KeyPair domainKeyPair = dataStore.loadDomainKeyPair(); // Load or create a key pair for the domains. This should not be the userKeyPair!
+
+ if (userKeyPair == null) {
+ userKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
+ dataStore.storeUserKeyPair(userKeyPair);
+ }
+ if (domainKeyPair == null) {
+ domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
+ dataStore.storeDomainKeyPair(domainKeyPair);
+ }
+
+ // ------------------------------------------------
+ // Start authorization process
+ // ------------------------------------------------
+
+ Session session = new Session(acmeServerUrl);
+ Account acct = getAccount(session, userKeyPair); // Get the Account. If there is no account yet, create a new one.
+ Order order = acct.newOrder().domains(domains).create(); // Order the certificate
+
+ // Perform all required authorizations
+ for (Authorization auth : order.getAuthorizations()) {
+ execHttpChallenge(auth, httpServer);
+ }
+
+ // Generate a "Certificate Signing Request" for all of the domains, and sign it with the domain key pair.
+ CSRBuilder csrBuilder = new CSRBuilder();
+ csrBuilder.addDomains(domains);
+ csrBuilder.sign(domainKeyPair);
+
+ order.execute(csrBuilder.getEncoded()); // Order the certificate
+
+ // Wait for the order to complete
+ try {
+ for (int attempts = 0; attempts < 10; attempts--) {
+ // Did the order pass or fail?
+ if (order.getStatus() == Status.VALID) {
+ break;
+ } else if (order.getStatus() == Status.INVALID) {
+ throw new AcmeException("Certificate order has failed, reason: " + order.getError());
+ }
+
+ // Wait for a few seconds
+ Thread.sleep(100L + 500L * attempts);
+
+ // Then update the status
+ order.update();
+ }
+ } catch (InterruptedException ex) {
+ logger.log(Level.SEVERE, "Interrupted", ex);
+ }
+
+ // Get the certificate
+ Certificate certificate = order.getCertificate();
+
+ logger.info("The certificate for domains '" + StringUtil.join(",", domains) + "' has been successfully generated.");
+
+ return certificate.getCertificate();
+ }
+
+
+ /**
+ * Finds your {@link Account} at the ACME server. It will be found by your user's
+ * public key. If your key is not known to the server yet, a new account will be
+ * created.
+ *
+ * This is a simple way of finding your {@link Account}. A better way is to get the
+ * URL of your new account with {@link Account#getLocation()} and store it somewhere.
+ * If you need to get access to your account later, reconnect to it via {@link
+ * Session#login(URL, KeyPair)} by using the stored location.
+ *
+ * @param session {@link Session} to bind with
+ * @return {@link Account}
+ */
+ private Account getAccount(Session session, KeyPair accountKey) throws AcmeException {
+ // Ask the user to accept the TOS, if server provides us with a link.
+ URI tos = session.getMetadata().getTermsOfService();
+ if (tos != null) {
+ logger.info("By using this service you accept the Terms of Service: " + tos);
+ }
+
+ Account account = new AccountBuilder()
+ .agreeToTermsOfService()
+ .useKeyPair(accountKey)
+ .create(session);
+ logger.info("Registered a new user, URL: " + account.getLocation());
+
+ return account;
+ }
+
+ /**
+ * Authorize a domain. It will be associated with your account, so you will be able to
+ * retrieve a signed certificate for the domain later.
+ *
+ * @param auth {@link Authorization} to perform
+ */
+ private void execHttpChallenge(Authorization auth, HttpServer httpServer) throws AcmeException {
+ logger.info("Authorization for domain: " + auth.getIdentifier().getDomain());
+
+ // The authorization is already valid. No need to process a challenge.
+ if (auth.getStatus() == Status.VALID) {
+ return;
+ }
+
+ // Find the desired challenge and prepare it.
+ Http01Challenge challenge = auth.findChallenge(Http01Challenge.class);
+ if (challenge == null) {
+ throw new AcmeException("Found no " + Http01Challenge.TYPE + " challenge.");
+ }
+
+ // If the challenge is already verified, there's no need to execute it again.
+ if (challenge.getStatus() == Status.VALID)
+ return;
+
+ String url = "http://" + auth.getIdentifier().getDomain();
+ String path = "/.well-known/acme-challenge/" + challenge.getToken();
+ String content = challenge.getAuthorization();
+
+ // Output the challenge, wait for acknowledge...
+ logger.fine("Adding challenge HttpPage at: " + url + path);
+ httpServer.setPage(path, new HttpStaticContentPage(content));
+
+ // Now trigger the challenge.
+ challenge.trigger();
+
+ // Poll for the challenge to complete.
+ try {
+ for (int attempts = 0; attempts < 10; attempts--) {
+ // Did the authorization fail?
+ if (challenge.getStatus() == Status.VALID) {
+ break;
+ } else if (challenge.getStatus() == Status.INVALID) {
+ throw new AcmeException("Certificate challenge failed: " + challenge.getError());
+ }
+
+ // Wait for a few seconds
+ Thread.sleep(100L + 500L * attempts);
+
+ // Then update the status
+ challenge.update();
+ }
+
+ // All reattempts are used up and there is still no valid authorization?
+ if (challenge.getStatus() != Status.VALID)
+ throw new AcmeException("Failed to pass the challenge for domain " + auth.getIdentifier().getDomain());
+ } catch (InterruptedException ex) {
+ logger.log(Level.SEVERE, "Interrupted", ex);
+ } finally {
+ // Cleanup
+ httpServer.removePage(path);
+ }
+ }
+}
diff --git a/src/zutil/net/acme/AcmeDataStore.java b/src/zutil/net/acme/AcmeDataStore.java
new file mode 100644
index 0000000..33f138c
--- /dev/null
+++ b/src/zutil/net/acme/AcmeDataStore.java
@@ -0,0 +1,34 @@
+package zutil.net.acme;
+
+import java.security.KeyPair;
+
+public interface AcmeDataStore {
+
+ /**
+ * Loads a user key pair.
+ *
+ * @return a KeyPair for the user account, null if no KeyPar was found.
+ */
+ KeyPair loadUserKeyPair();
+
+ /**
+ * Stores a user key pair for later usage.
+ *
+ * @param keyPair the keys for the user account
+ */
+ void storeUserKeyPair(KeyPair keyPair);
+
+ /**
+ * Loads a domain key pair.
+ *
+ * @return a KeyPair for the domains, null if no KeyPar was found.
+ */
+ KeyPair loadDomainKeyPair();
+
+ /**
+ * Stores a domain key pair for later usage.
+ *
+ * @param keyPair the keys for the domain
+ */
+ void storeDomainKeyPair(KeyPair keyPair);
+ }
\ No newline at end of file
diff --git a/src/zutil/net/acme/AcmeFileDataStore.java b/src/zutil/net/acme/AcmeFileDataStore.java
new file mode 100644
index 0000000..2805580
--- /dev/null
+++ b/src/zutil/net/acme/AcmeFileDataStore.java
@@ -0,0 +1,64 @@
+package zutil.net.acme;
+
+import org.shredzone.acme4j.util.KeyPairUtils;
+
+import java.io.*;
+import java.security.KeyPair;
+
+public class AcmeFileDataStore implements AcmeDataStore {
+
+ private final File userKeyFile;
+ private final File domainKeyFile;
+ private final File domainCsrFile;
+ private final File domainChainFile;
+
+
+ /**
+ * Create a new file based datastore for storing ACME protocol needed data.
+ *
+ * @param folder is the folder there the different files should be stored in.
+ */
+ public AcmeFileDataStore(File folder) {
+ this.userKeyFile = new File(folder, "user.key");
+ this.domainKeyFile = new File(folder, "domain.key");
+ this.domainCsrFile = new File(folder, "domain.csr");
+ this.domainChainFile = new File(folder, "domain-chain.crt");
+ }
+
+
+ public KeyPair loadUserKeyPair(){
+ if (domainKeyFile.exists()) {
+ try (FileReader fr = new FileReader(domainKeyFile)) {
+ return KeyPairUtils.readKeyPair(fr);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+ public void storeUserKeyPair(KeyPair keyPair){
+ try (FileWriter fw = new FileWriter(userKeyFile)) {
+ KeyPairUtils.writeKeyPair(keyPair, fw);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public KeyPair loadDomainKeyPair() {
+ if (userKeyFile.exists()) {
+ try (FileReader fr = new FileReader(userKeyFile)) {
+ return KeyPairUtils.readKeyPair(fr);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+ public void storeDomainKeyPair(KeyPair keyPair){
+ try (FileWriter fw = new FileWriter(userKeyFile)) {
+ KeyPairUtils.writeKeyPair(keyPair, fw);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/zutil/net/http/HttpServer.java b/src/zutil/net/http/HttpServer.java
index e97aa5e..7d104f5 100755
--- a/src/zutil/net/http/HttpServer.java
+++ b/src/zutil/net/http/HttpServer.java
@@ -61,21 +61,22 @@ public class HttpServer extends ThreadedTCPNetworkServer{
public static final int SESSION_TTL = 10*60*1000; // in milliseconds
- private Map pages;
- private HttpPage defaultPage;
- private Map> sessions;
- private int nextSessionId;
+ private Map pages = new ConcurrentHashMap<>();;
+ private Map> sessions = new ConcurrentHashMap<>();;
+ private int nextSessionId = 0;
+ private HttpPage defaultPage = null;
/**
* Creates a new instance of the sever
*
* @param port The port that the server should listen to
*/
- public HttpServer(int port) {
- this(port, null, null);
+ public HttpServer(int port) throws IOException {
+ super(port);
+ initGarbageCollector();
+
+ logger.info("HTTP Server ready and listening to port: " + port);
}
-
-
/**
* Creates a new instance of the sever
*
@@ -83,19 +84,17 @@ public class HttpServer extends ThreadedTCPNetworkServer{
* @param keyStore If this is not null then the server will use SSL connection with this keyStore file path
* @param keyStorePass If this is not null then the server will use a SSL connection with the given certificate
*/
- public HttpServer(int port, File keyStore, String keyStorePass) {
+ public HttpServer(int port, File keyStore, char[] keyStorePass) throws IOException {
super(port, keyStore, keyStorePass);
+ initGarbageCollector();
- pages = new ConcurrentHashMap<>();
- sessions = new ConcurrentHashMap<>();
- nextSessionId = 0;
-
- ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
- exec.scheduleWithFixedDelay(new SessionGarbageCollector(), 10000, SESSION_TTL / 2, TimeUnit.MILLISECONDS);
-
- logger.info("HTTP" + (keyStore==null ? "" : "S") + " Server ready and listening to port: " + port);
+ logger.info("HTTPS Server ready and listening to port: " + port);
}
+ private void initGarbageCollector() {
+ ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
+ exec.scheduleWithFixedDelay(new SessionGarbageCollector(), 10000, SESSION_TTL / 2, TimeUnit.MILLISECONDS);
+ }
/**
* This class acts as an garbage collector that
* removes old sessions from the session HashMap
@@ -118,16 +117,37 @@ public class HttpServer extends ThreadedTCPNetworkServer{
}
}
+
/**
- * Add a HttpPage to a specific URL
+ * Add a HttpPage to a specific URL.
*
- * @param name The URL or name of the page
+ * @param url The URL or name of the page
* @param page The page itself
*/
- public void setPage(String name, HttpPage page) {
- if (name.charAt(0) != '/')
- name = "/" +name;
- pages.put(name, page);
+ public void setPage(String url, HttpPage page) {
+ if (url.charAt(0) != '/')
+ url = "/" + url;
+ pages.put(url, page);
+ }
+
+ /**
+ * Add all pages to this server from the given server object.
+ *
+ * @param server is the HttpServer object that pages will be copied from.
+ */
+ public void setPages(HttpServer server) {
+ pages.putAll(server.pages);
+ }
+
+ /**
+ * Removes a page based on the URL.
+ *
+ * @param url The URL or name of the page
+ */
+ public void removePage(String url) {
+ if (url.charAt(0) != '/')
+ url = "/" + url;
+ pages.remove(url);
}
/**
diff --git a/src/zutil/net/http/page/HttpStaticContentPage.java b/src/zutil/net/http/page/HttpStaticContentPage.java
new file mode 100644
index 0000000..99fcb1b
--- /dev/null
+++ b/src/zutil/net/http/page/HttpStaticContentPage.java
@@ -0,0 +1,45 @@
+package zutil.net.http.page;
+
+import zutil.net.http.HttpHeader;
+import zutil.net.http.HttpPage;
+import zutil.net.http.HttpPrintStream;
+
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * A page serving static content
+ */
+public class HttpStaticContentPage implements HttpPage {
+ private String contentType;
+ private String content;
+
+
+ public HttpStaticContentPage(String content) {
+ this.setContent(content);
+ }
+
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+
+ @Override
+ public void respond(HttpPrintStream out,
+ HttpHeader headers,
+ Map session,
+ Map cookie,
+ Map request) throws IOException {
+ if (contentType != null)
+ out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, contentType);
+
+ out.setHeader(HttpHeader.HEADER_CONTENT_LENGTH, String.valueOf(content.length()));
+ out.print(content);
+ }
+}
diff --git a/src/zutil/net/mqtt/MqttBroker.java b/src/zutil/net/mqtt/MqttBroker.java
index b964279..b5cb2dc 100755
--- a/src/zutil/net/mqtt/MqttBroker.java
+++ b/src/zutil/net/mqtt/MqttBroker.java
@@ -55,7 +55,7 @@ public class MqttBroker extends ThreadedTCPNetworkServer {
private MqttSubscriptionListener globalListener;
private Map> subscriptionListeners = new HashMap<>();
- public MqttBroker() {
+ public MqttBroker() throws IOException {
super(MQTT_PORT);
}
diff --git a/src/zutil/net/threaded/ThreadedTCPNetworkServer.java b/src/zutil/net/threaded/ThreadedTCPNetworkServer.java
index 596020e..4037146 100755
--- a/src/zutil/net/threaded/ThreadedTCPNetworkServer.java
+++ b/src/zutil/net/threaded/ThreadedTCPNetworkServer.java
@@ -26,15 +26,17 @@ package zutil.net.threaded;
import zutil.log.LogUtil;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertificateException;
+import java.security.*;
+import java.security.cert.X509Certificate;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
@@ -46,48 +48,106 @@ import java.util.logging.Logger;
*
* @author Ziver
*/
-public abstract class ThreadedTCPNetworkServer extends Thread{
+public abstract class ThreadedTCPNetworkServer extends Thread {
private static final Logger logger = LogUtil.getLogger();
+ private Executor executor = Executors.newCachedThreadPool();
private final int port;
- private Executor executor;
- private File keyStore;
- private String keyStorePass;
+ private ServerSocket serverSocket;
/**
- * Creates a new instance of the sever
+ * Creates a new instance of the sever.
*
- * @param port The port that the server should listen to
+ * @param port the port that the server should listen to
*/
- public ThreadedTCPNetworkServer(int port) {
- this(port, null, null);
+ public ThreadedTCPNetworkServer(int port) throws IOException {
+ this.port = port;
+ this.serverSocket = new ServerSocket(port);
}
/**
- * Creates a new instance of the sever
+ * Creates a new SSL instance of the sever.
*
- * @param port The port that the server should listen to
- * @param keyStore If this is not null then the server will use SSL connection with this keyStore file path
- * @param keyStorePass If this is not null then the server will use a SSL connection with the given certificate
+ * @param port the port that the server should listen to.
+ * @param keyStoreFile the path to the key store file containing the server certificates
+ * @param keyStorePass the password to decrypt the key store file.
*/
- public ThreadedTCPNetworkServer(int port, File keyStore, String keyStorePass) {
+ public ThreadedTCPNetworkServer(int port, File keyStoreFile, char[] keyStorePass) throws IOException {
this.port = port;
- executor = Executors.newCachedThreadPool();
- this.keyStorePass = keyStorePass;
- this.keyStore = keyStore;
+ this.serverSocket = createSSLSocket(port, keyStoreFile, keyStorePass);
+ }
+ /**
+ * Creates a new SSL instance of the sever.
+ *
+ * @param port the port that the server should listen to.
+ * @param certificate the certificate for the servers domain.
+ */
+ public ThreadedTCPNetworkServer(int port, X509Certificate certificate) throws IOException {
+ this.port = port;
+ this.serverSocket = createSSLSocket(port, certificate);
+ }
+
+ /**
+ * Initiates a SSLServerSocket
+ *
+ * @param port the port the server should to
+ * @param keyStoreFile the cert file location
+ * @param keyStorePass the password for the cert file
+ * @return a SSLServerSocket object
+ */
+ private static ServerSocket createSSLSocket(int port, File keyStoreFile, char[] keyStorePass) throws IOException{
+ try {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(new FileInputStream(keyStoreFile), keyStorePass);
+
+ return createSSLSocket(port, keyStore, keyStorePass);
+ } catch (GeneralSecurityException e) {
+ throw new IOException("Unable to configure certificate.", e);
+ }
+ }
+
+ /**
+ * Initiates a SSLServerSocket
+ *
+ * @param port the port the server should to.
+ * @param certificate the certificate for the servers domain.
+ * @return a SSLServerSocket object
+ */
+ private static ServerSocket createSSLSocket(int port, X509Certificate certificate) throws IOException{
+ try {
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(null); // Create empty keystore
+ keyStore.setCertificateEntry("main_certificate", certificate);
+
+ return createSSLSocket(port, keyStore, null);
+ } catch (GeneralSecurityException e) {
+ throw new IOException("Unable to configure certificate.", e);
+ }
+ }
+
+ /**
+ * Initiates a SSLServerSocket
+ *
+ * @param port the port the server should to.
+ * @param keyStore the key store containing the domain certificates
+ * @param keyStorePass the password for the cert file
+ * @return a SSLServerSocket object
+ */
+ private static ServerSocket createSSLSocket(int port, KeyStore keyStore, char[] keyStorePass)
+ throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, KeyManagementException {
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(keyStore, keyStorePass);
+
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(keyManagerFactory.getKeyManagers(), null, SecureRandom.getInstanceStrong());
+ SSLServerSocketFactory socketFactory = ctx.getServerSocketFactory();
+
+ return socketFactory.createServerSocket(port);
}
public void run() {
- ServerSocket serverSocket = null;
try {
- if (keyStorePass != null && keyStore != null) {
- registerCertificate(keyStore, keyStorePass);
- serverSocket = initSSL(port);
- }
- else {
- serverSocket = new ServerSocket(port);
- }
- logger.info("Listening for TCP Connections on port: " +port);
+ logger.info("Listening for TCP Connections on port: " + port);
while (true) {
Socket connectionSocket = serverSocket.accept();
@@ -105,7 +165,9 @@ public abstract class ThreadedTCPNetworkServer extends Thread{
} finally {
if (serverSocket != null) {
try {
+ logger.info("Closing TCP socket listener (Port: " + port + ").");
serverSocket.close();
+ serverSocket = null;
} catch(IOException e) { logger.log(Level.SEVERE, null, e); }
}
}
@@ -116,33 +178,10 @@ public abstract class ThreadedTCPNetworkServer extends Thread{
* that will handle the newly made connection, if an null value is returned
* then the ThreadedTCPNetworkServer will close the new connection.
*
- * @param s is an new connection to an host
- * @return a new instance of an thread or null
+ * @param socket is an new connection to an host
+ * @return a new instance of an thread or null
*/
- protected abstract ThreadedTCPNetworkServerThread getThreadInstance(Socket s) throws IOException;
-
- /**
- * Initiates a SSLServerSocket
- *
- * @param port The port to listen to
- * @return The SSLServerSocket
- */
- private ServerSocket initSSL(int port) throws IOException{
- SSLServerSocketFactory sslserversocketfactory =
- (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
- return sslserversocketfactory.createServerSocket(port);
-
- }
-
- /**
- * Registers the given cert file to the KeyStore
- *
- * @param keyStore The cert file
- */
- protected void registerCertificate(File keyStore, String keyStorePass) {
- System.setProperty("javax.net.ssl.keyStore", keyStore.getAbsolutePath());
- System.setProperty("javax.net.ssl.keyStorePassword", keyStorePass);
- }
+ protected abstract ThreadedTCPNetworkServerThread getThreadInstance(Socket socket) throws IOException;
/**
* Stops the server and interrupts its internal thread.
diff --git a/src/zutil/osal/app/ffmpeg/FFmpeg.java b/src/zutil/osal/app/ffmpeg/FFmpeg.java
index 979878e..fceff9f 100644
--- a/src/zutil/osal/app/ffmpeg/FFmpeg.java
+++ b/src/zutil/osal/app/ffmpeg/FFmpeg.java
@@ -27,6 +27,7 @@ package zutil.osal.app.ffmpeg;
import zutil.osal.app.ffmpeg.FFmpegConstants.*;
import zutil.osal.app.ffmpeg.FFmpegProgressManager.FFmpegProgressListener;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -36,7 +37,6 @@ import java.util.List;
* @see FFmpeg Commandline Documentation
*/
public class FFmpeg {
-
private FFmpegLogLevel logLevel;
private boolean overwriteOutput = false;
private List inputs = new ArrayList<>();
@@ -71,9 +71,13 @@ public class FFmpeg {
if (listener == null)
throw new IllegalArgumentException("FFmpegProgressListener cannot be NULL.");
- if (progressManager != null)
- progressManager.close();
- progressManager = new FFmpegProgressManager(listener);
+ try {
+ if (progressManager != null)
+ progressManager.close();
+ progressManager = new FFmpegProgressManager(listener);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
diff --git a/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java b/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java
index ba0dd96..7c25ad4 100644
--- a/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java
+++ b/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java
@@ -91,10 +91,10 @@ public class FFmpegProgressManager extends ThreadedTCPNetworkServer {
private FFmpegProgressListener listener;
private String address;
- public FFmpegProgressManager(FFmpegProgressListener listener) {
+ public FFmpegProgressManager(FFmpegProgressListener listener) throws IOException {
this(listener, PROGRESS_DEFAULT_PORT);
}
- public FFmpegProgressManager(FFmpegProgressListener listener, int port) {
+ public FFmpegProgressManager(FFmpegProgressListener listener, int port) throws IOException {
super(port);
this.listener = listener;
this.address = "tcp://" + InetAddress.getLoopbackAddress() + ":" + port;
diff --git a/test/zutil/net/http/page/HttpGuessTheNumber.java b/test/zutil/net/http/page/HttpGuessTheNumber.java
index a3e05b4..4728ef9 100755
--- a/test/zutil/net/http/page/HttpGuessTheNumber.java
+++ b/test/zutil/net/http/page/HttpGuessTheNumber.java
@@ -46,7 +46,7 @@ public class HttpGuessTheNumber implements HttpPage {
private static final String COOKIE_KEY_HIGH = "high";
- public static void main(String[] args) {
+ public static void main(String[] args) throws IOException {
LogUtil.setGlobalLevel(Level.ALL);
LogUtil.setGlobalFormatter(new CompactLogFormatter());