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

de.svws_nrw.server.jetty.SvwsServer Maven / Gradle / Ivy

Go to download

Diese Bibliothek enthält den Java-Server für die Schulverwaltungssoftware in NRW

There is a newer version: 0.9.2
Show newest version
package de.svws_nrw.server.jetty;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;

import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.ee10.servlet.ServletMapping;
import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping;
import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler;
import org.eclipse.jetty.http2.HTTP2Cipher;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.security.Constraint;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.RequestLogWriter;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher;

import de.svws_nrw.api.RestAppAdminClient;
import de.svws_nrw.api.RestAppClient;
import de.svws_nrw.api.RestAppDav;
import de.svws_nrw.api.RestAppDebug;
import de.svws_nrw.api.RestAppSchemaRoot;
import de.svws_nrw.api.RestAppServer;
import de.svws_nrw.config.SVWSKonfiguration;
import de.svws_nrw.core.logger.Logger;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.core.Application;


/**
 * Diese Klasse repräsentiert den Http-Server des SVWS-Server-Projektes,
 * welcher auf einer Embedded-Version des Jetty-Servers basiert.
 */
public final class SvwsServer {

	/** Die Instanz dieses SVWS-Servers */
	private static SvwsServer _instance = null;

	/** Die Instanz des Jetty-Servers (siehe {@link Server}) */
	private final Server server;

	/** Der im Server verwendete {@link ServletContextHandler} */
	private final ServletContextHandler context_handler = new ServletContextHandler(ServletContextHandler.SESSIONS);

	/** Die Menge der Handler, welche dem Security Context - Handler des Jetty-Server zugeordnet sind */
	private final Handler.Sequence handlerCollection = new Handler.Sequence();

	/** Die Menge der Context-Handler, welche zu den Handlern gehören */
	private final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();

	/** Der Login-Service, welcher die Zugriffsberechtigungen auf die API prüft. */
	private final LoginService loginService = new SVWSLoginService("Authentifizierung bei dem SVWS-Server");


	/**
	 * Initialisiert den Jetty-Http-Server und fügt den Login-Service {@link SVWSLoginService}
	 * und die Http-Konfiguration basieren auf der {@link SVWSKonfiguration} hinzu.
	 */
	private SvwsServer() {
		// Create a server with a threadpool of max. 500 threads
		final QueuedThreadPool threadPool = new QueuedThreadPool();
		threadPool.setMaxThreads(500);
		server = new Server(threadPool);
		server.addBean(new ScheduledExecutorScheduler());

		// Server extra options
		server.setDumpAfterStart(false);
		server.setDumpBeforeStop(false);
		server.setStopAtShutdown(true);

		// Registriere den Login Service beim Jetty-Server
		server.addBean(loginService);

		// Initialisieren den ServletContextHandler für die Unterstützung mehrerer Servlets
		context_handler.setContextPath("/");
		contextHandlerCollection.addHandler(context_handler);

		// Ordne die Context-Handler den allgemeinen Handlern zu
		handlerCollection.setHandlers(new Handler[] { contextHandlerCollection, new DefaultHandler() });

		// Erstelle einen ConstraintSecurityHandler mit dem Login-Service, etc.
		server.setHandler(createConstraintSecurityHandler(server, loginService, handlerCollection));

		// Konfiguriere das Logging für API-Zugriffe
		addLoggingHandler();

		// Konfiguriere und ergänze die HTTP-Verbindungen, auf welchen der Server lauscht
		addHTTPServerConnections();

		// Füge die einzelnen Servlets mit den verschiedenen APIs zum SVWS-Server hinzu.
		addAPIApplications();
	}


	/**
	 * Gibt die Instanz des SVWS-Servers zurück.
	 *
	 * @return die SVWS-Server-Instanz
	 */
	public static SvwsServer instance() {
		if (_instance == null)
			_instance = new SvwsServer();
		return _instance;
	}


	/**
	 * Started den Server und blockiert den aufrufende Thread bis
	 * der zum Jetty-Server gehörende {@link ThreadPool} gestoppt wurde.
	 *
	 * @throws Exception   eine Exception beim Starten des Servers wird zurückgemeldet
	 */
	public void start() throws Exception {
		// Start the server
		server.start();
		server.join();
	}


	private static ConstraintMapping getSecurityConstraintMapping() {
		final Constraint constraint = Constraint.from("auth", Constraint.Authorization.SPECIFIC_ROLE, "user", "admin");
		final ConstraintMapping mapping = new ConstraintMapping();
		mapping.setPathSpec("/*");
		mapping.setMethodOmissions(new String[] { HttpMethod.OPTIONS });
		mapping.setConstraint(constraint);
		return mapping;
	}


	private static ConstraintSecurityHandler createConstraintSecurityHandler(final Server server, final LoginService loginService,
			final Handler.Sequence handlerCollection) {
		final ConstraintSecurityHandler security = new ConstraintSecurityHandler();
		security.addConstraintMapping(getSecurityConstraintMapping());
		security.setAuthenticator(new SVWSAuthenticator());
		security.setLoginService(loginService);
		security.setHandler(handlerCollection);
		server.setHandler(security);
		return security;
	}


	/**
	 * Fügt einen Logging-Handler für die API-Anfragen hinzu
	 */
	private void addLoggingHandler() {
		// logging
		if (SVWSKonfiguration.get().isLoggingEnabled()) {
			final String logPath = SVWSKonfiguration.get().getLoggingPath();
			try {
				Files.createDirectories(Paths.get(logPath));
			} catch (final IOException e) {
				e.printStackTrace();
			}
			final RequestLogWriter logWriter = new RequestLogWriter(logPath + "/yyyy_mm_dd.request.log");
			logWriter.setFilenameDateFormat("yyyy_MM_dd");
			logWriter.setRetainDays(90);
			logWriter.setAppend(true);
			logWriter.setTimeZone("GMT");
			final CustomRequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT);
			server.setRequestLog(requestLog);
			server.setTempDirectory(System.getProperty("java.io.tmpdir"));
		}
	}


	/**
	 * Gibt einen ServerConnector für das Lauschen auf HTTP-Verbindungen auf dem übergebenen Port zurück.
	 *
	 * @param disableTLS         gibt an, ob TLS deaktiviert sein soll
	 * @param preferHTTPv1_1     gibt an, ob HTTP-Verbindungen mit v1.1 gegenüber v2 bevorzugt werden sollen
	 * @param port               der Port, auf dem gelauscht werden soll
	 * @param name               der eindeutige Name der dem Connector zugeordnet wird.
	 * @param keyStorePath       der Pfad zum Java-Key-Store für TLS-Verbindungen
	 * @param keyStorePassword   das Kennwort für den Java-Key-Store
	 * @param keyAlias           der Alias für das zu verwendende Zertifikat
	 *
	 * @return der konfigurierte ServerConnector
	 */
	private ServerConnector getHttpServerConnector(final boolean disableTLS, final boolean preferHTTPv1_1, final int port,
			final String name, final String keyStorePath, final String keyStorePassword, final String keyAlias) {
		// HTTP Configuration
		final HttpConfiguration http_config = new HttpConfiguration();
		if (!disableTLS) {
			http_config.setSecureScheme("https");
			http_config.setSecurePort(port);
		}
		http_config.setOutputBufferSize(32768);
		http_config.setRequestHeaderSize(8192);
		http_config.setResponseHeaderSize(8192);
		http_config.setSendServerVersion(true);
		http_config.setSendDateHeader(false);

		// SSL Context Factory
		final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
		sslContextFactory.setKeyStorePath(keyStorePath + "/keystore");
		sslContextFactory.setKeyStorePassword(keyStorePassword);
		sslContextFactory.setKeyManagerPassword(keyStorePassword);
		sslContextFactory.setTrustStorePath(keyStorePath + "/keystore");
		sslContextFactory.setTrustStorePassword(keyStorePassword);
		if ((keyAlias != null) && (!keyAlias.isBlank()))
			sslContextFactory.setCertAlias(keyAlias);
		sslContextFactory.setIncludeProtocols("TLSv1.3", "TLSv1.2");
		sslContextFactory.setIncludeCipherSuites("TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_128_GCM_SHA256",
				"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
				"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256");
		sslContextFactory.setSniRequired(false);
		sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);

		// SSL HTTP Configuration
		final HttpConfiguration https_config = new HttpConfiguration(http_config);
		final SecureRequestCustomizer secureRequestCustomizer = new SecureRequestCustomizer();
		if (!disableTLS) {
			secureRequestCustomizer.setSniHostCheck(false);
		}
		https_config.addCustomizer(secureRequestCustomizer);

		// HTTP Connection Factory (v1.1 oder v2)
		final AbstractConnectionFactory connFactoryHTTPv1_1 = new HttpConnectionFactory(https_config);
		final AbstractConnectionFactory connFactoryHTTPv2 = new HTTP2ServerConnectionFactory(https_config);
		final ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
		if (disableTLS) {
			final ServerConnector connector = preferHTTPv1_1
					? new ServerConnector(server, connFactoryHTTPv1_1, connFactoryHTTPv2)
					: new ServerConnector(server, connFactoryHTTPv2, connFactoryHTTPv1_1);
			connector.setName(name);
			connector.setPort(port);
			return connector;
		}
		final ServerConnector connector = preferHTTPv1_1
				? new ServerConnector(server, sslContextFactory, alpn, connFactoryHTTPv1_1, connFactoryHTTPv2)
				: new ServerConnector(server, sslContextFactory, alpn, connFactoryHTTPv2, connFactoryHTTPv1_1);
		connector.setName(name);
		connector.setPort(port);
		return connector;
	}


	/**
	 * Erzeugt eine neue HTTP- bzw. HTTPS-Konfiguration basierend auf der {@link SVWSKonfiguration}
	 * und fügt diese zum Jetty-Server hinzu.
	 */
	@SuppressWarnings("resource")
	private void addHTTPServerConnections() {
		final SVWSKonfiguration config = SVWSKonfiguration.get();
		server.addConnector(getHttpServerConnector(
				config.isTLSDisabled(),
				config.useHTTPDefaultv11(),
				config.isTLSDisabled() ? config.getPortHTTP() : config.getPortHTTPS(),
				"Server",
				config.getTLSKeystorePath(),
				config.getTLSKeystorePassword(),
				config.getTLSKeyAlias()
		));
		if (!config.isDBRootAccessDisabled() && config.hatPortHTTPPrivilegedAccess())
			server.addConnector(getHttpServerConnector(
					config.isTLSDisabled(),
					config.useHTTPDefaultv11(),
					config.getPortHTTPPrivilegedAccess(),
					"Privileged",
					config.getTLSKeystorePath(),
					config.getTLSKeystorePassword(),
					config.getTLSKeyAlias()
			));
	}


	/**
	 * Fügt die Rest-Applikationen zum Server hinzu.
	 */
	private void addAPIApplications() {
		final SVWSKonfiguration config = SVWSKonfiguration.get();
		addApplication(RestAppServer.class, RestAppServer.getPathSpecification());
		addApplication(RestAppClient.class, "/*");
		addApplication(RestAppDav.class, "/dav/*");
		addApplication(RestAppDebug.class, RestAppDebug.getPathSpecification());
		if (!config.isDBRootAccessDisabled()) {
			addApplication(RestAppSchemaRoot.class, RestAppSchemaRoot.getPathSpecification());
			if ((config.getAdminClientPath() != null) && (!config.getAdminClientPath().isBlank()))
				addApplication(RestAppAdminClient.class, RestAppAdminClient.getPathSpecification());
		}
	}


	/**
	 * Fügt die angegebene API-Applikation zum Server hinzu.
	 *
	 * @param c           die Applikation
	 * @param pathSpecs   die Pfad-Spezifikationen
	 */
	private void addApplication(final Class c, final String... pathSpecs) {
		final ServletHandler servletHandler = context_handler.getServletHandler();
		final ServletHolder servlet = servletHandler.addServletWithMapping(HttpServletDispatcher.class, pathSpecs[0]);
		final ServletMapping mapping = servletHandler.getServletMapping(pathSpecs[0]);
		mapping.setPathSpecs(pathSpecs);
		Logger.global().logLn("Registriere API-Applikation " + c.getSimpleName() + ": " + Arrays.toString(mapping.getPathSpecs()));
		servlet.setInitParameter("jakarta.ws.rs.Application", c.getCanonicalName());
	}


}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy