/******************************************************************************* * Copyright © 2012-2015 eBay Software Foundation * This program is dual licensed under the MIT and Apache 2.0 licenses. * Please see LICENSE for more information. *******************************************************************************/ /** * */ package com.ebay.jetstream.servlet; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.channels.ByteChannel; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.mortbay.io.Buffer; import org.mortbay.io.nio.ChannelEndPoint; import org.mortbay.jetty.Connector; import org.mortbay.jetty.HttpConnection; import org.mortbay.jetty.HttpSchemes; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.ContextHandlerCollection; import org.mortbay.jetty.nio.AbstractNIOConnector; import org.mortbay.jetty.security.SslSocketConnector; import org.mortbay.jetty.servlet.Context; import org.mortbay.jetty.servlet.ServletHolder; import org.mortbay.jetty.webapp.WebAppContext; import org.mortbay.thread.BoundedThreadPool; import org.mortbay.thread.QueuedThreadPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextStartedEvent; import org.springframework.context.event.ContextStoppedEvent; import com.ebay.jetstream.common.ShutDownable; import com.ebay.jetstream.config.ConfigUtils; import com.ebay.jetstream.servlet.SSLServerHost; import com.ebay.jetstream.servlet.ServletDefinition; @SuppressWarnings("deprecation") public class JettyServer implements ApplicationListener, InitializingBean, ShutDownable { static class SimpleNIOConnector extends AbstractNIOConnector { private transient ServerSocketChannel m_acceptChannel; private final InetAddress m_bindAddress; public SimpleNIOConnector(InetAddress address, int port) { m_bindAddress = address; setHost(m_bindAddress.getHostName()); setPort(port); setUseDirectBuffers(false); } @Override protected void accept(int acceptorID) throws IOException, InterruptedException { SocketChannel channel = m_acceptChannel.accept(); channel.configureBlocking(true); Socket socket = channel.socket(); configure(socket); SimpleNIOHttpEndPoint endpoint = new SimpleNIOHttpEndPoint(this, channel); endpoint.dispatch(); } public void close() throws IOException { if (m_acceptChannel != null) { m_acceptChannel.close(); } m_acceptChannel = null; } @Override protected void connectionClosed(HttpConnection connection) { super.connectionClosed(connection); } @Override protected void connectionOpened(HttpConnection connection) { super.connectionOpened(connection); } public Object getConnection() { return m_acceptChannel; } public int getLocalPort() { return m_acceptChannel == null || !m_acceptChannel.isOpen() ? -1 : m_acceptChannel.socket().getLocalPort(); } public void open() throws IOException { // Create a new server socket and set to blocking mode as for now m_acceptChannel = ServerSocketChannel.open(); m_acceptChannel.configureBlocking(true); // Bind the server socket to the address and port InetSocketAddress addr = new InetSocketAddress(m_bindAddress, getPort()); setAcceptQueueSize(100); setIntegralScheme(HttpSchemes.HTTP); setConfidentialScheme(HttpSchemes.HTTP); m_acceptChannel.socket().bind(addr, getAcceptQueueSize()); } } static class SimpleNIOHttpEndPoint extends ChannelEndPoint implements Runnable { private int m_sotimeout; private final HttpConnection m_connection; private final SimpleNIOConnector m_connector; public SimpleNIOHttpEndPoint(SimpleNIOConnector connector, ByteChannel channel) { super(channel); m_connector = connector; m_connection = new HttpConnection(connector, this, connector.getServer()); } protected void connectionClosed() { m_connector.connectionClosed(m_connection); } protected void connectionOpened() { m_connector.connectionOpened(m_connection); } public void dispatch() throws IOException { if (!m_connector.getThreadPool().dispatch(this)) { LOGGER.warn( "dispatch failed for " + m_connection.toString()); close(); } } @Override public int fill(Buffer buffer) throws IOException { int len = super.fill(buffer); if (len < 0) getChannel().close(); return len; } public void run() { try { connectionOpened(); while (isOpen()) { if (m_connection.isIdle()) { if (m_connector.getServer().getThreadPool().isLowOnThreads()) { if (m_sotimeout != m_connector.getLowResourceMaxIdleTime()) { m_sotimeout = m_connector.getLowResourceMaxIdleTime(); ((SocketChannel) getChannel()).socket().setSoTimeout(m_sotimeout); } } } // Processing Http Request m_connection.handle(); } } catch (Exception e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Failed", e); } try { close(); } catch (IOException e1) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "closing", e1); } } } finally { connectionClosed(); } } } private final String LOGGING_COMPONENT = this.getClass().getSimpleName(); /** * Wanted to name it SSLHost but the spring init order was loading this after the servlet def (which infact required * this host to be loaded early on) */ private SSLServerHost m_httpsHost; private static final Logger LOGGER = LoggerFactory.getLogger(JettyServer.class.getName()); private boolean m_lazy = true; private boolean m_immediateStart = false; private InetAddress m_address; private int m_port; private int m_minThreadPoolSize = 3; private int m_maxThreadPoolSize = 9; private boolean m_boundedThreadPool = true; private String m_contextPath = "/"; private String m_resourceBase; private final List<ServletDefinition> m_servletDefinitions = new ArrayList<ServletDefinition>(); private Server m_server; private ContextHandlerCollection m_contexts; private Context m_context; private String m_webAppcontextPath; public String getWebAppcontextPath() { return m_webAppcontextPath; } public void setWebAppcontextPath(String m_webAppcontextPath) { this.m_webAppcontextPath = m_webAppcontextPath; } public void afterPropertiesSet() throws Exception { initServletDefinitions(); if (isImmediateStart()){ start(); m_context.start(); } } public InetAddress getAddress() { return m_address; } protected Context getContext() { if (getWebAppcontextPath() != null) { m_context = new WebAppContext( null,ConfigUtils .getInitialPropertyExpanded(getContextPath())); m_context.setResourceBase(getResourceBase()); getContexts().addHandler(m_context); getServer().setHandler(getContexts()); } else if (m_context == null) { getServer().setHandler(getContexts()); m_context = new Context(m_contexts, getContextPath(), Context.SESSIONS); if (getResourceBase() != null) { m_context.setResourceBase(getResourceBase()); } } return m_context; } public String getContextPath() { return m_contextPath; } protected ContextHandlerCollection getContexts() { if (m_contexts == null) m_contexts = new ContextHandlerCollection(); return m_contexts; } public int getPendingEvents() { // TODO Auto-generated method stub return 0; } public SSLServerHost getHttpsHost() { return m_httpsHost; } public int getMaxThreadPoolSize() { return m_maxThreadPoolSize; } public int getMinThreadPoolSize() { return m_minThreadPoolSize; } public int getPort() { return m_port; } public String getResourceBase() { return m_resourceBase; } public Server getServer() { if (m_server == null) { /* * try { m_address = InetAddress.getLocalHost(); // temporary to testSimpleNIOConnector } catch * (UnknownHostException e) { * * e.printStackTrace(); } */ InetAddress addr = getAddress(); int port = getPort(); if (addr == null) m_server = new Server(port); else m_server = new Server(); if (getHttpsHost() != null) m_server.setConnectors(new Connector[] { getSSLConnector() }); else if (addr != null) { LOGGER.warn( "SSL Host NOT Configured. Using Http"); m_server.setConnectors(new Connector[] { new SimpleNIOConnector(addr, port) }); } if (isBoundedThreadPool()) { BoundedThreadPool threadPool = new BoundedThreadPool(); threadPool.setMinThreads(getMinThreadPoolSize()); threadPool.setMaxThreads(getMaxThreadPoolSize()); m_server.setThreadPool(threadPool); } else { QueuedThreadPool threadPool = new QueuedThreadPool(); threadPool.setMinThreads(getMinThreadPoolSize()); threadPool.setMaxThreads(getMaxThreadPoolSize()); threadPool.setSpawnOrShrinkAt(getMinThreadPoolSize()); m_server.setThreadPool(threadPool); } } return m_server; } /** * @return the servletDefinitions */ public List<ServletDefinition> getServletDefinitions() { return m_servletDefinitions; } private Connector getSSLConnector() { SslSocketConnector sslConnector = new SslSocketConnector(); sslConnector.setPort(getPort()); sslConnector.setKeyPassword(getHttpsHost().getKeyStorePassword()); sslConnector.setKeystore(getHttpsHost().getKeyStorePath()); sslConnector.setTruststore(getHttpsHost().getTrustStorePath()); sslConnector.setTrustPassword(getHttpsHost().getTrustStorePassword()); return sslConnector; } /** * @param servletDefinitions * the servletDefinitions to set * @throws ClassNotFoundException */ private void initServletDefinitions() { try { Context context = getContext(); int order = isLazy() ? -1 : 0; for (ServletDefinition sd : m_servletDefinitions) { ServletHolder holder = new ServletHolder(sd.getServletClass()); if (order >= 0) order++; holder.setInitOrder(order); Map<String, String> initParams = sd.getInitParams(); if (initParams != null) holder.setInitParameters(initParams); context.addServlet(holder, sd.getUrlPath()); } } catch (Exception e) { LOGGER.error( e.getMessage(), e); } } public boolean isBoundedThreadPool() { return m_boundedThreadPool; } public boolean isImmediateStart() { return m_immediateStart; } public boolean isLazy() { return m_lazy; } public void join() { try { getServer().join(); } catch (InterruptedException e) { LOGGER.warn( e.getMessage()); } } public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ContextStartedEvent && !isImmediateStart()) { try { start(); } catch (Exception e) { LOGGER.error( e.getLocalizedMessage(), e); } } else if (event instanceof ContextClosedEvent || event instanceof ContextStoppedEvent) { stop(); } } public void setAddress(InetAddress address) { m_address = address; } public void setBoundedThreadPool(boolean boundedThreadPool) { m_boundedThreadPool = boundedThreadPool; } public void setContextPath(String contextPath) { m_contextPath = contextPath; } public void setHttpsHost(SSLServerHost httpsHost) { m_httpsHost = httpsHost; } public void setImmediateStart(boolean earlyStart) { m_immediateStart = earlyStart; } public void setLazy(boolean lazy) { m_lazy = lazy; } public void setMaxThreadPoolSize(int maxThreadPoolSize) { m_maxThreadPoolSize = maxThreadPoolSize; } public void setMinThreadPoolSize(int minThreadPoolSize) { m_minThreadPoolSize = minThreadPoolSize; } public void setPort(int httpListenerPort) { m_port = httpListenerPort; } public void setResourceBase(String resourceBase) { m_resourceBase = ConfigUtils.getInitialPropertyExpanded(resourceBase); } public void setServletDefinitions(List<ServletDefinition> servletDefinitions) { m_servletDefinitions.clear(); m_servletDefinitions.addAll(servletDefinitions); } public void shutDown() { stop(); } public void start() throws Exception { try { LOGGER.warn( "Starting Jetty Server."); getServer().start(); } catch (Exception e) { LOGGER.error( "Failed to start Jetty: " + e); throw e; } } public void stop() { try { getServer().stop(); } catch (Exception e) { LOGGER.error( "Failed to stop Jetty: " + e); } } }