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

pl.edu.icm.unity.engine.server.JettyServer Maven / Gradle / Ivy

/*
 * Copyright (c) 2013 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package pl.edu.icm.unity.engine.server;

import eu.unicore.security.canl.IAuthnAndTrustConfiguration;
import eu.unicore.util.configuration.ConfigurationException;
import eu.unicore.util.jetty.SecuredServerConnector;
import jakarta.servlet.ServletException;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.ee10.servlet.FilterHolder;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlets.CrossOriginFilter;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.http.UriCompliance.Violation;
import org.eclipse.jetty.rewrite.handler.HeaderPatternRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.session.DefaultSessionIdManager;
import org.eclipse.jetty.session.SessionIdManager;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.Lifecycle;
import org.springframework.stereotype.Component;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.exceptions.WrongArgumentException;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.PKIManagement;
import pl.edu.icm.unity.engine.api.config.UnityHttpServerConfiguration;
import pl.edu.icm.unity.engine.api.config.UnityServerConfiguration;
import pl.edu.icm.unity.engine.api.endpoint.WebAppEndpointInstance;
import pl.edu.icm.unity.engine.api.server.NetworkServer;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

import static pl.edu.icm.unity.engine.api.config.UnityHttpServerConfiguration.*;

/**
 * Manages HTTP server. Mostly responsible for creating proper hierarchy of HTTP handlers for deployed
 * {@link WebAppEndpointInstance} instances.
 * 

* Jetty structure which is used: * {@link ContextHandlerCollection} is used to manage all deployed contexts (fixed, one instance) * Endpoints provide a single {@link ServletContextHandler} which describes an endpoint's web application. *

* If needed it is wrapped in some rewrite handler. * @author K. Benedyczak */ @Component public class JettyServer implements Lifecycle, NetworkServer { private static final Logger log = Log.getLogger(Log.U_SERVER_CORE, UnityApplication.class); private List deployedEndpoints; private Map usedContextPaths; private ContextHandlerCollection mainContextHandler; private FilterHolder dosFilter = null; private final String defaultWebContentsPath; private final URL[] listenUrls; private final IAuthnAndTrustConfiguration securityConfiguration; private final UnityHttpServerConfiguration serverSettings; private Server theServer; @Autowired public JettyServer(UnityServerConfiguration cfg, PKIManagement pkiManagement, ListeningUrlsProvider listenUrlsProvider) { this(cfg.getJettyProperties(), cfg.getValue(UnityServerConfiguration.DEFAULT_WEB_CONTENT_PATH), cfg.getValue(UnityServerConfiguration.DEFAULT_WEB_PATH), pkiManagement.getMainAuthnAndTrust(), listenUrlsProvider.getListenUrls()); } JettyServer(UnityHttpServerConfiguration serverSettings, String defaultWebContentsPath, String defaultWebPath, IAuthnAndTrustConfiguration securityConfiguration, URL[] listenUrls) { this.securityConfiguration = securityConfiguration; this.listenUrls = listenUrls; this.serverSettings = serverSettings; this.defaultWebContentsPath = defaultWebContentsPath; initServer(); dosFilter = createDoSFilterInstance(); addRedirectHandler(defaultWebPath); } @Override public void start() { try { log.debug("Starting Jetty HTTP server"); theServer.start(); updatePortsIfNeeded(); log.info("Jetty HTTP server was started"); } catch (Exception e) { log.error("Problem starting HTTP Jetty server: " + e.getMessage(), e); } } private void addRedirectHandler(String defaultWebPath) throws ConfigurationException { if (defaultWebPath != null && !defaultWebPath.isEmpty()) { try { deployHandler(new RedirectHandler(defaultWebPath), "sys:redirect"); } catch (EngineException e) { log.error("Cannot deploy redirect handler " + e.getMessage(), e); } } } @Override public void stop() { try { log.debug("Stopping Jetty HTTP server"); theServer.stop(); log.info("Jetty HTTP server was stopped"); } catch (Exception e) { log.error("Problem stopping HTTP Jetty server: " + e.getMessage(), e); } } /** * Invoked after server is started: updates the listen URLs with the actual port, * if originally it was set to 0, what means that server should choose a random one */ private void updatePortsIfNeeded() { Connector[] conns = theServer.getConnectors(); for (int i=0; i 1) factory.setExcludeCipherSuites(disabledCiphers.split("[ ]+")); } log.info("SSL protocol was set to: '" + factory.getProtocol() + "'"); return ssl; } /** * @return an instance of insecure connector. It is only configured not * to send server version and supports connections logging. */ private ServerConnector getPlainConnectorInstance() { HttpConnectionFactory httpConnFactory = getHttpConnectionFactory(); return new ServerConnector(theServer, httpConnFactory); } /** * By default http connection factory is configured not to send server identification data. */ private HttpConnectionFactory getHttpConnectionFactory() { HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSendServerVersion(false); httpConfig.setSendXPoweredBy(false); httpConfig.setUriCompliance(new UriCompliance("allowForEncodedPathSeparators", Set.of(Violation.AMBIGUOUS_PATH_SEPARATOR))); SecureRequestCustomizer src = new SecureRequestCustomizer(); src.setSniHostCheck(serverSettings.getBooleanValue(SNI_HOSTNAME_CHECK)); httpConfig.addCustomizer(src); return new HttpConnectionFactory(httpConfig); } /** * Creates an insecure connector and configures it. */ private ServerConnector createPlainConnector(URL url) { log.info("Creating plain HTTP connector on: " + url); return getPlainConnectorInstance(); } /** * Sets parameters on the Connector, which are shared by all of them regardless of their type. * The default implementation sets port and hostname. */ private void configureConnector(ServerConnector connector, URL url) throws ConfigurationException { connector.setHost(url.getHost()); connector.setPort(url.getPort() == -1 ? url.getDefaultPort() : url.getPort()); connector.setIdleTimeout(serverSettings.getIntValue(UnityHttpServerConfiguration.MAX_IDLE_TIME)); } /** * Configures Gzip filter if gzipping is enabled, for all servlet * handlers which are configured. Warning: if you use a complex setup of * handlers it might be better to override this method and enable * compression selectively. */ private Handler configureGzip(Handler handler) throws ConfigurationException { boolean enableGzip = serverSettings.getBooleanValue(UnityHttpServerConfiguration.ENABLE_GZIP); if (enableGzip) { GzipHandler gzipHandler = new GzipHandler(); gzipHandler.setMinGzipSize(serverSettings.getIntValue(UnityHttpServerConfiguration.MIN_GZIP_SIZE)); log.info("Enabling GZIP compression filter"); gzipHandler.setServer(theServer); gzipHandler.setHandler(handler); return gzipHandler; } else return handler; } /** * @return array of URLs where the server is listening */ public URL[] getUrls() { return listenUrls; } private void configureErrorHandler() { theServer.setErrorHandler(new JettyErrorHandler(defaultWebContentsPath)); } @Override public boolean isRunning() { return (theServer == null) ? false : theServer.isRunning(); } private synchronized void initRootHandler() { usedContextPaths = new HashMap<>(); mainContextHandler = new ContextHandlerCollection(); deployedEndpoints = new ArrayList<>(16); } /** * Deploys a classic Unity endpoint. */ @Override public synchronized void deployEndpoint(WebAppEndpointInstance endpoint) throws EngineException { org.eclipse.jetty.ee10.servlet.ServletContextHandler handler = endpoint.getServletContextHandler(); handler.getServletHandler().setDecodeAmbiguousURIs(true); deployHandler(handler, endpoint.getEndpointDescription().getName()); deployedEndpoints.add(endpoint); } @Override public synchronized void deployHandler(ServletContextHandler handler, String endpointId) throws EngineException { String contextPath = handler.getContextPath(); if (usedContextPaths.containsKey(contextPath)) { throw new WrongArgumentException("There are (at least) two web " + "applications configured at the same context path: " + contextPath); } addDoSFilter(handler); addCORSFilter(handler); ClientIPSettingHandler clientIPSettingHandler = applyClientIPDiscoveryHandler(handler, endpointId); mainContextHandler.addHandler(clientIPSettingHandler); if(theServer.isStarted()) { try { clientIPSettingHandler.start(); } catch (Exception e) { mainContextHandler.removeHandler(clientIPSettingHandler); throw new EngineException("Can not start handler", e); } } usedContextPaths.put(contextPath, clientIPSettingHandler); } @Override public synchronized void undeployAllHandlers() throws EngineException { for (org.eclipse.jetty.server.Handler handler : usedContextPaths.values()) { try { handler.stop(); } catch (Exception e) { throw new EngineException("Can not stop handler", e); } } usedContextPaths.clear(); for (org.eclipse.jetty.server.Handler handler : List.copyOf(mainContextHandler.getHandlers())) { mainContextHandler.removeHandler(handler); } } @Override public synchronized void undeployHandler(String contextPath) throws EngineException { org.eclipse.jetty.server.Handler handler = usedContextPaths.get(contextPath); try { handler.stop(); } catch (Exception e) { throw new EngineException("Can not stop handler", e); } mainContextHandler.removeHandler(handler); usedContextPaths.remove(contextPath); } @Override public synchronized void undeployEndpoint(String id) throws EngineException { if(deployedEndpoints.stream().anyMatch(endp -> endp.getEndpointDescription().getName().equals(id))) undeployEE10Endpoint(id); else throw new WrongArgumentException("There is no deployed endpoint with id " + id); } public void undeployEE10Endpoint(String id) throws EngineException { WebAppEndpointInstance endpoint = null; for (WebAppEndpointInstance endp: deployedEndpoints) { if (endp.getEndpointDescription().getName().equals(id)) { endpoint = endp; break; } } if (endpoint == null) throw new WrongArgumentException("There is no deployed endpoint with id " + id); String contextPath = endpoint.getEndpointDescription().getEndpoint().getContextAddress(); org.eclipse.jetty.server.Handler handler = usedContextPaths.get(contextPath); try { handler.stop(); } catch (Exception e) { throw new EngineException("Can not stop handler", e); } mainContextHandler.removeHandler(handler); usedContextPaths.remove(contextPath); deployedEndpoints.remove(endpoint); } @Override public Set getUsedContextPaths() { return usedContextPaths.keySet(); } private org.eclipse.jetty.ee10.servlet.FilterHolder createDoSFilterInstance() { if (!serverSettings.getBooleanValue(UnityHttpServerConfiguration.ENABLE_DOS_FILTER)) return null; org.eclipse.jetty.ee10.servlet.FilterHolder holder = new org.eclipse.jetty.ee10.servlet.FilterHolder(new org.eclipse.jetty.ee10.servlets.DoSFilter()); Set keys = serverSettings.getSortedStringKeys(UnityHttpServerConfiguration.DOS_FILTER_PFX); for (String key: keys) holder.setInitParameter(key.substring( UnityHttpServerConfiguration.PREFIX.length() + UnityHttpServerConfiguration.DOS_FILTER_PFX.length()), serverSettings.getProperty(key)); return holder; } private void addDoSFilter(ServletContextHandler handler) { if (dosFilter != null) { log.info("Enabling DoS filter on context " + handler.getContextPath()); handler.addFilter(dosFilter, "/*", EnumSet.of(jakarta.servlet.DispatcherType.REQUEST)); } } private void addCORSFilter(ServletContextHandler handler) { boolean enable = serverSettings.getBooleanValue(UnityHttpServerConfiguration.ENABLE_CORS); if (!enable) return; log.info("Enabling CORS"); CrossOriginFilter cors = new CrossOriginFilter(); jakarta.servlet.FilterConfig config = new jakarta.servlet.FilterConfig() { @Override public jakarta.servlet.ServletContext getServletContext() { throw new UnsupportedOperationException("Not implemented"); } @Override public Enumeration getInitParameterNames() { throw new UnsupportedOperationException("Not implemented"); } @Override public String getInitParameter(String name) { return serverSettings.getValue(UnityHttpServerConfiguration.CORS_PFX + name); } @Override public String getFilterName() { return "CORS"; } }; try { cors.init(config); } catch (ServletException e) { throw new ConfigurationException("Error setting up CORS", e); } org.eclipse.jetty.ee10.servlet.FilterHolder filterHolder = new org.eclipse.jetty.ee10.servlet.FilterHolder(cors); handler.addFilter(filterHolder, "/*", EnumSet.of(jakarta.servlet.DispatcherType.REQUEST, jakarta.servlet.DispatcherType.FORWARD)); } private ClientIPSettingHandler applyClientIPDiscoveryHandler(Handler baseHandler, String endpointId) { ClientIPDiscovery ipDiscovery = new ClientIPDiscovery(serverSettings.getIntValue(PROXY_COUNT), serverSettings.getBooleanValue(ALLOW_NOT_PROXIED_TRAFFIC)); IPValidator ipValidator = new IPValidator( serverSettings.getListOfValues(ALLOWED_IMMEDIATE_CLIENTS)); log.info("Enabling client IP discovery filter"); ClientIPSettingHandler handler = new ClientIPSettingHandler(ipDiscovery, ipValidator, endpointId); handler.setServer(theServer); handler.setHandler(baseHandler); return handler; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy