/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.jetty; import java.io.IOException; import java.net.Socket; import java.util.Arrays; import java.util.concurrent.Executor; import java.util.logging.Level; import javax.servlet.ServletException; import org.eclipse.jetty.io.ArrayByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LowResourceMonitor; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory; import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory; import org.eclipse.jetty.spdy.server.http.PushStrategy; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.ThreadPool; import org.restlet.Server; import org.restlet.ext.jetty.internal.JettyServerCall; /** * Abstract Jetty web server connector. Here is the list of parameters that are * supported. They should be set in the Server's context before it is started: * <table> * <tr> * <th>Parameter name</th> * <th>Value type</th> * <th>Default value</th> * <th>Description</th> * </tr> * <tr> * <td>threadPool.minThreads</td> * <td>int</td> * <td>8</td> * <td>Thread pool minimum threads</td> * </tr> * <tr> * <td>threadPool.maxThreads</td> * <td>int</td> * <td>200</td> * <td>Thread pool max threads</td> * </tr> * <tr> * <td>threadPool.threadsPriority</td> * <td>int</td> * <td>{@link Thread#NORM_PRIORITY}</td> * <td>Thread pool threads priority</td> * </tr> * <tr> * <td>threadPool.idleTimeout</td> * <td>int</td> * <td>60000</td> * <td>Thread pool idle timeout in milliseconds; threads that are idle for * longer than this period may be stopped</td> * </tr> * <tr> * <td>threadPool.stopTimeout</td> * <td>long</td> * <td>5000</td> * <td>Thread pool stop timeout in milliseconds; the maximum time allowed for * the service to shutdown</td> * </tr> * <tr> * <td>connector.acceptors</td> * <td>int</td> * <td>-1</td> * <td>Connector acceptor thread count; when -1, Jetty will default to * {@link Runtime#availableProcessors()} / 2, with a minimum of 1</td> * </tr> * <tr> * <td>connector.selectors</td> * <td>int</td> * <td>-1</td> * <td>Connector selector thread count; when -1, Jetty will default to * {@link Runtime#availableProcessors()}</td> * </tr> * <tr> * <td>connector.acceptQueueSize</td> * <td>int</td> * <td>0</td> * <td>Connector accept queue size; also known as accept backlog</td> * </tr> * <tr> * <td>connector.idleTimeout</td> * <td>int</td> * <td>30000</td> * <td>Connector idle timeout in milliseconds; see * {@link Socket#setSoTimeout(int)}; this value is interpreted as the maximum * time between some progress being made on the connection; so if a single byte * is read or written, then the timeout is reset</td> * </tr> * <tr> * <td>connector.soLingerTime</td> * <td>int</td> * <td>-1</td> * <td>Connector TCP/IP SO linger time in milliseconds; when -1 is disabled; see * {@link Socket#setSoLinger(boolean, int)}</td> * </tr> * <tr> * <td>connector.stopTimeout</td> * <td>long</td> * <td>30000</td> * <td>Connector stop timeout in milliseconds; the maximum time allowed for the * service to shutdown</td> * </tr> * <tr> * <td>http.headerCacheSize</td> * <td>int</td> * <td>512</td> * <td>HTTP header cache size in bytes</td> * </tr> * <tr> * <td>http.requestHeaderSize</td> * <td>int</td> * <td>8*1024</td> * <td>HTTP request header size in bytes; larger headers will allow for more * and/or larger cookies plus larger form content encoded in a URL; however, * larger headers consume more memory and can make a server more vulnerable to * denial of service attacks</td> * </tr> * <tr> * <td>http.responseHeaderSize</td> * <td>int</td> * <td>8*1024</td> * <td>HTTP response header size in bytes; larger headers will allow for more * and/or larger cookies and longer HTTP headers (e.g. for redirection); * however, larger headers will also consume more memory</td> * </tr> * <tr> * <td>http.outputBufferSize</td> * <td>int</td> * <td>32*1024</td> * <td>HTTP output buffer size in bytes; a larger buffer can improve performance * by allowing a content producer to run without blocking, however larger * buffers consume more memory and may induce some latency before a client * starts processing the content</td> * </tr> * <tr> * <td>lowResource.period</td> * <td>int</td> * <td>1000</td> * <td>Low resource monitor period in milliseconds; when 0, low resource * monitoring is disabled</td> * </tr> * <tr> * <td>lowResource.threads</td> * <td>boolean</td> * <td>true</td> * <td>Low resource monitor, whether to check if we're low on threads</td> * </tr> * <tr> * <td>lowResource.maxMemory</td> * <td>int</td> * <td>0</td> * <td>Low resource monitor max memory in bytes; when 0, the check disabled; * memory used is calculated as (totalMemory-freeMemory)</td> * </tr> * <tr> * <td>lowResource.maxConnections</td> * <td>int</td> * <td>0</td> * <td>Low resource monitor max connections; when 0, the check is disabled</td> * </tr> * <tr> * <td>lowResource.idleTimeout</td> * <td>int</td> * <td>1000</td> * <td>Low resource monitor idle timeout in milliseconds; applied to EndPoints * when in the low resources state</td> * </tr> * <tr> * <td>lowResource.stopTimeout</td> * <td>long</td> * <td>30000</td> * <td>Low resource monitor stop timeout in milliseconds; the maximum time * allowed for the service to shutdown</td> * </tr> * <tr> * <td>spdy.version</td> * <td>int</td> * <td>0</td> * <td>SPDY max version; can be 0, 2, or 3; if 0, SPDY is not used. Make sure to * install the NON boot jar for the matching JDK 1.7 version in the JVM boot * classpath in order to make it work.</td> * </tr> * <tr> * <td>spdy.pushStrategy</td> * <td>String</td> * <td>null</td> * <td>SPDY push strategy; can be null or "referrer" (shortcut for * "org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy") or a class name.</td> * </tr> * </table> * * @see <a href="http://www.eclipse.org/jetty/">Jetty home page</a> * @see <a * href="http://www.eclipse.org/jetty/documentation/current/spdy.html">Jetty * SPDY page</a> * @see <a * href="http://www.eclipse.org/jetty/documentation/current/npn-chapter.html">Jetty * NPN configuration page</a> * @author Jerome Louvel * @author Tal Liron */ public abstract class JettyServerHelper extends org.restlet.engine.adapter.HttpServerHelper { /** * Jetty server wrapped by a parent Restlet HTTP server connector. * * @author Jerome Louvel * @author Tal Liron */ private static class WrappedServer extends org.eclipse.jetty.server.Server { private final JettyServerHelper helper; /** * Constructor. * * @param server * The Jetty HTTP server. * @param threadPool * The thread pool. */ public WrappedServer(JettyServerHelper server, ThreadPool threadPool) { super(threadPool); this.helper = server; } /** * Handler method converting a Jetty HttpChannel into a Restlet Call. * * @param channel * The channel to handle. */ @Override public void handle(HttpChannel<?> channel) throws IOException, ServletException { try { helper.handle(new JettyServerCall(helper.getHelped(), channel)); } catch (Throwable e) { channel.getEndPoint().close(); throw new IOException("Restlet exception", e); } } @Override public void handleAsync(HttpChannel<?> channel) throws IOException, ServletException { // TODO: should we handle async differently? try { helper.handle(new JettyServerCall(helper.getHelped(), channel)); } catch (Throwable e) { channel.getEndPoint().close(); throw new IOException("Restlet exception", e); } } } /** The wrapped Jetty server. */ private volatile org.eclipse.jetty.server.Server wrappedServer; /** * Constructor. * * @param server * The server to help. */ public JettyServerHelper(Server server) { super(server); } /** * Creates a Jetty HTTP configuration. * * @return A Jetty HTTP configuration. */ private HttpConfiguration createConfiguration() { final HttpConfiguration configuration = new HttpConfiguration(); configuration.setHeaderCacheSize(getHttpHeaderCacheSize()); configuration.setRequestHeaderSize(getHttpRequestHeaderSize()); configuration.setResponseHeaderSize(getHttpResponseHeaderSize()); configuration.setOutputBufferSize(getHttpOutputBufferSize()); return configuration; } /** * Creates new internal Jetty connection factories. * * @param configuration * The HTTP configuration. * @return New internal Jetty connection factories. */ protected ConnectionFactory[] createConnectionFactories( HttpConfiguration configuration) { HttpConnectionFactory http = new HttpConnectionFactory(configuration); int spdyVersion = getSpdyVersion(); if (spdyVersion == 0) return new ConnectionFactory[] { http }; else { /* * try { SPDYServerConnectionFactory. * checkProtocolNegotiationAvailable(); } catch( Exception e ) { * getLogger().log( Level.WARNING, * "Jetty NPN boot is not available in -Xbootclasspath", e ); return * null; } */ // Push strategy String pushStrategyName = getSpdyPushStrategy(); if (pushStrategyName == null) pushStrategyName = "org.eclipse.jetty.spdy.server.http.PushStrategy$None"; else if ("referrer".equalsIgnoreCase(pushStrategyName)) pushStrategyName = "org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy"; PushStrategy pushStrategy; try { pushStrategy = (PushStrategy) Class.forName(pushStrategyName) .newInstance(); } catch (Exception e) { getLogger().log(Level.WARNING, "Unable to create the Jetty SPDY push strategy", e); return null; } // SDPY connection factories HTTPSPDYServerConnectionFactory spdy3 = spdyVersion == 3 ? new HTTPSPDYServerConnectionFactory( 3, configuration, pushStrategy) : null; HTTPSPDYServerConnectionFactory spdy2 = new HTTPSPDYServerConnectionFactory( 2, configuration, pushStrategy); // NPN connection factory NPNServerConnectionFactory npn; if (spdyVersion == 3) npn = new NPNServerConnectionFactory(spdy3.getProtocol(), spdy2.getProtocol(), http.getProtocol()); else npn = new NPNServerConnectionFactory(spdy2.getProtocol(), http.getProtocol()); npn.setDefaultProtocol(http.getProtocol()); // All factories if (spdyVersion == 3) return new ConnectionFactory[] { npn, spdy3, spdy2, http }; else return new ConnectionFactory[] { npn, spdy2, http }; } } /** * Creates a Jetty connector. * * @param server * The Jetty server. * @return A Jetty connector. */ private Connector createConnector(org.eclipse.jetty.server.Server server) { final HttpConfiguration configuration = createConfiguration(); final ConnectionFactory[] connectionFactories = createConnectionFactories(configuration); final int acceptors = getConnectorAcceptors(); final int selectors = getConnectorSelectors(); final Executor executor = getConnectorExecutor(); final Scheduler scheduler = getConnectorScheduler(); final ByteBufferPool byteBufferPool = getConnectorByteBufferPool(); final ServerConnector connector = new ServerConnector(server, executor, scheduler, byteBufferPool, acceptors, selectors, connectionFactories); final String address = getHelped().getAddress(); if (address != null) connector.setHost(address); connector.setPort(getHelped().getPort()); connector.setAcceptQueueSize(getConnectorAcceptQueueSize()); connector.setIdleTimeout(getConnectorIdleTimeout()); connector.setSoLingerTime(getConnectorSoLingerTime()); connector.setStopTimeout(getConnectorStopTimeout()); return connector; } /** * Creates a Jetty low resource monitor. * * @param server * A Jetty server. * @return A Jetty low resource monitor or null. */ private LowResourceMonitor createLowResourceMonitor( org.eclipse.jetty.server.Server server) { final int period = getLowResourceMonitorPeriod(); if (period > 0) { final LowResourceMonitor lowResourceMonitor = new LowResourceMonitor( server); lowResourceMonitor.setMonitoredConnectors(Arrays.asList(server .getConnectors())); lowResourceMonitor.setPeriod(period); lowResourceMonitor .setMonitorThreads(getLowResourceMonitorThreads()); lowResourceMonitor.setMaxMemory(getLowResourceMonitorMaxMemory()); lowResourceMonitor .setMaxConnections(getLowResourceMonitorMaxConnections()); lowResourceMonitor .setLowResourcesIdleTimeout(getLowResourceMonitorIdleTimeout()); lowResourceMonitor .setStopTimeout(getLowResourceMonitorStopTimeout()); server.addBean(lowResourceMonitor); return lowResourceMonitor; } return null; } /** * Creates a Jetty server. * * @return A Jetty server. */ private org.eclipse.jetty.server.Server createServer() { // Thread pool final ThreadPool threadPool = createThreadPool(); // Server final org.eclipse.jetty.server.Server server = new WrappedServer(this, threadPool); // Connector final Connector connector = createConnector(server); server.addConnector(connector); // Low resource monitor (must be created after connectors have been // added) createLowResourceMonitor(server); return server; } /** * Creates a Jetty thread pool. * * @return A Jetty thread pool. */ private ThreadPool createThreadPool() { final QueuedThreadPool threadPool = new QueuedThreadPool(); threadPool.setMinThreads(getThreadPoolMinThreads()); threadPool.setMaxThreads(getThreadPoolMaxThreads()); threadPool.setThreadsPriority(getThreadPoolThreadsPriority()); threadPool.setIdleTimeout(getThreadPoolIdleTimeout()); threadPool.setStopTimeout(getThreadPoolStopTimeout()); return threadPool; } /** * Connector acceptor thread count. Defaults to -1. When -1, Jetty will * default to {@link Runtime#availableProcessors()} / 2, with a minimum of * 1. * * @return Connector acceptor thread count. */ public int getConnectorAcceptors() { return Integer.parseInt(getHelpedParameters().getFirstValue( "connector.acceptors", "-1")); } /** * Connector accept queue size. Defaults to 0. * <p> * Also known as accept backlog. * * @return Connector accept queue size. */ public int getConnectorAcceptQueueSize() { return Integer.parseInt(getHelpedParameters().getFirstValue( "connector.acceptQueueSize", "0")); } /** * Connector byte buffer pool. Defaults to null. When null, will use a new * {@link ArrayByteBufferPool}. * * @return Connector byte buffer pool or null. */ public ByteBufferPool getConnectorByteBufferPool() { return null; } /** * Connector executor. Defaults to null. When null, will use the server's * thread pool. * * @return Connector executor or null. */ public Executor getConnectorExecutor() { return null; } /** * Connector idle timeout in milliseconds. Defaults to 30000. * <p> * See {@link Socket#setSoTimeout(int)}. * <p> * This value is interpreted as the maximum time between some progress being * made on the connection. So if a single byte is read or written, then the * timeout is reset. * * @return Connector idle timeout. */ public int getConnectorIdleTimeout() { return Integer.parseInt(getHelpedParameters().getFirstValue( "connector.idleTimeout", "30000")); } /** * Connector scheduler. Defaults to null. When null, will use a new * {@link ScheduledExecutorScheduler}. * * @return Connector scheduler or null. */ public Scheduler getConnectorScheduler() { return null; } /** * Connector selector thread count. Defaults to -1. When 0, Jetty will * default to {@link Runtime#availableProcessors()}. * * @return Connector acceptor thread count. */ public int getConnectorSelectors() { return Integer.parseInt(getHelpedParameters().getFirstValue( "connector.selectors", "-1")); } /** * Connector TCP/IP SO linger time in milliseconds. Defaults to -1 * (disabled). * <p> * See {@link Socket#setSoLinger(boolean, int)}. * * @return Connector TCP/IP SO linger time. */ public int getConnectorSoLingerTime() { return Integer.parseInt(getHelpedParameters().getFirstValue( "connector.soLingerTime", "-1")); } /** * Connector stop timeout in milliseconds. Defaults to 30000. * <p> * The maximum time allowed for the service to shutdown. * * @return Connector stop timeout. */ public int getConnectorStopTimeout() { return Integer.parseInt(getHelpedParameters().getFirstValue( "connector.stopTimeout", "30000")); } /** * HTTP header cache size in bytes. Defaults to 512. * * @return HTTP header cache size. */ public int getHttpHeaderCacheSize() { return Integer.parseInt(getHelpedParameters().getFirstValue( "http.headerCacheSize", "512")); } /** * HTTP output buffer size in bytes. Defaults to 32*1024. * <p> * A larger buffer can improve performance by allowing a content producer to * run without blocking, however larger buffers consume more memory and may * induce some latency before a client starts processing the content. * * @return HTTP output buffer size. */ public int getHttpOutputBufferSize() { return Integer.parseInt(getHelpedParameters().getFirstValue( "http.outputBufferSize", "32768")); } /** * HTTP request header size in bytes. Defaults to 8*1024. * <p> * Larger headers will allow for more and/or larger cookies plus larger form * content encoded in a URL. However, larger headers consume more memory and * can make a server more vulnerable to denial of service attacks. * * @return HTTP request header size. */ public int getHttpRequestHeaderSize() { return Integer.parseInt(getHelpedParameters().getFirstValue( "http.requestHeaderSize", "8192")); } /** * HTTP response header size in bytes. Defaults to 8*1024. * <p> * Larger headers will allow for more and/or larger cookies and longer HTTP * headers (e.g. for redirection). However, larger headers will also consume * more memory. * * @return HTTP response header size. */ public int getHttpResponseHeaderSize() { return Integer.parseInt(getHelpedParameters().getFirstValue( "http.responseHeaderSize", "8192")); } /** * Low resource monitor idle timeout in milliseconds. Defaults to 1000. * <p> * Applied to EndPoints when in the low resources state. * * @return Low resource monitor idle timeout. */ public int getLowResourceMonitorIdleTimeout() { return Integer.parseInt(getHelpedParameters().getFirstValue( "lowResource.idleTimeout", "1000")); } /** * Low resource monitor max connections. Defaults to 0. When 0, the check is * disabled. * * @return Low resource monitor max connections. */ public int getLowResourceMonitorMaxConnections() { return Integer.parseInt(getHelpedParameters().getFirstValue( "lowResource.maxConnections", "0")); } /** * Low resource monitor max memory in bytes. Defaults to 0. When 0, the * check disabled. * <p> * Memory used is calculated as (totalMemory-freeMemory). * * @return Low resource monitor max memory. */ public long getLowResourceMonitorMaxMemory() { return Long.parseLong(getHelpedParameters().getFirstValue( "lowResource.maxMemory", "0")); } /** * Low resource monitor period in milliseconds. Defaults to 1000. When 0, * low resource monitoring is disabled. * * @return Low resource monitor period. */ public int getLowResourceMonitorPeriod() { return Integer.parseInt(getHelpedParameters().getFirstValue( "lowResource.period", "1000")); } /** * Low resource monitor stop timeout in milliseconds. Defaults to 30000. * <p> * The maximum time allowed for the service to shutdown. * * @return Low resource monitor stop timeout. */ public long getLowResourceMonitorStopTimeout() { return Long.parseLong(getHelpedParameters().getFirstValue( "lowResource.stopTimeout", "30000")); } /** * Low resource monitor, whether to check if we're low on threads. Defaults * to true. * * @return Low resource monitor threads. */ public boolean getLowResourceMonitorThreads() { return Boolean.parseBoolean(getHelpedParameters().getFirstValue( "lowResource.threads", "true")); } /** * SPDY push strategy. Defaults to null. * <p> * Can be null or "referrer" (shortcut for * "org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy") or a class * name. * * @return SPDY push strategy or null. */ public String getSpdyPushStrategy() { return getHelpedParameters().getFirstValue("spdy.pushStrategy"); } /** * SPDY max version. Defaults to 0. * <p> * Can be 0, 2, or 3. If 0, SPDY is not used. * * @return Low resource monitor stop timeout. */ public int getSpdyVersion() { return Integer.parseInt(getHelpedParameters().getFirstValue( "spdy.version", "0")); } /** * Thread pool idle timeout in milliseconds. Defaults to 60000. * <p> * Threads that are idle for longer than this period may be stopped. * * @return Thread pool idle timeout. */ public int getThreadPoolIdleTimeout() { return Integer.parseInt(getHelpedParameters().getFirstValue( "threadPool.idleTimeout", "60000")); } /** * Thread pool maximum threads. Defaults to 200. * * @return Thread pool maximum threads. */ public int getThreadPoolMaxThreads() { return Integer.parseInt(getHelpedParameters().getFirstValue( "threadPool.maxThreads", "200")); } /** * Thread pool minimum threads. Defaults to 8. * * @return Thread pool minimum threads. */ public int getThreadPoolMinThreads() { return Integer.parseInt(getHelpedParameters().getFirstValue( "threadPool.minThreads", "8")); } /** * Thread pool stop timeout in milliseconds. Defaults to 5000. * <p> * The maximum time allowed for the service to shutdown. * * @return Thread pool stop timeout. */ public long getThreadPoolStopTimeout() { return Long.parseLong(getHelpedParameters().getFirstValue( "threadPool.stopTimeout", "5000")); } /** * Thread pool threads priority. Defaults to {@link Thread#NORM_PRIORITY}. * * @return Thread pool maximum threads. */ public int getThreadPoolThreadsPriority() { return Integer.parseInt(getHelpedParameters().getFirstValue( "threadPool.threadsPriority", String.valueOf(Thread.NORM_PRIORITY))); } /** * Returns the wrapped Jetty server. * * @return The wrapped Jetty server. */ protected org.eclipse.jetty.server.Server getWrappedServer() { if (this.wrappedServer == null) this.wrappedServer = createServer(); return this.wrappedServer; } /** * Sets the wrapped Jetty server. * * @param wrappedServer * The wrapped Jetty server. */ protected void setWrappedServer( org.eclipse.jetty.server.Server wrappedServer) { this.wrappedServer = wrappedServer; } @Override public void start() throws Exception { super.start(); org.eclipse.jetty.server.Server server = getWrappedServer(); ServerConnector connector = (ServerConnector) server.getConnectors()[0]; getLogger().info( "Starting the Jetty " + getProtocols() + " server on port " + getHelped().getPort()); server.start(); // We won't know the local port until after the server starts setEphemeralPort(connector.getLocalPort()); } @Override public void stop() throws Exception { getLogger().info( "Stopping the Jetty " + getProtocols() + " server on port " + getHelped().getPort()); getWrappedServer().stop(); super.stop(); } }