/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2013-2016 ForgeRock AS. */ package org.opends.server.protocols.http; import static org.forgerock.http.grizzly.GrizzlySupport.newGrizzlyHttpHandler; import static org.opends.messages.ConfigMessages.*; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.io.IOException; import java.net.InetAddress; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import org.forgerock.http.ApiProducer; import org.forgerock.http.DescribedHttpApplication; import org.forgerock.http.Filter; import org.forgerock.http.Handler; import org.forgerock.http.HttpApplicationException; import org.forgerock.http.handler.Handlers; import org.forgerock.http.io.Buffer; import org.forgerock.http.protocol.Request; import org.forgerock.http.protocol.Response; import org.forgerock.http.protocol.Status; import org.forgerock.http.swagger.SwaggerApiProducer; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.config.server.ConfigChangeResult; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.config.server.ConfigurationChangeListener; import org.forgerock.opendj.ldap.DN; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.rest2ldap.ErrorLoggerFilter; import org.forgerock.opendj.server.config.server.ConnectionHandlerCfg; import org.forgerock.opendj.server.config.server.HTTPConnectionHandlerCfg; import org.forgerock.services.context.Context; import org.forgerock.util.promise.NeverThrowsException; import org.forgerock.util.promise.Promise; import org.forgerock.util.promise.PromiseImpl; import org.forgerock.util.time.TimeService; import org.glassfish.grizzly.http.HttpProbe; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.grizzly.http.server.NetworkListener; import org.glassfish.grizzly.http.server.ServerConfiguration; import org.glassfish.grizzly.monitoring.MonitoringConfig; import org.glassfish.grizzly.nio.transport.TCPNIOTransport; import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.grizzly.strategies.SameThreadIOStrategy; import org.glassfish.grizzly.utils.Charsets; import org.opends.server.api.AlertGenerator; import org.opends.server.api.ClientConnection; import org.opends.server.api.ConnectionHandler; import org.opends.server.api.KeyManagerProvider; import org.opends.server.api.ServerShutdownListener; import org.opends.server.api.TrustManagerProvider; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ServerContext; import org.opends.server.extensions.NullKeyManagerProvider; import org.opends.server.extensions.NullTrustManagerProvider; import org.opends.server.loggers.HTTPAccessLogger; import org.opends.server.monitors.ClientConnectionMonitorProvider; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.AbstractOperation; import org.opends.server.types.DirectoryException; import org.opends.server.types.HostPort; import org.opends.server.types.InitializationException; import org.opends.server.types.OperationType; import org.opends.server.util.DynamicConstants; import org.opends.server.util.SelectableCertificateKeyManager; import org.opends.server.util.StaticUtils; import io.swagger.models.Swagger; /** * This class defines a connection handler that will be used for communicating * with clients over HTTP. The connection handler is responsible for * starting/stopping the embedded web server. */ public class HTTPConnectionHandler extends ConnectionHandler implements ConfigurationChangeListener, ServerShutdownListener, AlertGenerator { /** The tracer object for the debug logger. */ private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** Default friendly name for this connection handler. */ private static final String DEFAULT_FRIENDLY_NAME = "HTTP Connection Handler"; /** SSL instance name used in context creation. */ private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS"; /** The initialization configuration. */ private HTTPConnectionHandlerCfg initConfig; /** The current configuration. */ private HTTPConnectionHandlerCfg currentConfig; /** Indicates whether the Directory Server is in the process of shutting down. */ private volatile boolean shutdownRequested; /** Indicates whether this connection handler is enabled. */ private boolean enabled; /** The set of listeners for this connection handler. */ private final List listeners = new LinkedList<>(); /** The HTTP server embedded in OpenDJ. */ private HttpServer httpServer; /** The HTTP probe that collects stats. */ private HTTPStatsProbe httpProbe; /** * Holds the current client connections. Using {@link ConcurrentHashMap} to * ensure no concurrent reads/writes can happen and adds/removes are fast. We * only use the keys, so it does not matter what value is put there. */ private final Map clientConnections = new ConcurrentHashMap<>(); /** The set of statistics collected for this connection handler. */ private HTTPStatistics statTracker; /** The client connection monitor provider associated with this connection handler. */ private ClientConnectionMonitorProvider connMonitor; /** The unique name assigned to this connection handler. */ private String handlerName; /** The protocol used by this connection handler. */ private String protocol; /** * The condition variable that will be used by the start method to wait for * the socket port to be opened and ready to process requests before returning. */ private final Object waitListen = new Object(); /** The friendly name of this connection handler. */ private String friendlyName; /** The SSL engine configurator is used for obtaining default SSL parameters. */ private SSLEngineConfigurator sslEngineConfigurator; private ServerContext serverContext; /** Default constructor. It is invoked by reflection to create this {@link ConnectionHandler}. */ public HTTPConnectionHandler() { super(DEFAULT_FRIENDLY_NAME); } /** * Registers a client connection to track it. * * @param clientConnection * the client connection to register */ void addClientConnection(ClientConnection clientConnection) { clientConnections.put(clientConnection, clientConnection); } @Override public ConfigChangeResult applyConfigurationChange(HTTPConnectionHandlerCfg config) { final ConfigChangeResult ccr = new ConfigChangeResult(); if (anyChangeRequiresRestart(config)) { ccr.setAdminActionRequired(true); ccr.addMessage(ERR_CONNHANDLER_CONFIG_CHANGES_REQUIRE_RESTART.get("HTTP")); } // Reconfigure SSL if needed. try { configureSSL(config); } catch (DirectoryException e) { logger.traceException(e); ccr.setResultCode(e.getResultCode()); ccr.addMessage(e.getMessageObject()); return ccr; } if (config.isEnabled() && this.currentConfig.isEnabled() && isListening()) { // Server was running and will still be running if the "enabled" was flipped, // leave it to the stop / start server to handle it. if (!this.currentConfig.isKeepStats() && config.isKeepStats()) { // It must now keep stats while it was not previously. setHttpStatsProbe(this.httpServer); } else if (this.currentConfig.isKeepStats() && !config.isKeepStats() && this.httpProbe != null) { // It must NOT keep stats anymore. getHttpConfig(this.httpServer).removeProbes(this.httpProbe); this.httpProbe = null; } } this.initConfig = config; this.currentConfig = config; this.enabled = this.currentConfig.isEnabled(); return ccr; } private boolean anyChangeRequiresRestart(HTTPConnectionHandlerCfg newCfg) { return !equals(newCfg.getListenPort(), initConfig.getListenPort()) || !Objects.equals(newCfg.getListenAddress(), initConfig.getListenAddress()) || !equals(newCfg.getMaxRequestSize(), currentConfig.getMaxRequestSize()) || !equals(newCfg.isAllowTCPReuseAddress(), currentConfig.isAllowTCPReuseAddress()) || !equals(newCfg.isUseTCPKeepAlive(), currentConfig.isUseTCPKeepAlive()) || !equals(newCfg.isUseTCPNoDelay(), currentConfig.isUseTCPNoDelay()) || !equals(newCfg.getMaxBlockedWriteTimeLimit(), currentConfig.getMaxBlockedWriteTimeLimit()) || !equals(newCfg.getBufferSize(), currentConfig.getBufferSize()) || !equals(newCfg.getAcceptBacklog(), currentConfig.getAcceptBacklog()) || !equals(newCfg.isUseSSL(), currentConfig.isUseSSL()) || !Objects.equals(newCfg.getKeyManagerProviderDN(), currentConfig.getKeyManagerProviderDN()) || !Objects.equals(newCfg.getSSLCertNickname(), currentConfig.getSSLCertNickname()) || !Objects.equals(newCfg.getTrustManagerProviderDN(), currentConfig.getTrustManagerProviderDN()) || !Objects.equals(newCfg.getSSLProtocol(), currentConfig.getSSLProtocol()) || !Objects.equals(newCfg.getSSLCipherSuite(), currentConfig.getSSLCipherSuite()) || !Objects.equals(newCfg.getSSLClientAuthPolicy(), currentConfig.getSSLClientAuthPolicy()); } private boolean equals(long l1, long l2) { return l1 == l2; } private boolean equals(boolean b1, boolean b2) { return b1 == b2; } private void configureSSL(HTTPConnectionHandlerCfg config) throws DirectoryException { protocol = config.isUseSSL() ? "HTTPS" : "HTTP"; if (config.isUseSSL()) { sslEngineConfigurator = createSSLEngineConfigurator(config); } else { sslEngineConfigurator = null; } } @Override public void finalizeConnectionHandler(LocalizableMessage finalizeReason) { shutdownRequested = true; // Unregister this as a change listener. currentConfig.removeHTTPChangeListener(this); if (connMonitor != null) { DirectoryServer.deregisterMonitorProvider(connMonitor); } if (statTracker != null) { DirectoryServer.deregisterMonitorProvider(statTracker); } } @Override public Map getAlerts() { Map alerts = new LinkedHashMap<>(); alerts.put(ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, ALERT_DESCRIPTION_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES); return alerts; } @Override public String getClassName() { return HTTPConnectionHandler.class.getName(); } @Override public Collection getClientConnections() { return clientConnections.keySet(); } @Override public DN getComponentEntryDN() { return currentConfig.dn(); } @Override public String getConnectionHandlerName() { return handlerName; } /** * Returns the current config of this connection handler. * * @return the current config of this connection handler */ HTTPConnectionHandlerCfg getCurrentConfig() { return this.currentConfig; } @Override public Collection getEnabledSSLCipherSuites() { final SSLEngineConfigurator configurator = sslEngineConfigurator; if (configurator != null) { return Arrays.asList(configurator.getEnabledCipherSuites()); } return super.getEnabledSSLCipherSuites(); } @Override public Collection getEnabledSSLProtocols() { final SSLEngineConfigurator configurator = sslEngineConfigurator; if (configurator != null) { return Arrays.asList(configurator.getEnabledProtocols()); } return super.getEnabledSSLProtocols(); } @Override public Collection getListeners() { return listeners; } /** * Returns the listen port for this connection handler. * * @return the listen port for this connection handler. */ int getListenPort() { return this.initConfig.getListenPort(); } @Override public String getProtocol() { return protocol; } @Override public String getShutdownListenerName() { return handlerName; } /** * Retrieves the set of statistics maintained by this connection handler. * * @return The set of statistics maintained by this connection handler. */ public HTTPStatistics getStatTracker() { return statTracker; } @Override public void initializeConnectionHandler(ServerContext serverContext, HTTPConnectionHandlerCfg config) throws ConfigException, InitializationException { this.serverContext = serverContext; this.enabled = config.isEnabled(); if (friendlyName == null) { friendlyName = config.name(); } int listenPort = config.getListenPort(); for (InetAddress a : config.getListenAddress()) { listeners.add(new HostPort(a.getHostAddress(), listenPort)); } handlerName = getHandlerName(config); // Configure SSL if needed. try { // This call may disable the connector if wrong SSL settings configureSSL(config); } catch (DirectoryException e) { logger.traceException(e); throw new InitializationException(e.getMessageObject()); } // Create and register monitors. statTracker = new HTTPStatistics(handlerName + " Statistics"); DirectoryServer.registerMonitorProvider(statTracker); connMonitor = new ClientConnectionMonitorProvider(this); DirectoryServer.registerMonitorProvider(connMonitor); // Register this as a change listener. config.addHTTPChangeListener(this); this.initConfig = config; this.currentConfig = config; } private String getHandlerName(HTTPConnectionHandlerCfg config) { StringBuilder nameBuffer = new StringBuilder(); nameBuffer.append(friendlyName); for (InetAddress a : config.getListenAddress()) { nameBuffer.append(" "); nameBuffer.append(a.getHostAddress()); } nameBuffer.append(" port "); nameBuffer.append(config.getListenPort()); return nameBuffer.toString(); } @Override public boolean isConfigurationAcceptable( ConnectionHandlerCfg configuration, List unacceptableReasons) { HTTPConnectionHandlerCfg config = (HTTPConnectionHandlerCfg) configuration; if (currentConfig == null || (!this.enabled && config.isEnabled())) { // Attempt to bind to the listen port on all configured addresses to // verify whether the connection handler will be able to start. LocalizableMessage errorMessage = checkAnyListenAddressInUse( config.getListenAddress(), config.getListenPort(), config.isAllowTCPReuseAddress(), config.dn()); if (errorMessage != null) { unacceptableReasons.add(errorMessage); return false; } } if (config.isEnabled() && config.isUseSSL()) { try { createSSLEngineConfigurator(config); } catch (DirectoryException e) { logger.traceException(e); unacceptableReasons.add(e.getMessageObject()); return false; } } return true; } /** * Checks whether any listen address is in use for the given port. The check * is performed by binding to each address and port. * * @param listenAddresses * the listen {@link InetAddress} to test * @param listenPort * the listen port to test * @param allowReuseAddress * whether addresses can be reused * @param configEntryDN * the configuration entry DN * @return an error message if at least one of the address is already in use, * null otherwise. */ private LocalizableMessage checkAnyListenAddressInUse( Collection listenAddresses, int listenPort, boolean allowReuseAddress, DN configEntryDN) { for (InetAddress a : listenAddresses) { try { if (isAddressInUse(a, listenPort, allowReuseAddress)) { throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString()); } } catch (IOException e) { logger.traceException(e); return ERR_CONNHANDLER_CANNOT_BIND.get( "HTTP", configEntryDN, a.getHostAddress(), listenPort, getExceptionMessage(e)); } } return null; } @Override public boolean isConfigurationChangeAcceptable( HTTPConnectionHandlerCfg configuration, List unacceptableReasons) { return isConfigurationAcceptable(configuration, unacceptableReasons); } /** * Indicates whether this connection handler should maintain usage statistics. * * @return true if this connection handler should maintain usage * statistics, or false if not. */ public boolean keepStats() { return currentConfig.isKeepStats(); } @Override public void processServerShutdown(LocalizableMessage reason) { shutdownRequested = true; } private boolean isListening() { return httpServer != null; } @Override public void start() { // The Directory Server start process should only return when the connection handlers port // are fully opened and working. // The start method therefore needs to wait for the created thread too. synchronized (waitListen) { super.start(); try { waitListen.wait(); } catch (InterruptedException e) { // If something interrupted the start its probably better to return ASAP } } } /** * Unregisters a client connection to stop tracking it. * * @param clientConnection * the client connection to unregister */ void removeClientConnection(ClientConnection clientConnection) { clientConnections.remove(clientConnection); } @Override public void run() { setName(handlerName); boolean lastIterationFailed = false; boolean starting = true; while (!shutdownRequested) { // If this connection handler is not enabled, then just sleep for a bit and check again. if (!this.enabled) { if (isListening()) { stopHttpServer(); } if (starting) { // This may happen if there was an initialisation error which led to disable the connector. // The main thread is waiting for the connector to listen on its port, which will not occur yet, // so notify here to allow the server startup to complete. synchronized (waitListen) { starting = false; waitListen.notify(); } } StaticUtils.sleep(1000); continue; } if (isListening()) { // If already listening, then sleep for a bit and check again. StaticUtils.sleep(1000); continue; } try { // At this point, the connection Handler either started correctly or failed // to start but the start process should be notified and resume its work in any cases. synchronized (waitListen) { waitListen.notify(); } // If we have gotten here, then we are about to start listening // for the first time since startup or since we were previously disabled. // Start the embedded HTTP server startHttpServer(); lastIterationFailed = false; } catch (Exception e) { // Clean up the messed up HTTP server cleanUpHttpServer(); // Error + alert about the horked config logger.traceException(e); logger.error( ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION, friendlyName, currentConfig.dn(), getExceptionMessage(e)); if (lastIterationFailed) { // The last time through the accept loop we also encountered a failure. // Rather than enter a potential infinite loop of failures, // disable this acceptor and log an error. LocalizableMessage message = ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get( friendlyName, currentConfig.dn(), stackTraceToSingleLineString(e)); logger.error(message); DirectoryServer.sendAlertNotification(this, ALERT_TYPE_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message); this.enabled = false; } else { lastIterationFailed = true; } } } // Initiate shutdown stopHttpServer(); } private void startHttpServer() throws Exception { if (HTTPAccessLogger.getHTTPAccessLogPublishers().isEmpty()) { logger.warn(WARN_CONFIG_LOGGER_NO_ACTIVE_HTTP_ACCESS_LOGGERS); } this.httpServer = createHttpServer(); this.httpServer.getServerConfiguration().addHttpHandler(newGrizzlyHttpHandler(new RootHttpApplication())); logger.trace("Starting HTTP server..."); this.httpServer.start(); logger.trace("HTTP server started"); logger.info(NOTE_CONNHANDLER_STARTED_LISTENING, handlerName); } private HttpServer createHttpServer() { final HttpServer server = new HttpServer(); final int requestSize = (int) currentConfig.getMaxRequestSize(); final ServerConfiguration serverConfig = server.getServerConfiguration(); serverConfig.setMaxBufferedPostSize(requestSize); serverConfig.setMaxFormPostSize(requestSize); serverConfig.setDefaultQueryEncoding(Charsets.UTF8_CHARSET); if (keepStats()) { setHttpStatsProbe(server); } // Configure the network listener final NetworkListener listener = new NetworkListener( "OpenDJ-HTTP", NetworkListener.DEFAULT_NETWORK_HOST, initConfig.getListenPort()); server.addListener(listener); // Configure the network transport final TCPNIOTransport transport = listener.getTransport(); transport.setReuseAddress(currentConfig.isAllowTCPReuseAddress()); transport.setKeepAlive(currentConfig.isUseTCPKeepAlive()); transport.setTcpNoDelay(currentConfig.isUseTCPNoDelay()); transport.setWriteTimeout(currentConfig.getMaxBlockedWriteTimeLimit(), TimeUnit.MILLISECONDS); final int bufferSize = (int) currentConfig.getBufferSize(); transport.setReadBufferSize(bufferSize); transport.setWriteBufferSize(bufferSize); transport.setIOStrategy(SameThreadIOStrategy.getInstance()); final int numRequestHandlers = getNumRequestHandlers(currentConfig.getNumRequestHandlers(), friendlyName); transport.setSelectorRunnersCount(numRequestHandlers); transport.setServerConnectionBackLog(currentConfig.getAcceptBacklog()); // Configure SSL if (sslEngineConfigurator != null) { listener.setSecure(true); listener.setSSLEngineConfig(sslEngineConfigurator); } return server; } private void setHttpStatsProbe(HttpServer server) { this.httpProbe = new HTTPStatsProbe(this.statTracker); getHttpConfig(server).addProbes(this.httpProbe); } private MonitoringConfig getHttpConfig(HttpServer server) { return server.getServerConfiguration().getMonitoringConfig().getHttpConfig(); } private void stopHttpServer() { if (this.httpServer != null) { logger.trace("Stopping HTTP server..."); this.httpServer.shutdownNow(); cleanUpHttpServer(); logger.trace("HTTP server stopped"); logger.info(NOTE_CONNHANDLER_STOPPED_LISTENING, handlerName); } } private void cleanUpHttpServer() { this.httpServer = null; this.httpProbe = null; } @Override public void toString(StringBuilder buffer) { buffer.append(handlerName); } private SSLEngineConfigurator createSSLEngineConfigurator(HTTPConnectionHandlerCfg config) throws DirectoryException { if (!config.isUseSSL()) { return null; } try { SSLContext sslContext = createSSLContext(config); SSLEngineConfigurator configurator = new SSLEngineConfigurator(sslContext); configurator.setClientMode(false); // configure with defaults from the JVM final SSLEngine defaults = sslContext.createSSLEngine(); configurator.setEnabledProtocols(defaults.getEnabledProtocols()); configurator.setEnabledCipherSuites(defaults.getEnabledCipherSuites()); final Set protocols = config.getSSLProtocol(); if (!protocols.isEmpty()) { configurator.setEnabledProtocols(protocols.toArray(new String[protocols.size()])); } final Set ciphers = config.getSSLCipherSuite(); if (!ciphers.isEmpty()) { configurator.setEnabledCipherSuites(ciphers.toArray(new String[ciphers.size()])); } switch (config.getSSLClientAuthPolicy()) { case DISABLED: configurator.setNeedClientAuth(false); configurator.setWantClientAuth(false); break; case REQUIRED: configurator.setNeedClientAuth(true); configurator.setWantClientAuth(true); break; case OPTIONAL: default: configurator.setNeedClientAuth(false); configurator.setWantClientAuth(true); break; } return configurator; } catch (Exception e) { logger.traceException(e); ResultCode resCode = DirectoryServer.getServerErrorResultCode(); throw new DirectoryException(resCode, ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.get(getExceptionMessage(e)), e); } } private SSLContext createSSLContext(HTTPConnectionHandlerCfg config) throws Exception { if (!config.isUseSSL()) { return null; } DN keyMgrDN = config.getKeyManagerProviderDN(); KeyManagerProvider keyManagerProvider = DirectoryServer.getKeyManagerProvider(keyMgrDN); if (keyManagerProvider == null) { logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName); logger.warn(INFO_DISABLE_CONNECTION, friendlyName); keyManagerProvider = new NullKeyManagerProvider(); enabled = false; } else if (!keyManagerProvider.containsAtLeastOneKey()) { logger.error(ERR_INVALID_KEYSTORE, friendlyName); logger.warn(INFO_DISABLE_CONNECTION, friendlyName); enabled = false; } final SortedSet aliases = new TreeSet<>(config.getSSLCertNickname()); final KeyManager[] keyManagers; if (aliases.isEmpty()) { keyManagers = keyManagerProvider.getKeyManagers(); } else { final Iterator it = aliases.iterator(); while (it.hasNext()) { if (!keyManagerProvider.containsKeyWithAlias(it.next())) { logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName); it.remove(); } } if (aliases.isEmpty()) { logger.warn(INFO_DISABLE_CONNECTION, friendlyName); enabled = false; } keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases, friendlyName); } DN trustMgrDN = config.getTrustManagerProviderDN(); TrustManagerProvider trustManagerProvider = DirectoryServer.getTrustManagerProvider(trustMgrDN); if (trustManagerProvider == null) { trustManagerProvider = new NullTrustManagerProvider(); } SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME); sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(), null); return sslContext; } /** * This is the root {@link org.forgerock.http.HttpApplication} handling all the requests from the * {@link HTTPConnectionHandler}. If accepted, requests are audited and then forwarded to the * global {@link ServerContext#getHTTPRouter()}. */ private final class RootHttpApplication implements DescribedHttpApplication { @Override public Handler start() throws HttpApplicationException { return Handlers.chainOf( serverContext.getHTTPRouter(), new HttpAccessLogFilter(serverContext), new ErrorLoggerFilter(), new ExecuteInWorkerThreadFilter(), new AllowDenyFilter(currentConfig.getDeniedClient(), currentConfig.getAllowedClient()), new CommonAuditTransactionIdFilter(serverContext), new CommonAuditHttpAccessCheckEnabledFilter(serverContext, new CommonAuditHttpAccessAuditFilter( DynamicConstants.PRODUCT_NAME, serverContext.getCommonAudit().getAuditServiceForHttpAccessLog(), TimeService.SYSTEM)), new LDAPContextInjectionFilter(serverContext, HTTPConnectionHandler.this)); } @Override public void stop() { // Nothing to do } @Override public org.forgerock.util.Factory getBufferFactory() { return null; } @Override public ApiProducer getApiProducer() { // Needed to enforce generation of CREST APIs return new SwaggerApiProducer(null); } } /** Moves the processing of the request in this Directory Server's worker thread. */ private static final class ExecuteInWorkerThreadFilter implements Filter { @Override public Promise filter(final Context context, final Request request, final Handler next) { final PromiseImpl promise = PromiseImpl.create(); try { DirectoryServer.getWorkQueue().submitOperation(new AsyncOperation<>( InternalClientConnection.getRootConnection(), new Runnable() { @Override public void run() { // Trap and forward runtime exceptions. next.handle(context, request).thenOnResult(promise).thenOnRuntimeException(promise); } })); } catch (Exception e) { promise.handleResult(new Response(Status.INTERNAL_SERVER_ERROR).setCause(e)); } return promise; } /** This operation is hack to be able to execute a {@link Runnable} in a Directory Server's worker thread. */ private static final class AsyncOperation extends AbstractOperation { private final Runnable runnable; AsyncOperation(InternalClientConnection icc, Runnable runnable) { super(icc, icc.nextOperationID(), icc.nextMessageID(), Collections. emptyList()); this.setInternalOperation(true); this.runnable = runnable; } @Override public void run() { runnable.run(); } @Override public OperationType getOperationType() { return null; } @Override public List getResponseControls() { return Collections.emptyList(); } @Override public void addResponseControl(org.opends.server.types.Control control) { } @Override public void removeResponseControl(org.opends.server.types.Control control) { } @Override public DN getProxiedAuthorizationDN() { return null; } @Override public void setProxiedAuthorizationDN(DN proxiedAuthorizationDN) { } @Override public void toString(StringBuilder buffer) { buffer.append(AsyncOperation.class.getSimpleName()); } } } }