You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2026/01/19 10:30:13 UTC

(httpcomponents-client) branch master updated: Add custom ExchangeId generator support

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/master by this push:
     new f457c8ba7 Add custom ExchangeId generator support
f457c8ba7 is described below

commit f457c8ba73fe7ea65a078f67ccabbc98a4d6bd61
Author: yhzdys <yh...@foxmail>
AuthorDate: Sat Jan 17 20:09:23 2026 +0800

    Add custom ExchangeId generator support
---
 .../http/impl/DefaultExchangeIdGenerator.java      | 47 ++++++++++++++++++++++
 .../http/impl/async/H2AsyncClientBuilder.java      | 20 +++++++++
 .../http/impl/async/HttpAsyncClientBuilder.java    | 19 +++++++++
 .../async/InternalAbstractHttpAsyncClient.java     |  7 +++-
 .../http/impl/async/InternalH2AsyncClient.java     |  4 +-
 .../http/impl/async/InternalHttpAsyncClient.java   |  4 +-
 .../http/impl/classic/HttpClientBuilder.java       | 19 +++++++++
 .../http/impl/classic/InternalHttpClient.java      |  7 +++-
 .../http/impl/classic/TestInternalHttpClient.java  | 32 ++++++++++++++-
 9 files changed, 151 insertions(+), 8 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultExchangeIdGenerator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultExchangeIdGenerator.java
new file mode 100644
index 000000000..eb2e16697
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultExchangeIdGenerator.java
@@ -0,0 +1,47 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.function.Supplier;
+
+/**
+ * Default implementation of {@link Supplier} for generating exchange IDs.
+ *
+ * @since 5.7
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public class DefaultExchangeIdGenerator implements Supplier<String> {
+
+    public static final Supplier<String> INSTANCE = new DefaultExchangeIdGenerator();
+
+    @Override
+    public String get() {
+        return ExecSupport.getNextExchangeId();
+    }
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
index f5cfc4de0..d28099790 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java
@@ -51,6 +51,7 @@
 import org.apache.hc.client5.http.impl.ChainElement;
 import org.apache.hc.client5.http.impl.CookieSpecSupport;
 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
+import org.apache.hc.client5.http.impl.DefaultExchangeIdGenerator;
 import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
 import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
@@ -76,6 +77,7 @@
 import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Resolver;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequestInterceptor;
@@ -182,6 +184,7 @@ private ExecInterceptorEntry(
     private LinkedList<ResponseInterceptorEntry> responseInterceptors;
     private LinkedList<ExecInterceptorEntry> execInterceptors;
 
+    private Supplier<String> exchangeIdGenerator;
     private HttpRoutePlanner routePlanner;
     private RedirectStrategy redirectStrategy;
     private HttpRequestRetryStrategy retryStrategy;
@@ -563,6 +566,17 @@ public final H2AsyncClientBuilder setDefaultHeaders(final Collection<? extends H
         return this;
     }
 
+    /**
+     * Sets exchange ID generator instance.
+     *
+     * @return this instance.
+     * @since 5.7
+     */
+    public final H2AsyncClientBuilder setExchangeIdGenerator(final Supplier<String> exchangeIdGenerator) {
+        this.exchangeIdGenerator = exchangeIdGenerator;
+        return this;
+    }
+
     /**
      * Sets {@link HttpRoutePlanner} instance.
      *
@@ -855,6 +869,11 @@ public CloseableHttpAsyncClient build() {
                     ChainElement.RETRY.name());
         }
 
+        Supplier<String> exchangeIdGeneratorCopy = this.exchangeIdGenerator;
+        if (exchangeIdGeneratorCopy == null) {
+            exchangeIdGeneratorCopy = DefaultExchangeIdGenerator.INSTANCE;
+        }
+
         HttpRoutePlanner routePlannerCopy = this.routePlanner;
         if (routePlannerCopy == null) {
             SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
@@ -983,6 +1002,7 @@ public CloseableHttpAsyncClient build() {
                 pushConsumerRegistry,
                 threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true),
                 connPool,
+                exchangeIdGeneratorCopy,
                 routePlannerCopy,
                 cookieSpecRegistryCopy,
                 authSchemeRegistryCopy,
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index 066fcf782..1f68dedc9 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -58,6 +58,7 @@
 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
 import org.apache.hc.client5.http.impl.DefaultClientConnectionReuseStrategy;
 import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.impl.DefaultExchangeIdGenerator;
 import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
 import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
@@ -90,6 +91,7 @@
 import org.apache.hc.core5.concurrent.DefaultThreadFactory;
 import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ConnectionReuseStrategy;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpHost;
@@ -230,6 +232,7 @@ private ExecInterceptorEntry(
     private LinkedList<ResponseInterceptorEntry> responseInterceptors;
     private LinkedList<ExecInterceptorEntry> execInterceptors;
 
+    private Supplier<String> exchangeIdGenerator;
     private HttpRoutePlanner routePlanner;
     private RedirectStrategy redirectStrategy;
     private HttpRequestRetryStrategy retryStrategy;
@@ -692,6 +695,17 @@ public final HttpAsyncClientBuilder setProxy(final HttpHost proxy) {
         return this;
     }
 
+    /**
+     * Sets exchange ID generator instance.
+     *
+     * @return this instance.
+     * @since 5.7
+     */
+    public final HttpAsyncClientBuilder setExchangeIdGenerator(final Supplier<String> exchangeIdGenerator) {
+        this.exchangeIdGenerator = exchangeIdGenerator;
+        return this;
+    }
+
     /**
      * Sets {@link HttpRoutePlanner} instance.
      *
@@ -1137,6 +1151,10 @@ public CloseableHttpAsyncClient build() {
             execChainDefinition.addFirst(new TlsRequiredAsyncExec(), ChainElement.TLS_REQUIRED.name());
         }
 
+        Supplier<String> exchangeIdGeneratorCopy = this.exchangeIdGenerator;
+        if (exchangeIdGeneratorCopy == null) {
+            exchangeIdGeneratorCopy = DefaultExchangeIdGenerator.INSTANCE;
+        }
 
         HttpRoutePlanner routePlannerCopy = this.routePlanner;
         if (routePlannerCopy == null) {
@@ -1271,6 +1289,7 @@ public CloseableHttpAsyncClient build() {
                 pushConsumerRegistry,
                 threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true),
                 connManagerCopy,
+                exchangeIdGeneratorCopy,
                 routePlannerCopy,
                 tlsConfig,
                 cookieSpecRegistryCopy,
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
index 6c1679604..9a81c7d94 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
@@ -50,13 +50,13 @@
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.cookie.CookieSpecFactory;
 import org.apache.hc.client5.http.cookie.CookieStore;
-import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.routing.RoutingSupport;
 import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.ComplexFuture;
 import org.apache.hc.core5.concurrent.DefaultThreadFactory;
 import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHost;
@@ -88,6 +88,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
     private static final Logger LOG = LoggerFactory.getLogger(InternalAbstractHttpAsyncClient.class);
 
     private final AsyncExecChainElement execChain;
+    private final Supplier<String> exchangeIdGenerator;
     private final Lookup<CookieSpecFactory> cookieSpecRegistry;
     private final Lookup<AuthSchemeFactory> authSchemeRegistry;
     private final CookieStore cookieStore;
@@ -103,6 +104,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
             final AsyncPushConsumerRegistry pushConsumerRegistry,
             final ThreadFactory threadFactory,
             final AsyncExecChainElement execChain,
+            final Supplier<String> exchangeIdGenerator,
             final Lookup<CookieSpecFactory> cookieSpecRegistry,
             final Lookup<AuthSchemeFactory> authSchemeRegistry,
             final CookieStore cookieStore,
@@ -112,6 +114,7 @@ abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBa
             final List<Closeable> closeables) {
         super(ioReactor, pushConsumerRegistry, threadFactory);
         this.execChain = execChain;
+        this.exchangeIdGenerator = exchangeIdGenerator;
         this.cookieSpecRegistry = cookieSpecRegistry;
         this.authSchemeRegistry = authSchemeRegistry;
         this.cookieStore = cookieStore;
@@ -232,7 +235,7 @@ protected <T> Future<T> doExecute(
                         resolvedTarget,
                         request,
                         clientContext);
-                final String exchangeId = ExecSupport.getNextExchangeId();
+                final String exchangeId = exchangeIdGenerator.get();
                 clientContext.setExchangeId(exchangeId);
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} preparing request execution", exchangeId);
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java
index 3c2705100..4d4c05612 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java
@@ -43,6 +43,7 @@
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
@@ -79,6 +80,7 @@ public final class InternalH2AsyncClient extends InternalAbstractHttpAsyncClient
             final AsyncPushConsumerRegistry pushConsumerRegistry,
             final ThreadFactory threadFactory,
             final InternalH2ConnPool connPool,
+            final Supplier<String> exchangeIdGenerator,
             final HttpRoutePlanner routePlanner,
             final Lookup<CookieSpecFactory> cookieSpecRegistry,
             final Lookup<AuthSchemeFactory> authSchemeRegistry,
@@ -87,7 +89,7 @@ public final class InternalH2AsyncClient extends InternalAbstractHttpAsyncClient
             final RequestConfig defaultConfig,
             final List<Closeable> closeables,
             final int maxQueuedRequests) {
-        super(ioReactor, pushConsumerRegistry, threadFactory, execChain,
+        super(ioReactor, pushConsumerRegistry, threadFactory, execChain, exchangeIdGenerator,
                 cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, HttpClientContext::castOrCreate,
                 defaultConfig, closeables);
         this.connPool = connPool;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
index 1604039f3..09d20f081 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
@@ -46,6 +46,7 @@
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
@@ -85,6 +86,7 @@ public final class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClie
             final AsyncPushConsumerRegistry pushConsumerRegistry,
             final ThreadFactory threadFactory,
             final AsyncClientConnectionManager manager,
+            final Supplier<String> exchangeIdGenerator,
             final HttpRoutePlanner routePlanner,
             final TlsConfig tlsConfig,
             final Lookup<CookieSpecFactory> cookieSpecRegistry,
@@ -95,7 +97,7 @@ public final class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClie
             final RequestConfig defaultConfig,
             final List<Closeable> closeables,
             final int maxQueuedRequests) {
-        super(ioReactor, pushConsumerRegistry, threadFactory, execChain,
+        super(ioReactor, pushConsumerRegistry, threadFactory, execChain, exchangeIdGenerator,
                 cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, contextAdaptor,
                 defaultConfig, closeables);
         this.manager = manager;
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
index f7dcf7ffa..d7c4a53ac 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/HttpClientBuilder.java
@@ -60,6 +60,7 @@
 import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
 import org.apache.hc.client5.http.impl.DefaultClientConnectionReuseStrategy;
 import org.apache.hc.client5.http.impl.DefaultConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.impl.DefaultExchangeIdGenerator;
 import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
 import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
@@ -88,6 +89,7 @@
 import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
 import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ConnectionReuseStrategy;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.HttpEntity;
@@ -191,6 +193,7 @@ private ExecInterceptorEntry(
     }
 
     private HttpRequestExecutor requestExec;
+    private Supplier<String> exchangeIdGenerator;
     private HttpClientConnectionManager connManager;
     private boolean connManagerShared;
     private SchemePortResolver schemePortResolver;
@@ -254,6 +257,17 @@ public final HttpClientBuilder setRequestExecutor(final HttpRequestExecutor requ
         return this;
     }
 
+    /**
+     * Sets exchange ID generator instance.
+     *
+     * @return this instance.
+     * @since 5.7
+     */
+    public final HttpClientBuilder setExchangeIdGenerator(final Supplier<String> exchangeIdGenerator) {
+        this.exchangeIdGenerator = exchangeIdGenerator;
+        return this;
+    }
+
     /**
      * Sets {@link HttpClientConnectionManager} instance.
      *
@@ -862,6 +876,10 @@ public CloseableHttpClient build() {
         if (requestExecCopy == null) {
             requestExecCopy = new HttpRequestExecutor();
         }
+        Supplier<String> exchangeIdGeneratorCopy = this.exchangeIdGenerator;
+        if (exchangeIdGeneratorCopy == null) {
+            exchangeIdGeneratorCopy = DefaultExchangeIdGenerator.INSTANCE;
+        }
         HttpClientConnectionManager connManagerCopy = this.connManager;
         if (connManagerCopy == null) {
             final PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder.create();
@@ -1133,6 +1151,7 @@ public CloseableHttpClient build() {
         return new InternalHttpClient(
                 connManagerCopy,
                 requestExecCopy,
+                exchangeIdGeneratorCopy,
                 execChain,
                 routePlannerCopy,
                 cookieSpecRegistryCopy,
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
index 07265fcb4..31c8dedf8 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/InternalHttpClient.java
@@ -43,7 +43,6 @@
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.cookie.CookieSpecFactory;
 import org.apache.hc.client5.http.cookie.CookieStore;
-import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
@@ -52,6 +51,7 @@
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.concurrent.CancellableDependency;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.HttpException;
@@ -85,6 +85,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
 
     private final HttpClientConnectionManager connManager;
     private final HttpRequestExecutor requestExecutor;
+    private final Supplier<String> exchangeIdGenerator;
     private final ExecChainElement execChain;
     private final HttpRoutePlanner routePlanner;
     private final Lookup<CookieSpecFactory> cookieSpecRegistry;
@@ -98,6 +99,7 @@ class InternalHttpClient extends CloseableHttpClient implements Configurable {
     public InternalHttpClient(
             final HttpClientConnectionManager connManager,
             final HttpRequestExecutor requestExecutor,
+            final Supplier<String> exchangeIdGenerator,
             final ExecChainElement execChain,
             final HttpRoutePlanner routePlanner,
             final Lookup<CookieSpecFactory> cookieSpecRegistry,
@@ -110,6 +112,7 @@ public InternalHttpClient(
         super();
         this.connManager = Args.notNull(connManager, "Connection manager");
         this.requestExecutor = Args.notNull(requestExecutor, "Request executor");
+        this.exchangeIdGenerator = Args.notNull(exchangeIdGenerator, "Exchange id generator");
         this.execChain = Args.notNull(execChain, "Execution chain");
         this.routePlanner = Args.notNull(routePlanner, "Route planner");
         this.cookieSpecRegistry = cookieSpecRegistry;
@@ -173,7 +176,7 @@ protected CloseableHttpResponse doExecute(
                     resolvedTarget,
                     request,
                     localcontext);
-            final String exchangeId = ExecSupport.getNextExchangeId();
+            final String exchangeId = exchangeIdGenerator.get();
             localcontext.setExchangeId(exchangeId);
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} preparing request execution", exchangeId);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java
index bbe7292dd..a6fdbd8b4 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestInternalHttpClient.java
@@ -39,9 +39,11 @@
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.cookie.CookieSpecFactory;
 import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.ExecSupport;
 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHost;
@@ -69,6 +71,8 @@ class TestInternalHttpClient {
     @Mock
     private ExecChainHandler execChain;
     @Mock
+    private Supplier<String> exchangeIdGenerator;
+    @Mock
     private HttpRoutePlanner routePlanner;
     @Mock
     private Lookup<CookieSpecFactory> cookieSpecRegistry;
@@ -90,10 +94,10 @@ class TestInternalHttpClient {
     @BeforeEach
     void setup() {
         MockitoAnnotations.openMocks(this);
-        client = new InternalHttpClient(connManager, requestExecutor, new ExecChainElement(execChain, null), routePlanner,
+        client = new InternalHttpClient(connManager, requestExecutor, exchangeIdGenerator,
+                new ExecChainElement(execChain, null), routePlanner,
                 cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider,
                 HttpClientContext::castOrCreate, defaultConfig, Arrays.asList(closeable1, closeable2));
-
     }
 
     @Test
@@ -101,6 +105,7 @@ void testExecute() throws Exception {
         final HttpGet httpget = new HttpGet("http://somehost/stuff");
         final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
 
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
         Mockito.when(routePlanner.determineRoute(
                 Mockito.eq(new HttpHost("somehost")),
                 Mockito.any(),
@@ -122,6 +127,7 @@ void testExecuteHttpException() throws Exception {
         final HttpGet httpget = new HttpGet("http://somehost/stuff");
         final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
 
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
         Mockito.when(routePlanner.determineRoute(
                 Mockito.eq(new HttpHost("somehost")),
                 Mockito.any(),
@@ -143,6 +149,7 @@ void testExecuteDefaultContext() throws Exception {
         final HttpGet httpget = new HttpGet("http://somehost/stuff");
         final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
 
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
         Mockito.when(routePlanner.determineRoute(
                 Mockito.eq(new HttpHost("somehost")),
                 Mockito.any(),
@@ -166,6 +173,7 @@ void testExecuteRequestConfig() throws Exception {
         final HttpGet httpget = new HttpGet("http://somehost/stuff");
         final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
 
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
         Mockito.when(routePlanner.determineRoute(
                 Mockito.eq(new HttpHost("somehost")),
                 Mockito.any(),
@@ -187,6 +195,7 @@ void testExecuteLocalContext() throws Exception {
         final HttpGet httpget = new HttpGet("http://somehost/stuff");
         final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
 
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
         Mockito.when(routePlanner.determineRoute(
                 Mockito.eq(new HttpHost("somehost")),
                 Mockito.any(),
@@ -240,6 +249,7 @@ void testClientCloseIOException() throws Exception {
     void testDoExecuteThrowsWhenNoTargetOrHost() throws Exception {
         final ClassicHttpRequest request = ClassicRequestBuilder.get("/foo").build();
         final HttpClientContext context = HttpClientContext.create();
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
         Mockito.when(routePlanner.determineRoute(
                 Mockito.eq(null),
                 Mockito.any(),
@@ -247,4 +257,22 @@ void testDoExecuteThrowsWhenNoTargetOrHost() throws Exception {
         Assertions.assertThrows(ClientProtocolException.class, () ->
                 client.executeOpen(null, request, context));
     }
+
+    @Test
+    void testExchangeIdGenerator() throws Exception {
+        final HttpGet httpget = new HttpGet("http://somehost/stuff");
+        final HttpRoute route = new HttpRoute(new HttpHost("somehost", 80));
+
+        Mockito.when(exchangeIdGenerator.get()).thenReturn(ExecSupport.getNextExchangeId());
+        Mockito.when(routePlanner.determineRoute(
+                Mockito.eq(new HttpHost("somehost")),
+                Mockito.any(),
+                Mockito.<HttpClientContext>any())).thenReturn(route);
+        Mockito.when(execChain.execute(
+                Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(
+                CloseableHttpResponse.adapt(new BasicClassicHttpResponse(200)));
+
+        client.execute(httpget, response -> null);
+        Mockito.verify(exchangeIdGenerator).get();
+    }
 }