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.Duration;
import io.dropwizard.util.Size;
import io.dropwizard.util.SizeUnit;
import io.dropwizard.validation.MinDuration;
import io.dropwizard.validation.MinSize;
import io.dropwizard.validation.PortRange;
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.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.hibernate.validator.valuehandling.UnwrapValidatedValue;
import javax.annotation.Nullable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
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.
blockingTimeout
(none)
The timeout applied to blocking operations. This timeout is in addition to the idleTimeout
, and applies to the total operation (as opposed to the idle timeout that applies to the time no data is being sent).
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.
soLingerTime
(disabled)
Enable/disable SO_LINGER
with the specified linger time.
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.
useForwardedHeaders
true
Whether or not to look at X-Forwarded-*
headers added by proxies. See ForwardedRequestCustomizer
for details.
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.
/**
* 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 blockingTimeout}</td>
* <td>(none)</td>
* <td>The timeout applied to blocking operations. This timeout is in addition to the {@code idleTimeout},
* and applies to the total operation (as opposed to the idle timeout that applies to the time no data
* is being sent).
* </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 soLingerTime}</td>
* <td>(disabled)</td>
* <td>Enable/disable {@code SO_LINGER} with the specified linger time.</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 useForwardedHeaders}</td>
* <td>true</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 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>
* </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
@MinSize(128)
private Size headerCacheSize = Size.bytes(512);
@NotNull
@MinSize(value = 8, unit = SizeUnit.KILOBYTES)
private Size outputBufferSize = Size.kilobytes(32);
@NotNull
@MinSize(value = 1, unit = SizeUnit.KILOBYTES)
private Size maxRequestHeaderSize = Size.kilobytes(8);
@NotNull
@MinSize(value = 1, unit = SizeUnit.KILOBYTES)
private Size maxResponseHeaderSize = Size.kilobytes(8);
@NotNull
@MinSize(value = 1, unit = SizeUnit.KILOBYTES)
private Size inputBufferSize = Size.kilobytes(8);
@NotNull
@MinDuration(value = 1, unit = TimeUnit.MILLISECONDS)
private Duration idleTimeout = Duration.seconds(30);
@Nullable
private Duration blockingTimeout;
@NotNull
@MinSize(value = 1, unit = SizeUnit.BYTES)
private Size minBufferPoolSize = Size.bytes(64);
@NotNull
@MinSize(value = 1, unit = SizeUnit.BYTES)
private Size bufferPoolIncrement = Size.bytes(1024);
@NotNull
@MinSize(value = 1, unit = SizeUnit.BYTES)
private Size maxBufferPoolSize = Size.kilobytes(64);
private long minRequestDataRate = 0L;
@Min(1)
@UnwrapValidatedValue
private Optional<Integer> acceptorThreads = Optional.empty();
@Min(1)
@UnwrapValidatedValue
private Optional<Integer> selectorThreads = Optional.empty();
@Min(0)
@Nullable
private Integer acceptQueueSize;
private boolean reuseAddress = true;
@Nullable
private Duration soLingerTime;
private boolean useServerHeader = false;
private boolean useDateHeader = true;
private boolean useForwardedHeaders = true;
private HttpCompliance httpCompliance = HttpCompliance.RFC7230;
@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 Size getHeaderCacheSize() {
return headerCacheSize;
}
@JsonProperty
public void setHeaderCacheSize(Size headerCacheSize) {
this.headerCacheSize = headerCacheSize;
}
@JsonProperty
public Size getOutputBufferSize() {
return outputBufferSize;
}
@JsonProperty
public void setOutputBufferSize(Size outputBufferSize) {
this.outputBufferSize = outputBufferSize;
}
@JsonProperty
public Size getMaxRequestHeaderSize() {
return maxRequestHeaderSize;
}
@JsonProperty
public void setMaxRequestHeaderSize(Size maxRequestHeaderSize) {
this.maxRequestHeaderSize = maxRequestHeaderSize;
}
@JsonProperty
public Size getMaxResponseHeaderSize() {
return maxResponseHeaderSize;
}
@JsonProperty
public void setMaxResponseHeaderSize(Size maxResponseHeaderSize) {
this.maxResponseHeaderSize = maxResponseHeaderSize;
}
@JsonProperty
public Size getInputBufferSize() {
return inputBufferSize;
}
@JsonProperty
public void setInputBufferSize(Size inputBufferSize) {
this.inputBufferSize = inputBufferSize;
}
@JsonProperty
public Duration getIdleTimeout() {
return idleTimeout;
}
@JsonProperty
public void setIdleTimeout(Duration idleTimeout) {
this.idleTimeout = idleTimeout;
}
@JsonProperty
@Nullable
public Duration getBlockingTimeout() {
return blockingTimeout;
}
@JsonProperty
public void setBlockingTimeout(Duration blockingTimeout) {
this.blockingTimeout = blockingTimeout;
}
@JsonProperty
public Size getMinBufferPoolSize() {
return minBufferPoolSize;
}
@JsonProperty
public void setMinBufferPoolSize(Size minBufferPoolSize) {
this.minBufferPoolSize = minBufferPoolSize;
}
@JsonProperty
public Size getBufferPoolIncrement() {
return bufferPoolIncrement;
}
@JsonProperty
public void setBufferPoolIncrement(Size bufferPoolIncrement) {
this.bufferPoolIncrement = bufferPoolIncrement;
}
@JsonProperty
public Size getMaxBufferPoolSize() {
return maxBufferPoolSize;
}
@JsonProperty
public void setMaxBufferPoolSize(Size maxBufferPoolSize) {
this.maxBufferPoolSize = maxBufferPoolSize;
}
@JsonProperty
public long getMinRequestDataRate() {
return minRequestDataRate;
}
@JsonProperty
public void setMinRequestDataRate(long minRequestDataRate) {
this.minRequestDataRate = minRequestDataRate;
}
@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
@Nullable
public Duration getSoLingerTime() {
return soLingerTime;
}
@JsonProperty
public void setSoLingerTime(Duration soLingerTime) {
this.soLingerTime = soLingerTime;
}
@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;
}
@JsonProperty
public HttpCompliance getHttpCompliance() {
return httpCompliance;
}
@JsonProperty
public void setHttpCompliance(HttpCompliance httpCompliance) {
this.httpCompliance = httpCompliance;
}
@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) {
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);
if (soLingerTime != null) {
connector.setSoLingerTime((int) soLingerTime.toMilliseconds());
}
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.setMinRequestDataRate(minRequestDataRate);
if (useForwardedHeaders) {
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
}
if (blockingTimeout != null) {
httpConfig.setBlockingTimeout(blockingTimeout.toMilliseconds());
}
return httpConfig;
}
protected ByteBufferPool buildBufferPool() {
return new ArrayByteBufferPool((int) minBufferPoolSize.toBytes(),
(int) bufferPoolIncrement.toBytes(),
(int) maxBufferPoolSize.toBytes());
}
}