# HTTP Client

Reactor Netty provides the easy-to-use and easy-to-configure `HttpClient`. It hides most of the Netty functionality that is required to create an `HTTP` client and adds Reactive Streams backpressure.

## 1. Connect

To connect the `HTTP` client to a given `HTTP` endpoint, you must create and configure a `HttpClient` instance. By default, the host is configured for `localhost` and the port is `80`. The following example shows how to do so:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/connect/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client = HttpClient.create();  (1)

    		client.get()                       (2)
    		      .uri("https://example.com/") (3)
    		      .response()                  (4)
    		      .block();
    	}
    }

1 | Creates a HttpClient instance ready for configuring.
2 | Specifies that `GET` method will be used.
3 | Specifies the path.
4 | Obtains the response HttpClientResponse

The following example uses `WebSocket`:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/websocket/Application.java

    import io.netty.buffer.Unpooled;
    import io.netty.util.CharsetUtil;
    import reactor.core.publisher.Flux;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client = HttpClient.create();

    		client.websocket()
    		      .uri("wss://echo.websocket.org")
    		      .handle((inbound, outbound) -> {
    		          inbound.receive()
    		                 .asString()
    		                 .take(1)
    		                 .subscribe(System.out::println);

    		          final byte[] msgBytes = "hello".getBytes(CharsetUtil.ISO_8859_1);
    		          return outbound.send(Flux.just(Unpooled.wrappedBuffer(msgBytes), Unpooled.wrappedBuffer(msgBytes)))
    		                         .neverComplete();
    		      })
    		      .blockLast();
    	}
    }

### 1.1. Host and Port

In order to connect to a specific host and port, you can apply the following configuration to the `HTTP` client:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/address/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .host("example.com") (1)
    				          .port(80);           (2)

    		client.get()
    		      .uri("/")
    		      .response()
    		      .block();
    	}
    }

1 | Configures the `HTTP` host
2 | Configures the `HTTP` port

| The port can be specified also with PORT environment variable.

## 2. Eager Initialization

By default, the initialization of the `HttpClient` resources happens on demand. This means that the `first request` absorbs the extra time needed to initialize and load:

- the event loop group

- the host name resolver

- the native transport libraries (when native transport is used)

- the native libraries for the security (in case of `OpenSsl`)

When you need to preload these resources, you can configure the `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/warmup/Application.java

    import reactor.core.publisher.Mono;
    import reactor.netty.ByteBufFlux;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client = HttpClient.create();

    		client.warmup() (1)
    		      .block();

    		client.post()
    		      .uri("https://example.com/")
    		      .send(ByteBufFlux.fromString(Mono.just("hello")))
    		      .response()
    		      .block(); (2)
    	}
    }

1 | Initialize and load the event loop group, the host name resolver, the native transport libraries and the native libraries for the security
2 | Host name resolution happens with the first request. In this example, a connection pool is used, so with the first request the connection to the URL is established, the subsequent requests to the same URL reuse the connections from the pool.

## 3. Writing Data

To send data to a given `HTTP` endpoint, you can provide a `Publisher` by using the `send(Publisher)` method. By default, `Transfer-Encoding: chunked` is applied for those `HTTP` methods for which a request body is expected. `Content-Length` provided through request headers disables `Transfer-Encoding: chunked`, if necessary. The following example sends `hello`:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/send/Application.java

    import reactor.core.publisher.Mono;
    import reactor.netty.ByteBufFlux;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client = HttpClient.create();

    		client.post()
    		      .uri("https://example.com/")
    		      .send(ByteBufFlux.fromString(Mono.just("hello"))) (1)
    		      .response()
    		      .block();
    	}
    }

1 | Sends a `hello` string to the given `HTTP` endpoint

### 3.1. Adding Headers and Other Metadata

When sending data to a given `HTTP` endpoint, you may need to send additional headers, cookies and other metadata. You can use the following configuration to do so:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/send/headers/Application.java

    import io.netty.handler.codec.http.HttpHeaderNames;
    import reactor.core.publisher.Mono;
    import reactor.netty.ByteBufFlux;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .headers(h -> h.set(HttpHeaderNames.CONTENT_LENGTH, 5)); (1)

    		client.post()
    		      .uri("https://example.com/")
    		      .send(ByteBufFlux.fromString(Mono.just("hello")))
    		      .response()
    		      .block();
    	}
    }

1 | Disables `Transfer-Encoding: chunked` and provides `Content-Length` header.

#### 3.1.1. Compression

You can enable compression on the `HTTP` client, which means the request header `Accept-Encoding` is added to the request headers. The following example shows how to do so:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/compression/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .compress(true);

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

#### 3.1.2. Auto-Redirect Support

You can configure the `HTTP` client to enable auto-redirect support.

Reactor Netty provides two different strategies for auto-redirect support:

- `followRedirect(boolean)`: Automatically follows redirects for standard HTTP redirect status codes `301|302|303|307|308`. This method uses a built-in predicate that checks if the response status code matches one of these values. When the status code is `303`, the `GET` method is used for the redirect.

- `followRedirect(BiPredicate<HttpClientRequest, HttpClientResponse>)`: Provides full control over redirect behavior through a custom predicate. The redirect decision is determined entirely by the result returned by the supplied predicate, which receives the `HttpClientRequest` and `HttpClientResponse` and must return `true` to enable auto-redirect. This allows you to implement custom logic, such as following redirects for non-standard status codes or based on specific conditions.

The following example uses `followRedirect(true)` to automatically follow standard 30x redirects:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/redirect/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .followRedirect(true);

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

## 4. Consuming Data

To receive data from a given `HTTP` endpoint, you can use one of the methods from `HttpClient.ResponseReceiver`. The following example uses the `responseContent` method:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/read/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client = HttpClient.create();

    		client.get()
    		      .uri("https://example.com/")
    		      .responseContent() (1)
    		      .aggregate()       (2)
    		      .asString()        (3)
    		      .block();
    	}
    }

1 | Receives data from a given `HTTP` endpoint
2 | Aggregates the data
3 | Transforms the data as string

### 4.1. Reading Headers and Other Metadata

When receiving data from a given `HTTP` endpoint, you can check response headers, status code, and other metadata. You can obtain this additional metadata by using `HttpClientResponse`. The following example shows how to do so.

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/read/status/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client = HttpClient.create();

    		client.get()
    		      .uri("https://example.com/")
    		      .responseSingle((resp, bytes) -> {
    		          System.out.println(resp.status()); (1)
    		          return bytes.asString();
    		      })
    		      .block();
    	}
    }

1 | Obtains the status code.

### 4.2. HTTP Response Decoder

By default, `Netty` configures some restrictions for the incoming responses, such as:

- The maximum length of the initial line.

- The maximum length of all headers.

- The maximum length of the content or each chunk.

For more information, see HttpResponseDecoder

By default, the `HTTP` client is configured with the following settings:

github.com/reactor/reactor-netty/tree/main/reactor-netty-http/src/main/java/reactor/netty/http/HttpDecoderSpec.java

    	public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH             = 4096;
    	public static final int DEFAULT_MAX_HEADER_SIZE                     = 8192;
    	/**
    	 * Default max chunk size.
    	 *
    	 * @deprecated as of 1.1.0. This will be removed in 2.0.0 as Netty 5 does not support this configuration.
    	 */
    	@Deprecated
    	public static final int DEFAULT_MAX_CHUNK_SIZE                      = 8192;
    	public static final boolean DEFAULT_VALIDATE_HEADERS                = true;
    	public static final int DEFAULT_INITIAL_BUFFER_SIZE                 = 128;
    	public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
    	public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS            = true;

github.com/reactor/reactor-netty/tree/main/reactor-netty-http/src/main/java/reactor/netty/http/client/HttpResponseDecoderSpec.java

    	public static final boolean DEFAULT_FAIL_ON_MISSING_RESPONSE         = false;
    	public static final boolean DEFAULT_PARSE_HTTP_AFTER_CONNECT_REQUEST = false;

    	/**
    	 * The maximum length of the content of the HTTP/2.0 clear-text upgrade request.
    	 * By default, the client will allow an upgrade request with up to 65536 as
    	 * the maximum length of the aggregated content.
    	 */
    	public static final int DEFAULT_H2C_MAX_CONTENT_LENGTH = 65536;

When you need to change these default settings, you can configure the `HTTP` client as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/responsedecoder/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .httpResponseDecoder(spec -> spec.maxHeaderSize(16384)); (1)

    		client.get()
    		      .uri("https://example.com/")
    		      .responseContent()
    		      .aggregate()
    		      .asString()
    		      .block();
    	}
    }

1 | The maximum length of all headers will be `16384`. When this value is exceeded, a TooLongFrameException is raised.

## 5. Lifecycle Callbacks

The following lifecycle callbacks are provided to let you extend the `HttpClient`.

Callback | Description
--- | ---
`doAfterRequest` | Invoked when the request has been sent.
`doAfterResolve` | Invoked after the remote address has been resolved successfully.
`doAfterResponseSuccess` | Invoked after the response has been fully received.
`doOnChannelInit` | Invoked when initializing the channel.
`doOnConnect` | Invoked when the channel is about to connect.
`doOnConnected` | Invoked after the channel has been connected.
`doOnDisconnected` | Invoked after the channel has been disconnected.
`doOnError` | Invoked when the request has not been sent and when the response has not been fully received.
`doOnRedirect` | Invoked when the response headers have been received, and the request is about to be redirected.
`doOnRequest` | Invoked when the request is about to be sent.
`doOnRequestError` | Invoked when the request has not been sent.
`doOnResolve` | Invoked when the remote address is about to be resolved.
`doOnResolveError` | Invoked in case the remote address hasn’t been resolved successfully.
`doOnResponse` | Invoked after the response headers have been received.
`doOnResponseError` | Invoked when the response has not been fully received.

The following example uses the `doOnConnected` and `doOnChannelInit` callbacks:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/lifecycle/Application.java

    import io.netty.handler.logging.LoggingHandler;
    import io.netty.handler.timeout.ReadTimeoutHandler;
    import reactor.netty.http.client.HttpClient;
    import java.util.concurrent.TimeUnit;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .doOnConnected(conn ->
    				              conn.addHandlerFirst(new ReadTimeoutHandler(10, TimeUnit.SECONDS)))   (1)
    				          .doOnChannelInit((observer, channel, remoteAddress) ->
    				              channel.pipeline()
    				                     .addFirst(new LoggingHandler("reactor.netty.examples")));      (2)

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

1 | `Netty` pipeline is extended with `ReadTimeoutHandler` when the channel has been connected.
2 | `Netty` pipeline is extended with `LoggingHandler` when initializing the channel.

## 6. TCP-level Configuration

When you need configurations on a TCP level, you can use the following snippet to extend the default `TCP` client configuration (add an option, bind address etc.):

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/channeloptions/Application.java

    import io.netty.channel.ChannelOption;
    import io.netty.channel.epoll.EpollChannelOption;
    //import io.netty.channel.socket.nio.NioChannelOption;
    //import jdk.net.ExtendedSocketOptions;
    import reactor.netty.http.client.HttpClient;
    import java.net.InetSocketAddress;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .bindAddress(() -> new InetSocketAddress("host", 1234))
    				          .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) (1)
    				          .option(ChannelOption.SO_KEEPALIVE, true)            (2)
    				          // The options below are available only when NIO transport (Java 11) is used
    				          // on Mac or Linux (Java does not currently support these extended options on Windows)
    				          // https://bugs.openjdk.java.net/browse/JDK-8194298
    				          //.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), 300)
    				          //.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), 60)
    				          //.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), 8);
    				          // The options below are available only when Epoll transport is used
    				          .option(EpollChannelOption.TCP_KEEPIDLE, 300)        (3)
    				          .option(EpollChannelOption.TCP_KEEPINTVL, 60)        (4)
    				          .option(EpollChannelOption.TCP_KEEPCNT, 8);          (5)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | Configures the connection establishment timeout to 10 seconds.
2 | Enables TCP `keepalive`. This means that TCP starts sending `keepalive` probes when a connection is idle for some time.
3 | The connection needs to remain idle for 5 minutes before TCP starts sending `keepalive` probes.
4 | Configures the time between individual `keepalive` probes to 1 minute.
5 | Configures the maximum number of TCP `keepalive` probes to 8.

See TCP Client for more about `TCP` level configurations.

### 6.1. Wire Logger

Reactor Netty provides wire logging for when the traffic between the peers needs to be inspected. By default, wire logging is disabled. To enable it, you must set the logger `reactor.netty.http.client.HttpClient` level to `DEBUG` and apply the following configuration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/wiretap/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .wiretap(true); (1)

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

1 | Enables the wire logging

#### 6.1.1. Wire Logger formatters

Reactor Netty supports 3 different formatters:

- AdvancedByteBufFormat#HEX_DUMP - the default

github.com/reactor/reactor-netty/tree/main/reactor-netty-core/src/main/java/reactor/netty/transport/logging/AdvancedByteBufFormat.java

    	/**
    	 * When wire logging is enabled with this format, both events and content will be logged.
    	 * The content will be in hex format.
    	 * <p>Examples:</p>
    	 * <pre>
    	 * {@code
    	 * reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] REGISTERED
    	 * reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] ACTIVE
    	 * reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] READ: 145B
    	 *          +-------------------------------------------------+
    	 *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    	 * +--------+-------------------------------------------------+----------------+
    	 * |00000000| 50 4f 53 54 20 2f 74 65 73 74 2f 57 6f 72 6c 64 |POST /test/World|
    	 * |00000010| 20 48 54 54 50 2f 31 2e 31 0d 0a 43 6f 6e 74 65 | HTTP/1.1..Conte|
    	 * |00000020| 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 70 6c |nt-Type: text/pl|
    	 * |00000030| 61 69 6e 0d 0a 75 73 65 72 2d 61 67 65 6e 74 3a |ain..user-agent:|
    	 * |00000040| 20 52 65 61 63 74 6f 72 4e 65 74 74 79 2f 64 65 | ReactorNetty/de|
    	 * ...
    	 * reactor.netty.http.HttpTests - [d5230a14, L:/0:0:0:0:0:0:0:1:60267 - R:/0:0:0:0:0:0:0:1:60269] WRITE: 38B
    	 *          +-------------------------------------------------+
    	 *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    	 * +--------+-------------------------------------------------+----------------+
    	 * |00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.|
    	 * |00000010| 0a 63 6f 6e 74 65 6e 74 2d 6c 65 6e 67 74 68 3a |.content-length:|
    	 * |00000020| 20 30 0d 0a 0d 0a                               | 0....          |
    	 * +--------+-------------------------------------------------+----------------+
    	 * }
    	 * </pre>
    	 */

- AdvancedByteBufFormat#SIMPLE

github.com/reactor/reactor-netty/tree/main/reactor-netty-core/src/main/java/reactor/netty/transport/logging/AdvancedByteBufFormat.java

    	/**
    	 * When wire logging is enabled with this format, only the events will be logged.
    	 * <p>Examples:</p>
    	 * <pre>
    	 * {@code
    	 * reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] REGISTERED
    	 * reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] ACTIVE
    	 * reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] READ: 145B
    	 * reactor.netty.http.HttpTests - [230d3686, L:/0:0:0:0:0:0:0:1:60241 - R:/0:0:0:0:0:0:0:1:60245] WRITE: 38B
    	 * }
    	 * </pre>
    	 */

- AdvancedByteBufFormat#TEXTUAL

github.com/reactor/reactor-netty/tree/main/reactor-netty-core/src/main/java/reactor/netty/transport/logging/AdvancedByteBufFormat.java

    	/**
    	 * When wire logging is enabled with this format, both events and content will be logged.
    	 * The content will be in plain text format.
    	 * <p>Examples:</p>
    	 * <pre>
    	 * {@code
    	 * reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] REGISTERED
    	 * reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] ACTIVE
    	 * reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] READ: 145B POST /test/World HTTP/1.1
    	 * Content-Type: text/plain
    	 * user-agent: ReactorNetty/dev
    	 * ...
    	 * reactor.netty.http.HttpTests - [02c3db6c, L:/0:0:0:0:0:0:0:1:60317 - R:/0:0:0:0:0:0:0:1:60319] WRITE: 38B HTTP/1.1 200 OK
    	 * content-length: 0
    	 * }
    	 * </pre>
    	 */

When you need to change the default formatter you can configure it as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/wiretap/custom/Application.java

    import io.netty.handler.logging.LogLevel;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.transport.logging.AdvancedByteBufFormat;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .wiretap("logger-name", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL); (1)

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

1 | Enables the wire logging, AdvancedByteBufFormat#TEXTUAL is used for printing the content.

### 6.2. Event Loop Group

By default `Reactor Netty` uses an “Event Loop Group”, where the number of the worker threads equals the number of processors available to the runtime on initialization (but with a minimum value of 4). This “Event Loop Group” is shared between all servers and clients in one JVM. When you need a different configuration, you can use one of the `LoopResources``#create` methods.

The following listing shows the default configuration for the Event Loop Group:

github.com/reactor/reactor-netty/tree/main/reactor-netty-core/src/main/java/reactor/netty/ReactorNetty.java

    	/**
    	 * Default worker thread count, fallback to available processor
    	 * (but with a minimum value of 4).
    	 */
    	public static final String IO_WORKER_COUNT = "reactor.netty.ioWorkerCount";
    	/**
    	 * Default selector thread count, fallback to -1 (no selector thread)
    	 * <p><strong>Note:</strong> In most use cases using a worker thread also as a selector thread works well.
    	 * A possible use case for specifying a separate selector thread might be when the worker threads are too busy
    	 * and connections cannot be accepted fast enough.
    	 * <p><strong>Note:</strong> Although more than 1 can be configured as a selector thread count, in reality
    	 * only 1 thread will be used as a selector thread.
    	 */
    	public static final String IO_SELECT_COUNT = "reactor.netty.ioSelectCount";
    	/**
    	 * Default worker thread count for UDP, fallback to available processor
    	 * (but with a minimum value of 4).
    	 */
    	public static final String UDP_IO_THREAD_COUNT = "reactor.netty.udp.ioThreadCount";
    	/**
    	 * Default quiet period that guarantees that the disposal of the underlying LoopResources
    	 * will not happen, fallback to 2 seconds.
    	 */
    	public static final String SHUTDOWN_QUIET_PERIOD = "reactor.netty.ioShutdownQuietPeriod";
    	/**
    	 * Default maximum amount of time to wait until the disposal of the underlying LoopResources
    	 * regardless if a task was submitted during the quiet period, fallback to 15 seconds.
    	 */
    	public static final String SHUTDOWN_TIMEOUT = "reactor.netty.ioShutdownTimeout";

    	/**
    	 * Default value whether the native transport (epoll, io_uring, kqueue) will be preferred,
    	 * fallback it will be preferred when available.
    	 * <p><strong>Note:</strong> On {@code Linux}, {@code Epoll} will be preferred by default.
    	 * If {@code IO_Uring} needs to be configured, a dependency to {@code io.netty:netty-transport-native-io_uring}
    	 * has to be added.
    	 */
    	public static final String NATIVE = "reactor.netty.native";

If you need changes to these settings, you can apply the following configuration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/eventloop/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.LoopResources;

    public class Application {

    	public static void main(String[] args) {
    		LoopResources loop = LoopResources.create("event-loop", 1, 4, true);
    		HttpClient client =
    				HttpClient.create()
    				          .runOn(loop);

    		client.get()
    		      .uri("https://example.com/")
    		      .responseContent()
    		      .aggregate()
    		      .asString()
    		      .block();
    	}
    }

#### 6.2.1. Disposing Event Loop Group

- If you use the default `Event Loop Group` provided by Reactor Netty, invoke `HttpResources``#disposeLoopsAndConnections`/`#disposeLoopsAndConnectionsLater` method.

| Disposing `HttpResources` means that every server/client that is using it, will not be able to use it anymore!

- If you use custom `LoopResources`, invoke `LoopResources``#dispose`/`#disposeLater` method.

| Disposing the custom `LoopResources` means that every server/client that is configured to use it, will not be able to use it anymore!

## 7. Connection Pool

By default, `HttpClient` (`HttpClient.create()`) uses a shared `ConnectionProvider`. This `ConnectionProvider` is configured to create a “fixed” connection pool per remote host (a remote host implies the combination of a hostname and its associated port number) with:

- `500` as the maximum number of active channels

- `1000` as the maximum number of further channel acquisition attempts allowed to be kept in a pending state

- The rest of the configurations are the defaults (check the system properties or the builder configurations below)

This means that the implementation creates a new channel if someone tries to acquire a channel as long as less than `500` have been created and are managed by the pool. When the maximum number of channels in the pool is reached, up to `1000` new attempts to acquire a channel are delayed (pending) until a channel is returned to the pool again, and further attempts are declined with an error.

github.com/reactor/reactor-netty/tree/main/reactor-netty-core/src/main/java/reactor/netty/ReactorNetty.java

    	/**
    	 * Default max connections. Fallback to
    	 * 2 * available number of processors (but with a minimum value of 16)
    	 */
    	public static final String POOL_MAX_CONNECTIONS = "reactor.netty.pool.maxConnections";
    	/**
    	 * Default acquisition timeout (milliseconds) before error. If -1 will never wait to
    	 * acquire before opening a new
    	 * connection in an unbounded fashion. Fallback 45 seconds
    	 */
    	public static final String POOL_ACQUIRE_TIMEOUT = "reactor.netty.pool.acquireTimeout";
    	/**
    	 * Default max idle time, fallback - max idle time is not specified.
    	 * <p><strong>Note:</strong> This configuration is not applicable for {@link reactor.netty.tcp.TcpClient}.
    	 * A TCP connection is always closed and never returned to the pool.
    	 */
    	public static final String POOL_MAX_IDLE_TIME = "reactor.netty.pool.maxIdleTime";
    	/**
    	 * Default max life time, fallback - max life time is not specified.
    	 * <p><strong>Note:</strong> This configuration is not applicable for {@link reactor.netty.tcp.TcpClient}.
    	 * A TCP connection is always closed and never returned to the pool.
    	 */
    	public static final String POOL_MAX_LIFE_TIME = "reactor.netty.pool.maxLifeTime";
    	/**
    	 * Default leasing strategy (fifo, lifo), fallback to fifo.
    	 * <ul>
    	 *     <li>fifo - The connection selection is first in, first out</li>
    	 *     <li>lifo - The connection selection is last in, first out</li>
    	 * </ul>
    	 * <p><strong>Note:</strong> This configuration is not applicable for {@link reactor.netty.tcp.TcpClient}.
    	 * A TCP connection is always closed and never returned to the pool.
    	 */
    	public static final String POOL_LEASING_STRATEGY = "reactor.netty.pool.leasingStrategy";
    	/**
    	 * Default {@code getPermitsSamplingRate} (between 0d and 1d (percentage))
    	 * to be used with a {@link SamplingAllocationStrategy}.
    	 * This strategy wraps a {@link PoolBuilder#sizeBetween(int, int) sizeBetween} {@link AllocationStrategy}
    	 * and samples calls to {@link AllocationStrategy#getPermits(int)}.
    	 * Fallback - sampling is not enabled.
    	 */
    	public static final String POOL_GET_PERMITS_SAMPLING_RATE = "reactor.netty.pool.getPermitsSamplingRate";
    	/**
    	 * Default {@code returnPermitsSamplingRate} (between 0d and 1d (percentage))
    	 * to be used with a {@link SamplingAllocationStrategy}.
    	 * This strategy wraps a {@link PoolBuilder#sizeBetween(int, int) sizeBetween} {@link AllocationStrategy}
    	 * and samples calls to {@link AllocationStrategy#returnPermits(int)}.
    	 * Fallback - sampling is not enabled.
    	 */
    	public static final String POOL_RETURN_PERMITS_SAMPLING_RATE = "reactor.netty.pool.returnPermitsSamplingRate";

When you need to change the default settings, you can configure the `ConnectionProvider` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/pool/config/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.ConnectionProvider;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .maxConnections(50)
    				                  .maxIdleTime(Duration.ofSeconds(20))           (1)
    				                  .maxLifeTime(Duration.ofSeconds(60))           (2)
    				                  .maxLifeTimeVariance(2.5)                      (3)
    				                  .pendingAcquireTimeout(Duration.ofSeconds(60)) (4)
    				                  .evictInBackground(Duration.ofSeconds(120))    (5)
    				                  .build();

    		HttpClient client = HttpClient.create(provider);

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);

    		provider.disposeLater()
    		        .block();
    	}
    }

1 | Configures the maximum time for a connection to stay idle to 20 seconds.
2 | Configures the maximum time for a connection to stay alive to 60 seconds.
3 | Configures a 2.5% variance on the max lifetime, so connections expire between 58.5s and 60s.
4 | Configures the maximum time for the pending acquire operation to 60 seconds.
5 | Every two minutes, the connection pool is regularly checked for connections that are applicable for removal.

| Notice that only the default `HttpClient` (`HttpClient.create()`) uses `500` as the maximum number of active channels. In the example above, when instantiating a custom `ConnectionProvider`, we are changing this value to `50` using `maxConnections`. Also, if you don’t set this parameter the default `maxConnections` is used (2 * available number of processors).

The following listing shows the available configurations:

Configuration name | Description
--- | ---
`disposeInactivePoolsInBackground` | When this option is enabled, connection pools are regularly checked in the background, and those that are empty and been inactive for a specified time become eligible for disposal. Connection pool is considered empty when there are no active connections, idle connections and pending acquisitions. By default, this background disposal of inactive pools is disabled.
`disposeTimeout` | When `ConnectionProvider#dispose()` or `ConnectionProvider#disposeLater()` is called, trigger a graceful shutdown for the connection pools, with this grace period timeout. From there on, all calls for acquiring a connection will fail fast with an exception. However, for the provided `Duration`, pending acquires will get a chance to be served. Note: The rejection of new acquires and the grace timer start immediately, irrespective of subscription to the `Mono` returned by `ConnectionProvider#disposeLater()`. Subsequent calls return the same `Mono`, effectively getting notifications from the first graceful shutdown call and ignoring subsequently provided timeouts. By default, dispose timeout is not specified.
`evictInBackground` | When this option is enabled, each connection pool regularly checks for connections that are eligible for removal according to eviction criteria like `maxIdleTime`. By default, this background eviction is disabled.
`fifo` | Configure the connection pool so that if there are idle connections (i.e. pool is under-utilized), the next acquire operation will get the `Least Recently Used` connection (LRU, i.e. the connection that was released first among the current idle connections). Default leasing strategy.
`lifo` | Configure the connection pool so that if there are idle connections (i.e. pool is under-utilized), the next acquire operation will get the `Most Recently Used` connection (MRU, i.e. the connection that was released last among the current idle connections).
`maxConnections` | The maximum number of connections (per connection pool) before start pending. Default to 2 * available number of processors (but with a minimum value of 16).
`maxIdleTime` | The time after which the channel is eligible to be closed when idle (resolution: ms). Default: max idle time is not specified.
`maxLifeTime` | The total life time after which the channel is eligible to be closed (resolution: ms). Default: max life time is not specified.
`maxLifeTimeVariance` | A variance percentage for `maxLifeTime`, introducing per-connection jitter to prevent simultaneous expiry of connections created around the same time. The effective max lifetime for each connection will be in the range `[maxLifeTime * (1 - variance/100), maxLifeTime]`. For example, with a `maxLifeTime` of 60 seconds and a variance of 2.5%, connections would expire between 58.5s and 60s. Only meaningful when `maxLifeTime` is configured. Default: 0 (no variance).
`metrics` | Enables/disables built-in integration with Micrometer. `ConnectionProvider.MeterRegistrar` can be provided for integration with another metrics system. By default, metrics are not enabled.
`pendingAcquireMaxCount` | The maximum number of extra attempts at acquiring a connection to keep in a pending queue. If -1 is specified, the pending queue does not have upper limit. Default to 2 * max connections.
`pendingAcquireTimeout` | The maximum time before which a pending acquire must complete, or a TimeoutException is thrown (resolution: ms). If -1 is specified, no such timeout is applied. Default: 45 seconds.

| When you expect a high load, be cautious with a connection pool with a very high value for maximum connections. You might experience `reactor.netty.http.client.PrematureCloseException` exception with a root cause "Connect Timeout" due to too many concurrent connections opened/acquired.

If you need to disable the connection pool, you can apply the following configuration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/pool/Application.java

    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.newConnection()
    				          .doOnConnected(conn -> System.out.println("Connection " + conn.channel()));

    		String response =
    				// A new connection is established for every request
    				client.get()
    				      .uri("https://httpbin.org/get")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);

    		response =
    				// A new connection is established for every request
    				client.post()
    				      .uri("https://httpbin.org/post")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

### 7.1. HTTP/2

When the `HTTP` client is configured for `HTTP/2`, a specialized connection pool manages `HTTP/2` connections and their streams. By default, the pool opens new connections as needed for concurrent requests. Since `HTTP/2` supports multiplexing — multiple streams over a single connection — you can fine-tune the connection pool behavior using `Http2AllocationStrategy`.

The following table describes the available `Http2AllocationStrategy` configuration options:

Configuration name | Description
--- | ---
`maxConnections` | The maximum number of live connections to keep in the pool. Default: `Integer.MAX_VALUE` (no upper limit).
`minConnections` | The minimum number of live connections to keep in the pool (can be best effort). When configured with a value greater than zero, strict connection reuse is also enabled. Default: `0`.
`maxConcurrentStreams` | The maximum number of concurrent streams that can be opened to the remote peer. The minimum of this configuration and the remote peer configuration is used. Default: `-1` (use the remote peer configuration).
`strictConnectionReuse` | Whether the pool should avoid opening additional connections as long as existing connections have not reached their max concurrent streams limit. When enabled, the pool will maximize `HTTP/2` multiplexing. This is automatically enabled when `minConnections` is greater than zero. Default: `false`.
`streamBatchSize` | The maximum number of streams that can be opened at once when a suitable connection is found in the pool. Instead of opening only one stream and then re-evaluating the available connections, the pool will attempt to open up to `streamBatchSize` streams on that connection, limited by the connection’s available capacity. This setting only takes effect when strict connection reuse is active (either by enabling `strictConnectionReuse(true)` or by setting `minConnections` to a value greater than zero). Default: `1` (open one stream at a time).

The following example shows how to configure the `HTTP/2` connection pool:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/http2/pool/Application.java

    import reactor.netty.http.HttpProtocol;
    import reactor.netty.http.client.Http2AllocationStrategy;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.ConnectionProvider;

    public class Application {

    	public static void main(String[] args) {
    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .allocationStrategy(
    				                      Http2AllocationStrategy.builder()
    				                                             .maxConnections(20)                (1)
    				                                             .minConnections(5)                 (2)
    				                                             .maxConcurrentStreams(50)          (3)
    				                                             .build())
    				                  .build();

    		HttpClient client =
    				HttpClient.create(provider)
    				          .protocol(HttpProtocol.H2)
    				          .secure();

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);

    		provider.disposeLater()
    		        .block();
    	}
    }

1 | Configures the maximum number of live connections to `20`.
2 | Configures the minimum number of live connections to `5`. This also enables strict connection reuse.
3 | Configures the maximum number of concurrent streams per connection to `50`.

#### 7.1.1. Strict Connection Reuse

By default, when the `HTTP` client needs to open a new stream and there are concurrent acquire requests, the pool may allocate additional connections even though existing connections could still accept more streams. Enabling strict connection reuse changes this behavior so that the pool maximizes `HTTP/2` multiplexing by reusing existing connections before allocating new ones.

This can be particularly beneficial when:

- You want to minimize the number of connections to the remote peer.

- Your workload benefits from `HTTP/2` multiplexing (many concurrent small requests).

- You want to reduce connection establishment overhead (TCP + TLS handshakes).

Strict connection reuse can be enabled in two ways:

- Explicitly, by setting `strictConnectionReuse(true)`:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/http2/pool/StrictReuseApplication.java

    import reactor.netty.http.HttpProtocol;
    import reactor.netty.http.client.Http2AllocationStrategy;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.ConnectionProvider;

    public class StrictReuseApplication {

    	public static void main(String[] args) {
    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .allocationStrategy(
    				                      Http2AllocationStrategy.builder()
    				                                             .maxConnections(20)
    				                                             .strictConnectionReuse(true) (1)
    				                                             .build())
    				                  .build();

    		HttpClient client =
    				HttpClient.create(provider)
    				          .protocol(HttpProtocol.H2)
    				          .secure();

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);

    		provider.disposeLater()
    		        .block();
    	}
    }

1 | Enables strict connection reuse. The pool will avoid opening additional connections as long as existing connections have not reached their max concurrent streams limit. Implicitly, by configuring `minConnections` with a value greater than zero (see the example above).

### 7.2. Disposing Connection Pool

- If you use the default `ConnectionProvider` provided by Reactor Netty, invoke `HttpResources``#disposeLoopsAndConnections`/`#disposeLoopsAndConnectionsLater` method.

| Disposing `HttpResources` means that every client that is using it, will not be able to use it anymore!

- If you use custom `ConnectionProvider`, invoke `ConnectionProvider``#dispose`/`#disposeLater`/`#disposeWhen` method.

| Disposing the custom `ConnectionProvider` means that every client that is configured to use it, will not be able to use it anymore!

### 7.3. Metrics

The pooled `ConnectionProvider` supports built-in integration with `Micrometer`. It exposes all metrics with a prefix of `reactor.netty.connection.provider`.

Pooled `ConnectionProvider` metrics

metric name | type | description
--- | --- | ---
reactor.netty.connection.provider.total.connections | Gauge | The number of all connections, active or idle. See Total Connections
reactor.netty.connection.provider.active.connections | Gauge | The number of the connections that have been successfully acquired and are in active use. See Active Connections
reactor.netty.connection.provider.max.connections | Gauge | The maximum number of active connections that are allowed. See Max Connections
reactor.netty.connection.provider.idle.connections | Gauge | The number of the idle connections. See Idle Connections
reactor.netty.connection.provider.pending.connections | Gauge | The number of requests that are waiting for a connection. See Pending Connections
reactor.netty.connection.provider.pending.connections.time | Timer | Time spent in pending acquire a connection from the connection pool. See Pending Connections Time
reactor.netty.connection.provider.max.pending.connections | Gauge | The maximum number of requests that will be queued while waiting for a ready connection. See Max Pending Connections

The following table provides information for the HTTP client metrics when it is configured to serve `HTTP/2` traffic:

metric name | type | description
--- | --- | ---
reactor.netty.connection.provider.active.streams | Gauge | The number of the active HTTP/2 streams. See Active Streams
reactor.netty.connection.provider.pending.streams | Gauge | The number of requests that are waiting for opening HTTP/2 stream. See Pending Streams

The following example enables that integration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/pool/metrics/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.ConnectionProvider;

    public class Application {

    	public static void main(String[] args) {
    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .maxConnections(50)
    				                  .metrics(true) (1)
    				                  .build();

    		HttpClient client = HttpClient.create(provider);

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);

    		provider.disposeLater()
    		        .block();
    	}
    }

1 | Enables the built-in integration with Micrometer

When HTTP client metrics are needed for an integration with a system other than `Micrometer` or you want to provide your own integration with Micrometer, you can provide your own metrics, as follows.

The following `HTTP` example enables that integration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/pool/metrics/http/Application.java

    public class Application {

    	public static void main(String[] args) {
    		AtomicReference<ConnectionPoolMetrics> metrics = new AtomicReference<>();

    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .maxConnections(50)
    				                  .metrics(true, () -> new CustomHttpMeterRegistrar(metrics)) (1)
    				                  .build();

    		HttpClient client = HttpClient.create(provider);

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    		System.out.println("Acquired size " + metrics.get().acquiredSize());
    		System.out.println("Idle size " + metrics.get().idleSize());
    		System.out.println("Pending size " + metrics.get().pendingAcquireSize());

    		provider.disposeLater()
    		        .block();
    	}

    	static final class CustomHttpMeterRegistrar implements ConnectionProvider.MeterRegistrar {
    		AtomicReference<ConnectionPoolMetrics> metrics;

    		CustomHttpMeterRegistrar(AtomicReference<ConnectionPoolMetrics> metrics) {
    			this.metrics = metrics;
    		}

    		@Override
    		public void registerMetrics(String poolName, String id, SocketAddress remoteAddress, ConnectionPoolMetrics metrics) { (2)
    			this.metrics.set(metrics);
    		}

    		@Override
    		public void deRegisterMetrics(String poolName, String id, SocketAddress remoteAddress) { (3)
    			// no op
    		}
    	}
    }

1 | Enables `HTTP` Connection Pool metrics and provides `ConnectionProvider.MeterRegistrar` implementation.
2 | It is called when registering metrics for the `HTTP` connection pool.
3 | It is called when deregistering metrics for the `HTTP` connection pool.

The following `HTTP2`, `H2C`, `HTTP3` example enables that integration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/pool/metrics/http2/Application.java

    public class Application {

    	public static void main(String[] args) {
    		AtomicReference<HttpConnectionPoolMetrics> metrics = new AtomicReference<>();

    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .maxConnections(50)
    				                  .metrics(true, () -> new CustomHttp2MeterRegistrar(metrics)) (1)
    				                  .build();

    		HttpClient client = HttpClient.create(provider).protocol(HttpProtocol.H2);

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    		System.out.println("Active stream size " + metrics.get().activeStreamSize());
    		System.out.println("Pending stream size " + metrics.get().pendingAcquireSize());

    		provider.disposeLater()
    		        .block();
    	}

    	static final class CustomHttp2MeterRegistrar extends HttpMeterRegistrarAdapter {
    		AtomicReference<HttpConnectionPoolMetrics> metrics;

    		CustomHttp2MeterRegistrar(AtomicReference<HttpConnectionPoolMetrics> metrics) {
    			this.metrics = metrics;
    		}

    		@Override
    		public void registerMetrics(String poolName, String id, SocketAddress remoteAddress, HttpConnectionPoolMetrics metrics) { (2)
    			this.metrics.set(metrics);
    		}

    		@Override
    		public void deRegisterMetrics(String poolName, String id, SocketAddress remoteAddress) { (3)
    			// no op
    		}
    	}
    }

1 | Enables `HTTP2`, `H2C`, `HTTP3` Connection Pool metrics and provides `HttpMeterRegistrarAdapter` implementation.
2 | It is called when registering metrics for the `HTTP2`, `H2C`, `HTTP3` connection pool.
3 | It is called when deregistering metrics for the `HTTP2`, `H2C`, `HTTP3` connection pool.

## 8. SSL and TLS

When you need SSL or TLS, you can apply the configuration shown in the next example. By default, if `OpenSSL` is available, a SslProvider.OPENSSL provider is used as a provider. Otherwise, a SslProvider.JDK provider is used You can switch the provider by using SslContextBuilder or by setting `-Dio.netty.handler.ssl.noOpenSsl=true`. The following example uses `SslContextBuilder`:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/security/Application.java

    import reactor.netty.http.Http11SslContextSpec;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forClient();

    		HttpClient client =
    				HttpClient.create()
    				          .secure(spec -> spec.sslContext(http11SslContextSpec));

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

### 8.1. Server Name Indication

By default, the `HTTP` client sends the remote host name as `SNI` server name. When you need to change this default setting, you can configure the `HTTP` client as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/sni/Application.java

    import io.netty.handler.ssl.SslContext;
    import io.netty.handler.ssl.SslContextBuilder;
    import reactor.netty.http.client.HttpClient;

    import javax.net.ssl.SNIHostName;

    public class Application {

    	public static void main(String[] args) throws Exception {
    		SslContext sslContext = SslContextBuilder.forClient().build();

    		HttpClient client =
    				HttpClient.create()
    				          .secure(spec -> spec.sslContext(sslContext)
    				                              .serverNames(new SNIHostName("test.com")));

    		client.get()
    		      .uri("https://127.0.0.1:8080/")
    		      .response()
    		      .block();
    	}
    }

## 9. Retry Strategies

By default, the `HTTP` client retries the request once if it was aborted on the `TCP` level.

## 10. HTTP/2

By default, the `HTTP` client supports `HTTP/1.1`. If you need `HTTP/2`, you can get it through configuration. In addition to the protocol configuration, if you need `H2` but not `H2C (cleartext)`, you must also configure SSL.

| As Application-Layer Protocol Negotiation (ALPN) is not supported “out-of-the-box” by JDK8 (although some vendors backported ALPN to JDK8), you need an additional dependency to a native library that supports it — for example, `netty-tcnative-boringssl-static`.

The following listing presents a simple `H2` example:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/http2/H2Application.java

    import io.netty.handler.codec.http.HttpHeaders;
    import reactor.core.publisher.Mono;
    import reactor.netty.http.HttpProtocol;
    import reactor.netty.http.client.HttpClient;
    import reactor.util.function.Tuple2;

    public class H2Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .protocol(HttpProtocol.H2) (1)
    				          .secure();                 (2)

    		Tuple2<String, HttpHeaders> response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseSingle((res, bytes) -> bytes.asString()
    				                                           .zipWith(Mono.just(res.responseHeaders())))
    				      .block();

    		System.out.println("Used stream ID: " + response.getT2().get("x-http2-stream-id"));
    		System.out.println("Response: " + response.getT1());
    	}
    }

1 | Configures the client to support only `HTTP/2`
2 | Configures `SSL`

The following listing presents a simple `H2C` example:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/http2/H2CApplication.java

    import io.netty.handler.codec.http.HttpHeaders;
    import reactor.core.publisher.Mono;
    import reactor.netty.http.HttpProtocol;
    import reactor.netty.http.client.HttpClient;
    import reactor.util.function.Tuple2;

    public class H2CApplication {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .protocol(HttpProtocol.H2C);

    		Tuple2<String, HttpHeaders> response =
    				client.get()
    				      .uri("http://localhost:8080/")
    				      .responseSingle((res, bytes) -> bytes.asString()
    				                                           .zipWith(Mono.just(res.responseHeaders())))
    				      .block();

    		System.out.println("Used stream ID: " + response.getT2().get("x-http2-stream-id"));
    		System.out.println("Response: " + response.getT1());
    	}
    }

### 10.1. Protocol Selection

github.com/reactor/reactor-netty/tree/main/reactor-netty-http/src/main/java/reactor/netty/http/HttpProtocol.java

    public enum HttpProtocol {

    	/**
    	 * The default supported HTTP protocol by HttpServer and HttpClient.
    	 */
    	HTTP11,

    	/**
    	 * HTTP/2.0 support with TLS
    	 * <p>If used along with HTTP/1.1 protocol, HTTP/2.0 will be the preferred protocol.
    	 * While negotiating the application level protocol, HTTP/2.0 or HTTP/1.1 can be chosen.
    	 * <p>If used without HTTP/1.1 protocol, HTTP/2.0 will always be offered as a protocol
    	 * for communication with no fallback to HTTP/1.1.
    	 */
    	H2,

    	/**
    	 * HTTP/2.0 support with clear-text.
    	 * <p>If used along with HTTP/1.1 protocol, will support H2C "upgrade":
    	 * Request or consume requests as HTTP/1.1 first, looking for HTTP/2.0 headers
    	 * and {@literal Connection: Upgrade}. A server will typically reply a successful
    	 * 101 status if upgrade is successful or a fallback HTTP/1.1 response. When
    	 * successful the client will start sending HTTP/2.0 traffic.
    	 * <p>If used without HTTP/1.1 protocol, will support H2C "prior-knowledge": Doesn't
    	 * require {@literal Connection: Upgrade} handshake between a client and server but
    	 * fallback to HTTP/1.1 will not be supported.
    	 */
    	H2C,

    	/**
    	 * HTTP/3.0 support.
    	 * @since 1.2.0
    	 */
    	HTTP3
    }

## 11. HTTP/3

By default, the `HTTP` client supports `HTTP/1.1`. If you need `HTTP/3`, you can get it through configuration. In addition to the protocol configuration, you need to add dependency to `io.netty:netty-codec-native-quic`.

The following listing presents a simple `HTTP3` example:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/http3/Application.java

    import io.netty.handler.codec.http.HttpHeaders;
    import reactor.core.publisher.Mono;
    import reactor.netty.http.HttpProtocol;
    import reactor.netty.http.client.HttpClient;
    import reactor.util.function.Tuple2;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) throws Exception {
    		HttpClient client =
    				HttpClient.create()
    				          .protocol(HttpProtocol.HTTP3)                                  (1)
    				          .http3Settings(spec -> spec.idleTimeout(Duration.ofSeconds(5)) (2)
    				                                     .maxData(10000000)
    				                                     .maxStreamDataBidirectionalLocal(1000000));

    		Tuple2<String, HttpHeaders> response =
    				client.get()
    				      .uri("https://www.google.com/")
    				      .responseSingle((res, bytes) -> bytes.asString()
    				                                           .zipWith(Mono.just(res.responseHeaders())))
    				      .block();

    		System.out.println("Used stream ID: " + response.getT2().get("x-http3-stream-id"));
    		System.out.println("Response: " + response.getT1());
    	}
    }

1 | Configures the client to support only `HTTP/3`
2 | Configures `HTTP/3` settings

## 12. Proxy Support

Reactor Netty supports the proxy functionality provided by Netty and provides a way to specify `non proxy hosts` through the `ProxyProvider` builder.

Netty’s HTTP proxy support always uses `CONNECT` method in order to establish a tunnel to the specified proxy regardless of the scheme that is used `http` or `https`. (More information: Netty enforce HTTP proxy to support HTTP CONNECT method). Some proxies might not support `CONNECT` method when the scheme is `http` or might need to be configured in order to support this way of communication. Sometimes this might be the reason for not being able to connect to the proxy. Consider checking the proxy documentation whether it supports or needs an additional configuration in order to support `CONNECT` method.

| When a proxy is configured and no custom DNS resolver has been specified, Reactor Netty automatically uses `NoopAddressResolverGroup` to skip client-side DNS resolution and delegate it to the proxy. If a custom DNS resolver is explicitly configured (e.g. the JVM built-in resolver via `DefaultAddressResolverGroup.INSTANCE` or via the `resolver(…​)` method), Reactor Netty respects that configuration and does not override it. In that case, the configured resolver must be able to resolve the target hostname on the client side, even when using a proxy.

The following example uses `ProxyProvider`:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/proxy/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.transport.ProxyProvider;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
    				                             .host("proxy")
    				                             .port(8080)
    				                             .nonProxyHosts("localhost")
    				                             .connectTimeoutMillis(20_000)); (1)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | Configures the connection establishment timeout to 20 seconds.

The `proxyWhen(…​)` method allows for dynamic proxy configuration.

- `proxyWhen(…​)` takes an `HttpClientConfig` and enables setting a proxy for each individual HTTP request, providing flexibility to choose different proxies depending on the request.

- Once the proxy is set using `proxyWhen(…​)`, any previous proxy settings via `proxy(…​)` or `noProxy()` will be ignored. github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/proxy/deferred/Application.java

    import reactor.core.publisher.Mono;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.transport.ProxyProvider;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .proxyWhen((httpClientConfig, spec) -> {    // only applied
    				              if (httpClientConfig.uri().startsWith("https://example.com")) {
    				                  return Mono.justOrEmpty(
    				                          spec.type(ProxyProvider.Proxy.HTTP)
    				                              .host("proxy")
    				                              .port(8080)
    				                              .nonProxyHosts("localhost")
    				                              .connectTimeoutMillis(20_000));
    				              }

    				              return Mono.empty();
    				          })
    				          .proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
    				                             .host("ignored-proxy-domain")
    				                             .port(9000)
    				                             .connectTimeoutMillis(20_000))   // ignored
    				          .noProxy(); // ignored

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

## 13. Metrics

The HTTP client supports built-in integration with `Micrometer`. It exposes all metrics with a prefix of `reactor.netty.http.client`.

The following table provides information for the HTTP client metrics:

metric name | type | description
--- | --- | ---
reactor.netty.http.client.data.received | DistributionSummary | Amount of the data received, in bytes. See Data Received
reactor.netty.http.client.data.sent | DistributionSummary | Amount of the data sent, in bytes. See Data Sent
reactor.netty.http.client.errors | Counter | Number of errors that occurred. See Errors Count
reactor.netty.http.client.tls.handshake.time | Timer | Time spent for TLS handshake. See Tls Handshake Time
reactor.netty.http.client.connect.time | Timer | Time spent for connecting to the remote address. See Connect Time
reactor.netty.http.client.address.resolver | Timer | Time spent for resolving the address. See Hostname Resolution Time
reactor.netty.http.client.data.received.time | Timer | Time spent in consuming incoming data. See Http Client Data Received Time
reactor.netty.http.client.data.sent.time | Timer | Time spent in sending outgoing data. See Http Client Data Sent Time
reactor.netty.http.client.response.time | Timer | Total time for the request/response See Http Client Response Time

These additional metrics are also available:

Pooled `ConnectionProvider` metrics

metric name | type | description
--- | --- | ---
reactor.netty.connection.provider.total.connections | Gauge | The number of all connections, active or idle. See Total Connections
reactor.netty.connection.provider.active.connections | Gauge | The number of the connections that have been successfully acquired and are in active use. See Active Connections
reactor.netty.connection.provider.max.connections | Gauge | The maximum number of active connections that are allowed. See Max Connections
reactor.netty.connection.provider.idle.connections | Gauge | The number of the idle connections. See Idle Connections
reactor.netty.connection.provider.pending.connections | Gauge | The number of requests that are waiting for a connection. See Pending Connections
reactor.netty.connection.provider.pending.connections.time | Timer | Time spent in pending acquire a connection from the connection pool. See Pending Connections Time
reactor.netty.connection.provider.max.pending.connections | Gauge | The maximum number of requests that will be queued while waiting for a ready connection. See Max Pending Connections

The following table provides information for the HTTP client metrics when it is configured to serve `HTTP/2` traffic:

metric name | type | description
--- | --- | ---
reactor.netty.connection.provider.active.streams | Gauge | The number of the active HTTP/2 streams. See Active Streams
reactor.netty.connection.provider.pending.streams | Gauge | The number of requests that are waiting for opening HTTP/2 stream. See Pending Streams

`ByteBufAllocator` metrics

metric name | type | description
--- | --- | ---
reactor.netty.bytebuf.allocator.used.heap.memory | Gauge | The number of bytes reserved by heap buffer allocator. See Used Heap Memory
reactor.netty.bytebuf.allocator.used.direct.memory | Gauge | The number of bytes reserved by direct buffer allocator. See Used Direct Memory
reactor.netty.bytebuf.allocator.heap.arenas | Gauge | The number of heap arenas (when `PooledByteBufAllocator`). See Heap Arenas
reactor.netty.bytebuf.allocator.direct.arenas | Gauge | The number of direct arenas (when `PooledByteBufAllocator`). See Direct Arenas
reactor.netty.bytebuf.allocator.threadlocal.caches | Gauge | The number of thread local caches (when `PooledByteBufAllocator`). See Thread Local Caches
reactor.netty.bytebuf.allocator.small.cache.size | Gauge | The size of the small cache (when `PooledByteBufAllocator`). See Small Cache Size
reactor.netty.bytebuf.allocator.normal.cache.size | Gauge | The size of the normal cache (when `PooledByteBufAllocator`). See Normal Cache Size
reactor.netty.bytebuf.allocator.chunk.size | Gauge | The chunk size for an arena (when `PooledByteBufAllocator`). See Chunk Size
reactor.netty.bytebuf.allocator.active.heap.memory | Gauge | The actual bytes consumed by in-use buffers allocated from heap buffer pools (when `PooledByteBufAllocator`). See Active Heap Memory
reactor.netty.bytebuf.allocator.active.direct.memory | Gauge | The actual bytes consumed by in-use buffers allocated from direct buffer pools (when `PooledByteBufAllocator`). See Active Direct Memory

`EventLoop` metrics

metric name | type | description
--- | --- | ---
reactor.netty.eventloop.pending.tasks | Gauge | The number of tasks that are pending for processing on an event loop. See Pending Tasks

The following example enables that integration:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/metrics/Application.java

    import io.micrometer.core.instrument.Metrics;
    import io.micrometer.core.instrument.config.MeterFilter;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		Metrics.globalRegistry (1)
    		       .config()
    		       .meterFilter(MeterFilter.maximumAllowableTags("reactor.netty.http.client", "URI", 100, MeterFilter.deny()));

    		HttpClient client =
    				HttpClient.create()
    				          .metrics(true, s -> {
    				              if (s.startsWith("/stream/")) { (2)
    				                  return "/stream/{n}";
    				              }
    				              else if (s.startsWith("/bytes/")) {
    				                  return "/bytes/{n}";
    				              }
    				              return s;
    				          }); (3)

    		client.get()
    		      .uri("https://httpbin.org/stream/2")
    		      .responseContent()
    		      .blockLast();

    		client.get()
    		      .uri("https://httpbin.org/bytes/1024")
    		      .responseContent()
    		      .blockLast();
    	}
    }

1 | Applies upper limit for the meters with `URI` tag
2 | Templated URIs will be used as a URI tag value when possible
3 | Enables the built-in integration with Micrometer

| In order to avoid a memory and CPU overhead of the enabled metrics, it is important to convert the real URIs to templated URIs when possible. Without a conversion to a template-like form, each distinct URI leads to the creation of a distinct tag, which takes a lot of memory for the metrics.

| Always apply an upper limit for the meters with URI tags. Configuring an upper limit on the number of meters can help in cases when the real URIs cannot be templated. You can find more information at `maximumAllowableTags`.

When HTTP client metrics are needed for an integration with a system other than `Micrometer` or you want to provide your own integration with `Micrometer`, you can provide your own metrics recorder, as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/metrics/custom/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.http.client.HttpClientMetricsRecorder;

    import java.net.SocketAddress;
    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .metrics(true, CustomHttpClientMetricsRecorder::new); (1)

    		client.get()
    		      .uri("https://httpbin.org/stream/2")
    		      .response()
    		      .block();
    	}

1 | Enables HTTP client metrics and provides `HttpClientMetricsRecorder` implementation.

## 14. Tracing

The HTTP client supports built-in integration with `Micrometer Tracing`.

The following table provides information for the HTTP client spans:

contextual name | description
--- | ---
HTTP <HTTP METHOD> | Information and total time for the request. See Http Client Response Span.
hostname resolution | Information and time spent for resolving the address. See Hostname Resolution Span.
connect | Information and time spent for connecting to the remote address. See Connect Span.
tls handshake | Information and time spent for TLS handshake. See Tls Handshake Span.

The following example enables that integration. This concrete example uses `Brave` and reports the information to `Zipkin`. See the `Micrometer Tracing` documentation for `OpenTelemetry` setup.

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/tracing/Application.java

    import brave.Tracing;
    import brave.propagation.StrictCurrentTraceContext;
    import brave.sampler.Sampler;
    import io.micrometer.tracing.CurrentTraceContext;
    import io.micrometer.tracing.Tracer;
    import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
    import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext;
    import io.micrometer.tracing.brave.bridge.BravePropagator;
    import io.micrometer.tracing.brave.bridge.BraveTracer;
    import io.micrometer.tracing.propagation.Propagator;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.http.observability.ReactorNettyPropagatingSenderTracingObservationHandler;
    import reactor.netty.observability.ReactorNettyTracingObservationHandler;
    import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
    import zipkin2.reporter.urlconnection.URLConnectionSender;

    import static reactor.netty.Metrics.OBSERVATION_REGISTRY;

    public class Application {

    	public static void main(String[] args) {
    		init(); (1)

    		HttpClient client =
    				HttpClient.create()
    				          .metrics(true, s -> {
    				              if (s.startsWith("/stream/")) { (2)
    				                  return "/stream/{n}";
    				              }
    				              return s;
    				          }); (3)

    		client.get()
    		      .uri("https://httpbin.org/stream/3")
    		      .responseContent()
    		      .blockLast();
    	}

1 | Initializes Brave, Zipkin, and the Observation registry.
2 | Templated URIs are used as an URI tag value when possible.
3 | Enables the built-in integration with Micrometer.

The result in `Zipkin` looks like:

### 14.1. Access Current Observation

Project Micrometer provides `a library` that assists with context propagation across different types of context mechanisms such as `ThreadLocal`, `Reactor Context` and others.

The following example shows how to use this library in a custom `ChannelHandler`:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/tracing/custom/Application.java

    import brave.Tracing;
    import brave.propagation.StrictCurrentTraceContext;
    import brave.sampler.Sampler;
    import io.micrometer.context.ContextSnapshot;
    import io.micrometer.context.ContextSnapshotFactory;
    import io.micrometer.tracing.CurrentTraceContext;
    import io.micrometer.tracing.Tracer;
    import io.micrometer.tracing.brave.bridge.BraveBaggageManager;
    import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext;
    import io.micrometer.tracing.brave.bridge.BravePropagator;
    import io.micrometer.tracing.brave.bridge.BraveTracer;
    import io.micrometer.tracing.propagation.Propagator;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelOutboundHandlerAdapter;
    import io.netty.channel.ChannelPromise;
    import reactor.netty.NettyPipeline;
    import reactor.netty.http.client.HttpClient;
    import reactor.netty.http.observability.ReactorNettyPropagatingSenderTracingObservationHandler;
    import reactor.netty.observability.ReactorNettyTracingObservationHandler;
    import zipkin2.reporter.brave.AsyncZipkinSpanHandler;
    import zipkin2.reporter.urlconnection.URLConnectionSender;

    import static reactor.netty.Metrics.OBSERVATION_REGISTRY;

    public class Application {

    	public static void main(String[] args) {
    		init(); (1)

    		HttpClient client =
    				HttpClient.create()
    				          .metrics(true, s -> {
    				              if (s.startsWith("/stream/")) { (2)
    				                  return "/stream/{n}";
    				              }
    				              return s;
    				          }) (3)
    				          .doOnConnected(conn -> conn.channel().pipeline().addAfter(NettyPipeline.HttpCodec,
    				                  "custom-channel-handler", CustomChannelOutboundHandler.INSTANCE)); (4)

    		client.get()
    		      .uri("https://httpbin.org/stream/3")
    		      .responseContent()
    		      .blockLast();
    	}

    	static final class CustomChannelOutboundHandler extends ChannelOutboundHandlerAdapter {

    		static final ChannelHandler INSTANCE = new CustomChannelOutboundHandler();

    		@Override
    		public boolean isSharable() {
    			return true;
    		}

    		@Override
    		public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
    			try (ContextSnapshot.Scope scope = ContextSnapshotFactory.builder().build().setThreadLocalsFrom(ctx.channel())) {
    				System.out.println("Current Observation in Scope: " + OBSERVATION_REGISTRY.getCurrentObservation());
    				//"FutureReturnValueIgnored" this is deliberate
    				ctx.write(msg, promise);
    			}
    			System.out.println("Current Observation: " + OBSERVATION_REGISTRY.getCurrentObservation());
    		}
    	}

1 | Initializes Brave, Zipkin, and the Observation registry.
2 | Templated URIs are used as an URI tag value when possible.
3 | Enables the built-in integration with Micrometer.
4 | Custom `ChannelHandler` that uses context propagation library. This concrete example overrides only `ChannelOutboundHandlerAdapter#write`, if it is needed, the same logic can be used for the rest of the methods. Also, this concrete example sets all `ThreadLocal` values for which there is a value in the given `Channel`, if another behaviour is needed please check `context propagation library API`. For example, you may want to set only some of the `ThreadLocal` values.

| When you enable Reactor Netty tracing within a framework, you may need to let Reactor Netty use the `ObservationRegistry` created by this framework. For this purpose you need to invoke `reactor.netty.Metrics#observationRegistry`. You may also need to configure the Reactor Netty `ObservationHandlers` using the API provided by the framework.

## 15. Unix Domain Sockets

The `HTTP` client supports Unix Domain Sockets (UDS) when native transport is in use for all java versions and when NIO transport is in use for java 17 and above.

The following example shows how to use UDS support:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/uds/Application.java

    import io.netty.channel.unix.DomainSocketAddress;
    import reactor.netty.http.client.HttpClient;

    //import java.net.UnixDomainSocketAddress;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          // The configuration below is available only when NIO transport is used with Java 17+
    				          //.remoteAddress(() -> UnixDomainSocketAddress.of("/tmp/test.sock"));
    				          // The configuration below is available only when Epoll/KQueue transport is used
    				          .remoteAddress(() -> new DomainSocketAddress("/tmp/test.sock")); (1)

    		client.get()
    		      .uri("/")
    		      .response()
    		      .block();
    	}
    }

1 | Specifies `DomainSocketAddress` that will be used

## 16. Host Name Resolution

By default, the `HttpClient` uses Netty’s domain name lookup mechanism that resolves a domain name asynchronously. This is as an alternative of the JVM’s built-in blocking resolver.

When you need to change the default settings, you can configure the `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/resolver/Application.java

    import reactor.netty.http.client.HttpClient;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .resolver(spec -> spec.queryTimeout(Duration.ofMillis(500))); (1)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | The timeout of each DNS query performed by this resolver will be 500ms.

The following listing shows the available configurations. Additionally, `TCP fallback` is enabled by default.

Configuration name | Description
--- | ---
`bindAddressSupplier` | The supplier of the local address to bind to.
`cacheMaxTimeToLive` | The max time to live of the cached DNS resource records (resolution: seconds). If the time to live of the DNS resource record returned by the DNS server is greater than this max time to live, this resolver ignores the time to live from the DNS server and uses this max time to live. Default to `Integer.MAX_VALUE`.
`cacheMinTimeToLive` | The min time to live of the cached DNS resource records (resolution: seconds). If the time to live of the DNS resource record returned by the DNS server is less than this min time to live, this resolver ignores the time to live from the DNS server and uses this min time to live. Default: 0.
`cacheNegativeTimeToLive` | The time to live of the cache for the failed DNS queries (resolution: seconds). Default: 0.
`completeOncePreferredResolved` | When this setting is enabled, the resolver notifies as soon as all queries for the preferred address type are complete. When this setting is disabled, the resolver notifies when all possible address types are complete. This configuration is applicable for `DnsNameResolver#resolveAll(String)`. By default, this setting is enabled.
`datagramChannelStrategy` | Sets the strategy that is used to determine how a `DatagramChannel` is used by the resolver for sending queries over UDP protocol. Default to `DnsNameResolverChannelStrategy#ChannelPerResolver`
`disableOptionalRecord` | Disables the automatic inclusion of an optional record that tries to give a hint to the remote DNS server about how much data the resolver can read per response. By default, this setting is enabled.
`disableRecursionDesired` | Specifies whether this resolver has to send a DNS query with the recursion desired (RD) flag set. By default, this setting is enabled.
`dnsAddressResolverGroupProvider` | Sets a custom function to create a `DnsAddressResolverGroup` given a `DnsNameResolverBuilder`
`hostsFileEntriesResolver` | Sets a custom `HostsFileEntriesResolver` to be used for hosts file entries. Default: `DefaultHostsFileEntriesResolver`.
`maxPayloadSize` | Sets the capacity of the datagram packet buffer (in bytes). Default: 4096.
`maxQueriesPerResolve` | Sets the maximum allowed number of DNS queries to send when resolving a host name. Default: 16.
`ndots` | Sets the number of dots that must appear in a name before an initial absolute query is made. Default: -1 (to determine the value from the OS on Unix or use a value of 1 otherwise).
`queryTimeout` | Sets the timeout of each DNS query performed by this resolver (resolution: milliseconds). Default: 5000.
`resolveCache` | The cache to use to store resolved DNS entries.
`resolvedAddressTypes` | The list of the protocol families of the resolved address.
`retryTcpOnTimeout` | Specifies whether this resolver will also fallback to TCP if a timeout is detected. By default, the resolver will only try to use TCP if the response is marked as truncated.
`roundRobinSelection` | Enables an `AddressResolverGroup` of `DnsNameResolver` that supports random selection of destination addresses if multiple are provided by the nameserver. See `RoundRobinDnsAddressResolverGroup`. Default: `DnsAddressResolverGroup`
`runOn` | Performs the communication with the DNS servers on the given `LoopResources`. By default, the LoopResources specified on the client level are used.
`searchDomains` | The list of search domains of the resolver. By default, the effective search domain list is populated by using the system DNS search domains.
`trace` | A specific logger and log level to be used by this resolver when generating detailed trace information in case of resolution failure.

Sometimes, you may want to switch to the JVM built-in resolver. To do so, you can configure the `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/resolver/custom/Application.java

    import io.netty.resolver.DefaultAddressResolverGroup;
    import reactor.netty.http.client.HttpClient;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .resolver(DefaultAddressResolverGroup.INSTANCE); (1)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | Sets the JVM built-in resolver.

## 17. Timeout Configuration

This section describes various timeout configuration options that can be used in `HttpClient`. Configuring a proper timeout may improve or solve issues in the communication process. The configuration options can be grouped as follows:

- Connection Pool Timeout

- HttpClient Timeout

  - Response Timeout

  - Connection Timeout

  - SSL/TLS Timeout

  - Proxy Timeout

  - Host Name Resolution Timeout

### 17.1. Connection Pool Timeout

By default, `HttpClient` uses a connection pool. When a request is completed successfully and if the connection is not scheduled for closing, the connection is returned to the connection pool and can thus be reused for processing another request. The connection may be reused immediately for another request or may stay idle in the connection pool for some time.

The following list describes the available timeout configuration options:

- `maxIdleTime` - The maximum time (resolution: ms) that this connection stays idle in the connection pool. By default, `maxIdleTime` is not specified.

| When you configure `maxIdleTime`, you should consider the idle timeout configuration on the target server. Choose a configuration that is equal to or less than the one on the target server. By doing so, you can reduce the I/O issues caused by a connection closed by the target server.

- `maxLifeTime` - The maximum time (resolution: ms) that this connection stays alive. By default, `maxLifeTime` is not specified.

- `pendingAcquireTimeout` - The maximum time (resolution: ms) after which a pending acquire operation must complete, or a `PoolAcquireTimeoutException` is thrown. Default: 45s.

By default, these timeouts are checked on connection `release` or `acquire` operations and, if some timeout is reached, the connection is closed and removed from the connection pool. However, you can also configure the connection pool, by setting `evictInBackground`, to perform periodic checks on connections.

To customize the default settings, you can configure `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/pool/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.resources.ConnectionProvider;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		ConnectionProvider provider =
    				ConnectionProvider.builder("custom")
    				                  .maxConnections(50)
    				                  .maxIdleTime(Duration.ofSeconds(20))           (1)
    				                  .maxLifeTime(Duration.ofSeconds(60))           (2)
    				                  .maxLifeTimeVariance(2.5)                      (3)
    				                  .pendingAcquireTimeout(Duration.ofSeconds(60)) (4)
    				                  .evictInBackground(Duration.ofSeconds(120))    (5)
    				                  .build();

    		HttpClient client = HttpClient.create(provider);

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);

    		provider.disposeLater()
    		        .block();
    	}
    }

1 | Configures the maximum time for a connection to stay idle to 20 seconds.
2 | Configures the maximum time for a connection to stay alive to 60 seconds.
3 | Configures the maximum time for the pending acquire operation to 60 seconds.
4 | Every two minutes, the connection pool is regularly checked for connections that are applicable for removal.

### 17.2. HttpClient Timeout

This section provides information for the various timeout configuration options at the `HttpClient` level.

| Reactor Netty uses Reactor Core as its Reactive Streams implementation, and you may want to use the `timeout` operator that `Mono` and `Flux` provide. Keep in mind, however, that it is better to use the more specific timeout configuration options available in Reactor Netty, since they provide more control for a specific purpose and use case. By contrast, the `timeout` operator can only apply to the operation as a whole, from establishing the connection to the remote peer to receiving the response.

#### 17.2.1. Response Timeout

`HttpClient` provides an API for configuring a default response timeout for all requests. You can change this default response timeout through an API for a specific request. By default, `responseTimeout` is not specified.

| It is always a good practice to configure a response timeout.

To customize the default settings, you can configure `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/read/timeout/Application.java

    import reactor.core.publisher.Mono;
    import reactor.netty.http.client.HttpClient;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .responseTimeout(Duration.ofSeconds(1));    (1)

    		String response1 =
    				client.post()
    				      .uri("https://example.com/")
    				      .send((req, out) -> {
    				          req.responseTimeout(Duration.ofSeconds(2)); (2)
    				          return out.sendString(Mono.just("body1"));
    				      })
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response1);

    		String response2 =
    				client.post()
    				      .uri("https://example.com/")
    				      .send((req, out) -> out.sendString(Mono.just("body2")))
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response2);
    	}
    }

1 | Configures the default response timeout to 1 second.
2 | Configures a response timeout for a specific request to 2 seconds.

#### 17.2.2. Connection Timeout

The following listing shows all available connection timeout configuration options, but some of them may apply only to a specific transport.

- `CONNECT_TIMEOUT_MILLIS` - If the connection establishment attempt to the remote peer does not finish within the configured connect timeout (resolution: ms), the connection establishment attempt fails. Default: 30s.

- `SO_KEEPALIVE` - When the connection stays idle for some time (the time is implementation dependent, but the default is typically two hours), TCP automatically sends a `keepalive` probe to the remote peer. By default, `SO_KEEPALIVE` is not enabled. When you run with `Epoll`/`NIO` (since Java 11 on Mac or Linux) transport, you may also configure:

  - `TCP_KEEPIDLE` - The maximum time (resolution: seconds) that this connection stays idle before TCP starts sending `keepalive` probes, if `SO_KEEPALIVE` has been set. The maximum time is implementation dependent, but the default is typically two hours.

  - `TCP_KEEPINTVL` (Epoll)/`TCP_KEEPINTERVAL` (NIO) - The time (resolution: seconds) between individual `keepalive` probes.

  - `TCP_KEEPCNT` (Epoll)/`TCP_KEEPCOUNT` (NIO) - The maximum number of `keepalive` probes TCP should send before dropping the connection.

| Sometimes, between the client and the server, you may have a network component that silently drops the idle connections without sending a response. From the Reactor Netty point of view, in this use case, the remote peer just does not respond. To be able to handle such a use case you may consider configuring `SO_KEEPALIVE`.

To customize the default settings, you can configure `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/channeloptions/Application.java

    import io.netty.channel.ChannelOption;
    import io.netty.channel.epoll.EpollChannelOption;
    //import io.netty.channel.socket.nio.NioChannelOption;
    //import jdk.net.ExtendedSocketOptions;
    import reactor.netty.http.client.HttpClient;
    import java.net.InetSocketAddress;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .bindAddress(() -> new InetSocketAddress("host", 1234))
    				          .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) (1)
    				          .option(ChannelOption.SO_KEEPALIVE, true)            (2)
    				          // The options below are available only when NIO transport (Java 11) is used
    				          // on Mac or Linux (Java does not currently support these extended options on Windows)
    				          // https://bugs.openjdk.java.net/browse/JDK-8194298
    				          //.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), 300)
    				          //.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), 60)
    				          //.option(NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), 8);
    				          // The options below are available only when Epoll transport is used
    				          .option(EpollChannelOption.TCP_KEEPIDLE, 300)        (3)
    				          .option(EpollChannelOption.TCP_KEEPINTVL, 60)        (4)
    				          .option(EpollChannelOption.TCP_KEEPCNT, 8);          (5)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | Configures the connection establishment timeout to 10 seconds.
2 | Enables TCP `keepalive`. This means that TCP starts sending `keepalive` probes when a connection is idle for some time.
3 | The connection needs to remain idle for 5 minutes before TCP starts sending `keepalive` probes.
4 | Configures the time between individual `keepalive` probes to 1 minute.
5 | Configures the maximum number of TCP `keepalive` probes to 8.

### 17.3. HTTP/2 Connection Health Check

For `HTTP/2 connections`, it’s recommended to configure `PING` frames for connection maintenance and health monitoring. The `HttpClient` uses `PING` frames to detect unresponsive connections when `maxIdleTime` on the connection pool is configured.

Benefits of using `PING` frames:

- Actively probes connection liveness when idle

- Detects unresponsive connections through timely health checks

- Closes stale connections automatically

- Maintains reliable connection pool state

| `HTTP/2` `PING` frame-based health checks require `maxIdleTime` on the connection pool to be configured as a prerequisite. Without idle timeout configuration, PING frames will not be sent and connections may remain open indefinitely, preventing proper detection of inactive or unresponsive connections. The idle timeout and PING settings work together: Connection becomes idle (no activity for `maxIdleTime`) A PING frame is sent to check if the peer is responsive If no ACK within `pingAckTimeout`, retry based on `pingAckDropThreshold` If all attempts fail, the connection is closed

To enable `PING` frames for `HTTP/2` connections, configure the `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/liveness/Application.java

    public class Application {

    	public static void main(String[] args) {
    		ConnectionProvider provider =
    				ConnectionProvider.builder("liveness")
    				                  .maxIdleTime(Duration.ofSeconds(2))                              (1)
    				                  .build();

    		HttpClient client =
    				HttpClient.create(provider)
    				          .protocol(HttpProtocol.H2)
    				          .secure()
    				          .http2Settings(builder -> builder.pingAckTimeout(Duration.ofMillis(600)) (2)
    				                                           .pingAckDropThreshold(2));              (3)

    		Tuple2<String, HttpHeaders> response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseSingle((res, bytes) -> bytes.asString()
    				                                           .zipWith(Mono.just(res.responseHeaders())))
    				      .block();

    		System.out.println("Used stream ID: " + response.getT2().get("x-http2-stream-id"));
    		System.out.println("Response: " + response.getT1());
    	}
    }

1 | Configure `maxIdleTime` to enable PING-based health checks. When a connection is idle for this duration, a PING frame will be sent.
2 | Configure `pingAckTimeout` - the maximum time to wait for each PING ACK response. If no ACK is received within this timeout, another PING is sent (up to the threshold).
3 | Configure `pingAckDropThreshold` - the maximum number of PING frames to send before closing the connection. For example, `2` means send up to 2 PING frames (if first fails, try once more) before closing.

#### 17.3.1. SSL/TLS Timeout

`HttpClient` supports the SSL/TLS functionality provided by Netty.

The following list describes the available timeout configuration options:

- `handshakeTimeout` - Use this option to configure the SSL handshake timeout (resolution: ms). Default: 10s.

| You should consider increasing the SSL handshake timeout when expecting slow network connections.

- `closeNotifyFlushTimeout` - Use this option to configure the SSL `close_notify` flush timeout (resolution: ms). Default: 3s.

- `closeNotifyReadTimeout` - Use this option to configure the SSL `close_notify` read timeout (resolution: ms). Default: 0s.

To customize the default settings, you can configure `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/security/custom/Application.java

    import reactor.netty.http.Http11SslContextSpec;
    import reactor.netty.http.client.HttpClient;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		Http11SslContextSpec http11SslContextSpec = Http11SslContextSpec.forClient();

    		HttpClient client =
    				HttpClient.create()
    				          .secure(spec -> spec.sslContext(http11SslContextSpec)
    				                              .handshakeTimeout(Duration.ofSeconds(30))         (1)
    				                              .closeNotifyFlushTimeout(Duration.ofSeconds(10))  (2)
    				                              .closeNotifyReadTimeout(Duration.ofSeconds(10))); (3)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | Configures the SSL handshake timeout to 30 seconds.
2 | Configures the SSL `close_notify` flush timeout to 10 seconds.
3 | Configures the SSL `close_notify` read timeout to 10 seconds.

#### 17.3.2. Proxy Timeout

`HttpClient` supports the proxy functionality provided by Netty and provides a way to specify the connection establishment timeout. If the connection establishment attempt to the remote peer does not finish within the timeout, the connection establishment attempt fails. Default: 10s.

To customize the default settings, you can configure `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/proxy/Application.java

    import reactor.netty.http.client.HttpClient;
    import reactor.netty.transport.ProxyProvider;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .proxy(spec -> spec.type(ProxyProvider.Proxy.HTTP)
    				                             .host("proxy")
    				                             .port(8080)
    				                             .nonProxyHosts("localhost")
    				                             .connectTimeoutMillis(20_000)); (1)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | Configures the connection establishment timeout to 20 seconds.

#### 17.3.3. Host Name Resolution Timeout

By default, the `HttpClient` uses Netty’s domain name lookup mechanism to resolve a domain name asynchronously.

The following list describes the available timeout configuration options:

- `cacheMaxTimeToLive` - The maximum time to live of the cached DNS resource records (resolution: seconds). If the time to live of the DNS resource record returned by the DNS server is greater than this maximum time to live, this resolver ignores the time to live from the DNS server and uses this maximum time to live. Default: `Integer.MAX_VALUE`.

- `cacheMinTimeToLive` - The minimum time to live of the cached DNS resource records (resolution: seconds). If the time to live of the DNS resource record returned by the DNS server is less than this minimum time to live, this resolver ignores the time to live from the DNS server and uses this minimum time to live. Default: 0s.

- `cacheNegativeTimeToLive` - The time to live of the cache for the failed DNS queries (resolution: seconds). Default: 0s.

- `queryTimeout` - Sets the timeout of each DNS query performed by this resolver (resolution: milliseconds). Default: 5s.

To customize the default settings, you can configure `HttpClient` as follows:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/resolver/Application.java

    import reactor.netty.http.client.HttpClient;

    import java.time.Duration;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .resolver(spec -> spec.queryTimeout(Duration.ofMillis(500))); (1)

    		String response =
    				client.get()
    				      .uri("https://example.com/")
    				      .responseContent()
    				      .aggregate()
    				      .asString()
    				      .block();

    		System.out.println("Response " + response);
    	}
    }

1 | The timeout of each DNS query performed by this resolver will be 500ms.

## 18. HTTP Authentication

Reactor Netty `HttpClient` provides a mechanism for automatic retry of requests when HTTP authentication is expected, allowing you to implement custom authentication such as SPNEGO/Negotiate, OAuth, Bearer tokens, or any other HTTP-based authentication scheme.

This mechanism provides two types of APIs for HTTP authentication:

- `httpAuthentication(BiPredicate, BiConsumer)` - For authentication where credentials can be computed immediately without delay. The predicate determines when to retry with authentication. Defaults to 1 maximum retry attempt.

- `httpAuthentication(BiPredicate, BiConsumer, int maxRetries)` - Same as above but allows configuring the maximum count of retry attempts.

- `httpAuthenticationWhen(BiPredicate, BiFunction)` - For authentication where credentials need to be fetched from external sources or require delayed computation. Returns a `Mono<Void>` to support deferred credential retrieval. Defaults to 1 maximum retry attempt.

- `httpAuthenticationWhen(BiPredicate, BiFunction, int maxRetries)` - Same as above but allows configuring the maximum count of retry attempts.

This approach gives you complete control over the authentication flow while Reactor Netty handles the retry mechanism.

### 18.1. How It Works

The typical HTTP authentication flow works as follows:

1. The client sends an HTTP request to a protected resource.

2. The server responds with an authentication challenge (e.g., `401 Unauthorized` with a `WWW-Authenticate` header).

3. The authenticator function is invoked to add authentication credentials to the request.

4. The request is retried with the authentication credentials.

5. If authentication is successful, the server returns the requested resource.

### 18.2. Immediate Authentication with httpAuthentication

For authentication scenarios where credentials can be computed immediately without needing to fetch them from external sources, use `httpAuthentication(BiPredicate, BiConsumer)`. This method requires both a predicate to determine when to retry and a consumer to add authentication headers.

#### 18.2.1. Token-Based Authentication Example

The following example demonstrates how to implement Bearer token authentication with configurable retry count:

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/authentication/token/Application.java

    import io.netty.handler.codec.http.HttpHeaderNames;
    import reactor.netty.http.client.HttpClient;

    import java.net.SocketAddress;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .httpAuthentication(
    				              (req, res) -> res.status().code() == 401, (1)
    				              (req, addr) -> { (2)
    				                  String token = generateAuthToken(addr);
    				                  req.header(HttpHeaderNames.AUTHORIZATION, "Bearer " + token);
    				              },
    				              3 (3)
    				          );

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}

    	/**
    	 * Generates an authentication token for the given remote address.
    	 * In production code, this would retrieve or generate a valid token.
    	 */
    	static String generateAuthToken(SocketAddress remoteAddress) {
    		// Never hardcode tokens in production code.
    		// The one below is just an example.
    		return "sample-token-123";
    	}
    }

1 | The predicate checks for `401 Unauthorized` responses to trigger authentication retry.
2 | The authenticator adds the `Authorization` header with a Bearer token.
3 | Configures the maximum count of authentication retry attempts to 3.

#### 18.2.2. Basic Authentication Example

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/authentication/basic/Application.java

    import io.netty.handler.codec.http.HttpHeaderNames;
    import reactor.netty.http.client.HttpClient;

    import java.nio.charset.StandardCharsets;
    import java.util.Base64;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          .httpAuthentication(
    				              (req, res) -> res.status().code() == 401, (1)
    				              (req, addr) -> { (2)
    				                  // Never hardcode credentials in production code.
    				                  // The one below is just an example.
    				                  String credentials = "username:password";
    				                  String encodedCredentials = Base64.getEncoder()
    				                      .encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
    				                  req.header(HttpHeaderNames.AUTHORIZATION, "Basic " + encodedCredentials);
    				              } (3)
    				          );

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

1 | The predicate checks for `401 Unauthorized` responses to trigger authentication retry.
2 | The authenticator adds Basic authentication credentials to the `Authorization` header.
3 | Uses the default maximum retry count of 1.

### 18.3. Asynchronous Authentication with httpAuthenticationWhen

When credentials need to be fetched asynchronously (e.g., from external token services, databases, or requiring I/O operations), use the `httpAuthenticationWhen(BiPredicate, BiFunction)` method.

#### 18.3.1. SPNEGO/Negotiate Authentication Example

The following example demonstrates how the authentication API can be used with SPNEGO (Kerberos) using Java’s GSS-API:

| This is a simplified example to showcase the API.

github.com/reactor/reactor-netty/tree/main/reactor-netty-examples/src/main/java/reactor/netty/examples/documentation/http/client/authentication/spnego/Application.java

    import io.netty.handler.codec.http.HttpHeaderNames;
    import org.ietf.jgss.GSSContext;
    import org.ietf.jgss.GSSException;
    import org.ietf.jgss.GSSManager;
    import org.ietf.jgss.GSSName;
    import org.ietf.jgss.Oid;
    import reactor.core.publisher.Mono;
    import reactor.netty.http.client.HttpClient;

    import java.net.InetSocketAddress;
    import java.util.Base64;

    public class Application {

    	public static void main(String[] args) {
    		HttpClient client =
    				HttpClient.create()
    				          // This is a simplified example demonstrating how the HTTP authentication API
    				          // can be used for SPNEGO/Kerberos.
    				          .httpAuthenticationWhen(
    				              (req, res) -> res.status().code() == 401 && (1)
    				                            res.responseHeaders().contains("WWW-Authenticate", "Negotiate", true),
    				              (req, addr) -> { (2)
    				                  try {
    				                      GSSManager manager = GSSManager.getInstance();
    				                      String hostName = ((InetSocketAddress) addr).getHostString();
    				                      String serviceName = "HTTP@" + hostName;
    				                      GSSName serverName = manager.createName(serviceName, GSSName.NT_HOSTBASED_SERVICE);

    				                      Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2");
    				                      GSSContext context = manager.createContext(
    				                          serverName, krb5Mechanism, null, GSSContext.DEFAULT_LIFETIME);

    				                      byte[] token = context.initSecContext(new byte[0], 0, 0);
    				                      String encodedToken = Base64.getEncoder().encodeToString(token);

    				                      req.header(HttpHeaderNames.AUTHORIZATION, "Negotiate " + encodedToken);

    				                      context.dispose();
    				                  }
    				                  catch (GSSException e) {
    				                      return Mono.error(new RuntimeException(
    				                          "Failed to generate SPNEGO token", e));
    				                  }
    				                  return Mono.empty();
    				              },
    				              2 (3)
    				          );

    		client.get()
    		      .uri("https://example.com/")
    		      .response()
    		      .block();
    	}
    }

1 | Custom predicate checks for `401 Unauthorized` with `WWW-Authenticate: Negotiate` header.
2 | The authenticator generates a SPNEGO token using GSS-API and adds it to the `Authorization` header.
3 | Configures the maximum count of authentication retry attempts to 2.

### 18.4. Custom Authentication Scenarios

The authentication framework is flexible enough to support various authentication scenarios:

#### 18.4.1. OAuth 2.0 Authentication

    HttpClient client = HttpClient.create()
        .httpAuthenticationWhen(
            (req, res) -> res.status().code() == 401, (1)
            (req, addr) -> {
                return fetchOAuthToken() (2)
                    .doOnNext(token ->
                        req.header(HttpHeaderNames.AUTHORIZATION, "Bearer " + token))
                    .then();
            }
        );

1 | Retry authentication on `401 Unauthorized` responses.
2 | Asynchronously fetch an OAuth token and add it to the request.

### 18.5. Accessing Authentication Retry Count

During the authentication process, you may need to track how many times authentication has been retried for a given request. The `authenticationRetryCount()` method on `HttpClientInfos` provides this information.

This is useful for:

- Implementing custom retry logic or backoff strategies

- Logging and debugging authentication flows

- Applying different authentication strategies based on retry count

#### 18.5.1. Example: Logging Authentication Retries

    HttpClient client = HttpClient.create()
        .httpAuthenticationWhen(
            (req, res) -> res.status().code() == 401,
            (req, addr) -> {
                int retryCount = req.authenticationRetryCount(); (1)
                log.info("Authentication retry attempt: {}", retryCount);

                String token = generateToken(retryCount); (2)
                req.header(HttpHeaderNames.AUTHORIZATION, "Bearer " + token);
                return Mono.empty();
            },
            3 (3)
        );

1 | Get the current authentication retry count.
2 | Use the retry count to implement custom logic (e.g., different token generation).
3 | Configure maximum authentication retry attempts to 3.

| The retry count starts at `0` for the first authentication attempt and increments by 1 for each subsequent retry. When the retry count reaches the configured `maxRetries`, authentication will fail if still unsuccessful.

### 18.6. Important Notes

- The authenticator function is invoked only when the predicate returns `true` (indicating authentication is needed).

- For `httpAuthentication`, use a `BiConsumer` when credentials can be computed immediately without delay. No `Mono` is needed as headers are added directly.

- For `httpAuthenticationWhen`, use a `BiFunction` that returns `Mono<Void>` when credentials need to be fetched from external sources or require delayed computation.

- The authenticator receives the request and remote address, allowing you to customize authentication based on the target server.

- By default, authentication is retried once per request. You can configure the maximum count of retry attempts using the `maxRetries` parameter. If authentication fails after all retry attempts are exhausted, the last response (e.g., `401 Unauthorized`) is returned to the caller.

- For security reasons, ensure that sensitive credentials are not logged or exposed in error messages.

- Related Project Reactor Documentation
  - Reactor Core
  - Reactor Netty
  - Reactor Kafka
  - Reactor BlockHound

Search in all Project Reactor Docs