package io.dropwizard.jetty;

import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.util.DataSize;
import io.dropwizard.util.DataSizeUnit;
import io.dropwizard.util.Duration;
import io.dropwizard.validation.MinDataSize;
import io.dropwizard.validation.MinDuration;
import io.dropwizard.validation.PortRange;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.HttpCompliance;
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.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.ProxyConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.ThreadPool;

import javax.annotation.Nullable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.valueextraction.Unwrapping;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.codahale.metrics.MetricRegistry.name;

Builds HTTP connectors.

Configuration Parameters:
Name Default Description
port 8080 The TCP/IP port on which to listen for incoming connections.
bindHost (none) The hostname to bind to.
inheritChannel false Whether this connector uses a channel inherited from the JVM. Use it with Server::Starter, to launch an instance of Jetty on demand.
headerCacheSize 512 bytes The size of the header field cache.
outputBufferSize 32KiB The size of the buffer into which response content is aggregated before being sent to the client. 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.
maxRequestHeaderSize 8KiB The maximum size of a request header. 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.
maxResponseHeaderSize 8KiB The maximum size of a response header. Larger headers will allow for more and/or larger cookies and longer HTTP headers (eg for redirection). However, larger headers will also consume more memory.
inputBufferSize 8KiB The size of the per-connection input buffer.
idleTimeout 30 seconds The maximum idle time for a connection, which roughly translates to the Socket.setSoTimeout(int) call, although with NIO implementations other mechanisms may be used to implement the timeout.

The max idle time is applied:
  • When waiting for a new message to be received on a connection
  • When waiting for a new message to be sent on a connection

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.
minBufferPoolSize 64 bytes The minimum size of the buffer pool.
bufferPoolIncrement 1KiB The increment by which the buffer pool should be increased.
maxBufferPoolSize 64KiB The maximum size of the buffer pool.
acceptorThreads (Jetty's default) The number of worker threads dedicated to accepting connections. By default is max(1, min(4, #CPUs/8)).
selectorThreads (Jetty's default) The number of worker threads dedicated to sending and receiving data. By default is max(1, min(4, #CPUs/2)).
acceptQueueSize (OS default) The size of the TCP/IP accept queue for the listening socket.
reuseAddress true Whether or not SO_REUSEADDR is enabled on the listening socket.
useServerHeader false Whether or not to add the Server header to each response.
useDateHeader true Whether or not to add the Date header to each response.
minResponseDataPerSecond 0 bytes The minimum response data rate in bytes per second; or <=0 for no limit
minRequestDataPerSecond 0 bytes The minimum request data rate in bytes per second; or <=0 for no limit
useForwardedHeaders false Whether or not to look at X-Forwarded-* headers added by proxies. See ForwardedRequestCustomizer for details.
useProxyProtocol false Enable jetty proxy protocol header support.
httpCompliance RFC7230 This sets the http compliance level used by Jetty when parsing http, this can be useful when using a non-RFC7230 compliant front end, such as nginx, which can produce multi-line headers when forwarding client certificates using proxy_set_header X-SSL-CERT $ssl_client_cert; Possible values are set forth in the org.eclipse.jetty.http.HttpCompliance enum:
  • RFC7230: Disallow header folding.
  • RFC2616: Allow header folding.
requestCookieCompliance RFC6265 This sets the cookie compliance level used by Jetty when parsing request Cookie headers, this can be useful when needing to support Version=1 cookies defined in RFC2109 (and continued in RFC2965) which allows for special/reserved characters (control, separator, et al) to be enclosed within double quotes when used in a cookie value; Possible values are set forth in the org.eclipse.jetty.http.CookieCompliance enum:
  • RFC6265: Special characters in cookie values must be encoded.
  • RFC2965: Allows for special characters enclosed within double quotes.
responseCookieCompliance RFC6265 This sets the cookie compliance level used by Jetty when generating response Set-Cookie headers, this can be useful when needing to support Version=1 cookies defined in RFC2109 (and continued in RFC2965) which allows for special/reserved characters (control, separator, et al) to be enclosed within double quotes when used in a cookie value; Possible values are set forth in the org.eclipse.jetty.http.CookieCompliance enum:
  • RFC6265: Special characters in cookie values must be encoded.
  • RFC2965: Allows for special characters enclosed within double quotes.
/** * Builds HTTP connectors. * * <p/> * <b>Configuration Parameters:</b> * <table> * <tr> * <td>Name</td> * <td>Default</td> * <td>Description</td> * </tr> * <tr> * <td>{@code port}</td> * <td>8080</td> * <td>The TCP/IP port on which to listen for incoming connections.</td> * </tr> * <tr> * <td>{@code bindHost}</td> * <td>(none)</td> * <td>The hostname to bind to.</td> * </tr> * <tr> * <td>{@code inheritChannel}</td> * <td>false</td> * <td> * Whether this connector uses a channel inherited from the JVM. * Use it with <a href="https://github.com/kazuho/p5-Server-Starter">Server::Starter</a>, * to launch an instance of Jetty on demand. * </td> * </tr> * <tr> * <td>{@code headerCacheSize}</td> * <td>512 bytes</td> * <td>The size of the header field cache.</td> * </tr> * <tr> * <td>{@code outputBufferSize}</td> * <td>32KiB</td> * <td> * The size of the buffer into which response content is aggregated before being sent to * the client. 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>{@code maxRequestHeaderSize}</td> * <td>8KiB</td> * <td> * The maximum size of a request header. 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>{@code maxResponseHeaderSize}</td> * <td>8KiB</td> * <td> * The maximum size of a response header. Larger headers will allow for more and/or * larger cookies and longer HTTP headers (eg for redirection). However, larger headers * will also consume more memory. * </td> * </tr> * <tr> * <td>{@code inputBufferSize}</td> * <td>8KiB</td> * <td>The size of the per-connection input buffer.</td> * </tr> * <tr> * <td>{@code idleTimeout}</td> * <td>30 seconds</td> * <td> * The maximum idle time for a connection, which roughly translates to the * {@link java.net.Socket#setSoTimeout(int)} call, although with NIO implementations * other mechanisms may be used to implement the timeout. * <p/> * The max idle time is applied: * <ul> * <li>When waiting for a new message to be received on a connection</li> * <li>When waiting for a new message to be sent on a connection</li> * </ul> * <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. * </td> * </tr> * <tr> * <td>{@code minBufferPoolSize}</td> * <td>64 bytes</td> * <td>The minimum size of the buffer pool.</td> * </tr> * <tr> * <td>{@code bufferPoolIncrement}</td> * <td>1KiB</td> * <td>The increment by which the buffer pool should be increased.</td> * </tr> * <tr> * <td>{@code maxBufferPoolSize}</td> * <td>64KiB</td> * <td>The maximum size of the buffer pool.</td> * </tr> * <tr> * <td>{@code acceptorThreads}</td> * <td>(Jetty's default)</td> * <td>The number of worker threads dedicated to accepting connections. * By default is <i>max</i>(1, <i>min</i>(4, #CPUs/8)).</td> * </tr> * <tr> * <td>{@code selectorThreads}</td> * <td>(Jetty's default)</td> * <td>The number of worker threads dedicated to sending and receiving data. * By default is <i>max</i>(1, <i>min</i>(4, #CPUs/2)).</td> * </tr> * <tr> * <td>{@code acceptQueueSize}</td> * <td>(OS default)</td> * <td>The size of the TCP/IP accept queue for the listening socket.</td> * </tr> * <tr> * <td>{@code reuseAddress}</td> * <td>true</td> * <td>Whether or not {@code SO_REUSEADDR} is enabled on the listening socket.</td> * </tr> * <tr> * <td>{@code useServerHeader}</td> * <td>false</td> * <td>Whether or not to add the {@code Server} header to each response.</td> * </tr> * <tr> * <td>{@code useDateHeader}</td> * <td>true</td> * <td>Whether or not to add the {@code Date} header to each response.</td> * </tr> * <tr> * <td>{@code minResponseDataPerSecond}</td> * <td>0 bytes</td> * <td> * The minimum response data rate in bytes per second; or &lt;=0 for no limit * </td> * </tr> * <tr> * <td>{@code minRequestDataPerSecond}</td> * <td>0 bytes</td> * <td> * The minimum request data rate in bytes per second; or &lt;=0 for no limit * </td> * </tr> * <tr> * <td>{@code useForwardedHeaders}</td> * <td>false</td> * <td> * Whether or not to look at {@code X-Forwarded-*} headers added by proxies. See * {@link ForwardedRequestCustomizer} for details. * </td> * </tr> * <tr> * <td>{@code useProxyProtocol}</td> * <td>false</td> * <td> * Enable jetty proxy protocol header support. * </td> * </tr> * <tr> * <td>{@code httpCompliance}</td> * <td>RFC7230</td> * <td> * This sets the http compliance level used by Jetty when parsing http, this can be useful when using a * non-RFC7230 compliant front end, such as nginx, which can produce multi-line headers when forwarding * client certificates using proxy_set_header X-SSL-CERT $ssl_client_cert; * * Possible values are set forth in the org.eclipse.jetty.http.HttpCompliance enum: * <ul> * <li>RFC7230: Disallow header folding.</li> * <li>RFC2616: Allow header folding.</li> * </ul> * </td> * </tr> * <tr> * <td>{@code requestCookieCompliance}</td> * <td>RFC6265</td> * <td> * This sets the cookie compliance level used by Jetty when parsing request {@code Cookie} headers, * this can be useful when needing to support Version=1 cookies defined in RFC2109 (and continued in * RFC2965) which allows for special/reserved characters (control, separator, et al) to be enclosed within * double quotes when used in a cookie value; * * Possible values are set forth in the org.eclipse.jetty.http.CookieCompliance enum: * <ul> * <li>RFC6265: Special characters in cookie values must be encoded.</li> * <li>RFC2965: Allows for special characters enclosed within double quotes.</li> * </ul> * </td> * </tr> * <tr> * <td>{@code responseCookieCompliance}</td> * <td>RFC6265</td> * <td> * This sets the cookie compliance level used by Jetty when generating response {@code Set-Cookie} headers, * this can be useful when needing to support Version=1 cookies defined in RFC2109 (and continued in * RFC2965) which allows for special/reserved characters (control, separator, et al) to be enclosed within * double quotes when used in a cookie value; * * Possible values are set forth in the org.eclipse.jetty.http.CookieCompliance enum: * <ul> * <li>RFC6265: Special characters in cookie values must be encoded.</li> * <li>RFC2965: Allows for special characters enclosed within double quotes.</li> * </ul> * </td> * </tr> * </table> */
@JsonTypeName("http") public class HttpConnectorFactory implements ConnectorFactory { public static ConnectorFactory application() { final HttpConnectorFactory factory = new HttpConnectorFactory(); factory.port = 8080; return factory; } public static ConnectorFactory admin() { final HttpConnectorFactory factory = new HttpConnectorFactory(); factory.port = 8081; return factory; } @PortRange private int port = 8080; @Nullable private String bindHost; private boolean inheritChannel = false; @NotNull @MinDataSize(128) private DataSize headerCacheSize = DataSize.bytes(512); @NotNull @MinDataSize(value = 8, unit = DataSizeUnit.KIBIBYTES) private DataSize outputBufferSize = DataSize.kibibytes(32); @NotNull @MinDataSize(value = 1, unit = DataSizeUnit.KIBIBYTES) private DataSize maxRequestHeaderSize = DataSize.kibibytes(8); @NotNull @MinDataSize(value = 1, unit = DataSizeUnit.KIBIBYTES) private DataSize maxResponseHeaderSize = DataSize.kibibytes(8); @NotNull @MinDataSize(value = 1, unit = DataSizeUnit.KIBIBYTES) private DataSize inputBufferSize = DataSize.kibibytes(8); @NotNull @MinDuration(value = 1, unit = TimeUnit.MILLISECONDS) private Duration idleTimeout = Duration.seconds(30); @NotNull @MinDataSize(0) private DataSize minResponseDataPerSecond = DataSize.bytes(0); @NotNull @MinDataSize(0) private DataSize minRequestDataPerSecond = DataSize.bytes(0); @NotNull @MinDataSize(value = 1, unit = DataSizeUnit.BYTES) private DataSize minBufferPoolSize = DataSize.bytes(64); @NotNull @MinDataSize(value = 1, unit = DataSizeUnit.BYTES) private DataSize bufferPoolIncrement = DataSize.bytes(1024); @NotNull @MinDataSize(value = 1, unit = DataSizeUnit.BYTES) private DataSize maxBufferPoolSize = DataSize.kibibytes(64); @Min(value = 1, payload = Unwrapping.Unwrap.class) private Optional<Integer> acceptorThreads = Optional.empty(); @Min(value = 1, payload = Unwrapping.Unwrap.class) private Optional<Integer> selectorThreads = Optional.empty(); @Min(0) @Nullable private Integer acceptQueueSize; private boolean reuseAddress = true; private boolean useServerHeader = false; private boolean useDateHeader = true; private boolean useForwardedHeaders = false; private boolean useProxyProtocol = false; private HttpCompliance httpCompliance = HttpCompliance.RFC7230; private CookieCompliance requestCookieCompliance = CookieCompliance.RFC6265; private CookieCompliance responseCookieCompliance = CookieCompliance.RFC6265; @JsonProperty public int getPort() { return port; } @JsonProperty public void setPort(int port) { this.port = port; } @JsonProperty @Nullable public String getBindHost() { return bindHost; } @JsonProperty public void setBindHost(String bindHost) { this.bindHost = bindHost; } @JsonProperty public boolean isInheritChannel() { return inheritChannel; } @JsonProperty public void setInheritChannel(boolean inheritChannel) { this.inheritChannel = inheritChannel; } @JsonProperty public DataSize getHeaderCacheSize() { return headerCacheSize; } @JsonProperty public void setHeaderCacheSize(DataSize headerCacheSize) { this.headerCacheSize = headerCacheSize; } @JsonProperty public DataSize getOutputBufferSize() { return outputBufferSize; } @JsonProperty public void setOutputBufferSize(DataSize outputBufferSize) { this.outputBufferSize = outputBufferSize; } @JsonProperty public DataSize getMaxRequestHeaderSize() { return maxRequestHeaderSize; } @JsonProperty public void setMaxRequestHeaderSize(DataSize maxRequestHeaderSize) { this.maxRequestHeaderSize = maxRequestHeaderSize; } @JsonProperty public DataSize getMaxResponseHeaderSize() { return maxResponseHeaderSize; } @JsonProperty public void setMaxResponseHeaderSize(DataSize maxResponseHeaderSize) { this.maxResponseHeaderSize = maxResponseHeaderSize; } @JsonProperty public DataSize getInputBufferSize() { return inputBufferSize; } @JsonProperty public void setInputBufferSize(DataSize inputBufferSize) { this.inputBufferSize = inputBufferSize; } @JsonProperty public Duration getIdleTimeout() { return idleTimeout; } @JsonProperty public void setIdleTimeout(Duration idleTimeout) { this.idleTimeout = idleTimeout; } @JsonProperty public DataSize getMinBufferPoolSize() { return minBufferPoolSize; } @JsonProperty public void setMinBufferPoolSize(DataSize minBufferPoolSize) { this.minBufferPoolSize = minBufferPoolSize; } @JsonProperty public DataSize getBufferPoolIncrement() { return bufferPoolIncrement; } @JsonProperty public void setBufferPoolIncrement(DataSize bufferPoolIncrement) { this.bufferPoolIncrement = bufferPoolIncrement; } @JsonProperty public DataSize getMaxBufferPoolSize() { return maxBufferPoolSize; } @JsonProperty public void setMaxBufferPoolSize(DataSize maxBufferPoolSize) { this.maxBufferPoolSize = maxBufferPoolSize; } @JsonProperty public DataSize getMinResponseDataPerSecond() { return minResponseDataPerSecond; } @JsonProperty public void setMinResponseDataPerSecond(DataSize minResponseDataPerSecond) { this.minResponseDataPerSecond = minResponseDataPerSecond; } @JsonProperty public DataSize getMinRequestDataPerSecond() { return minRequestDataPerSecond; } @JsonProperty public void setMinRequestDataPerSecond(DataSize minRequestDataPerSecond) { this.minRequestDataPerSecond = minRequestDataPerSecond; } @JsonProperty public Optional<Integer> getAcceptorThreads() { return acceptorThreads; } @JsonProperty public void setAcceptorThreads(Optional<Integer> acceptorThreads) { this.acceptorThreads = acceptorThreads; } @JsonProperty public Optional<Integer> getSelectorThreads() { return selectorThreads; } @JsonProperty public void setSelectorThreads(Optional<Integer> selectorThreads) { this.selectorThreads = selectorThreads; } @JsonProperty @Nullable public Integer getAcceptQueueSize() { return acceptQueueSize; } @JsonProperty public void setAcceptQueueSize(Integer acceptQueueSize) { this.acceptQueueSize = acceptQueueSize; } @JsonProperty public boolean isReuseAddress() { return reuseAddress; } @JsonProperty public void setReuseAddress(boolean reuseAddress) { this.reuseAddress = reuseAddress; } @JsonProperty public boolean isUseServerHeader() { return useServerHeader; } @JsonProperty public void setUseServerHeader(boolean useServerHeader) { this.useServerHeader = useServerHeader; } @JsonProperty public boolean isUseDateHeader() { return useDateHeader; } @JsonProperty public void setUseDateHeader(boolean useDateHeader) { this.useDateHeader = useDateHeader; } @JsonProperty public boolean isUseForwardedHeaders() { return useForwardedHeaders; } @JsonProperty public void setUseForwardedHeaders(boolean useForwardedHeaders) { this.useForwardedHeaders = useForwardedHeaders; }
Since:2.0
/** * @since 2.0 */
@JsonProperty public boolean isUseProxyProtocol() { return useProxyProtocol; }
Since:2.0
/** * @since 2.0 */
@JsonProperty public void setUseProxyProtocol(boolean useProxyProtocol) { this.useProxyProtocol = useProxyProtocol; } @JsonProperty public HttpCompliance getHttpCompliance() { return httpCompliance; } @JsonProperty public void setHttpCompliance(HttpCompliance httpCompliance) { this.httpCompliance = httpCompliance; }
Since:2.0
/** * @since 2.0 */
@JsonProperty public CookieCompliance getRequestCookieCompliance() { return requestCookieCompliance; }
Since:2.0
/** * @since 2.0 */
@JsonProperty public void setRequestCookieCompliance(CookieCompliance requestCookieCompliance) { this.requestCookieCompliance = requestCookieCompliance; }
Since:2.0
/** * @since 2.0 */
@JsonProperty public CookieCompliance getResponseCookieCompliance() { return responseCookieCompliance; }
Since:2.0
/** * @since 2.0 */
@JsonProperty public void setResponseCookieCompliance(CookieCompliance responseCookieCompliance) { this.responseCookieCompliance = responseCookieCompliance; } @Override public Connector build(Server server, MetricRegistry metrics, String name, @Nullable ThreadPool threadPool) { final HttpConfiguration httpConfig = buildHttpConfiguration(); final HttpConnectionFactory httpConnectionFactory = buildHttpConnectionFactory(httpConfig); final Scheduler scheduler = new ScheduledExecutorScheduler(); final ByteBufferPool bufferPool = buildBufferPool(); return buildConnector(server, scheduler, bufferPool, name, threadPool, new Jetty93InstrumentedConnectionFactory(httpConnectionFactory, metrics.timer(httpConnections()))); }
Get name of the timer that tracks incoming HTTP connections
/** * Get name of the timer that tracks incoming HTTP connections */
protected String httpConnections() { return name(HttpConnectionFactory.class, bindHost, Integer.toString(port), "connections"); } protected ServerConnector buildConnector(Server server, Scheduler scheduler, ByteBufferPool bufferPool, String name, @Nullable ThreadPool threadPool, ConnectionFactory... factories) { if (useProxyProtocol) { factories = ArrayUtil.prependToArray(new ProxyConnectionFactory(), factories, ConnectorFactory.class); } final ServerConnector connector = new ServerConnector(server, threadPool, scheduler, bufferPool, acceptorThreads.orElse(-1), selectorThreads.orElse(-1), factories); connector.setPort(port); connector.setHost(bindHost); connector.setInheritChannel(inheritChannel); if (acceptQueueSize != null) { connector.setAcceptQueueSize(acceptQueueSize); } else { // if we do not set the acceptQueueSize, when jetty // creates the ServerSocket, it uses the default backlog of 50, and // not the value from the OS. Therefore we set to the value // obtained from NetUtil, which will attempt to read the value from the OS. // somaxconn setting connector.setAcceptQueueSize(NetUtil.getTcpBacklog()); } connector.setReuseAddress(reuseAddress); connector.setIdleTimeout(idleTimeout.toMilliseconds()); connector.setName(name); return connector; } protected HttpConnectionFactory buildHttpConnectionFactory(HttpConfiguration httpConfig) { final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig, httpCompliance); httpConnectionFactory.setInputBufferSize((int) inputBufferSize.toBytes()); return httpConnectionFactory; } protected HttpConfiguration buildHttpConfiguration() { final HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setHeaderCacheSize((int) headerCacheSize.toBytes()); httpConfig.setOutputBufferSize((int) outputBufferSize.toBytes()); httpConfig.setRequestHeaderSize((int) maxRequestHeaderSize.toBytes()); httpConfig.setResponseHeaderSize((int) maxResponseHeaderSize.toBytes()); httpConfig.setSendDateHeader(useDateHeader); httpConfig.setSendServerVersion(useServerHeader); httpConfig.setMinResponseDataRate(minResponseDataPerSecond.toBytes()); httpConfig.setMinRequestDataRate(minRequestDataPerSecond.toBytes()); httpConfig.setRequestCookieCompliance(requestCookieCompliance); httpConfig.setResponseCookieCompliance(responseCookieCompliance); if (useForwardedHeaders) { httpConfig.addCustomizer(new ForwardedRequestCustomizer()); } return httpConfig; } protected ByteBufferPool buildBufferPool() { return new ArrayByteBufferPool((int) minBufferPoolSize.toBytes(), (int) bufferPoolIncrement.toBytes(), (int) maxBufferPoolSize.toBytes()); } }