All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.enioka.jqm.webserver.jetty.JettyServer Maven / Gradle / Ivy

package com.enioka.jqm.webserver.jetty;

import java.io.File;
import java.io.IOException;

import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
import org.kohsuke.MetaInfServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.enioka.jqm.engine.api.exceptions.JqmInitError;
import com.enioka.jqm.webserver.api.WebServer;
import com.enioka.jqm.webserver.api.WebServerConfiguration;

/**
 * Main implementation of the HTTP(S) server.
* Some parts inspired by https://northcoder.com/post/jetty-11-secure-connections/ */ @MetaInfServices(WebServer.class) public class JettyServer implements WebServer { private static Logger jqmlogger = LoggerFactory.getLogger(JettyServer.class); private Server server = null; @Override public void start(WebServerConfiguration configuration) { // Start is also a restart method this.stop(); // Only load Jetty if web APIs are allowed in the cluster if (!configuration.isWsEnabled() || (!configuration.isStartAdmin() && !configuration.isStartClient() && !configuration.isStartSimple())) { jqmlogger.info("Jetty will not start - parameter disableWsApi is set to true"); return; } // Only load Jetty if at least one application should start if (!configuration.isStartAdmin() && !configuration.isStartClient() && !configuration.isStartSimple()) { jqmlogger.info("Jetty will not start - all web APIs are disabled on this node"); return; } // Only load Jetty if at least one war is present... File war = new File(configuration.getWarPath()); if (!war.exists() || !war.isFile()) { jqmlogger.info("Jetty will not start - there are no web applications to load inside the webapp directory"); return; } // Create server server = new Server(); server.setDumpAfterStart(jqmlogger.isTraceEnabled()); server.setConnectors(null); HttpConfiguration http = new HttpConfiguration(); if (configuration.isUseTls()) { jqmlogger.info("JQM will use HTTPS for all communications (with TLS)"); http.setSecureScheme("https"); http.setSecurePort(configuration.getPort()); http.addCustomizer(getSecureRequestCustomizer()); // HTTP/1.1 HttpConnectionFactory httpCF = new HttpConnectionFactory(http); // HTTP/2 var http2CF = new HTTP2ServerConnectionFactory(http); // ALPN is used to negotiate HTTP2 over a previously established connection var alpnCF = new ALPNServerConnectionFactory(); alpnCF.setDefaultProtocol(httpCF.getProtocol()); // fallback protocol // TLS CF var tlsHttp2CF = new SslConnectionFactory(getSSLContextFactory(configuration), alpnCF.getProtocol()); // Add the server connector var connector = new ServerConnector(server, tlsHttp2CF, alpnCF, http2CF, httpCF); // Order is important - HTTP2 over HTTP1. connector.setPort(configuration.getPort()); connector.setHost(getHost(configuration)); server.addConnector(connector); } else { jqmlogger.info("JQM will use plain HTTP for all communications (no TLS)"); http.setSendServerVersion(false); HttpConnectionFactory httpCF = new HttpConnectionFactory(http); var connector = new ServerConnector(server, httpCF); connector.setPort(configuration.getPort()); connector.setHost(getHost(configuration)); server.addConnector(connector); } // Add the webapp var webAppContext = loadWar(configuration); if (webAppContext != null) { server.setHandler(webAppContext); } // Start try { server.start(); } catch (Exception e) { // JETTY-839: threadpool not daemon nor close on exception. Explicit closing required as workaround. this.stop(); throw new JqmInitError("Could not start web server - check there is no other process binding on this port & interface", e); } jqmlogger.info("Jetty has started on port " + getActualPort()); } @Override public void stop() { if (server == null) { return; } jqmlogger.trace("Jetty will now stop"); try { this.server.stop(); this.server.join(); this.server.destroy(); this.server = null; jqmlogger.info("Jetty has stopped"); } catch (Exception e) { jqmlogger.error( "An error occured during Jetty stop. It is not an issue if it happens during JQM node shutdown, but one during restart (memeory leak).", e); } } @Override public int getActualPort() { if (server == null || server.getConnectors() == null || server.getConnectors().length == 0) { return 0; } return ((ServerConnector) server.getConnectors()[0]).getLocalPort(); } private SecureRequestCustomizer getSecureRequestCustomizer() { var src = new SecureRequestCustomizer(); src.setSniRequired(false); src.setStsMaxAge(3600); src.setStsIncludeSubDomains(true); return src; } private SslContextFactory.Server getSSLContextFactory(WebServerConfiguration configuration) { jqmlogger.info("Jetty web server uses keystore {} and truststore {}", configuration.getKeyStorePath(), configuration.getTrustStorePath()); var sslContextFactory = new SslContextFactory.Server(); sslContextFactory.setKeyStorePath(configuration.getKeyStorePath()); sslContextFactory.setKeyStorePassword(configuration.getKeyStorePassword()); sslContextFactory.setKeyStoreType("PKCS12"); sslContextFactory.setTrustStorePath(configuration.getTrustStorePath()); sslContextFactory.setTrustStorePassword(configuration.getTrustStorePassword()); sslContextFactory.setTrustStoreType("JKS"); sslContextFactory.setWantClientAuth(true); sslContextFactory.addExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA"); sslContextFactory.addExcludeProtocols("SSLv3", "TLSv1", "TLSv1.1"); return sslContextFactory; } private String getHost(WebServerConfiguration configuration) { // Host either comes from node configuirain or from a system property that overloads it (shortcut for container deployments) // TODO: put this in other project. var hostName = configuration.getHost(); String interfaceFromProperty = System.getProperty("com.enioka.jqm.interface"); if (interfaceFromProperty != null && !interfaceFromProperty.isEmpty()) { hostName = interfaceFromProperty; } if (hostName == null || hostName.isEmpty()) { jqmlogger.info("No hostname specified - binding Jetty on all interfaces"); return null; } // Special cases if ("localhost".equals(hostName)) { jqmlogger.debug("Jetty will bind on loopback as required"); return "localhost"; } else if ("0.0.0.0".equals(hostName)) { jqmlogger.debug("Jetty will bind on all interfaces as required"); return null; } // Normal case return hostName; } private WebAppContext loadWar(WebServerConfiguration configuration) { File war = new File(configuration.getWarPath()); if (!war.exists() || !war.isFile()) { return null; } jqmlogger.info("Jetty will now load the web service application war"); // Load web application. var webAppContext = new WebAppContext(war.getPath(), "/"); webAppContext.setDisplayName("JqmWebServices"); // Allow access to the extensions directory, but not engine classes. WebAppContext.addServerClasses(server, "-org.eclipse.jetty.servlet.DefaultServlet"); WebAppContext.addSystemClasses(server, "org.slf4j,org.slf4j.bridge"); // Avoid annoying default Jetty configuration that loads classes outside the classpath... webAppContext.setDefaultsDescriptor(null); // JQM configuration should be on the classpath try { webAppContext.setExtraClasspath("conf/jqm.properties"); } catch (IOException e) { throw new JqmInitError("Could not add JQM configuration to the webapp classpath", e); } webAppContext.setInitParameter("jqmnodeid", configuration.getLocalNodeId() + ""); webAppContext.setInitParameter("startSimple", configuration.isStartSimple() + ""); webAppContext.setInitParameter("startClient", configuration.isStartClient() + ""); webAppContext.setInitParameter("startAdmin", configuration.isStartAdmin() + ""); // Set configurations (order is important: need to unpack war before reading web.xml) webAppContext.setConfigurations(new Configuration[] { new WebInfConfiguration(), new WebXmlConfiguration(), new MetaInfConfiguration(), new FragmentConfiguration(), new AnnotationConfiguration() }); return webAppContext; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy