In Spring WebFlux, WebClient uses a client connector to establish and manage HTTP connections. The client connector is responsible for handling low-level details related to connecting to a server, managing connections, and processing HTTP requests and responses. By default, WebClient uses the Reactor Netty library as the client connector, but you can customize it based on your requirements.

Here's an example of using a custom client connector with WebClient:

1. Create a Custom Client Connector:

Create a custom client connector by implementing the ClientHttpConnector interface. For this example, let's use the Apache HttpComponents library as an alternative client connector.

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.classic.methods.RequestBuilder;
import org.apache.hc.client5.http.classic.methods.RequestBuilderBase;
import org.apache.hc.client5.http.classic.methods.ResponseHandler;
import org.apache.hc.client5.http.classic.methods.StringResponseHandler;
import org.apache.hc.client5.http.classic.methods.RetryExec;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.server.reactive.AbstractListenerServerHttpResponse;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

import java.io.IOException;
import java.net.URI;

public class CustomClientHttpConnector extends ReactorClientHttpConnector {

    private final CloseableHttpClient httpClient;

    public CustomClientHttpConnector(CloseableHttpClient httpClient) {
        super();
        this.httpClient = httpClient;
    }

    @Override
    protected ClientHttpRequest createRequest(HttpMethod httpMethod, URI uri) {
        return new CustomClientHttpRequest(httpMethod, uri);
    }

    private class CustomClientHttpRequest implements ClientHttpRequest {

        private final HttpUriRequestBase httpRequest;

        public CustomClientHttpRequest(HttpMethod httpMethod, URI uri) {
            this.httpRequest = createHttpRequest(httpMethod, uri);
        }

        private HttpUriRequestBase createHttpRequest(HttpMethod httpMethod, URI uri) {
            switch (httpMethod) {
                case GET:
                    return RequestBuilder.get(uri).build();
                // Handle other HTTP methods as needed
                default:
                    throw new UnsupportedOperationException("Unsupported HTTP method: " + httpMethod);
            }
        }

        @Override
        public OutputStream getBody() {
            throw new UnsupportedOperationException("Request body not supported for this example");
        }

        @Override
        public ClientHttpResponse execute() throws IOException {
            try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                return new CustomClientHttpResponse(response);
            }
        }
    }

    private class CustomClientHttpResponse implements ClientHttpResponse {

        private final CloseableHttpResponse httpResponse;

        public CustomClientHttpResponse(CloseableHttpResponse httpResponse) {
            this.httpResponse = httpResponse;
        }

        @Override
        public HttpStatus getStatusCode() throws IOException {
            return HttpStatus.valueOf(httpResponse.getCode());
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return httpResponse.getCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return httpResponse.getReasonPhrase();
        }

        @Override
        public HttpHeaders getHeaders() {
            HttpHeaders headers = new HttpHeaders();
            httpResponse.getHeaders().forEach(header ->
                    headers.addAll(header.getName(), header.getValue()));
            return headers;
        }

        @Override
        public Flux<DataBuffer> getBody() {
            try {
                String responseBody = EntityUtils.toString(httpResponse.getEntity());
                byte[] bytes = responseBody.getBytes(StandardCharsets.UTF_8);
                DataBuffer buffer = new DefaultDataBufferFactory().wrap(bytes);
                return Flux.just(buffer);
            } catch (IOException | ParseException e) {
                return Flux.error(e);
            }
        }

        @Override
        public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
            throw new UnsupportedOperationException("Write operations are not supported for this example");
        }

        @Override
        public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
            throw new UnsupportedOperationException("Write operations are not supported for this example");
        }

        @Override
        public Mono<Void> setComplete() {
            return Mono.empty();
        }

        @Override
        public Context currentContext() {
            return Context.empty();
        }
    }
}

In this example, CustomClientHttpConnector extends ReactorClientHttpConnector and overrides the createRequest method to return a CustomClientHttpRequest. The CustomClientHttpRequest creates an HttpUriRequestBase based on the specified HttpMethod and URI.

The CustomClientHttpResponse wraps a CloseableHttpResponse and implements the ClientHttpResponse interface, providing methods to retrieve the status code, headers, and body.

2. Use the Custom Client Connector with WebClient:

Once you have your custom client connector, you can use it with WebClient.

import org.apache.hc.client5.http.classic.CloseableHttpClient;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class MyConfig {

    @Bean
    public WebClient.Builder webClientBuilder(CloseableHttpClient httpClient) {
        return WebClient.builder()
                .clientConnector(new CustomClientHttpConnector(httpClient));
    }
}

In this example, we create a WebClient.Builder bean and set the custom client connector using the clientConnector method.

This approach allows you to use a custom client connector with WebClient and integrate with any HTTP client library of your choice. Note that the example uses Apache HttpComponents for illustration, and you can adapt it to use other HTTP client libraries based on your project requirements.

Enjoy Learning.