diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java b/core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java index 62c93410e38..b8141f2f809 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java @@ -158,7 +158,10 @@ public ClientFactoryBuilder workerGroup(int numThreads) { /** * Sets the factory that creates an {@link EventLoopScheduler} which is responsible for assigning an * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. + * @deprecated This factory method lacks the ability to build a sophisticated scheduler. + * Use the method that requires an EventLoopSchedulerFactory instead. */ + @Deprecated public ClientFactoryBuilder eventLoopSchedulerFactory( Function eventLoopSchedulerFactory) { requireNonNull(eventLoopSchedulerFactory, "eventLoopSchedulerFactory"); @@ -169,6 +172,19 @@ public ClientFactoryBuilder eventLoopSchedulerFactory( return this; } + /** + * Sets the factory that creates an {@link EventLoopScheduler} which is responsible for assigning an + * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. + * @deprecated This factory method lacks the ability to build a sophisticated scheduler. + * Use the method that requires an EventLoopSchedulerFactory instead. + */ + @Deprecated + public ClientFactoryBuilder eventLoopSchedulerFactory(EventLoopSchedulerFactory eventLoopSchedulerFactory){ + requireNonNull(eventLoopSchedulerFactory, "eventLoopSchedulerFactory"); + option(ClientFactoryOptions.CUSTOM_EVENT_LOOP_SCHEDULER_FACTORY, eventLoopSchedulerFactory); + return this; + } + /** * Sets the maximum number of {@link EventLoop}s which will be used to handle HTTP/1.1 connections * except the ones specified by {@link #maxNumEventLoopsFunction(ToIntFunction)}. diff --git a/core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java b/core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java index eeac4c9fa53..e9619e614e6 100644 --- a/core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java +++ b/core/src/main/java/com/linecorp/armeria/client/ClientFactoryOptions.java @@ -1,3 +1,4 @@ + /* * Copyright 2019 LINE Corporation * @@ -53,559 +54,581 @@ public final class ClientFactoryOptions extends AbstractOptions, ClientFactoryOptionValue> { - /** - * The worker {@link EventLoopGroup}. - */ - public static final ClientFactoryOption WORKER_GROUP = - ClientFactoryOption.define("WORKER_GROUP", CommonPools.workerGroup()); - - /** - * Whether to shut down the worker {@link EventLoopGroup} when the {@link ClientFactory} is closed. - */ - public static final ClientFactoryOption SHUTDOWN_WORKER_GROUP_ON_CLOSE = - ClientFactoryOption.define("SHUTDOWN_WORKER_GROUP_ON_CLOSE", false); - - /** - * The factory that creates an {@link EventLoopScheduler} which is responsible for assigning an - * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. - */ - public static final ClientFactoryOption> - EVENT_LOOP_SCHEDULER_FACTORY = ClientFactoryOption.define( - "EVENT_LOOP_SCHEDULER_FACTORY", - eventLoopGroup -> new DefaultEventLoopScheduler(eventLoopGroup, 0, 0, ImmutableList.of())); - - /** - * The {@link Consumer} which can arbitrarily configure the {@link SslContextBuilder} that will be - * applied to the SSL session. - */ - public static final ClientFactoryOption> TLS_CUSTOMIZER = - ClientFactoryOption.define("TLS_CUSTOMIZER", b -> { /* no-op */ }); - - /** - * Whether to allow the bad cipher suites listed in - * RFC7540 for TLS handshake. - * - *

Note that enabling this option increases the security risk of your connection. - * Use it only when you must communicate with a legacy system that does not support - * secure cipher suites. - * See Section 9.2.2, RFC7540 for - * more information. - * - * @deprecated It's not recommended to enable this option. Use it only when you have no other way to - * communicate with an insecure peer than this. - */ - @Deprecated - public static final ClientFactoryOption TLS_ALLOW_UNSAFE_CIPHERS = - ClientFactoryOption.define("tlsAllowUnsafeCiphers", Flags.tlsAllowUnsafeCiphers()); - - /** - * The factory that creates an {@link AddressResolverGroup} which resolves remote addresses into - * {@link InetSocketAddress}es. - */ - public static final ClientFactoryOption>> ADDRESS_RESOLVER_GROUP_FACTORY = - ClientFactoryOption.define("ADDRESS_RESOLVER_GROUP_FACTORY", - eventLoopGroup -> new DnsResolverGroupBuilder().build(eventLoopGroup)); - - /** - * The HTTP/2 initial connection flow-control - * window size. - */ - public static final ClientFactoryOption HTTP2_INITIAL_CONNECTION_WINDOW_SIZE = - ClientFactoryOption.define("HTTP2_INITIAL_CONNECTION_WINDOW_SIZE", - Flags.defaultHttp2InitialConnectionWindowSize()); - - /** - * The SETTINGS_INITIAL_WINDOW_SIZE - * for HTTP/2 stream-level flow control. - */ - public static final ClientFactoryOption HTTP2_INITIAL_STREAM_WINDOW_SIZE = - ClientFactoryOption.define("HTTP2_INITIAL_STREAM_WINDOW_SIZE", - Flags.defaultHttp2InitialStreamWindowSize()); - - /** - * The SETTINGS_MAX_FRAME_SIZE - * that indicates the size of the largest frame payload that this client is willing to receive. - */ - public static final ClientFactoryOption HTTP2_MAX_FRAME_SIZE = - ClientFactoryOption.define("HTTP2_MAX_FRAME_SIZE", Flags.defaultHttp2MaxFrameSize()); - - /** - * The HTTP/2 SETTINGS_MAX_HEADER_LIST_SIZE - * that indicates the maximum size of header list that the client is prepared to accept, in octets. - */ - public static final ClientFactoryOption HTTP2_MAX_HEADER_LIST_SIZE = - ClientFactoryOption.define("HTTP2_MAX_HEADER_LIST_SIZE", Flags.defaultHttp2MaxHeaderListSize()); - - /** - * The maximum length of an HTTP/1 response initial line. - */ - public static final ClientFactoryOption HTTP1_MAX_INITIAL_LINE_LENGTH = - ClientFactoryOption.define("HTTP1_MAX_INITIAL_LINE_LENGTH", - Flags.defaultHttp1MaxInitialLineLength()); - - /** - * The maximum length of all headers in an HTTP/1 response. - */ - public static final ClientFactoryOption HTTP1_MAX_HEADER_SIZE = - ClientFactoryOption.define("HTTP1_MAX_HEADER_SIZE", Flags.defaultHttp1MaxHeaderSize()); - - /** - * The maximum length of each chunk in an HTTP/1 response content. - */ - public static final ClientFactoryOption HTTP1_MAX_CHUNK_SIZE = - ClientFactoryOption.define("HTTP1_MAX_CHUNK_SIZE", Flags.defaultHttp1MaxChunkSize()); - - /** - * The idle timeout of a socket connection in milliseconds. - */ - public static final ClientFactoryOption IDLE_TIMEOUT_MILLIS = - ClientFactoryOption.define("IDLE_TIMEOUT_MILLIS", Flags.defaultClientIdleTimeoutMillis()); - - /** - * If the idle timeout is reset when an HTTP/2 PING frame or the response of {@code "OPTIONS * HTTP/1.1"} - * is received. - */ - @UnstableApi - public static final ClientFactoryOption KEEP_ALIVE_ON_PING = - ClientFactoryOption.define("KEEP_ALIVE_ON_PING", Flags.defaultClientKeepAliveOnPing()); - - /** - * The PING interval in milliseconds. - * When neither read nor write was performed for the specified period of time, - * a PING frame is sent for HTTP/2 - * or an OPTIONS request with - * an asterisk ("*") is sent for HTTP/1. - */ - public static final ClientFactoryOption PING_INTERVAL_MILLIS = - ClientFactoryOption.define("PING_INTERVAL_MILLIS", Flags.defaultPingIntervalMillis()); - - /** - * The client-side max age of a connection for keep-alive in milliseconds. - * If the value is greater than {@code 0}, a connection is disconnected after the specified - * amount of time since the connection was established. - * This option is disabled by default, which means unlimited. - */ - public static final ClientFactoryOption MAX_CONNECTION_AGE_MILLIS = - ClientFactoryOption.define("MAX_CONNECTION_AGE_MILLIS", clampedDefaultMaxClientConnectionAge()); - - private static long clampedDefaultMaxClientConnectionAge() { - final long connectionAgeMillis = Flags.defaultMaxClientConnectionAgeMillis(); - if (connectionAgeMillis > 0 && connectionAgeMillis < MIN_MAX_CONNECTION_AGE_MILLIS) { - return MIN_MAX_CONNECTION_AGE_MILLIS; - } - return connectionAgeMillis; - } - - /** - * The client-side maximum allowed number of requests that can be sent through one connection. - * This option is disabled by default, which means unlimited. - */ - public static final ClientFactoryOption MAX_NUM_REQUESTS_PER_CONNECTION = - ClientFactoryOption.define("MAX_NUM_REQUESTS_PER_CONNECTION", - Flags.defaultMaxClientNumRequestsPerConnection()); - - /** - * Whether to send an HTTP/2 preface string instead of an HTTP/1 upgrade request to negotiate - * the protocol version of a cleartext HTTP connection. - */ - public static final ClientFactoryOption USE_HTTP2_PREFACE = - ClientFactoryOption.define("USE_HTTP2_PREFACE", Flags.defaultUseHttp2Preface()); - - /** - * Whether to use HTTP/1.1 instead of HTTP/2. If enabled, the client will not attempt to upgrade to - * HTTP/2 for {@link SessionProtocol#HTTP} and {@link SessionProtocol#HTTPS}. - */ - @UnstableApi - public static final ClientFactoryOption PREFER_HTTP1 = - ClientFactoryOption.define("PREFER_HTTP1", Flags.defaultPreferHttp1()); - - /** - * Whether to use HTTP/2 without ALPN. This is useful if you want to communicate with an HTTP/2 - * server over TLS but the server does not support ALPN. - */ - @UnstableApi - public static final ClientFactoryOption USE_HTTP2_WITHOUT_ALPN = - ClientFactoryOption.define("USE_HTTP2_WITHOUT_ALPN", Flags.defaultUseHttp2WithoutAlpn()); - - /** - * Whether to use HTTP pipelining for - * HTTP/1 connections. - */ - public static final ClientFactoryOption USE_HTTP1_PIPELINING = - ClientFactoryOption.define("USE_HTTP1_PIPELINING", Flags.defaultUseHttp1Pipelining()); - - /** - * The listener which is notified on a connection pool event. - */ - public static final ClientFactoryOption CONNECTION_POOL_LISTENER = - ClientFactoryOption.define("CONNECTION_POOL_LISTENER", ConnectionPoolListener.noop()); - - /** - * The {@link MeterRegistry} which collects various stats. - */ - public static final ClientFactoryOption METER_REGISTRY = - ClientFactoryOption.define("METER_REGISTRY", Flags.meterRegistry()); - - /** - * The {@link ProxyConfigSelector} which determines the {@link ProxyConfig} to be used. - */ - public static final ClientFactoryOption PROXY_CONFIG_SELECTOR = - ClientFactoryOption.define("PROXY_CONFIG_SELECTOR", ProxyConfigSelector.of(ProxyConfig.direct())); - - /** - * The {@link Http1HeaderNaming} which converts a lower-cased HTTP/2 header name into - * another HTTP/1 header name. - */ - public static final ClientFactoryOption HTTP1_HEADER_NAMING = - ClientFactoryOption.define("HTTP1_HEADER_NAMING", Http1HeaderNaming.ofDefault()); - - /** - * The {@link ChannelOption}s of the sockets created by the {@link ClientFactory}. - */ - public static final ClientFactoryOption, Object>> CHANNEL_OPTIONS = - ClientFactoryOption.define("CHANNEL_OPTIONS", ImmutableMap.of(), newOptions -> { - for (ChannelOption channelOption : ChannelUtil.prohibitedOptions()) { - checkArgument(!newOptions.containsKey(channelOption), - "prohibited channel option: %s", channelOption); - } - return newOptions; - }, (oldValue, newValue) -> { - final Map, Object> newOptions = newValue.value(); - if (newOptions.isEmpty()) { - return oldValue; - } - final Map, Object> oldOptions = oldValue.value(); - if (oldOptions.isEmpty()) { - return newValue; - } - final ImmutableMap.Builder, Object> builder = - ImmutableMap.builderWithExpectedSize(oldOptions.size() + newOptions.size()); - oldOptions.forEach((key, value) -> { - if (!newOptions.containsKey(key)) { - builder.put(key, value); - } - }); - builder.putAll(newOptions); - return newValue.option().newValue(builder.build()); + /** + * The worker {@link EventLoopGroup}. + */ + public static final ClientFactoryOption WORKER_GROUP = + ClientFactoryOption.define("WORKER_GROUP", CommonPools.workerGroup()); + + /** + * Whether to shut down the worker {@link EventLoopGroup} when the {@link ClientFactory} is closed. + */ + public static final ClientFactoryOption SHUTDOWN_WORKER_GROUP_ON_CLOSE = + ClientFactoryOption.define("SHUTDOWN_WORKER_GROUP_ON_CLOSE", false); + + /** + * The factory that creates an {@link EventLoopScheduler} which is responsible + * for assigning an + * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. + * @deprecated This factory method lacks the ability to build a sophisticated scheduler. + * Use the Custom_EVENT_LOOP_SCHEDULER_FACTORY instead. + */ + @Deprecated + public static final ClientFactoryOption> + EVENT_LOOP_SCHEDULER_FACTORY = ClientFactoryOption.define( + "EVENT_LOOP_SCHEDULER_FACTORY", + eventLoopGroup -> new DefaultEventLoopScheduler(eventLoopGroup, 0, 0, ImmutableList.of())); + + /** + * The factory that creates an {@link EventLoopScheduler} which is responsible + * for assigning an + * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. + */ + public static final ClientFactoryOption CUSTOM_EVENT_LOOP_SCHEDULER_FACTORY = ClientFactoryOption + .define("EVENT_LOOP_SCHEDULER_FACTORY", + (eventLoopGroup, + maxNumEventLoopFunctions, + maxNumEventLoopsPerEndpoint, + maxNumEventLoopsPerHttp1Endpoint, + idleTimeOutMillis) -> { + return new DefaultEventLoopScheduler(eventLoopGroup, + maxNumEventLoopsPerEndpoint, + maxNumEventLoopsPerHttp1Endpoint, + maxNumEventLoopFunctions); + }); + + /** + * The {@link Consumer} which can arbitrarily configure the {@link SslContextBuilder} that will be + * applied to the SSL session. + */ + public static final ClientFactoryOption> TLS_CUSTOMIZER = + ClientFactoryOption.define("TLS_CUSTOMIZER", b -> { /* no-op */ }); + + /** + * Whether to allow the bad cipher suites listed in + * RFC7540 for TLS handshake. + * + *

Note that enabling this option increases the security risk of your connection. + * Use it only when you must communicate with a legacy system that does not support + * secure cipher suites. + * See Section 9.2.2, RFC7540 for + * more information. + * + * @deprecated It's not recommended to enable this option. Use it only when you have no other way to + * communicate with an insecure peer than this. + */ + @Deprecated + public static final ClientFactoryOption TLS_ALLOW_UNSAFE_CIPHERS = + ClientFactoryOption.define("tlsAllowUnsafeCiphers", Flags.tlsAllowUnsafeCiphers()); + + /** + * The factory that creates an {@link AddressResolverGroup} which resolves remote addresses into + * {@link InetSocketAddress}es. + */ + public static final ClientFactoryOption>> ADDRESS_RESOLVER_GROUP_FACTORY = + ClientFactoryOption.define("ADDRESS_RESOLVER_GROUP_FACTORY", + eventLoopGroup -> new DnsResolverGroupBuilder().build(eventLoopGroup)); + + /** + * The HTTP/2 initial connection flow-control + * window size. + */ + public static final ClientFactoryOption HTTP2_INITIAL_CONNECTION_WINDOW_SIZE = + ClientFactoryOption.define("HTTP2_INITIAL_CONNECTION_WINDOW_SIZE", + Flags.defaultHttp2InitialConnectionWindowSize()); + + /** + * The SETTINGS_INITIAL_WINDOW_SIZE + * for HTTP/2 stream-level flow control. + */ + public static final ClientFactoryOption HTTP2_INITIAL_STREAM_WINDOW_SIZE = + ClientFactoryOption.define("HTTP2_INITIAL_STREAM_WINDOW_SIZE", + Flags.defaultHttp2InitialStreamWindowSize()); + + /** + * The SETTINGS_MAX_FRAME_SIZE + * that indicates the size of the largest frame payload that this client is willing to receive. + */ + public static final ClientFactoryOption HTTP2_MAX_FRAME_SIZE = + ClientFactoryOption.define("HTTP2_MAX_FRAME_SIZE", Flags.defaultHttp2MaxFrameSize()); + + /** + * The HTTP/2 SETTINGS_MAX_HEADER_LIST_SIZE + * that indicates the maximum size of header list that the client is prepared to accept, in octets. + */ + public static final ClientFactoryOption HTTP2_MAX_HEADER_LIST_SIZE = + ClientFactoryOption.define("HTTP2_MAX_HEADER_LIST_SIZE", Flags.defaultHttp2MaxHeaderListSize()); + + /** + * The maximum length of an HTTP/1 response initial line. + */ + public static final ClientFactoryOption HTTP1_MAX_INITIAL_LINE_LENGTH = + ClientFactoryOption.define("HTTP1_MAX_INITIAL_LINE_LENGTH", + Flags.defaultHttp1MaxInitialLineLength()); + + /** + * The maximum length of all headers in an HTTP/1 response. + */ + public static final ClientFactoryOption HTTP1_MAX_HEADER_SIZE = + ClientFactoryOption.define("HTTP1_MAX_HEADER_SIZE", Flags.defaultHttp1MaxHeaderSize()); + + /** + * The maximum length of each chunk in an HTTP/1 response content. + */ + public static final ClientFactoryOption HTTP1_MAX_CHUNK_SIZE = + ClientFactoryOption.define("HTTP1_MAX_CHUNK_SIZE", Flags.defaultHttp1MaxChunkSize()); + + /** + * The idle timeout of a socket connection in milliseconds. + */ + public static final ClientFactoryOption IDLE_TIMEOUT_MILLIS = + ClientFactoryOption.define("IDLE_TIMEOUT_MILLIS", Flags.defaultClientIdleTimeoutMillis()); + + /** + * If the idle timeout is reset when an HTTP/2 PING frame or the response of {@code "OPTIONS * HTTP/1.1"} + * is received. + */ + @UnstableApi + public static final ClientFactoryOption KEEP_ALIVE_ON_PING = + ClientFactoryOption.define("KEEP_ALIVE_ON_PING", Flags.defaultClientKeepAliveOnPing()); + + /** + * The PING interval in milliseconds. + * When neither read nor write was performed for the specified period of time, + * a PING frame is sent for HTTP/2 + * or an OPTIONS request with + * an asterisk ("*") is sent for HTTP/1. + */ + public static final ClientFactoryOption PING_INTERVAL_MILLIS = + ClientFactoryOption.define("PING_INTERVAL_MILLIS", Flags.defaultPingIntervalMillis()); + + /** + * The client-side max age of a connection for keep-alive in milliseconds. + * If the value is greater than {@code 0}, a connection is disconnected after the specified + * amount of time since the connection was established. + * This option is disabled by default, which means unlimited. + */ + public static final ClientFactoryOption MAX_CONNECTION_AGE_MILLIS = + ClientFactoryOption.define("MAX_CONNECTION_AGE_MILLIS", clampedDefaultMaxClientConnectionAge()); + + private static long clampedDefaultMaxClientConnectionAge() { + final long connectionAgeMillis = Flags.defaultMaxClientConnectionAgeMillis(); + if (connectionAgeMillis > 0 && connectionAgeMillis < MIN_MAX_CONNECTION_AGE_MILLIS) { + return MIN_MAX_CONNECTION_AGE_MILLIS; + } + return connectionAgeMillis; + } + + /** + * The client-side maximum allowed number of requests that can be sent through one connection. + * This option is disabled by default, which means unlimited. + */ + public static final ClientFactoryOption MAX_NUM_REQUESTS_PER_CONNECTION = + ClientFactoryOption.define("MAX_NUM_REQUESTS_PER_CONNECTION", + Flags.defaultMaxClientNumRequestsPerConnection()); + + /** + * Whether to send an HTTP/2 preface string instead of an HTTP/1 upgrade request to negotiate + * the protocol version of a cleartext HTTP connection. + */ + public static final ClientFactoryOption USE_HTTP2_PREFACE = + ClientFactoryOption.define("USE_HTTP2_PREFACE", Flags.defaultUseHttp2Preface()); + + /** + * Whether to use HTTP/1.1 instead of HTTP/2. If enabled, the client will not attempt to upgrade to + * HTTP/2 for {@link SessionProtocol#HTTP} and {@link SessionProtocol#HTTPS}. + */ + @UnstableApi + public static final ClientFactoryOption PREFER_HTTP1 = + ClientFactoryOption.define("PREFER_HTTP1", Flags.defaultPreferHttp1()); + + /** + * Whether to use HTTP/2 without ALPN. This is useful if you want to communicate with an HTTP/2 + * server over TLS but the server does not support ALPN. + */ + @UnstableApi + public static final ClientFactoryOption USE_HTTP2_WITHOUT_ALPN = + ClientFactoryOption.define("USE_HTTP2_WITHOUT_ALPN", Flags.defaultUseHttp2WithoutAlpn()); + + /** + * Whether to use HTTP pipelining for + * HTTP/1 connections. + */ + public static final ClientFactoryOption USE_HTTP1_PIPELINING = + ClientFactoryOption.define("USE_HTTP1_PIPELINING", Flags.defaultUseHttp1Pipelining()); + + /** + * The listener which is notified on a connection pool event. + */ + public static final ClientFactoryOption CONNECTION_POOL_LISTENER = + ClientFactoryOption.define("CONNECTION_POOL_LISTENER", ConnectionPoolListener.noop()); + + /** + * The {@link MeterRegistry} which collects various stats. + */ + public static final ClientFactoryOption METER_REGISTRY = + ClientFactoryOption.define("METER_REGISTRY", Flags.meterRegistry()); + + /** + * The {@link ProxyConfigSelector} which determines the {@link ProxyConfig} to be used. + */ + public static final ClientFactoryOption PROXY_CONFIG_SELECTOR = + ClientFactoryOption.define("PROXY_CONFIG_SELECTOR", ProxyConfigSelector.of(ProxyConfig.direct())); + + /** + * The {@link Http1HeaderNaming} which converts a lower-cased HTTP/2 header name into + * another HTTP/1 header name. + */ + public static final ClientFactoryOption HTTP1_HEADER_NAMING = + ClientFactoryOption.define("HTTP1_HEADER_NAMING", Http1HeaderNaming.ofDefault()); + + /** + * The {@link ChannelOption}s of the sockets created by the {@link ClientFactory}. + */ + public static final ClientFactoryOption, Object>> CHANNEL_OPTIONS = + ClientFactoryOption.define("CHANNEL_OPTIONS", ImmutableMap.of(), newOptions -> { + for (ChannelOption channelOption : ChannelUtil.prohibitedOptions()) { + checkArgument(!newOptions.containsKey(channelOption), + "prohibited channel option: %s", channelOption); + } + return newOptions; + }, (oldValue, newValue) -> { + final Map, Object> newOptions = newValue.value(); + if (newOptions.isEmpty()) { + return oldValue; + } + final Map, Object> oldOptions = oldValue.value(); + if (oldOptions.isEmpty()) { + return newValue; + } + final ImmutableMap.Builder, Object> builder = + ImmutableMap.builderWithExpectedSize(oldOptions.size() + newOptions.size()); + oldOptions.forEach((key, value) -> { + if (!newOptions.containsKey(key)) { + builder.put(key, value); + } }); - - /** - * The {@link Consumer} that customizes the Netty {@link ChannelPipeline}. - * This customizer is run right before {@link ChannelPipeline#connect(SocketAddress)} - * is invoked by Armeria. This customizer is no-op by default. - * - *

Note that usage of this customizer is an advanced - * feature and may produce unintended side effects, including complete - * breakdown. It is not recommended if you are not familiar with - * Armeria and Netty internals. - */ - @UnstableApi - public static final ClientFactoryOption> CHANNEL_PIPELINE_CUSTOMIZER = - ClientFactoryOption.define("CHANNEL_PIPELINE_CUSTOMIZER", v -> { /* no-op */ }); - - private static final ClientFactoryOptions EMPTY = new ClientFactoryOptions(ImmutableList.of()); - - /** - * Returns an empty singleton {@link ClientFactoryOptions}. - */ - public static ClientFactoryOptions of() { - return EMPTY; - } - - /** - * Returns the {@link ClientFactoryOptions} with the specified {@link ClientFactoryOptionValue}s. - */ - public static ClientFactoryOptions of(ClientFactoryOptionValue... values) { - requireNonNull(values, "values"); - if (values.length == 0) { - return EMPTY; - } - return of(Arrays.asList(values)); - } - - /** - * Returns the {@link ClientFactoryOptions} with the specified {@link ClientFactoryOptionValue}s. - */ - public static ClientFactoryOptions of(Iterable> values) { - requireNonNull(values, "values"); - if (values instanceof ClientFactoryOptions) { - return (ClientFactoryOptions) values; - } - return new ClientFactoryOptions(values); - } - - /** - * Merges the specified {@link ClientFactoryOptions} and {@link ClientFactoryOptionValue}s. - * - * @return the merged {@link ClientFactoryOptions} - */ - public static ClientFactoryOptions of(ClientFactoryOptions baseOptions, - ClientFactoryOptionValue... additionalValues) { - requireNonNull(baseOptions, "baseOptions"); - requireNonNull(additionalValues, "additionalValues"); - if (additionalValues.length == 0) { - return baseOptions; - } - return new ClientFactoryOptions(baseOptions, Arrays.asList(additionalValues)); - } - - /** - * Merges the specified {@link ClientFactoryOptions} and {@link ClientFactoryOptionValue}s. - * - * @return the merged {@link ClientFactoryOptions} - */ - public static ClientFactoryOptions of(ClientFactoryOptions baseOptions, - Iterable> additionalValues) { - requireNonNull(baseOptions, "baseOptions"); - requireNonNull(additionalValues, "additionalValues"); - return new ClientFactoryOptions(baseOptions, additionalValues); - } - - private ClientFactoryOptions(Iterable> values) { - super(values); - } - - private ClientFactoryOptions(ClientFactoryOptions baseOptions, - Iterable> additionalValues) { - - super(baseOptions, additionalValues); - } - - /** - * Returns the worker {@link EventLoopGroup}. - */ - public EventLoopGroup workerGroup() { - return get(WORKER_GROUP); - } - - /** - * Returns the flag whether to shut down the worker {@link EventLoopGroup} - * when the {@link ClientFactory} is closed. - */ - public boolean shutdownWorkerGroupOnClose() { - return get(SHUTDOWN_WORKER_GROUP_ON_CLOSE); - } - - /** - * Returns the factory that creates an {@link EventLoopScheduler} which is responsible for assigning an - * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. - */ - public Function eventLoopSchedulerFactory() { - return get(EVENT_LOOP_SCHEDULER_FACTORY); - } - - /** - * Returns the {@link ChannelOption}s of the sockets created by the {@link ClientFactory}. - */ - public Map, Object> channelOptions() { - return get(CHANNEL_OPTIONS); - } - - /** - * Returns the {@link Consumer} which can arbitrarily configure the {@link SslContextBuilder} that will be - * applied to the SSL session. - */ - public Consumer tlsCustomizer() { - return get(TLS_CUSTOMIZER); - } - - /** - * Returns the factory that creates an {@link AddressResolverGroup} which resolves remote addresses into - * {@link InetSocketAddress}es. - */ - public Function> addressResolverGroupFactory() { - - return get(ADDRESS_RESOLVER_GROUP_FACTORY); - } - - /** - * Returns the HTTP/2 initial connection - * flow-control window size. - */ - public int http2InitialConnectionWindowSize() { - return get(HTTP2_INITIAL_CONNECTION_WINDOW_SIZE); - } - - /** - * Returns the SETTINGS_INITIAL_WINDOW_SIZE - * for HTTP/2 stream-level flow control. - */ - public int http2InitialStreamWindowSize() { - return get(HTTP2_INITIAL_STREAM_WINDOW_SIZE); - } - - /** - * Returns the SETTINGS_MAX_FRAME_SIZE - * that indicates the size of the largest frame payload that this client is willing to receive. - */ - public int http2MaxFrameSize() { - return get(HTTP2_MAX_FRAME_SIZE); - } - - /** - * Returns the HTTP/2 - * SETTINGS_MAX_HEADER_LIST_SIZE that indicates the maximum size of header list - * that the client is prepared to accept, in octets. - */ - public long http2MaxHeaderListSize() { - return get(HTTP2_MAX_HEADER_LIST_SIZE); - } - - /** - * Returns the maximum length of an HTTP/1 response initial line. - */ - public int http1MaxInitialLineLength() { - return get(HTTP1_MAX_INITIAL_LINE_LENGTH); - } - - /** - * Returns the maximum length of all headers in an HTTP/1 response. - */ - public int http1MaxHeaderSize() { - return get(HTTP1_MAX_HEADER_SIZE); - } - - /** - * Returns the maximum length of each chunk in an HTTP/1 response content. - */ - public int http1MaxChunkSize() { - return get(HTTP1_MAX_CHUNK_SIZE); - } - - /** - * Returns the idle timeout of a socket connection in milliseconds. - */ - public long idleTimeoutMillis() { - return get(IDLE_TIMEOUT_MILLIS); - } - - /** - * Returns whether to keep connection alive when an HTTP/2 PING frame or the response of - * {@code "OPTIONS * HTTP/1.1"} is received. - */ - @UnstableApi - public boolean keepAliveOnPing() { - return get(KEEP_ALIVE_ON_PING); - } - - /** - * Returns the PING interval in milliseconds. - * When neither read nor write was performed for the specified period of time, - * a PING frame is sent for HTTP/2 - * or an OPTIONS request with - * an asterisk ("*") is sent for HTTP/1. - */ - public long pingIntervalMillis() { - return get(PING_INTERVAL_MILLIS); - } - - /** - * Returns the client-side max age of a connection for keep-alive in milliseconds. - * If the value is greater than {@code 0}, a connection is disconnected after the specified - * amount of the time since the connection was established. - */ - public long maxConnectionAgeMillis() { - return get(MAX_CONNECTION_AGE_MILLIS); - } - - /** - * Returns the client-side maximum allowed number of requests that can be sent through one connection. - */ - public int maxNumRequestsPerConnection() { - return get(MAX_NUM_REQUESTS_PER_CONNECTION); - } - - /** - * Returns whether to send an HTTP/2 preface string instead of an HTTP/1 upgrade request to negotiate - * the protocol version of a cleartext HTTP connection. - */ - public boolean useHttp2Preface() { - return get(USE_HTTP2_PREFACE); - } - - /** - * Returns whether to use HTTP/1.1 instead of HTTP/2 . If {@code true}, the client will not attempt to - * upgrade to HTTP/2 for {@link SessionProtocol#HTTP} and {@link SessionProtocol#HTTPS}. - */ - @UnstableApi - public boolean preferHttp1() { - return get(PREFER_HTTP1); - } - - /** - * Returns whether to use HTTP/2 over TLS without ALPN. - */ - @UnstableApi - public boolean useHttp2WithoutAlpn() { - return get(USE_HTTP2_WITHOUT_ALPN); - } - - /** - * Returns whether to use HTTP pipelining for - * HTTP/1 connections. - */ - public boolean useHttp1Pipelining() { - return get(USE_HTTP1_PIPELINING); - } - - /** - * Returns the listener which is notified on a connection pool event. - */ - public ConnectionPoolListener connectionPoolListener() { - return get(CONNECTION_POOL_LISTENER); - } - - /** - * Returns the {@link MeterRegistry} which collects various stats. - */ - public MeterRegistry meterRegistry() { - return get(METER_REGISTRY); - } - - /** - * The {@link ProxyConfigSelector} which determines the {@link ProxyConfig} to be used. - */ - public ProxyConfigSelector proxyConfigSelector() { - return get(PROXY_CONFIG_SELECTOR); - } - - /** - * Returns the {@link Http1HeaderNaming} which converts a lower-cased HTTP/2 header name into - * another header name. This is useful when communicating with a legacy system that only supports - * case-sensitive HTTP/1 headers. - */ - public Http1HeaderNaming http1HeaderNaming() { - return get(HTTP1_HEADER_NAMING); - } - - /** - * Returns whether to allow the bad cipher suites listed in - * RFC7540 for TLS handshake. - * - *

Note that enabling this option increases the security risk of your connection. - * Use it only when you must communicate with a legacy system that does not support - * secure cipher suites. - * See Section 9.2.2, RFC7540 for - * more information. - */ - public boolean tlsAllowUnsafeCiphers() { - return get(TLS_ALLOW_UNSAFE_CIPHERS); - } - - /** - * The {@link Consumer} that customizes the Netty {@link ChannelPipeline}. - * This customizer is run right before {@link ChannelPipeline#connect(SocketAddress)} - * is invoked by Armeria. This customizer is no-op by default. - * - *

Note that usage of this customizer is an advanced - * feature and may produce unintended side effects, including complete - * breakdown. It is not recommended if you are not familiar with - * Armeria and Netty internals. - */ - @UnstableApi - public Consumer channelPipelineCustomizer() { - return get(CHANNEL_PIPELINE_CUSTOMIZER); - } + builder.putAll(newOptions); + return newValue.option().newValue(builder.build()); + }); + + /** + * The {@link Consumer} that customizes the Netty {@link ChannelPipeline}. + * This customizer is run right before {@link ChannelPipeline#connect(SocketAddress)} + * is invoked by Armeria. This customizer is no-op by default. + * + *

Note that usage of this customizer is an advanced + * feature and may produce unintended side effects, including complete + * breakdown. It is not recommended if you are not familiar with + * Armeria and Netty internals. + */ + @UnstableApi + public static final ClientFactoryOption> CHANNEL_PIPELINE_CUSTOMIZER = + ClientFactoryOption.define("CHANNEL_PIPELINE_CUSTOMIZER", v -> { /* no-op */ }); + + private static final ClientFactoryOptions EMPTY = new ClientFactoryOptions(ImmutableList.of()); + + /** + * Returns an empty singleton {@link ClientFactoryOptions}. + */ + public static ClientFactoryOptions of() { + return EMPTY; + } + + /** + * Returns the {@link ClientFactoryOptions} with the specified {@link ClientFactoryOptionValue}s. + */ + public static ClientFactoryOptions of(ClientFactoryOptionValue... values) { + requireNonNull(values, "values"); + if (values.length == 0) { + return EMPTY; + } + return of(Arrays.asList(values)); + } + + /** + * Returns the {@link ClientFactoryOptions} with the specified {@link ClientFactoryOptionValue}s. + */ + public static ClientFactoryOptions of(Iterable> values) { + requireNonNull(values, "values"); + if (values instanceof ClientFactoryOptions) { + return (ClientFactoryOptions) values; + } + return new ClientFactoryOptions(values); + } + + /** + * Merges the specified {@link ClientFactoryOptions} and {@link ClientFactoryOptionValue}s. + * + * @return the merged {@link ClientFactoryOptions} + */ + public static ClientFactoryOptions of(ClientFactoryOptions baseOptions, + ClientFactoryOptionValue... additionalValues) { + requireNonNull(baseOptions, "baseOptions"); + requireNonNull(additionalValues, "additionalValues"); + if (additionalValues.length == 0) { + return baseOptions; + } + return new ClientFactoryOptions(baseOptions, Arrays.asList(additionalValues)); + } + + /** + * Merges the specified {@link ClientFactoryOptions} and {@link ClientFactoryOptionValue}s. + * + * @return the merged {@link ClientFactoryOptions} + */ + public static ClientFactoryOptions of(ClientFactoryOptions baseOptions, + Iterable> additionalValues) { + requireNonNull(baseOptions, "baseOptions"); + requireNonNull(additionalValues, "additionalValues"); + return new ClientFactoryOptions(baseOptions, additionalValues); + } + + private ClientFactoryOptions(Iterable> values) { + super(values); + } + + private ClientFactoryOptions(ClientFactoryOptions baseOptions, + Iterable> additionalValues) { + + super(baseOptions, additionalValues); + } + + /** + * Returns the worker {@link EventLoopGroup}. + */ + public EventLoopGroup workerGroup() { + return get(WORKER_GROUP); + } + + /** + * Returns the flag whether to shut down the worker {@link EventLoopGroup} + * when the {@link ClientFactory} is closed. + */ + public boolean shutdownWorkerGroupOnClose() { + return get(SHUTDOWN_WORKER_GROUP_ON_CLOSE); + } + + /** + * Returns the factory that creates an {@link EventLoopScheduler} which is responsible for assigning an + * {@link EventLoop} to handle a connection to the specified {@link Endpoint}. + */ + public Function eventLoopSchedulerFactory() { + return get(EVENT_LOOP_SCHEDULER_FACTORY); + } + + /** + * Returns the {@link ChannelOption}s of the sockets created by the {@link ClientFactory}. + */ + public Map, Object> channelOptions() { + return get(CHANNEL_OPTIONS); + } + + /** + * Returns the {@link Consumer} which can arbitrarily configure the {@link SslContextBuilder} that will be + * applied to the SSL session. + */ + public Consumer tlsCustomizer() { + return get(TLS_CUSTOMIZER); + } + + /** + * Returns the factory that creates an {@link AddressResolverGroup} which resolves remote addresses into + * {@link InetSocketAddress}es. + */ + public Function> addressResolverGroupFactory() { + + return get(ADDRESS_RESOLVER_GROUP_FACTORY); + } + + /** + * Returns the HTTP/2 initial connection + * flow-control window size. + */ + public int http2InitialConnectionWindowSize() { + return get(HTTP2_INITIAL_CONNECTION_WINDOW_SIZE); + } + + /** + * Returns the SETTINGS_INITIAL_WINDOW_SIZE + * for HTTP/2 stream-level flow control. + */ + public int http2InitialStreamWindowSize() { + return get(HTTP2_INITIAL_STREAM_WINDOW_SIZE); + } + + /** + * Returns the SETTINGS_MAX_FRAME_SIZE + * that indicates the size of the largest frame payload that this client is willing to receive. + */ + public int http2MaxFrameSize() { + return get(HTTP2_MAX_FRAME_SIZE); + } + + /** + * Returns the HTTP/2 + * SETTINGS_MAX_HEADER_LIST_SIZE that indicates the maximum size of header list + * that the client is prepared to accept, in octets. + */ + public long http2MaxHeaderListSize() { + return get(HTTP2_MAX_HEADER_LIST_SIZE); + } + + /** + * Returns the maximum length of an HTTP/1 response initial line. + */ + public int http1MaxInitialLineLength() { + return get(HTTP1_MAX_INITIAL_LINE_LENGTH); + } + + /** + * Returns the maximum length of all headers in an HTTP/1 response. + */ + public int http1MaxHeaderSize() { + return get(HTTP1_MAX_HEADER_SIZE); + } + + /** + * Returns the maximum length of each chunk in an HTTP/1 response content. + */ + public int http1MaxChunkSize() { + return get(HTTP1_MAX_CHUNK_SIZE); + } + + /** + * Returns the idle timeout of a socket connection in milliseconds. + */ + public long idleTimeoutMillis() { + return get(IDLE_TIMEOUT_MILLIS); + } + + /** + * Returns whether to keep connection alive when an HTTP/2 PING frame or the response of + * {@code "OPTIONS * HTTP/1.1"} is received. + */ + @UnstableApi + public boolean keepAliveOnPing() { + return get(KEEP_ALIVE_ON_PING); + } + + /** + * Returns the PING interval in milliseconds. + * When neither read nor write was performed for the specified period of time, + * a PING frame is sent for HTTP/2 + * or an OPTIONS request with + * an asterisk ("*") is sent for HTTP/1. + */ + public long pingIntervalMillis() { + return get(PING_INTERVAL_MILLIS); + } + + /** + * Returns the client-side max age of a connection for keep-alive in milliseconds. + * If the value is greater than {@code 0}, a connection is disconnected after the specified + * amount of the time since the connection was established. + */ + public long maxConnectionAgeMillis() { + return get(MAX_CONNECTION_AGE_MILLIS); + } + + /** + * Returns the client-side maximum allowed number of requests that can be sent through one connection. + */ + public int maxNumRequestsPerConnection() { + return get(MAX_NUM_REQUESTS_PER_CONNECTION); + } + + /** + * Returns whether to send an HTTP/2 preface string instead of an HTTP/1 upgrade request to negotiate + * the protocol version of a cleartext HTTP connection. + */ + public boolean useHttp2Preface() { + return get(USE_HTTP2_PREFACE); + } + + /** + * Returns whether to use HTTP/1.1 instead of HTTP/2 . If {@code true}, the client will not attempt to + * upgrade to HTTP/2 for {@link SessionProtocol#HTTP} and {@link SessionProtocol#HTTPS}. + */ + @UnstableApi + public boolean preferHttp1() { + return get(PREFER_HTTP1); + } + + /** + * Returns whether to use HTTP/2 over TLS without ALPN. + */ + @UnstableApi + public boolean useHttp2WithoutAlpn() { + return get(USE_HTTP2_WITHOUT_ALPN); + } + + /** + * Returns whether to use HTTP pipelining for + * HTTP/1 connections. + */ + public boolean useHttp1Pipelining() { + return get(USE_HTTP1_PIPELINING); + } + + /** + * Returns the listener which is notified on a connection pool event. + */ + public ConnectionPoolListener connectionPoolListener() { + return get(CONNECTION_POOL_LISTENER); + } + + /** + * Returns the {@link MeterRegistry} which collects various stats. + */ + public MeterRegistry meterRegistry() { + return get(METER_REGISTRY); + } + + /** + * The {@link ProxyConfigSelector} which determines the {@link ProxyConfig} to be used. + */ + public ProxyConfigSelector proxyConfigSelector() { + return get(PROXY_CONFIG_SELECTOR); + } + + /** + * Returns the {@link Http1HeaderNaming} which converts a lower-cased HTTP/2 header name into + * another header name. This is useful when communicating with a legacy system that only supports + * case-sensitive HTTP/1 headers. + */ + public Http1HeaderNaming http1HeaderNaming() { + return get(HTTP1_HEADER_NAMING); + } + + /** + * Returns whether to allow the bad cipher suites listed in + * RFC7540 for TLS handshake. + * + *

Note that enabling this option increases the security risk of your connection. + * Use it only when you must communicate with a legacy system that does not support + * secure cipher suites. + * See Section 9.2.2, RFC7540 for + * more information. + */ + public boolean tlsAllowUnsafeCiphers() { + return get(TLS_ALLOW_UNSAFE_CIPHERS); + } + + /** + * The {@link Consumer} that customizes the Netty {@link ChannelPipeline}. + * This customizer is run right before {@link ChannelPipeline#connect(SocketAddress)} + * is invoked by Armeria. This customizer is no-op by default. + * + *

Note that usage of this customizer is an advanced + * feature and may produce unintended side effects, including complete + * breakdown. It is not recommended if you are not familiar with + * Armeria and Netty internals. + */ + @UnstableApi + public Consumer channelPipelineCustomizer() { + return get(CHANNEL_PIPELINE_CUSTOMIZER); + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/EventLoopSchedulerFactory.java b/core/src/main/java/com/linecorp/armeria/client/EventLoopSchedulerFactory.java new file mode 100644 index 00000000000..bab6458ef66 --- /dev/null +++ b/core/src/main/java/com/linecorp/armeria/client/EventLoopSchedulerFactory.java @@ -0,0 +1,15 @@ +package com.linecorp.armeria.client; + +import io.netty.channel.EventLoopGroup; + +import java.util.List; +import java.util.function.ToIntFunction; + +@FunctionalInterface +public interface EventLoopSchedulerFactory { + EventLoopScheduler newScheduler(EventLoopGroup eventLoopGroup, + List> maxNumEventLoopFunctions, + int maxNumEventLoopsPerEndpoint, + int maxNumEventLoopsPerHttp1Endpoint, + long idleTimeOutMillis); +}