/* Copyright (c) 2011 Danish Maritime Authority.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.maritimecloud.mms.server;
import net.maritimecloud.mms.server.connection.client.DefaultTransportListener;
import net.maritimecloud.mms.server.connection.transport.ServerTransportJsr356Endpoint;
import net.maritimecloud.mms.server.rest.*;
import net.maritimecloud.mms.server.security.MmsSecurityManager;
import org.cakeframework.container.ServiceManager;
import org.cakeframework.container.lifecycle.RunOnStart;
import org.cakeframework.container.lifecycle.RunOnStop;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.glassfish.jersey.CommonProperties;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Builder;
import java.lang.management.ManagementFactory;
import static java.util.Objects.requireNonNull;
/**
* Configures a web server with a WebSocket interface used for the MMS transport layer
* and a REST interface used for management of the MMS server.
*
* @author Kasper Nielsen
*/
public class WebServer {
/** The logger. */
static final Logger LOG = LoggerFactory.getLogger(WebServer.class);
/** The actual WebSocket server */
private final Server server;
final MmsServer is;
final DefaultTransportListener defaultTransport;
final ServerEventListener eventListener;
/**
* Constructor
*
* @param listener the server event listener
* @param defaultTransport the transport listener
* @param configuration the MMS configuration
* @param securityManager the security manager
* @param is the MMS server
*/
public WebServer(ServerEventListener listener, DefaultTransportListener defaultTransport,
MmsServerConfiguration configuration, MmsSecurityManager securityManager, MmsServer is) {
this.eventListener = requireNonNull(listener);
this.defaultTransport = requireNonNull(defaultTransport);
this.is = requireNonNull(is);
this.server = new Server();
// Configure HTTP and HTTPS of the server based on the configuration
configureServer(this.server, configuration, securityManager);
}
/**
* Called when the MMS server starts up
* @param sm the service manager
*/
@RunOnStart
public void start(ServiceManager sm) throws Exception {
MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbContainer);
// New handler
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
// Configure the WebSocket interface
configureWebSockets(context);
// Configure the REST interface
configureRest(sm, context);
server.start();
LOG.info("System is ready accept client connections");
}
/**
* Called when the MMS server terminates
*/
@RunOnStop
public void stop() throws Exception {
server.stop();
}
/**
* Configures the HTTP (ws) and HTTPS (wss) transport of the server
*
* @param server the server to configure
* @param configuration the configuration to use
* @param securityManager the security manager
*/
private void configureServer(Server server, MmsServerConfiguration configuration, MmsSecurityManager securityManager) {
// Configure HTTP
if (configuration.getServerPort() != null) {
ServerConnector connector = new ServerConnector(server);
connector.setPort(configuration.getServerPort());
connector.setReuseAddress(true);
server.addConnector(connector);
}
// Configure HTTPS
if (configuration.getSecurePort() != null) {
HttpConfiguration http_config = new HttpConfiguration();
http_config.setSecureScheme("https");
http_config.setSecurePort(configuration.getSecurePort());
http_config.setOutputBufferSize(32768);
// SSL Context Factory for HTTPS
SslContextFactory sslContextFactory = securityManager.getSslContextFactory();
// HTTPS Configuration
HttpConfiguration https_config = new HttpConfiguration(http_config);
https_config.addCustomizer(new SecureRequestCustomizer());
// HTTPS connector
ServerConnector https = new ServerConnector(server, new SslConnectionFactory(sslContextFactory,
HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(https_config));
https.setPort(configuration.getSecurePort());
https.setIdleTimeout(500000);
server.addConnector(https);
}
}
/**
* Configures the WebSocket interface
*
* @param context the servlet context
*/
private void configureWebSockets(ServletContextHandler context) throws DeploymentException, ServletException {
// Enable javax.websocket configuration for the context
ServerContainer wsContainer = WebSocketServerContainerInitializer.configureContext(context);
wsContainer.setDefaultMaxTextMessageBufferSize(10 * 1024 * 1024);
// Add our default endpoint.
Builder b = ServerEndpointConfig.Builder.create(ServerTransportJsr356Endpoint.class, "/");
b.configurator(new ServerEndpointConfig.Configurator() {
@SuppressWarnings("unchecked")
public <S> S getEndpointInstance(Class<S> endpointClass) throws InstantiationException {
return (S) is.getService(ServiceManager.class).inject(ServerTransportJsr356Endpoint.class);
}
});
wsContainer.addEndpoint(b.build());
}
/**
* Configures the REST interface
*
* @param sm the service manager
* @param context the servlet context
*/
private void configureRest(ServiceManager sm, ServletContextHandler context) {
final ResourceConfig config = new ResourceConfig();
// Register the REST endpoints
config.register(sm.inject(JSONMessageBodyWriter.class));
config.register(sm.inject(EndpointInvoke.class));
config.register(sm.inject(ClientResource.class));
config.register(sm.inject(MetricsResource.class));
config.register(sm.inject(JSONMetricRegistryBodyWriter.class));
config.register(sm.inject(DmaExceptionMapper.class));
ServletHolder sho = new ServletHolder(new ServletContainer(config));
// sho.setClassName("org.glassfish.jersey.servlet.ServletContainer");
// This flag is set to disable internal buffering in jersey.
// this is mainly done to avoid delays from when people request something. To the first output is delivered
sho.setInitParameter(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, "-1");
context.addServlet(sho, "/*");
}
}