
io.muserver.MuServerBuilder Maven / Gradle / Ivy
Show all versions of mu-server Show documentation
package io.muserver;
import io.muserver.handlers.ResourceType;
import io.muserver.rest.MuRuntimeDelegate;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpServerKeepAliveHandler;
import io.netty.handler.flow.FlowControlHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import io.netty.util.HashedWheelTimer;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLParameters;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
* A builder for creating a web server.
* Use the withXXX()
methods to set the ports, config, and request handlers needed.
*/
public class MuServerBuilder {
static {
MuRuntimeDelegate.ensureSet();
}
private static final Logger log = LoggerFactory.getLogger(MuServerBuilder.class);
private static final int LENGTH_OF_METHOD_AND_PROTOCOL = 17; // e.g. "OPTIONS HTTP/1.1 "
private static final int DEFAULT_NIO_THREADS = Math.min(16, Runtime.getRuntime().availableProcessors() * 2);
private long minimumGzipSize = 1400;
private int httpPort = -1;
private int httpsPort = -1;
private int maxHeadersSize = 8192;
private int maxUrlSize = 8192 - LENGTH_OF_METHOD_AND_PROTOCOL;
private int nioThreads = DEFAULT_NIO_THREADS;
private final List handlers = new ArrayList<>();
private boolean gzipEnabled = true;
private Set mimeTypesToGzip = ResourceType.gzippableMimeTypes(ResourceType.getResourceTypes());
private boolean addShutdownHook = false;
private String host;
private HttpsConfigBuilder sslContextBuilder;
private Http2Config http2Config;
private long requestReadTimeoutMillis = TimeUnit.MINUTES.toMillis(2);
private long idleTimeoutMills = TimeUnit.MINUTES.toMillis(10);
private ExecutorService executor;
private long maxRequestSize = 24 * 1024 * 1024;
private List responseCompleteListeners;
private HashedWheelTimer wheelTimer;
private List rateLimiters;
private WriteBufferWaterMark writeBufferWaterMark = WriteBufferWaterMark.DEFAULT;
private UnhandledExceptionHandler unhandledExceptionHandler;
private boolean haProxyProtocolEnabled = false;
/**
* @param port The HTTP port to use. A value of 0 will have a random port assigned; a value of -1 will
* result in no HTTP connector.
* @return The current Mu Server Builder
*/
public MuServerBuilder withHttpPort(int port) {
this.httpPort = port;
return this;
}
/**
* Use this to specify which network interface to bind to.
*
* @param host The host to bind to, for example "127.0.0.1"
to restrict connections from localhost
* only, or "0.0.0.0"
to allow connections from the local network.
* @return The current Mu Server Builder
*/
public MuServerBuilder withInterface(String host) {
this.host = host;
return this;
}
/**
* @param stopServerOnShutdown If true, then a shutdown hook which stops this server will be added to the JVM Runtime
* @return The current Mu Server Builder
*/
public MuServerBuilder addShutdownHook(boolean stopServerOnShutdown) {
this.addShutdownHook = stopServerOnShutdown;
return this;
}
/**
* Enables gzip for certain resource types. The default is true
. By default, the
* gzippable resource types are taken from {@link ResourceType#getResourceTypes()} where
* {@link ResourceType#gzip()} is true
.
*
* @param enabled True to enable; false to disable
* @return The current Mu Server builder
* @see #withGzip(long, Set)
*/
public MuServerBuilder withGzipEnabled(boolean enabled) {
this.gzipEnabled = enabled;
return this;
}
/**
* Enables gzip for files of at least the specified size that match the given mime-types.
* By default, gzip is enabled for text-based mime types over 1400 bytes. It is recommended
* to keep the defaults and only use this method if you have very specific requirements
* around GZIP.
*
* @param minimumGzipSize The size in bytes before gzip is used. The default is 1400.
* @param mimeTypesToGzip The mime-types that should be gzipped. In general, only text
* files should be gzipped.
* @return The current Mu Server Builder
*/
public MuServerBuilder withGzip(long minimumGzipSize, Set mimeTypesToGzip) {
this.gzipEnabled = true;
this.mimeTypesToGzip = mimeTypesToGzip;
this.minimumGzipSize = minimumGzipSize;
return this;
}
/**
* Sets the HTTPS config. Defaults to {@link HttpsConfigBuilder#unsignedLocalhost()}}
*
* @param httpsConfig An HTTPS Config builder.
* @return The current Mu Server Builder
*/
public MuServerBuilder withHttpsConfig(HttpsConfigBuilder httpsConfig) {
this.sslContextBuilder = httpsConfig;
return this;
}
/**
* Sets the HTTPS port to use. To set the SSL certificate config, see {@link #withHttpsConfig(HttpsConfigBuilder)}
*
* @param port A value of 0 will result in a random port being assigned; a value of -1 will
* disable HTTPS.
* @return The current Mu Server builder
*/
public MuServerBuilder withHttpsPort(int port) {
this.httpsPort = port;
return this;
}
/**
* Sets the configuration for HTTP2
*
* @param http2Config A config
* @return The current Mu Server builder
* @see Http2ConfigBuilder
*/
public MuServerBuilder withHttp2Config(Http2Config http2Config) {
this.http2Config = http2Config;
return this;
}
/**
* Sets the configuration for HTTP2
*
* @param http2Config A config
* @return The current Mu Server builder
* @see Http2ConfigBuilder
*/
public MuServerBuilder withHttp2Config(Http2ConfigBuilder http2Config) {
return withHttp2Config(http2Config.build());
}
/**
* Sets the thread executor service to run requests on. By default {@link Executors#newCachedThreadPool()}
* is used.
*
* @param executor The executor service to use to handle requests
* @return The current Mu Server builder
*/
public MuServerBuilder withHandlerExecutor(ExecutorService executor) {
this.executor = executor;
return this;
}
/**
* The number of nio threads to handle requests.
* Generally only a small number is required as NIO threads are only used for non-blocking
* reads and writes of data. Request handlers are executed on a separate thread pool which
* can be specified with {@link #withHandlerExecutor(ExecutorService)}.
* Note that websocket callbacks are handled on these NIO threads.
*
* @param nioThreads The nio threads. Default is 2 * processor's count but not more than 16
* @return The current Mu Server builder
*/
public MuServerBuilder withNioThreads(int nioThreads) {
this.nioThreads = nioThreads;
return this;
}
/**
* Specifies the maximum size in bytes of the HTTP request headers. Defaults to 8192.
* If a request has headers exceeding this value, it will be rejected and a 431
* status code will be returned. Large values increase the risk of Denial-of-Service attacks
* due to the extra memory allocated in each request.
* It is recommended to not specify a value unless you are finding legitimate requests are
* being rejected with 413
errors.
*
* @param size The maximum size in bytes that can be used for headers.
* @return The current Mu Server builder.
*/
public MuServerBuilder withMaxHeadersSize(int size) {
this.maxHeadersSize = size;
return this;
}
/**
* The maximum length that a URL can be. If it exceeds this value, a 414
error is
* returned to the client. The default value is 8175.
*
* @param size The maximum number of characters allowed in URLs sent to this server.
* @return The current Mu Server builder
*/
public MuServerBuilder withMaxUrlSize(int size) {
this.maxUrlSize = size;
return this;
}
/**
* The maximum allowed request body size. If exceeded, a 413 will be returned.
*
* @param maxSizeInBytes The maximum request body size allowed, in bytes. The default is 24MB.
* @return The current Mu Server builder
*/
public MuServerBuilder withMaxRequestSize(long maxSizeInBytes) {
this.maxRequestSize = maxSizeInBytes;
return this;
}
/**
* Sets the idle timeout for connections. If no bytes are sent or received within this time then
* the connection is closed.
* The default is 5 minutes.
*
* @param duration The allowed timeout duration, or 0 to disable timeouts.
* @param unit The unit of the duration.
* @return This builder
* @see #withRequestTimeout(long, TimeUnit)
*/
public MuServerBuilder withIdleTimeout(long duration, TimeUnit unit) {
if (duration < 0) {
throw new IllegalArgumentException("The duration must be 0 or greater");
}
Mutils.notNull("unit", unit);
this.idleTimeoutMills = unit.toMillis(duration);
return this;
}
/**
* Sets the idle timeout for reading request bodies. If a slow client that is uploading a request body pauses
* for this amount of time, the request will be closed (if the response has not started, the client will receive
* a 408 error).
* The default is 2 minutes.
*
* @param duration The allowed timeout duration, or 0 to disable timeouts.
* @param unit The unit of the duration.
* @return This builder
* @see #withIdleTimeout(long, TimeUnit)
*/
public MuServerBuilder withRequestTimeout(long duration, TimeUnit unit) {
if (duration < 0) {
throw new IllegalArgumentException("The duration must be 0 or greater");
}
Mutils.notNull("unit", unit);
this.requestReadTimeoutMillis = unit.toMillis(duration);
return this;
}
/**
* Adds a request handler.
* Note that handlers are executed in the order added to the builder, but all async
* handlers are executed before synchronous handlers.
*
* @param handler A handler builder. The build()
method will be called on this
* to create the handler. If null, then no handler is added.
* @return The current Mu Server Handler.
* @see #addHandler(Method, String, RouteHandler)
*/
public MuServerBuilder addHandler(MuHandlerBuilder handler) {
if (handler == null) {
return this;
}
return addHandler(handler.build());
}
/**
* Adds a request handler.
* Note that handlers are executed in the order added to the builder, but all async
* handlers are executed before synchronous handlers.
*
* @param handler The handler to add. If null, then no handler is added.
* @return The current Mu Server Handler.
* @see #addHandler(Method, String, RouteHandler)
*/
public MuServerBuilder addHandler(MuHandler handler) {
if (handler != null) {
handlers.add(handler);
}
return this;
}
/**
* Registers a new handler that will only be called if it matches the given route info
*
* @param method The method to match, or null
to accept any method.
* @param uriTemplate A URL template. Supports plain URLs like /abc
or paths
* with named parameters such as /abc/{id}
or named parameters
* with regexes such as /abc/{id : [0-9]+}
where the named
* parameter values can be accessed with the pathParams
* parameter in the route handler.
* @param handler The handler to invoke if the method and URI matches. If null, then no handler is added.
* @return Returns the server builder
*/
public MuServerBuilder addHandler(Method method, String uriTemplate, RouteHandler handler) {
if (handler == null) {
return this;
}
return addHandler(Routes.route(method, uriTemplate, handler));
}
/**
* Adds a listener that is notified when each response completes
*
* @param listener A listener. If null, then nothing is added.
* @return Returns the server builder
*/
public MuServerBuilder addResponseCompleteListener(ResponseCompleteListener listener) {
if (listener != null) {
if (this.responseCompleteListeners == null) {
this.responseCompleteListeners = new ArrayList<>();
}
this.responseCompleteListeners.add(listener);
}
return this;
}
MuServerBuilder withWriteBufferWaterMark(int low, int high) {
this.writeBufferWaterMark = new WriteBufferWaterMark(low, high);
return this;
}
/**
* Adds a rate limiter to incoming requests.
* The selector specified in this method allows you to control the limit buckets that are used. For
* example, to set a limit on client IP addresses the selector would return {@link MuRequest#remoteAddress()}.
* The selector also specifies the number of requests allowed for the bucket per time period, such that
* different buckets can have different limits.
* The following example shows how to allow 100 requests per second per IP address:
*
* {@code
* MuServerBuilder.httpsServer()
* .withRateLimiter(request -> RateLimit.builder()
* .withBucket(request.remoteAddress())
* .withRate(100)
* .withWindow(1, TimeUnit.SECONDS)
* .build())
* }
*
* Note that multiple limiters can be added which allows different limits across different dimensions.
* For example, you may allow 100 requests per second based on IP address and
* also a limit based on a cookie, request path, or other value.
*
* @param selector A function that returns a string based on the request, or null to not have a limit applied
* @return This builder
*/
public MuServerBuilder withRateLimiter(RateLimitSelector selector) {
if (wheelTimer == null) {
wheelTimer = new HashedWheelTimer(new DefaultThreadFactory("mu-limit-timer"));
wheelTimer.start();
rateLimiters = new ArrayList<>();
}
RateLimiterImpl rateLimiter = new RateLimiterImpl(selector, wheelTimer);
this.rateLimiters.add(rateLimiter);
return this;
}
/**
* Sets the handler to use for exceptions thrown by other handlers, allowing for things such as custom error pages.
* Note that if the response has already started sending data, you will not be able to add a custom error
* message. In this case, you may want to allow for the default error handling by returning false
.
* The following shows a pattern to filter out certain errors:
*
* muServerBuilder.withExceptionHandler((request, response, exception) -> {
* if (response.hasStartedSendingData()) return false; // cannot customise the response
* if (exception instanceof NotAuthorizedException) return false;
* response.contentType(ContentTypes.TEXT_PLAIN_UTF8);
* response.write("Oh I'm worry, there was a problem");
* return true;
* })
*
* @param exceptionHandler The handler to be called when an unhandled exception is encountered
* @return This builder
*/
public MuServerBuilder withExceptionHandler(UnhandledExceptionHandler exceptionHandler) {
this.unhandledExceptionHandler = exceptionHandler;
return this;
}
/**
* @return The current value of this property
*/
public long minimumGzipSize() {
return minimumGzipSize;
}
/**
* @return The current value of this property
*/
public int httpPort() {
return httpPort;
}
/**
* @return The current value of this property
*/
public int httpsPort() {
return httpsPort;
}
/**
* @return The current value of this property
*/
public int maxHeadersSize() {
return maxHeadersSize;
}
/**
* @return The current value of this property
*/
public int maxUrlSize() {
return maxUrlSize;
}
/**
* @return The current value of this property
*/
public int nioThreads() {
return nioThreads;
}
/**
* @return The current value of this property
*/
public List handlers() {
return Collections.unmodifiableList(handlers);
}
/**
* @return The current value of this property
*/
public boolean gzipEnabled() {
return gzipEnabled;
}
/**
* @return The current value of this property
*/
public Set mimeTypesToGzip() {
return Collections.unmodifiableSet(mimeTypesToGzip);
}
/**
* @return The current value of this property
*/
public boolean addShutdownHook() {
return addShutdownHook;
}
/**
* @return The current value of this property
*/
public String interfaceHost() {
return host;
}
/**
* @return The current value of this property
*/
public HttpsConfigBuilder httpsConfigBuilder() {
if (sslContextBuilder != null && !(sslContextBuilder instanceof HttpsConfigBuilder)) {
throw new IllegalStateException("Please switch to using HttpsConfigBuilder to set HTTPS config");
}
return (HttpsConfigBuilder) sslContextBuilder;
}
/**
* @return The current value of this property
*/
public Http2Config http2Config() {
return http2Config;
}
/**
* @return The current value of this property
*/
public long requestReadTimeoutMillis() {
return requestReadTimeoutMillis;
}
/**
* @return The current value of this property
*/
public long idleTimeoutMills() {
return idleTimeoutMills;
}
/**
* @return The current value of this property
*/
public ExecutorService executor() {
return executor;
}
/**
* @return The current value of this property
*/
public long maxRequestSize() {
return maxRequestSize;
}
/**
* @return The current value of this property
*/
public boolean haProxyProtocolEnabled() { return this.haProxyProtocolEnabled; }
/**
* @return The current value of this property
*/
public List responseCompleteListeners() {
return Collections.unmodifiableList(responseCompleteListeners);
}
/**
* @return The current value of this property
*/
public List rateLimiters() {
return rateLimiters.stream().map(RateLimiter.class::cast).collect(Collectors.toList());
}
/**
* @return The current value of this property
*/
public UnhandledExceptionHandler unhandledExceptionHandler() {
return unhandledExceptionHandler;
}
/**
* Creates a new server builder. Call {@link #withHttpsPort(int)} or {@link #withHttpPort(int)} to specify
* the port to use, and call {@link #start()} to start the server.
*
* @return A new Mu Server builder
*/
public static MuServerBuilder muServer() {
return new MuServerBuilder();
}
/**
* Creates a new server builder which will run as HTTP on a random port.
*
* @return A new Mu Server builder with the HTTP port set to 0
*/
public static MuServerBuilder httpServer() {
return muServer().withHttpPort(0);
}
/**
* Creates a new server builder which will run as HTTPS on a random port.
*
* @return A new Mu Server builder with the HTTPS port set to 0
*/
public static MuServerBuilder httpsServer() {
return muServer().withHttpsPort(0);
}
/**
* If enabled, then HA Proxy Protocol
* parsing on new connections will be enabled.
* @param enabled true
to enable. The default is false
* @return This builder
*/
public MuServerBuilder withHAProxyProtocolEnabled(boolean enabled) {
this.haProxyProtocolEnabled = enabled;
return this;
}
/**
* Creates and starts this server. An exception is thrown if it fails to start.
*
* @return The running server.
*/
public MuServer start() {
if (httpPort < 0 && httpsPort < 0) {
throw new IllegalArgumentException("No ports were configured. Please call MuServerBuilder.withHttpPort(int) or MuServerBuilder.withHttpsPort(int)");
}
ServerSettings settings = new ServerSettings(minimumGzipSize, maxHeadersSize, requestReadTimeoutMillis, maxRequestSize, maxUrlSize, gzipEnabled, mimeTypesToGzip, rateLimiters);
ExecutorService handlerExecutor = this.executor;
if (handlerExecutor == null) {
DefaultThreadFactory threadFactory = new DefaultThreadFactory("muhandler");
handlerExecutor = new ThreadPoolExecutor(8, 400, 60, TimeUnit.SECONDS, new SynchronousQueue<>(), threadFactory);
}
NettyHandlerAdapter nettyHandlerAdapter = new NettyHandlerAdapter(handlerExecutor, handlers, responseCompleteListeners);
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup(this.nioThreads);
List channels = new ArrayList<>();
ExecutorService finalHandlerExecutor = handlerExecutor;
Runnable shutdown = () -> {
try {
if (wheelTimer != null) {
wheelTimer.stop();
}
for (Channel channel : channels) {
channel.close().sync();
}
finalHandlerExecutor.shutdown();
bossGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS).sync();
workerGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS).sync();
} catch (Exception e) {
log.info("Error while shutting down. Will ignore. Error was: " + e.getMessage());
}
};
try {
GlobalTrafficShapingHandler trafficShapingHandler = new GlobalTrafficShapingHandler(workerGroup, 0, 0, 1000);
MuStatsImpl stats = new MuStatsImpl(trafficShapingHandler.trafficCounter());
SslContextProvider sslContextProvider = null;
boolean http2Enabled = http2Config != null && http2Config.enabled;
MuServerImpl server = new MuServerImpl(stats, http2Enabled, settings, unhandledExceptionHandler);
Channel httpChannel = httpPort < 0 ? null : createChannel(bossGroup, workerGroup, nettyHandlerAdapter, host, httpPort, null, trafficShapingHandler, server, false, idleTimeoutMills, writeBufferWaterMark, haProxyProtocolEnabled);
Channel httpsChannel;
if (httpsPort < 0) {
httpsChannel = null;
} else {
HttpsConfigBuilder toUse = this.sslContextBuilder != null ? this.sslContextBuilder : HttpsConfigBuilder.unsignedLocalhost();
SslContext nettySslContext = toUse.toNettySslContext(http2Enabled);
log.debug("SSL Context is " + nettySslContext);
sslContextProvider = new SslContextProvider(nettySslContext);
httpsChannel = createChannel(bossGroup, workerGroup, nettyHandlerAdapter, host, httpsPort, sslContextProvider, trafficShapingHandler, server, http2Enabled, idleTimeoutMills, writeBufferWaterMark, haProxyProtocolEnabled);
}
URI uri = null;
if (httpChannel != null) {
channels.add(httpChannel);
uri = getUriFromChannel(httpChannel, "http", host);
}
URI httpsUri = null;
if (httpsChannel != null) {
channels.add(httpsChannel);
httpsUri = getUriFromChannel(httpsChannel, "https", host);
((SSLInfoImpl) sslContextProvider.sslInfo()).setHttpsUri(httpsUri);
}
InetSocketAddress serverAddress = (InetSocketAddress) channels.get(0).localAddress();
server.onStarted(uri, httpsUri, shutdown, serverAddress, sslContextProvider);
if (addShutdownHook) {
Runtime.getRuntime().addShutdownHook(new Thread(server::stop));
}
return server;
} catch (Exception ex) {
shutdown.run();
throw new MuException("Error while starting server", ex);
}
}
private static URI getUriFromChannel(Channel httpChannel, String protocol, String host) {
host = host == null ? "localhost" : host;
InetSocketAddress a = (InetSocketAddress) httpChannel.localAddress();
return URI.create(protocol + "://" + host.toLowerCase() + ":" + a.getPort());
}
private static Channel createChannel(NioEventLoopGroup bossGroup, NioEventLoopGroup workerGroup, NettyHandlerAdapter nettyHandlerAdapter, String host, int port, SslContextProvider sslContextProvider, GlobalTrafficShapingHandler trafficShapingHandler, MuServerImpl server, final boolean http2, long idleTimeoutMills, WriteBufferWaterMark writeBufferWaterMark, boolean haProxyProtocolEnabled) throws InterruptedException {
boolean usesSsl = sslContextProvider != null;
String proto = usesSsl ? "https" : "http";
ServerBootstrap b = new ServerBootstrap();
b.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, writeBufferWaterMark);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline p = socketChannel.pipeline();
p.addLast("idle", new IdleStateHandler(0, 0, idleTimeoutMills, TimeUnit.MILLISECONDS));
p.addLast(trafficShapingHandler);
if (haProxyProtocolEnabled) {
HAProxyMessageDecoder haProxyMessageDecoder = new HAProxyMessageDecoder();
p.addLast("HAProxyMessageDecoder", haProxyMessageDecoder);
}
if (usesSsl) {
SslHandler sslHandler = sslContextProvider.get().newHandler(socketChannel.alloc());
SSLParameters params = sslHandler.engine().getSSLParameters();
params.setUseCipherSuitesOrder(true);
sslHandler.engine().setSSLParameters(params);
p.addLast("ssl", sslHandler);
}
boolean addAlpn = http2 && usesSsl;
if (addAlpn) {
p.addLast(BackPressureHandler.NAME, new BackPressureHandler());
p.addLast("alpn", new AlpnHandler(nettyHandlerAdapter, server, proto));
}
p.addLast("conerror", new ChannelInboundHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
server.stats.onFailedToConnect();
}
});
if (!addAlpn) {
setupHttp1Pipeline(p, nettyHandlerAdapter, server, proto);
}
}
});
ChannelFuture bound = host == null ? b.bind(port) : b.bind(host, port);
return bound.sync().channel();
}
static void setupHttp1Pipeline(ChannelPipeline p, NettyHandlerAdapter nettyHandlerAdapter, MuServerImpl server, String proto) {
p.addLast("decoder", new HttpRequestDecoder(server.settings().maxUrlSize + LENGTH_OF_METHOD_AND_PROTOCOL, server.settings().maxHeadersSize, 8192));
p.addLast("encoder", new HttpResponseEncoder() {
@Override
protected boolean isContentAlwaysEmpty(HttpResponse msg) {
return super.isContentAlwaysEmpty(msg) || msg instanceof NettyResponseAdaptor.EmptyHttpResponse;
}
});
if (server.settings().gzipEnabled) {
p.addLast("compressor", new SelectiveHttpContentCompressor(server.settings()));
}
p.addLast("keepalive", new HttpServerKeepAliveHandler());
p.addLast("flowControl", new FlowControlHandler());
p.addLast(BackPressureHandler.NAME, new BackPressureHandler());
p.addLast("preread", new PreReader());
p.addLast("muhandler", new Http1Connection(nettyHandlerAdapter, server, proto));
}
@Override
public String toString() {
return "MuServerBuilder{" +
"minimumGzipSize=" + minimumGzipSize +
", httpPort=" + httpPort +
", httpsPort=" + httpsPort +
", maxHeadersSize=" + maxHeadersSize +
", maxUrlSize=" + maxUrlSize +
", nioThreads=" + nioThreads +
", handlers=" + handlers +
", gzipEnabled=" + gzipEnabled +
", mimeTypesToGzip=" + mimeTypesToGzip +
", addShutdownHook=" + addShutdownHook +
", host='" + host + '\'' +
", sslContextBuilder=" + sslContextBuilder +
", http2Config=" + http2Config +
", requestReadTimeoutMillis=" + requestReadTimeoutMillis +
", idleTimeoutMills=" + idleTimeoutMills +
", executor=" + executor +
", maxRequestSize=" + maxRequestSize +
", responseCompleteListeners=" + responseCompleteListeners +
", rateLimiters=" + rateLimiters +
'}';
}
}