Skip to content

Commit

Permalink
Websocket port finding (qzind#812)
Browse files Browse the repository at this point in the history
Adjust websocket port finding so secure/insecure are looped separately
Closes qzind#810
  • Loading branch information
Brett Berenz authored May 13, 2021
1 parent d2a0442 commit c54ef80
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 42 deletions.
125 changes: 84 additions & 41 deletions src/qz/ws/PrintSocketServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,33 @@
import org.apache.log4j.rolling.RollingFileAppender;
import org.apache.log4j.rolling.SizeBasedTriggeringPolicy;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qz.common.Constants;
import qz.common.TrayManager;
import qz.installer.Installer;
import qz.installer.certificate.*;
import qz.installer.certificate.CertificateManager;
import qz.installer.certificate.ExpiryTask;
import qz.installer.certificate.KeyPairWrapper;
import qz.installer.certificate.NativeCertificateInstaller;
import qz.utils.ArgParser;
import qz.utils.ArgValue;
import qz.utils.FileUtilities;
import qz.utils.SystemUtilities;

import javax.swing.*;
import java.io.*;
import java.net.BindException;
import java.util.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

Expand All @@ -66,7 +69,7 @@ public class PrintSocketServer {

public static void main(String[] args) {
ArgParser parser = new ArgParser(args);
if(parser.intercept()) {
if (parser.intercept()) {
System.exit(parser.getExitCode());
}
forceHeadless = parser.hasFlag(ArgValue.HEADLESS);
Expand All @@ -81,13 +84,14 @@ public static void main(String[] args) {
certificateManager = Installer.getInstance().certGen(false);
// Reoccurring (e.g. hourly) cert expiration check
new ExpiryTask(certificateManager).schedule();
} catch(Exception e) {
}
catch(Exception e) {
log.error("Something went critically wrong loading HTTPS", e);
}
Installer.getInstance().addUserSettings();

// Linux needs the cert installed in user-space on every launch for Chrome SSL to work
if(!SystemUtilities.isWindows() && !SystemUtilities.isMac()) {
if (!SystemUtilities.isWindows() && !SystemUtilities.isMac()) {
NativeCertificateInstaller.getInstance().install(certificateManager.getKeyPair(KeyPairWrapper.Type.CA).getCert());
}

Expand Down Expand Up @@ -125,38 +129,34 @@ private static void setupFileLogging() {
}

public static void runServer() {
final AtomicBoolean running = new AtomicBoolean(false);
while(!running.get() && securePortIndex.get() < SECURE_PORTS.size() && insecurePortIndex.get() < INSECURE_PORTS.size()) {
Server server = new Server(getInsecurePortInUse());
if (certificateManager != null) {
// Bind the secure socket on the proper port number (i.e. 9341), add it as an additional connector
SslConnectionFactory sslConnection = new SslConnectionFactory(certificateManager.configureSslContextFactory(), HttpVersion.HTTP_1_1.asString());
HttpConnectionFactory httpConnection = new HttpConnectionFactory(new HttpConfiguration());

ServerConnector connector = new ServerConnector(server, sslConnection, httpConnection);
connector.setHost(certificateManager.getProperties().getProperty("wss.host"));
connector.setPort(getSecurePortInUse());
server.addConnector(connector);
} else {
log.warn("Could not start secure WebSocket");
}
Server server = findAvailableSecurePort();
Connector secureConnector = null;
if (server.getConnectors().length > 0 && !server.getConnectors()[0].isFailed()) {
secureConnector = server.getConnectors()[0];
}

final AtomicBoolean running = new AtomicBoolean(false);
while(!running.get() && insecurePortIndex.get() < INSECURE_PORTS.size()) {
try {
ServerConnector connector = new ServerConnector(server);
connector.setPort(getInsecurePortInUse());
if (secureConnector != null) {
//setup insecure connector before secure
server.setConnectors(new Connector[] {connector, secureConnector});
} else {
server.setConnectors(new Connector[] {connector});
}

ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);

// Handle WebSocket connections
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
filter.addMapping(new ServletPathSpec("/"), new WebSocketCreator() {
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) {
return new PrintSocketClient();
}
});
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configure(context);
filter.addMapping(new ServletPathSpec("/"), (req, resp) -> new PrintSocketClient());
filter.getFactory().getPolicy().setMaxTextMessageSize(MAX_MESSAGE_SIZE);

// Handle HTTP landing page
ServletHolder httpServlet = new ServletHolder(new HttpAboutServlet(certificateManager));
httpServlet.setInitParameter("resourceBase","/");
httpServlet.setInitParameter("resourceBase", "/");
context.addServlet(httpServlet, "/");
context.addServlet(httpServlet, "/json");

Expand All @@ -165,28 +165,71 @@ public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse
server.start();

running.set(true);
trayManager.setServer(server, running, securePortIndex, insecurePortIndex);
log.info("Server started on port(s) " + TrayManager.getPorts(server));
trayManager.setServer(server, running, securePortIndex, insecurePortIndex);

server.join();
}
catch(BindException | MultiException e) {
catch(IOException | MultiException e) {
//order of getConnectors is the order we added them -> insecure first
if (server.getConnectors()[0].isFailed()) {
insecurePortIndex.incrementAndGet();
}
if (server.getConnectors().length > 1 && server.getConnectors()[1].isFailed()) {
securePortIndex.incrementAndGet();
}

//explicitly stop the server, because if only 1 port has an exception the other will still be opened
try { server.stop(); }catch(Exception ignore) { ignore.printStackTrace(); }
try { server.stop(); }catch(Exception stopEx) { stopEx.printStackTrace(); }
}
catch(Exception e) {
e.printStackTrace();
trayManager.displayErrorMessage(e.getLocalizedMessage());
break;
}
}
}

private static Server findAvailableSecurePort() {
Server server = new Server();

if (certificateManager != null) {
final AtomicBoolean runningSecure = new AtomicBoolean(false);
while(!runningSecure.get() && securePortIndex.get() < SECURE_PORTS.size()) {
try {
// Bind the secure socket on the proper port number (i.e. 8181), add it as an additional connector
SslConnectionFactory sslConnection = new SslConnectionFactory(certificateManager.configureSslContextFactory(), HttpVersion.HTTP_1_1.asString());
HttpConnectionFactory httpConnection = new HttpConnectionFactory(new HttpConfiguration());

ServerConnector secureConnector = new ServerConnector(server, sslConnection, httpConnection);
secureConnector.setHost(certificateManager.getProperties().getProperty("wss.host"));
secureConnector.setPort(getSecurePortInUse());
server.setConnectors(new Connector[] {secureConnector});

server.start();
log.trace("Established secure WebSocket on port {}", getSecurePortInUse());

//only starting to test port availability; insecure port will actually start
server.stop();
runningSecure.set(true);
}
catch(IOException | MultiException e) {
if (server.getConnectors()[0].isFailed()) {
securePortIndex.incrementAndGet();
}

try { server.stop(); }catch(Exception stopEx) { stopEx.printStackTrace(); }
}
catch(Exception e) {
e.printStackTrace();
trayManager.displayErrorMessage(e.getLocalizedMessage());
break;
}
}
}

if (server.getConnectors().length == 0 || server.getConnectors()[0].isFailed()) {
log.warn("Could not start secure WebSocket");
}

return server;
}

/**
Expand All @@ -207,7 +250,7 @@ public static int getInsecurePortInUse() {
}

public static Properties getTrayProperties() {
return certificateManager == null ? null : certificateManager.getProperties();
return certificateManager == null? null:certificateManager.getProperties();
}

}
2 changes: 1 addition & 1 deletion src/qz/ws/SingleInstanceChecker.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void onClose(int statusCode, String reason) {

@OnWebSocketError
public void onError(Throwable e) {
if (!e.getMessage().contains("Connection refused")) {
if (!e.getMessage().contains("Connection refused") && !e.getMessage().contains("Failed to upgrade to websocket")) {
log.warn("WebSocket error", e);
}
}
Expand Down

0 comments on commit c54ef80

Please sign in to comment.