All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.linecorp.armeria.server.ServerBuilder Maven / Gradle / Ivy

/*
 * Copyright 2015 LINE Corporation
 *
 * LINE Corporation licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package com.linecorp.armeria.server;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.linecorp.armeria.common.SessionProtocol.HTTP;
import static com.linecorp.armeria.common.SessionProtocol.HTTPS;
import static com.linecorp.armeria.common.SessionProtocol.PROXY;
import static com.linecorp.armeria.server.ServerConfig.validateDefaultMaxRequestLength;
import static com.linecorp.armeria.server.ServerConfig.validateDefaultRequestTimeoutMillis;
import static com.linecorp.armeria.server.ServerConfig.validateNonNegative;
import static java.util.Objects.requireNonNull;

import java.io.File;
import java.net.InetSocketAddress;
import java.security.cert.CertificateException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.net.ssl.SSLException;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import com.linecorp.armeria.common.CommonPools;
import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.Request;
import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.server.annotation.ExceptionHandlerFunction;
import com.linecorp.armeria.server.annotation.RequestConverterFunction;
import com.linecorp.armeria.server.annotation.ResponseConverterFunction;
import com.linecorp.armeria.server.logging.AccessLogWriters;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.handler.ssl.SslContext;
import io.netty.util.DomainNameMapping;
import io.netty.util.DomainNameMappingBuilder;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;

/**
 * Builds a new {@link Server} and its {@link ServerConfig}.
 * 

Example

*
{@code
 * ServerBuilder sb = new ServerBuilder();
 * // Add a port to listen
 * sb.http(8080);
 * // Build and add a virtual host.
 * sb.virtualHost(new VirtualHostBuilder("*.foo.com").service(...).build());
 * // Add services to the default virtual host.
 * sb.service(...);
 * sb.serviceUnder(...);
 * // Build a server.
 * Server s = sb.build();
 * }
* *

Example 2

*
{@code
 * ServerBuilder sb = new ServerBuilder();
 * Server server =
 *     sb.http(8080) // Add a port to listen
 *       .withDefaultVirtualHost() // Add services to the default virtual host.
 *           .service(...)
 *           .serviceUnder(...)
 *       .and().withVirtualHost("*.foo.com") // Add a another virtual host.
 *           .service(...)
 *           .serviceUnder(...)
 *       .and().build(); // Build a server.
 * }
* * *

What happens if no HTTP(S) port is specified?

* *

When no TCP/IP port number or local address is specified, {@link ServerBuilder} will automatically bind * to a random TCP/IP port assigned by the OS. It will serve HTTPS if you configured TLS (or HTTP otherwise), * e.g. * *

{@code
 * // Build an HTTP server that runs on an ephemeral TCP/IP port.
 * Server httpServer = new ServerBuilder().service(...).build();
 *
 * // Build an HTTPS server that runs on an ephemeral TCP/IP port.
 * Server httpsServer = new ServerBuilder().tls(...).service(...).build();
 * }
* * @see VirtualHostBuilder */ public final class ServerBuilder { // Defaults to no graceful shutdown. private static final Duration DEFAULT_GRACEFUL_SHUTDOWN_QUIET_PERIOD = Duration.ZERO; private static final Duration DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT = Duration.ZERO; private static final String DEFAULT_SERVICE_LOGGER_PREFIX = "armeria.services"; private static final int PROXY_PROTOCOL_DEFAULT_MAX_TLV_SIZE = 65535 - 216; // Prohibit deprecate option @SuppressWarnings("deprecation") private static final Set> PROHIBITED_SOCKET_OPTIONS = ImmutableSet.of( ChannelOption.ALLOW_HALF_CLOSURE, ChannelOption.AUTO_READ, ChannelOption.AUTO_CLOSE, ChannelOption.MAX_MESSAGES_PER_READ, ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, EpollChannelOption.EPOLL_MODE); private final List ports = new ArrayList<>(); private final List serverListeners = new ArrayList<>(); private final List virtualHosts = new ArrayList<>(); private final List virtualHostBuilders = new ArrayList<>(); private final ChainedVirtualHostBuilder defaultVirtualHostBuilder = new ChainedVirtualHostBuilder(this); private boolean updatedDefaultVirtualHostBuilder; private RejectedPathMappingHandler rejectedPathMappingHandler = RejectedPathMappingHandler.WARN; @Nullable private VirtualHost defaultVirtualHost; private EventLoopGroup workerGroup = CommonPools.workerGroup(); private boolean shutdownWorkerGroupOnStop; private final Map, Object> channelOptions = new Object2ObjectArrayMap<>(); private final Map, Object> childChannelOptions = new Object2ObjectArrayMap<>(); private int maxNumConnections = Flags.maxNumConnections(); private long idleTimeoutMillis = Flags.defaultServerIdleTimeoutMillis(); private long defaultRequestTimeoutMillis = Flags.defaultRequestTimeoutMillis(); private long defaultMaxRequestLength = Flags.defaultMaxRequestLength(); private int maxHttp1InitialLineLength = Flags.defaultMaxHttp1InitialLineLength(); private int maxHttp1HeaderSize = Flags.defaultMaxHttp1HeaderSize(); private int maxHttp1ChunkSize = Flags.defaultMaxHttp1ChunkSize(); private int proxyProtocolMaxTlvSize = PROXY_PROTOCOL_DEFAULT_MAX_TLV_SIZE; private Duration gracefulShutdownQuietPeriod = DEFAULT_GRACEFUL_SHUTDOWN_QUIET_PERIOD; private Duration gracefulShutdownTimeout = DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT; private Executor blockingTaskExecutor = CommonPools.blockingTaskExecutor(); private MeterRegistry meterRegistry = Metrics.globalRegistry; private String serviceLoggerPrefix = DEFAULT_SERVICE_LOGGER_PREFIX; private Consumer accessLogWriter = AccessLogWriters.disabled(); @Nullable private Function, Service> decorator; /** * Adds an HTTP port that listens on all available network interfaces. * * @param port the HTTP port number. * * @see #http(InetSocketAddress) * @see What happens if no HTTP(S) port is specified? */ public ServerBuilder http(int port) { return port(new ServerPort(port, HTTP)); } /** * Adds an HTTP port that listens to the specified {@code localAddress}. * * @param localAddress the local address to bind * * @see #http(int) * @see What happens if no HTTP(S) port is specified? */ public ServerBuilder http(InetSocketAddress localAddress) { return port(new ServerPort(requireNonNull(localAddress, "localAddress"), HTTP)); } /** * Adds an HTTPS port that listens on all available network interfaces. * * @param port the HTTPS port number. * * @see #https(InetSocketAddress) * @see What happens if no HTTP(S) port is specified? */ public ServerBuilder https(int port) { return port(new ServerPort(port, HTTPS)); } /** * Adds an HTTPS port that listens to the specified {@code localAddress}. * * @param localAddress the local address to bind * * @see #http(int) * @see What happens if no HTTP(S) port is specified? */ public ServerBuilder https(InetSocketAddress localAddress) { return port(new ServerPort(requireNonNull(localAddress, "localAddress"), HTTPS)); } /** * Adds a new {@link ServerPort} that listens to the specified {@code port} of all available network * interfaces using the specified protocol. * * @deprecated Use {@link #http(int)} or {@link #https(int)}. * @see What happens if no HTTP(S) port is specified? */ @Deprecated public ServerBuilder port(int port, String protocol) { return port(port, SessionProtocol.of(requireNonNull(protocol, "protocol"))); } /** * Adds a new {@link ServerPort} that listens to the specified {@code port} of all available network * interfaces using the specified {@link SessionProtocol}s. Specify multiple protocols to serve more than * one protocol on the same port: * *
{@code
     * ServerBuilder sb = new ServerBuilder();
     * // Serve both HTTP and HTTPS at port 8080.
     * sb.port(8080,
     *         SessionProtocol.HTTP,
     *         SessionProtocol.HTTPS);
     * // Enable HTTPS with PROXY protocol support at port 8443.
     * sb.port(8443,
     *         SessionProtocol.PROXY,
     *         SessionProtocol.HTTPS);
     * }
*/ public ServerBuilder port(int port, SessionProtocol... protocols) { return port(new ServerPort(port, protocols)); } /** * Adds a new {@link ServerPort} that listens to the specified {@code port} of all available network * interfaces using the specified {@link SessionProtocol}s. Specify multiple protocols to serve more than * one protocol on the same port: * *
{@code
     * ServerBuilder sb = new ServerBuilder();
     * // Serve both HTTP and HTTPS at port 8080.
     * sb.port(8080,
     *         Arrays.asList(SessionProtocol.HTTP,
     *                       SessionProtocol.HTTPS));
     * // Enable HTTPS with PROXY protocol support at port 8443.
     * sb.port(8443,
     *         Arrays.asList(SessionProtocol.PROXY,
     *                       SessionProtocol.HTTPS));
     * }
*/ public ServerBuilder port(int port, Iterable protocols) { return port(new ServerPort(port, protocols)); } /** * Adds a new {@link ServerPort} that listens to the specified {@code localAddress} using the specified * protocol. * * @deprecated Use {@link #http(InetSocketAddress)} or {@link #https(InetSocketAddress)}. */ @Deprecated public ServerBuilder port(InetSocketAddress localAddress, String protocol) { return port(localAddress, SessionProtocol.of(requireNonNull(protocol, "protocol"))); } /** * Adds a new {@link ServerPort} that listens to the specified {@code localAddress} using the specified * {@link SessionProtocol}s. Specify multiple protocols to serve more than one protocol on the same port: * *
{@code
     * ServerBuilder sb = new ServerBuilder();
     * // Serve both HTTP and HTTPS at port 8080.
     * sb.port(new InetSocketAddress(8080),
     *         SessionProtocol.HTTP,
     *         SessionProtocol.HTTPS);
     * // Enable HTTPS with PROXY protocol support at port 8443.
     * sb.port(new InetSocketAddress(8443),
     *         SessionProtocol.PROXY,
     *         SessionProtocol.HTTPS);
     * }
*/ public ServerBuilder port(InetSocketAddress localAddress, SessionProtocol... protocols) { return port(new ServerPort(localAddress, protocols)); } /** * Adds a new {@link ServerPort} that listens to the specified {@code localAddress} using the specified * {@link SessionProtocol}s. Specify multiple protocols to serve more than one protocol on the same port: * *
{@code
     * ServerBuilder sb = new ServerBuilder();
     * // Serve both HTTP and HTTPS at port 8080.
     * sb.port(new InetSocketAddress(8080),
     *         Arrays.asList(SessionProtocol.HTTP,
     *                       SessionProtocol.HTTPS));
     * // Enable HTTPS with PROXY protocol support at port 8443.
     * sb.port(new InetSocketAddress(8443),
     *         Arrays.asList(SessionProtocol.PROXY,
     *                       SessionProtocol.HTTPS));
     * }
*/ public ServerBuilder port(InetSocketAddress localAddress, Iterable protocols) { return port(new ServerPort(localAddress, protocols)); } /** * Adds the specified {@link ServerPort}. * * @see What happens if no HTTP(S) port is specified? */ public ServerBuilder port(ServerPort port) { requireNonNull(port, "port"); if (port.localAddress().getPort() != 0) { ports.forEach(p -> checkArgument(!p.localAddress().equals(port.localAddress()), "duplicate local address: %s", port.localAddress())); } ports.add(port); return this; } /** * Sets the {@link ChannelOption} of the server socket bound by {@link Server}. * Note that the previously added option will be overridden if the same option is set again. * *
{@code
     * ServerBuilder sb = new ServerBuilder();
     * sb.channelOption(ChannelOption.BACKLOG, 1024);
     * }
*/ public ServerBuilder channelOption(ChannelOption option, T value) { requireNonNull(option, "option"); checkArgument(!PROHIBITED_SOCKET_OPTIONS.contains(option), "prohibited socket option: %s", option); option.validate(value); channelOptions.put(option, value); return this; } /** * Sets the {@link ChannelOption} of sockets accepted by {@link Server}. * Note that the previously added option will be overridden if the same option is set again. * *
{@code
     * ServerBuilder sb = new ServerBuilder();
     * sb.childChannelOption(ChannelOption.SO_REUSEADDR, true)
     *   .childChannelOption(ChannelOption.SO_KEEPALIVE, true);
     * }
*/ public ServerBuilder childChannelOption(ChannelOption option, T value) { requireNonNull(option, "option"); checkArgument(!PROHIBITED_SOCKET_OPTIONS.contains(option), "prohibited socket option: %s", option); option.validate(value); childChannelOptions.put(option, value); return this; } /** * Adds the name-based virtual host * specified by {@link VirtualHost}. */ public ServerBuilder virtualHost(VirtualHost virtualHost) { virtualHosts.add(requireNonNull(virtualHost, "virtualHost")); return this; } /** * Sets the worker {@link EventLoopGroup} which is responsible for performing socket I/O and running * {@link Service#serve(ServiceRequestContext, Request)}. * If not set, {@linkplain CommonPools#workerGroup() the common worker group} is used. * * @param shutdownOnStop whether to shut down the worker {@link EventLoopGroup} * when the {@link Server} stops */ public ServerBuilder workerGroup(EventLoopGroup workerGroup, boolean shutdownOnStop) { this.workerGroup = requireNonNull(workerGroup, "workerGroup"); shutdownWorkerGroupOnStop = shutdownOnStop; return this; } /** * Sets the maximum allowed number of open connections. */ public ServerBuilder maxNumConnections(int maxNumConnections) { this.maxNumConnections = ServerConfig.validateMaxNumConnections(maxNumConnections); return this; } @VisibleForTesting int maxNumConnections() { return maxNumConnections; } /** * Sets the idle timeout of a connection in milliseconds for keep-alive. * * @param idleTimeoutMillis the timeout in milliseconds. {@code 0} disables the timeout. */ public ServerBuilder idleTimeoutMillis(long idleTimeoutMillis) { return idleTimeout(Duration.ofMillis(idleTimeoutMillis)); } /** * Sets the idle timeout of a connection for keep-alive. * * @param idleTimeout the timeout. {@code 0} disables the timeout. */ public ServerBuilder idleTimeout(Duration idleTimeout) { requireNonNull(idleTimeout, "idleTimeout"); idleTimeoutMillis = ServerConfig.validateIdleTimeoutMillis(idleTimeout.toMillis()); return this; } /** * Sets the default timeout of a request in milliseconds. * * @param defaultRequestTimeoutMillis the timeout in milliseconds. {@code 0} disables the timeout. */ public ServerBuilder defaultRequestTimeoutMillis(long defaultRequestTimeoutMillis) { this.defaultRequestTimeoutMillis = validateDefaultRequestTimeoutMillis(defaultRequestTimeoutMillis); return this; } /** * Sets the default timeout of a request. * * @param defaultRequestTimeout the timeout. {@code 0} disables the timeout. */ public ServerBuilder defaultRequestTimeout(Duration defaultRequestTimeout) { return defaultRequestTimeoutMillis( requireNonNull(defaultRequestTimeout, "defaultRequestTimeout").toMillis()); } /** * Sets the maximum allowed length of the content decoded at the session layer. * e.g. the content length of an HTTP request. * * @param defaultMaxRequestLength the maximum allowed length. {@code 0} disables the length limit. */ public ServerBuilder defaultMaxRequestLength(long defaultMaxRequestLength) { this.defaultMaxRequestLength = validateDefaultMaxRequestLength(defaultMaxRequestLength); return this; } /** * Sets the maximum length of an HTTP/1 response initial line. */ public ServerBuilder maxHttp1InitialLineLength(int maxHttp1InitialLineLength) { this.maxHttp1InitialLineLength = validateNonNegative( maxHttp1InitialLineLength, "maxHttp1InitialLineLength"); return this; } /** * Sets the maximum length of all headers in an HTTP/1 response. */ public ServerBuilder maxHttp1HeaderSize(int maxHttp1HeaderSize) { this.maxHttp1HeaderSize = validateNonNegative(maxHttp1HeaderSize, "maxHttp1HeaderSize"); return this; } /** * Sets the maximum length of each chunk in an HTTP/1 response content. * The content or a chunk longer than this value will be split into smaller chunks * so that their lengths never exceed it. */ public ServerBuilder maxHttp1ChunkSize(int maxHttp1ChunkSize) { this.maxHttp1ChunkSize = validateNonNegative(maxHttp1ChunkSize, "maxHttp1ChunkSize"); return this; } /** * Sets the amount of time to wait after calling {@link Server#stop()} for * requests to go away before actually shutting down. * * @param quietPeriodMillis the number of milliseconds to wait for active * requests to go end before shutting down. 0 means the server will * stop right away without waiting. * @param timeoutMillis the number of milliseconds to wait before shutting * down the server regardless of active requests. This should be set to * a time greater than {@code quietPeriodMillis} to ensure the server * shuts down even if there is a stuck request. */ public ServerBuilder gracefulShutdownTimeout(long quietPeriodMillis, long timeoutMillis) { return gracefulShutdownTimeout( Duration.ofMillis(quietPeriodMillis), Duration.ofMillis(timeoutMillis)); } /** * Sets the amount of time to wait after calling {@link Server#stop()} for * requests to go away before actually shutting down. * * @param quietPeriod the number of milliseconds to wait for active * requests to go end before shutting down. {@link Duration#ZERO} means * the server will stop right away without waiting. * @param timeout the number of milliseconds to wait before shutting * down the server regardless of active requests. This should be set to * a time greater than {@code quietPeriod} to ensure the server shuts * down even if there is a stuck request. */ public ServerBuilder gracefulShutdownTimeout(Duration quietPeriod, Duration timeout) { requireNonNull(quietPeriod, "quietPeriod"); requireNonNull(timeout, "timeout"); gracefulShutdownQuietPeriod = validateNonNegative(quietPeriod, "quietPeriod"); gracefulShutdownTimeout = validateNonNegative(timeout, "timeout"); ServerConfig.validateGreaterThanOrEqual(gracefulShutdownTimeout, "quietPeriod", gracefulShutdownQuietPeriod, "timeout"); return this; } /** * Sets the {@link Executor} dedicated to the execution of blocking tasks or invocations. * If not set, {@linkplain CommonPools#blockingTaskExecutor() the common pool} is used. */ public ServerBuilder blockingTaskExecutor(Executor blockingTaskExecutor) { this.blockingTaskExecutor = requireNonNull(blockingTaskExecutor, "blockingTaskExecutor"); return this; } /** * Sets the {@link MeterRegistry} that collects various stats. */ public ServerBuilder meterRegistry(MeterRegistry meterRegistry) { this.meterRegistry = requireNonNull(meterRegistry, "meterRegistry"); return this; } /** * Sets the prefix of {@linkplain ServiceRequestContext#logger() service logger} names. * The default value is "{@value #DEFAULT_SERVICE_LOGGER_PREFIX}". A service logger name prefix must be * a string of valid Java identifier names concatenated by period ({@code '.'}), such as a package name. */ public ServerBuilder serviceLoggerPrefix(String serviceLoggerPrefix) { this.serviceLoggerPrefix = ServiceConfig.validateLoggerName(serviceLoggerPrefix, "serviceLoggerPrefix"); return this; } /** * Sets the format of this {@link Server}'s access log. The specified {@code accessLogFormat} would be * parsed by {@link AccessLogWriters#custom(String)}. */ public ServerBuilder accessLogFormat(String accessLogFormat) { accessLogWriter = AccessLogWriters.custom(requireNonNull(accessLogFormat, "accessLogFormat")); return this; } /** * Sets an access log writer of this {@link Server}. {@link AccessLogWriters#disabled()} is used by default. * * @see AccessLogWriters to find pre-defined access log writers. */ @SuppressWarnings("unchecked") public ServerBuilder accessLogWriter(Consumer accessLogWriter) { this.accessLogWriter = (Consumer) requireNonNull(accessLogWriter, "accessLogWriter"); return this; } /** * Sets the maximum size of additional data for PROXY protocol. The default value of this property is * {@value #PROXY_PROTOCOL_DEFAULT_MAX_TLV_SIZE}. * *

Note: limiting TLV size only affects processing of v2, binary headers. Also, as allowed by * the 1.5 spec, TLV data is currently ignored. For maximum performance, it would be best to configure * your upstream proxy host to NOT send TLV data and set this property to {@code 0}. */ public ServerBuilder proxyProtocolMaxTlvSize(int proxyProtocolMaxTlvSize) { checkArgument(proxyProtocolMaxTlvSize >= 0, "proxyProtocolMaxTlvSize: %s (expected: >= 0)", proxyProtocolMaxTlvSize); this.proxyProtocolMaxTlvSize = proxyProtocolMaxTlvSize; return this; } /** * Sets the {@link SslContext} of the default {@link VirtualHost}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public ServerBuilder tls(SslContext sslContext) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.tls(sslContext); return this; } /** * Configures SSL or TLS of the default {@link VirtualHost} from the specified {@code keyCertChainFile} * and cleartext {@code keyFile}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public ServerBuilder tls(File keyCertChainFile, File keyFile) throws SSLException { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.tls(keyCertChainFile, keyFile); return this; } /** * Configures SSL or TLS of the default {@link VirtualHost} from the specified {@code keyCertChainFile}, * {@code keyFile} and {@code keyPassword}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public ServerBuilder tls( File keyCertChainFile, File keyFile, @Nullable String keyPassword) throws SSLException { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.tls(keyCertChainFile, keyFile, keyPassword); return this; } /** * Configures SSL or TLS of the default {@link VirtualHost} with an auto-generated self-signed * certificate. Note: You should never use this in production but only for a testing * purpose. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already * @throws CertificateException if failed to generate a self-signed certificate */ public ServerBuilder tlsSelfSigned() throws SSLException, CertificateException { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.tlsSelfSigned(); return this; } /** * Sets the {@link SslContext} of the default {@link VirtualHost}. * * @deprecated Use {@link #tls(SslContext)}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ @Deprecated public ServerBuilder sslContext(SslContext sslContext) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.tls(sslContext); return this; } /** * Sets the {@link SslContext} of the default {@link VirtualHost} from the specified * {@link SessionProtocol}, {@code keyCertChainFile} and cleartext {@code keyFile}. * * @deprecated Use {@link #tls(File, File)}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ @Deprecated public ServerBuilder sslContext( SessionProtocol protocol, File keyCertChainFile, File keyFile) throws SSLException { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.sslContext(protocol, keyCertChainFile, keyFile); return this; } /** * Sets the {@link SslContext} of the default {@link VirtualHost} from the specified * {@link SessionProtocol}, {@code keyCertChainFile}, {@code keyFile} and {@code keyPassword}. * * @deprecated Use {@link #tls(File, File, String)}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ @Deprecated public ServerBuilder sslContext( SessionProtocol protocol, File keyCertChainFile, File keyFile, String keyPassword) throws SSLException { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.sslContext(protocol, keyCertChainFile, keyFile, keyPassword); return this; } /** * Binds the specified {@link Service} at the specified path pattern of the default {@link VirtualHost}. * * @deprecated Use {@link #service(String, Service)} instead. */ @Deprecated public ServerBuilder serviceAt(String pathPattern, Service service) { return service(pathPattern, service); } /** * Binds the specified {@link Service} under the specified directory of the default {@link VirtualHost}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public ServerBuilder serviceUnder(String pathPrefix, Service service) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.serviceUnder(pathPrefix, service); return this; } /** * Binds the specified {@link Service} at the specified path pattern of the default {@link VirtualHost}. * e.g. *

    *
  • {@code /login} (no path parameters)
  • *
  • {@code /users/{userId}} (curly-brace style)
  • *
  • {@code /list/:productType/by/:ordering} (colon style)
  • *
  • {@code exact:/foo/bar} (exact match)
  • *
  • {@code prefix:/files} (prefix match)
  • *
  • glob:/~*/downloads/** (glob pattern)
  • *
  • {@code regex:^/files/(?.*)$} (regular expression)
  • *
* * @throws IllegalArgumentException if the specified path pattern is invalid * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public ServerBuilder service(String pathPattern, Service service) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.service(pathPattern, service); return this; } /** * Binds the specified {@link Service} at the specified {@link PathMapping} of the default * {@link VirtualHost}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public ServerBuilder service(PathMapping pathMapping, Service service) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.service(pathMapping, service); return this; } /** * Binds the specified {@link Service} at the specified {@link PathMapping} of the default * {@link VirtualHost}. * * @deprecated Use a logging framework integration such as {@code RequestContextExportingAppender} in * {@code armeria-logback}. */ @Deprecated public ServerBuilder service(PathMapping pathMapping, Service service, String loggerName) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.service(pathMapping, service, loggerName); return this; } /** * Binds the specified {@link ServiceWithPathMappings} at multiple {@link PathMapping}s * of the default {@link VirtualHost}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public > ServerBuilder service(T serviceWithPathMappings) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.service(serviceWithPathMappings); return this; } /** * Decorates and binds the specified {@link ServiceWithPathMappings} at multiple {@link PathMapping}s * of the default {@link VirtualHost}. * * @throws IllegalStateException if the default {@link VirtualHost} has been set via * {@link #defaultVirtualHost(VirtualHost)} already */ public , R extends Service> ServerBuilder service(T serviceWithPathMappings, Function decorator) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.service(serviceWithPathMappings, decorator); return this; } /** * Binds the specified annotated service object under the path prefix {@code "/"}. */ public ServerBuilder annotatedService(Object service) { return annotatedService("/", service, Function.identity(), ImmutableList.of()); } /** * Binds the specified annotated service object under the path prefix {@code "/"}. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public ServerBuilder annotatedService(Object service, Object... exceptionHandlersAndConverters) { return annotatedService("/", service, Function.identity(), requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters")); } /** * Binds the specified annotated service object under the path prefix {@code "/"}. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public ServerBuilder annotatedService(Object service, Function, ? extends Service> decorator, Object... exceptionHandlersAndConverters) { return annotatedService("/", service, decorator, requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters")); } /** * Binds the specified annotated service object under the specified path prefix. */ public ServerBuilder annotatedService(String pathPrefix, Object service) { return annotatedService(pathPrefix, service, Function.identity(), ImmutableList.of()); } /** * Binds the specified annotated service object under the specified path prefix. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public ServerBuilder annotatedService(String pathPrefix, Object service, Object... exceptionHandlersAndConverters) { return annotatedService(pathPrefix, service, Function.identity(), requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters")); } /** * Binds the specified annotated service object under the specified path prefix. * * @param exceptionHandlersAndConverters instances of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public ServerBuilder annotatedService(String pathPrefix, Object service, Function, ? extends Service> decorator, Object... exceptionHandlersAndConverters) { return annotatedService(pathPrefix, service, decorator, ImmutableList.copyOf(requireNonNull(exceptionHandlersAndConverters, "exceptionHandlersAndConverters"))); } /** * Binds the specified annotated service object under the specified path prefix. * * @param exceptionHandlersAndConverters an iterable object of {@link ExceptionHandlerFunction}, * {@link RequestConverterFunction} and/or * {@link ResponseConverterFunction} */ public ServerBuilder annotatedService(String pathPrefix, Object service, Function, ? extends Service> decorator, Iterable exceptionHandlersAndConverters) { defaultVirtualHostBuilderUpdated(); defaultVirtualHostBuilder.annotatedService(pathPrefix, service, decorator, exceptionHandlersAndConverters); return this; } private void defaultVirtualHostBuilderUpdated() { updatedDefaultVirtualHostBuilder = true; if (defaultVirtualHost != null) { throw new IllegalStateException("ServerBuilder.defaultVirtualHost() invoked already."); } } /** * Sets the default {@link VirtualHost}, which is used when no other {@link VirtualHost}s match the * host name of a client request. e.g. the {@code "Host"} header in HTTP or host name in TLS SNI extension * * @throws IllegalStateException * if other default {@link VirtualHost} builder methods have been invoked already, including: *
    *
  • {@link #tls(SslContext)}
  • *
  • {@link #service(String, Service)}
  • *
  • {@link #serviceUnder(String, Service)}
  • *
  • {@link #service(PathMapping, Service)}
  • *
* * @see #virtualHost(VirtualHost) */ public ServerBuilder defaultVirtualHost(VirtualHost defaultVirtualHost) { requireNonNull(defaultVirtualHost, "defaultVirtualHost"); if (updatedDefaultVirtualHostBuilder) { throw new IllegalStateException("invoked other default VirtualHost builder methods already"); } this.defaultVirtualHost = defaultVirtualHost; return this; } /** * Adds the specified {@link ServerListener}. */ public ServerBuilder serverListener(ServerListener serverListener) { requireNonNull(serverListener, "serverListener"); serverListeners.add(serverListener); return this; } /** * Adds the name-based virtual host * specified by {@link VirtualHost}. * * @return {@link VirtualHostBuilder} for build the default virtual host */ public ChainedVirtualHostBuilder withDefaultVirtualHost() { defaultVirtualHostBuilderUpdated(); return defaultVirtualHostBuilder; } /** * Adds the name-based virtual host * specified by {@link VirtualHost}. * * @param hostnamePattern virtual host name regular expression * @return {@link VirtualHostBuilder} for build the virtual host */ public ChainedVirtualHostBuilder withVirtualHost(String hostnamePattern) { final ChainedVirtualHostBuilder virtualHostBuilder = new ChainedVirtualHostBuilder(hostnamePattern, this); virtualHostBuilders.add(virtualHostBuilder); return virtualHostBuilder; } /** * Adds the name-based virtual host * specified by {@link VirtualHost}. * * @param defaultHostname default hostname of this virtual host * @param hostnamePattern virtual host name regular expression * @return {@link VirtualHostBuilder} for build the virtual host */ public ChainedVirtualHostBuilder withVirtualHost(String defaultHostname, String hostnamePattern) { final ChainedVirtualHostBuilder virtualHostBuilder = new ChainedVirtualHostBuilder(defaultHostname, hostnamePattern, this); virtualHostBuilders.add(virtualHostBuilder); return virtualHostBuilder; } /** * Decorates all {@link Service}s with the specified {@code decorator}. * * @param decorator the {@link Function} that decorates a {@link Service} * @param the type of the {@link Service} being decorated * @param the type of the {@link Service} {@code decorator} will produce */ public , R extends Service> ServerBuilder decorator(Function decorator) { requireNonNull(decorator, "decorator"); @SuppressWarnings("unchecked") final Function, Service> castDecorator = (Function, Service>) decorator; if (this.decorator != null) { this.decorator = this.decorator.andThen(castDecorator); } else { this.decorator = castDecorator; } return this; } /** * Sets the {@link RejectedPathMappingHandler} which will be invoked when an attempt to bind * a {@link Service} at a certain {@link PathMapping} is rejected. By default, the duplicate * mappings are logged at WARN level. */ public ServerBuilder rejectedPathMappingHandler(RejectedPathMappingHandler handler) { rejectedPathMappingHandler = requireNonNull(handler, "handler"); return this; } /** * Returns a newly-created {@link Server} based on the configuration properties set so far. */ public Server build() { final VirtualHost defaultVirtualHost; if (this.defaultVirtualHost != null) { defaultVirtualHost = this.defaultVirtualHost.decorate(decorator); } else { defaultVirtualHost = defaultVirtualHostBuilder.build().decorate(decorator); } virtualHostBuilders.forEach(vhb -> virtualHosts.add(vhb.build())); final List virtualHosts; if (decorator != null) { virtualHosts = this.virtualHosts.stream() .map(h -> h.decorate(decorator)) .collect(Collectors.toList()); } else { virtualHosts = this.virtualHosts; } final List ports; // Pre-populate the domain name mapping for later matching. final DomainNameMapping sslContexts; final SslContext defaultSslContext = findDefaultSslContext(defaultVirtualHost, virtualHosts); this.ports.forEach( port -> checkState(port.protocols().stream().anyMatch(p -> p != PROXY), "protocols: %s (expected: at least one %s or %s)", port.protocols(), HTTP, HTTPS)); if (defaultSslContext == null) { sslContexts = null; if (!this.ports.isEmpty()) { ports = ImmutableList.copyOf(this.ports); for (final ServerPort p : ports) { if (p.hasTls()) { throw new IllegalArgumentException("TLS not configured; cannot serve HTTPS"); } } } else { ports = ImmutableList.of(new ServerPort(0, HTTP)); } } else { if (!this.ports.isEmpty()) { ports = ImmutableList.copyOf(this.ports); } else { ports = ImmutableList.of(new ServerPort(0, HTTPS)); } final DomainNameMappingBuilder mappingBuilder = new DomainNameMappingBuilder<>(defaultSslContext); for (VirtualHost h : virtualHosts) { final SslContext sslCtx = h.sslContext(); if (sslCtx != null) { mappingBuilder.add(h.hostnamePattern(), sslCtx); } } sslContexts = mappingBuilder.build(); } final Server server = new Server(new ServerConfig( ports, normalizeDefaultVirtualHost(defaultVirtualHost, defaultSslContext), virtualHosts, workerGroup, shutdownWorkerGroupOnStop, maxNumConnections, idleTimeoutMillis, defaultRequestTimeoutMillis, defaultMaxRequestLength, maxHttp1InitialLineLength, maxHttp1HeaderSize, maxHttp1ChunkSize, gracefulShutdownQuietPeriod, gracefulShutdownTimeout, blockingTaskExecutor, meterRegistry, serviceLoggerPrefix, accessLogWriter, proxyProtocolMaxTlvSize, channelOptions, childChannelOptions), sslContexts); serverListeners.forEach(server::addListener); return server; } private VirtualHost normalizeDefaultVirtualHost(VirtualHost h, @Nullable SslContext defaultSslContext) { final SslContext sslCtx = h.sslContext() != null ? h.sslContext() : defaultSslContext; return new VirtualHost( h.defaultHostname(), "*", sslCtx, h.serviceConfigs().stream().map( e -> new ServiceConfig(e.pathMapping(), e.service(), e.loggerName().orElse(null))) .collect(Collectors.toList()), h.producibleMediaTypes(), rejectedPathMappingHandler); } @Nullable private static SslContext findDefaultSslContext(VirtualHost defaultVirtualHost, List virtualHosts) { SslContext lastSslContext = null; for (VirtualHost h : virtualHosts) { if (h.sslContext() != null) { lastSslContext = h.sslContext(); } } if (defaultVirtualHost.sslContext() != null) { lastSslContext = defaultVirtualHost.sslContext(); } return lastSslContext; } @Override public String toString() { return ServerConfig.toString( getClass(), ports, defaultVirtualHost, virtualHosts, workerGroup, shutdownWorkerGroupOnStop, maxNumConnections, idleTimeoutMillis, defaultRequestTimeoutMillis, defaultMaxRequestLength, maxHttp1InitialLineLength, maxHttp1HeaderSize, maxHttp1ChunkSize, proxyProtocolMaxTlvSize, gracefulShutdownQuietPeriod, gracefulShutdownTimeout, blockingTaskExecutor, meterRegistry, serviceLoggerPrefix, accessLogWriter, channelOptions, childChannelOptions ); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy