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

dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 1a7647136a6 SOLR-17776: Harmonize SolrJ timeouts (#3357)
1a7647136a6 is described below

commit 1a7647136a6e014bc003018c8e627e358cdf2d7c
Author: David Smiley <[email protected]>
AuthorDate: Wed Jun 18 20:06:29 2025 -0400

    SOLR-17776: Harmonize SolrJ timeouts (#3357)
    
    SolrJ: If Http2SolrClient.Builder.withHttpClient is used, (and it's used 
more in 9.8, shifted from deprecated Apache HttpClient  based clients), 
settings affecting HttpClient construction cannot be customized; an 
IllegalStateException will now be thrown if you try.
    The connection timeout cannot be customized, but idle & request timeouts 
can be.  Callers (various places in Solr) no longer customize the connection 
timeout.
    
    The HttpJdkSolrClient wasn't setting the connection timeout as per the 
builder configuration.
    
    Co-authored-by: Luke Kot-Zaniewski <[email protected]>
    
    (cherry picked from commit dd89cd604a6dd563f15dfcec4f7a9ce5076b2410)
---
 solr/CHANGES.txt                                   |   7 +
 .../src/java/org/apache/solr/cloud/Overseer.java   |   1 -
 .../java/org/apache/solr/cloud/SyncStrategy.java   |   1 -
 .../java/org/apache/solr/handler/IndexFetcher.java |   4 -
 .../pages/major-changes-in-solr-9.adoc             |   8 +
 .../solr/client/solrj/io/SolrClientCache.java      |  16 +-
 .../impl/ConcurrentUpdateHttp2SolrClient.java      |   1 +
 .../solr/client/solrj/impl/Http2SolrClient.java    | 239 +++++++++++----------
 .../solr/client/solrj/impl/HttpJdkSolrClient.java  |  20 +-
 .../solr/client/solrj/impl/HttpSolrClientBase.java |  17 +-
 .../solrj/impl/HttpSolrClientBuilderBase.java      |  31 ++-
 .../client/solrj/impl/BasicHttpSolrClientTest.java |  21 ++
 .../client/solrj/impl/Http2SolrClientTest.java     |  72 ++++++-
 .../client/solrj/impl/HttpSolrClientTestBase.java  |   5 +
 14 files changed, 263 insertions(+), 180 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 137f6e7bdb1..228a0ce6df8 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -61,6 +61,13 @@ Improvements
 * SOLR-16951: Add PKI Auth Caching for both generation of the PKI Auth Tokens 
and validation of received tokens.
   The default PKI Auth validity TTL has been increased from 5 seconds to 10 
seconds. (Houston Putman)
 
+* SOLR-17776: SolrJ: If Http2SolrClient.Builder.withHttpClient is used, (and 
it's used more in 9.8, shifted from deprecated Apache HttpClient
+  based clients), settings affecting HttpClient construction cannot be 
customized; an IllegalStateException will now be thrown if you try.
+  The connection timeout cannot be customized, but idle & request timeouts can 
be.  Callers (various places in Solr) no longer customize the connection 
timeout.
+  (David Smiley, Luke Kot-Zaniewski)
+
+* SOLR-17776: The HttpJdkSolrClient wasn't setting the connection timeout as 
per the builder configuration. (David Smiley)
+
 Optimizations
 ---------------------
 * SOLR-17578: Remove ZkController internal core supplier, for slightly faster 
reconnection after Zookeeper session loss. (Pierre Salagnac)
diff --git a/solr/core/src/java/org/apache/solr/cloud/Overseer.java 
b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
index ab666a70bf4..8210a052dfb 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Overseer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
@@ -861,7 +861,6 @@ public class Overseer implements SolrCloseable {
             new Http2SolrClient.Builder()
                 .withHttpClient(getCoreContainer().getDefaultHttpSolrClient())
                 .withIdleTimeout(30000, TimeUnit.MILLISECONDS)
-                .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
                 .build();
         var client =
             new CloudHttp2SolrClient.Builder(
diff --git a/solr/core/src/java/org/apache/solr/cloud/SyncStrategy.java 
b/solr/core/src/java/org/apache/solr/cloud/SyncStrategy.java
index 2f9a3248b6b..a686743f6b5 100644
--- a/solr/core/src/java/org/apache/solr/cloud/SyncStrategy.java
+++ b/solr/core/src/java/org/apache/solr/cloud/SyncStrategy.java
@@ -360,7 +360,6 @@ public class SyncStrategy {
           try (SolrClient client =
               new Http2SolrClient.Builder(baseUrl)
                   .withHttpClient(solrClient)
-                  .withConnectionTimeout(30000, TimeUnit.MILLISECONDS)
                   .withIdleTimeout(120000, TimeUnit.MILLISECONDS)
                   .build()) {
             client.request(recoverRequestCmd);
diff --git a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java 
b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
index a81b570eaee..cc18d79fe7c 100644
--- a/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
+++ b/solr/core/src/java/org/apache/solr/handler/IndexFetcher.java
@@ -186,8 +186,6 @@ public class IndexFetcher {
 
   private final Http2SolrClient solrClient;
 
-  private Integer connTimeout;
-
   private Integer soTimeout;
 
   private boolean skipCommitOnLeaderVersionZero = true;
@@ -265,7 +263,6 @@ public class IndexFetcher {
         .withHttpClient(updateShardHandler.getRecoveryOnlyHttpClient())
         .withBasicAuthCredentials(httpBasicAuthUser, httpBasicAuthPassword)
         .withIdleTimeout(soTimeout, TimeUnit.MILLISECONDS)
-        .withConnectionTimeout(connTimeout, TimeUnit.MILLISECONDS)
         .build();
   }
 
@@ -300,7 +297,6 @@ public class IndexFetcher {
     String compress = (String) initArgs.get(COMPRESSION);
     useInternalCompression = ReplicationHandler.INTERNAL.equals(compress);
     useExternalCompression = ReplicationHandler.EXTERNAL.equals(compress);
-    connTimeout = getParameter(initArgs, 
HttpClientUtil.PROP_CONNECTION_TIMEOUT, 30000, null);
     soTimeout = getParameter(initArgs, HttpClientUtil.PROP_SO_TIMEOUT, 120000, 
null);
 
     String httpBasicAuthUser = (String) 
initArgs.get(HttpClientUtil.PROP_BASIC_AUTH_USER);
diff --git 
a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc 
b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
index 2005d6c6100..a220a63b8f8 100644
--- 
a/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
+++ 
b/solr/solr-ref-guide/modules/upgrade-notes/pages/major-changes-in-solr-9.adoc
@@ -67,6 +67,14 @@ It is always strongly recommended that you fully reindex 
your documents after a
 In Solr 8, it was possible to add docValues to a schema without re-indexing 
via `UninvertDocValuesMergePolicy`, an advanced/expert utility.
 Due to changes in Lucene 9, that isn't possible any more.
 
+== Solr 9.9
+
+=== SolrJ
+
+If Http2SolrClient.Builder.withHttpClient is used, (and it's used more in 9.8, 
shifted from deprecated Apache HttpClient based clients), settings affecting 
HttpClient construction cannot be customized; an IllegalStateException will now 
be thrown if you try.
+The connection timeout cannot be customized, but idle & request timeouts can 
be.
+Callers (various places in Solr) no longer customize the connection timeout.
+
 == Solr 9.8.1
 === Configuration
 In `solr.xml`, the `indexSearcherExecutorThreads` now defaults to 0.
diff --git 
a/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
 
b/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
index d6ee3847ca0..4cd1c627312 100644
--- 
a/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
+++ 
b/solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/SolrClientCache.java
@@ -189,17 +189,13 @@ public class SolrClientCache implements Closeable {
     final var builder = new Http2SolrClient.Builder(url);
     if (http2SolrClient != null) {
       builder.withHttpClient(http2SolrClient);
+      // cannot set connection timeout
+    } else {
+      builder.withConnectionTimeout(minConnTimeout, TimeUnit.MILLISECONDS);
     }
-    long idleTimeout = minSocketTimeout;
-    if (builder.getIdleTimeoutMillis() != null) {
-      idleTimeout = Math.max(idleTimeout, builder.getIdleTimeoutMillis());
-    }
-    builder.withIdleTimeout(idleTimeout, TimeUnit.MILLISECONDS);
-    long connTimeout = minConnTimeout;
-    if (builder.getConnectionTimeout() != null) {
-      connTimeout = Math.max(idleTimeout, builder.getConnectionTimeout());
-    }
-    builder.withConnectionTimeout(connTimeout, TimeUnit.MILLISECONDS);
+    builder.withIdleTimeout(
+        Math.max(minSocketTimeout, builder.getIdleTimeoutMillis()), 
TimeUnit.MILLISECONDS);
+
     return builder;
   }
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClient.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClient.java
index 5065256070c..eb5e6be8e4d 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ConcurrentUpdateHttp2SolrClient.java
@@ -256,6 +256,7 @@ public class ConcurrentUpdateHttp2SolrClient extends 
SolrClient {
               responseListener = out.getResponseListener();
             }
 
+            // just wait for the headers, so the idle timeout is sensible
             Response response =
                 responseListener.get(client.getIdleTimeout(), 
TimeUnit.MILLISECONDS);
             rspBody = responseListener.getInputStream();
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
index 5355c7a4b60..73d20a69d80 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
@@ -42,6 +42,7 @@ import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.client.solrj.ResponseParser;
+import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrClientFunction;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
@@ -98,15 +99,12 @@ import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
 /**
- * Difference between this {@link Http2SolrClient} and {@link HttpSolrClient}:
+ * An HTTP {@link SolrClient} using Jetty {@link HttpClient}. This is Solr's 
most mature client for
+ * direct HTTP.
  *
- * <ul>
- *   <li>{@link Http2SolrClient} sends requests in HTTP/2
- *   <li>{@link Http2SolrClient} can point to multiple urls
- *   <li>{@link Http2SolrClient} does not expose its internal httpClient like 
{@link
- *       HttpSolrClient#getHttpClient()}, sharing connection pools should be 
done by {@link
- *       Http2SolrClient.Builder#withHttpClient(Http2SolrClient)}
- * </ul>
+ * <p>Despite the name, this client supports HTTP 1.1 and 2 -- toggle with 
{@link
+ * HttpSolrClientBuilderBase#useHttp1_1(boolean)}. In retrospect, the name 
should have been {@code
+ * HttpJettySolrClient}.
  */
 public class Http2SolrClient extends HttpSolrClientBase {
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -118,6 +116,8 @@ public class Http2SolrClient extends HttpSolrClientBase {
 
   private final HttpClient httpClient;
 
+  private final long idleTimeoutMillis;
+
   private List<HttpListenerFactory> listenerFactory = new ArrayList<>();
   protected AsyncTracker asyncTracker = new AsyncTracker();
 
@@ -133,6 +133,20 @@ public class Http2SolrClient extends HttpSolrClientBase {
     super(serverBaseUrl, builder);
 
     if (builder.httpClient != null) {
+      // Validate that no conflicting options are provided when using an 
existing HttpClient
+      if (builder.followRedirects != null
+          || builder.connectionTimeoutMillis != null
+          || builder.maxConnectionsPerHost != null
+          || builder.useHttp1_1
+              != builder.httpClient.getTransport() instanceof 
HttpClientTransportOverHTTP
+          || builder.proxyHost != null
+          || builder.sslConfig != null
+          || builder.cookieStore != null
+          || builder.keyStoreReloadIntervalSecs != null) {
+        throw new IllegalArgumentException(
+            "You cannot provide the HttpClient and also specify options that 
are used to build a new client");
+      }
+
       this.httpClient = builder.httpClient;
       if (this.executor == null) {
         this.executor = builder.executor;
@@ -148,8 +162,19 @@ public class Http2SolrClient extends HttpSolrClientBase {
       this.listenerFactory.addAll(builder.listenerFactory);
     }
     updateDefaultMimeTypeForParser();
-
     
this.httpClient.setFollowRedirects(Boolean.TRUE.equals(builder.followRedirects));
+    this.idleTimeoutMillis = builder.getIdleTimeoutMillis();
+
+    try {
+      applyHttpClientBuilderFactory();
+    } catch (RuntimeException e) {
+      try {
+        this.close();
+      } catch (Exception exceptionOnClose) {
+        e.addSuppressed(exceptionOnClose);
+      }
+      throw e;
+    }
 
     assert ObjectReleaseTracker.track(this);
   }
@@ -162,6 +187,29 @@ public class Http2SolrClient extends HttpSolrClientBase {
     this.authenticationStore = (AuthenticationStoreHolder) 
httpClient.getAuthenticationStore();
   }
 
+  private void applyHttpClientBuilderFactory() {
+    String factoryClassName =
+        
System.getProperty(HttpClientUtil.SYS_PROP_HTTP_CLIENT_BUILDER_FACTORY);
+    if (factoryClassName != null) {
+      log.debug("Using Http Builder Factory: {}", factoryClassName);
+      HttpClientBuilderFactory factory;
+      try {
+        factory =
+            Class.forName(factoryClassName)
+                .asSubclass(HttpClientBuilderFactory.class)
+                .getDeclaredConstructor()
+                .newInstance();
+      } catch (InstantiationException
+          | IllegalAccessException
+          | ClassNotFoundException
+          | InvocationTargetException
+          | NoSuchMethodException e) {
+        throw new RuntimeException("Unable to instantiate " + 
Http2SolrClient.class.getName(), e);
+      }
+      factory.setup(this);
+    }
+  }
+
   @Deprecated(since = "9.7")
   public void addListenerFactory(HttpListenerFactory factory) {
     this.listenerFactory.add(factory);
@@ -178,8 +226,6 @@ public class Http2SolrClient extends HttpSolrClientBase {
   }
 
   private HttpClient createHttpClient(Builder builder) {
-    HttpClient httpClient;
-
     executor = builder.executor;
     if (executor == null) {
       BlockingArrayQueue<Runnable> queue = new BlockingArrayQueue<>(256, 256);
@@ -191,21 +237,24 @@ public class Http2SolrClient extends HttpSolrClientBase {
       shutdownExecutor = false;
     }
 
-    SslContextFactory.Client sslContextFactory;
-    if (builder.sslConfig == null) {
-      sslContextFactory = getDefaultSslContextFactory();
-    } else {
-      sslContextFactory = builder.sslConfig.createClientContextFactory();
-    }
+    SSLConfig sslConfig =
+        builder.sslConfig != null ? builder.sslConfig : 
Http2SolrClient.defaultSSLConfig;
+    SslContextFactory.Client sslContextFactory =
+        (sslConfig == null)
+            ? getDefaultSslContextFactory()
+            : sslConfig.createClientContextFactory();
 
+    Long keyStoreReloadIntervalSecs = builder.keyStoreReloadIntervalSecs;
+    if (keyStoreReloadIntervalSecs == null && 
Boolean.getBoolean("solr.keyStoreReload.enabled")) {
+      keyStoreReloadIntervalSecs = 
Long.getLong("solr.jetty.sslContext.reload.scanInterval", 30);
+    }
     if (sslContextFactory != null
         && sslContextFactory.getKeyStoreResource() != null
-        && builder.keyStoreReloadIntervalSecs != null
-        && builder.keyStoreReloadIntervalSecs > 0) {
+        && keyStoreReloadIntervalSecs != null
+        && keyStoreReloadIntervalSecs > 0) {
       scanner = new KeyStoreScanner(sslContextFactory);
       try {
-        scanner.setScanInterval(
-            (int) Math.min(builder.keyStoreReloadIntervalSecs, 
Integer.MAX_VALUE));
+        scanner.setScanInterval((int) Math.min(keyStoreReloadIntervalSecs, 
Integer.MAX_VALUE));
         scanner.start();
         if (log.isDebugEnabled()) {
           log.debug("Key Store Scanner started");
@@ -227,6 +276,7 @@ public class Http2SolrClient extends HttpSolrClientBase {
     clientConnector.setSslContextFactory(sslContextFactory);
     clientConnector.setSelectors(2);
 
+    HttpClient httpClient;
     HttpClientTransport transport;
     if (builder.useHttp1_1) {
       if (log.isDebugEnabled()) {
@@ -256,17 +306,17 @@ public class Http2SolrClient extends HttpSolrClientBase {
     httpClient.setMaxRequestsQueuedPerDestination(
         asyncTracker.getMaxRequestsQueuedPerDestination());
     httpClient.setUserAgentField(new HttpField(HttpHeader.USER_AGENT, 
USER_AGENT));
-    httpClient.setIdleTimeout(idleTimeoutMillis);
+    httpClient.setConnectTimeout(builder.getConnectionTimeoutMillis());
+    // note: idle & request timeouts are set per request
 
-    if (builder.cookieStore != null) {
-      httpClient.setCookieStore(builder.cookieStore);
+    var cookieStore = builder.getCookieStore();
+    if (cookieStore != null) {
+      httpClient.setCookieStore(cookieStore);
     }
 
     this.authenticationStore = new AuthenticationStoreHolder();
     httpClient.setAuthenticationStore(this.authenticationStore);
 
-    httpClient.setConnectTimeout(builder.connectionTimeoutMillis);
-
     setupProxy(builder, httpClient);
 
     try {
@@ -289,15 +339,14 @@ public class Http2SolrClient extends HttpSolrClientBase {
     if (builder.proxyIsSocks4) {
       proxy = new Socks4Proxy(address, builder.proxyIsSecure);
     } else {
-      final Protocol protocol;
-      if (builder.useHttp1_1) {
-        protocol = HttpClientTransportOverHTTP.HTTP11;
-      } else {
-        // see HttpClientTransportOverHTTP2#newOrigin
-        String protocolName = builder.proxyIsSecure ? "h2" : "h2c";
-        protocol = new Protocol(List.of(protocolName), false);
-      }
-      proxy = new HttpProxy(address, builder.proxyIsSecure, protocol);
+      // Move protocol initialization closer to where it's used
+      proxy =
+          new HttpProxy(
+              address,
+              builder.proxyIsSecure,
+              builder.useHttp1_1
+                  ? HttpClientTransportOverHTTP.HTTP11
+                  : new Protocol(List.of(builder.proxyIsSecure ? "h2" : 
"h2c"), false));
     }
     httpClient.getProxyConfiguration().addProxy(proxy);
   }
@@ -333,6 +382,11 @@ public class Http2SolrClient extends HttpSolrClientBase {
     this.authenticationStore.updateAuthenticationStore(authenticationStore);
   }
 
+  /** (visible for testing) */
+  public long getIdleTimeout() {
+    return idleTimeoutMillis;
+  }
+
   public static class OutStream implements Closeable {
     private final String origCollection;
     private final ModifiableSolrParams origParams;
@@ -536,6 +590,7 @@ public class Http2SolrClient extends HttpSolrClientBase {
     try {
       InputStreamResponseListener listener = new 
InputStreamReleaseTrackingResponseListener();
       req = sendRequest(makeRequest(solrRequest, url, false), listener);
+      // only waits for headers, so use the idle timeout
       Response response = listener.get(idleTimeoutMillis, 
TimeUnit.MILLISECONDS);
       url = req.getURI().toString();
       InputStream is = listener.getInputStream();
@@ -611,13 +666,18 @@ public class Http2SolrClient extends HttpSolrClientBase {
     ResponseParser parser =
         solrRequest.getResponseParser() == null ? this.parser : 
solrRequest.getResponseParser();
     String contentType = response.getHeaders().get(HttpHeader.CONTENT_TYPE);
+
+    // Move variable initializations closer to where they are used
+    String responseMethod = response.getRequest() == null ? "" : 
response.getRequest().getMethod();
+
+    // Initialize mimeType and encoding only when needed
     String mimeType = null;
     String encoding = null;
     if (contentType != null) {
       mimeType = MimeTypes.getContentTypeWithoutCharset(contentType);
       encoding = MimeTypes.getCharsetFromContentType(contentType);
     }
-    String responseMethod = response.getRequest() == null ? "" : 
response.getRequest().getMethod();
+
     return processErrorsAndResponse(
         response.getStatus(),
         response.getReason(),
@@ -643,12 +703,9 @@ public class Http2SolrClient extends HttpSolrClientBase {
 
   private void decorateRequest(Request req, SolrRequest<?> solrRequest, 
boolean isAsync) {
     req.headers(headers -> headers.remove(HttpHeader.ACCEPT_ENCODING));
+    req.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+    req.timeout(requestTimeoutMillis, TimeUnit.MILLISECONDS);
 
-    if (requestTimeoutMillis > 0) {
-      req.timeout(requestTimeoutMillis, TimeUnit.MILLISECONDS);
-    } else {
-      req.timeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
-    }
     if (solrRequest.getUserPrincipal() != null) {
       req.attribute(REQ_PRINCIPAL_KEY, solrRequest.getUserPrincipal());
     }
@@ -724,22 +781,26 @@ public class Http2SolrClient extends HttpSolrClientBase {
 
     if (SolrRequest.METHOD.POST == solrRequest.getMethod()
         || SolrRequest.METHOD.PUT == solrRequest.getMethod()) {
-      RequestWriter.ContentWriter contentWriter = 
requestWriter.getContentWriter(solrRequest);
-      Collection<ContentStream> streams =
-          contentWriter == null ? requestWriter.getContentStreams(solrRequest) 
: null;
-
-      boolean isMultipart = isMultipart(streams);
-
+      // Move method initialization closer to where it's used
       HttpMethod method =
           SolrRequest.METHOD.POST == solrRequest.getMethod() ? HttpMethod.POST 
: HttpMethod.PUT;
 
+      RequestWriter.ContentWriter contentWriter = 
requestWriter.getContentWriter(solrRequest);
+
       if (contentWriter != null) {
         var content = new 
OutputStreamRequestContent(contentWriter.getContentType());
         var r = httpClient.newRequest(url + 
wparams.toQueryString()).method(method).body(content);
         decorateRequest(r, solrRequest, isAsync);
         return new MakeRequestReturnValue(r, contentWriter, content);
+      }
+
+      // Only get streams if contentWriter is null
+      Collection<ContentStream> streams = 
requestWriter.getContentStreams(solrRequest);
 
-      } else if (streams == null || isMultipart) {
+      // Move isMultipart initialization closer to where it's used
+      boolean isMultipart = isMultipart(streams);
+
+      if (streams == null || isMultipart) {
         // send server list and request list as query string params
         ModifiableSolrParams queryParams = 
calculateQueryParams(this.urlParamNames, wparams);
         queryParams.add(calculateQueryParams(solrRequest.getQueryParams(), 
wparams));
@@ -796,14 +857,18 @@ public class Http2SolrClient extends HttpSolrClientBase {
         }
         if (streams != null) {
           for (ContentStream contentStream : streams) {
+            // Move contentType initialization closer to where it's used
             String contentType = contentStream.getContentType();
             if (contentType == null) {
               contentType = "multipart/form-data"; // default
             }
+
+            // Move name initialization closer to where it's used
             String name = contentStream.getName();
             if (name == null) {
               name = "";
             }
+
             HttpFields.Mutable fields = HttpFields.build(1);
             fields.add(HttpHeader.CONTENT_TYPE, contentType);
             content.addFilePart(
@@ -817,6 +882,7 @@ public class Http2SolrClient extends HttpSolrClientBase {
       }
     } else {
       // application/x-www-form-urlencoded
+      // Move queryString initialization into the else block where it's used
       String queryString = wparams.toQueryString();
       // remove the leading "?" if there is any
       queryString = queryString.startsWith("?") ? queryString.substring(1) : 
queryString;
@@ -1083,7 +1149,10 @@ public class Http2SolrClient extends HttpSolrClientBase {
       return this;
     }
 
-    private CookieStore getDefaultCookieStore() {
+    private CookieStore getCookieStore() {
+      if (cookieStore == null) {
+        return cookieStore;
+      }
       if (Boolean.getBoolean("solr.http.disableCookies")) {
         return new HttpCookieStore.Empty();
       }
@@ -1100,65 +1169,7 @@ public class Http2SolrClient extends HttpSolrClientBase {
 
     @Override
     public Http2SolrClient build() {
-      if (sslConfig == null) {
-        sslConfig = Http2SolrClient.defaultSSLConfig;
-      }
-      if (cookieStore == null) {
-        cookieStore = getDefaultCookieStore();
-      }
-      if (idleTimeoutMillis == null || idleTimeoutMillis <= 0) {
-        idleTimeoutMillis = (long) HttpClientUtil.DEFAULT_SO_TIMEOUT;
-      }
-      if (connectionTimeoutMillis == null) {
-        connectionTimeoutMillis = (long) 
HttpClientUtil.DEFAULT_CONNECT_TIMEOUT;
-      }
-
-      if (keyStoreReloadIntervalSecs != null
-          && keyStoreReloadIntervalSecs > 0
-          && this.httpClient != null) {
-        log.warn("keyStoreReloadIntervalSecs can't be set when using external 
httpClient");
-        keyStoreReloadIntervalSecs = null;
-      } else if (keyStoreReloadIntervalSecs == null
-          && this.httpClient == null
-          && Boolean.getBoolean("solr.keyStoreReload.enabled")) {
-        keyStoreReloadIntervalSecs = 
Long.getLong("solr.jetty.sslContext.reload.scanInterval", 30);
-      }
-
-      Http2SolrClient client = new Http2SolrClient(baseSolrUrl, this);
-      try {
-        httpClientBuilderSetup(client);
-      } catch (RuntimeException e) {
-        try {
-          client.close();
-        } catch (Exception exceptionOnClose) {
-          e.addSuppressed(exceptionOnClose);
-        }
-        throw e;
-      }
-      return client;
-    }
-
-    private void httpClientBuilderSetup(Http2SolrClient client) {
-      String factoryClassName =
-          
System.getProperty(HttpClientUtil.SYS_PROP_HTTP_CLIENT_BUILDER_FACTORY);
-      if (factoryClassName != null) {
-        log.debug("Using Http Builder Factory: {}", factoryClassName);
-        HttpClientBuilderFactory factory;
-        try {
-          factory =
-              Class.forName(factoryClassName)
-                  .asSubclass(HttpClientBuilderFactory.class)
-                  .getDeclaredConstructor()
-                  .newInstance();
-        } catch (InstantiationException
-            | IllegalAccessException
-            | ClassNotFoundException
-            | InvocationTargetException
-            | NoSuchMethodException e) {
-          throw new RuntimeException("Unable to instantiate " + 
Http2SolrClient.class.getName(), e);
-        }
-        factory.setup(client);
-      }
+      return new Http2SolrClient(baseSolrUrl, this);
     }
 
     /**
@@ -1171,18 +1182,15 @@ public class Http2SolrClient extends HttpSolrClientBase 
{
       if (this.basicAuthAuthorizationStr == null) {
         this.basicAuthAuthorizationStr = 
http2SolrClient.basicAuthAuthorizationStr;
       }
-      if (this.followRedirects == null) {
-        this.followRedirects = http2SolrClient.httpClient.isFollowRedirects();
-      }
       if (this.idleTimeoutMillis == null) {
         this.idleTimeoutMillis = http2SolrClient.idleTimeoutMillis;
       }
-      if (this.requestWriter == null) {
-        this.requestWriter = http2SolrClient.requestWriter;
-      }
       if (this.requestTimeoutMillis == null) {
         this.requestTimeoutMillis = http2SolrClient.requestTimeoutMillis;
       }
+      if (this.requestWriter == null) {
+        this.requestWriter = http2SolrClient.requestWriter;
+      }
       if (this.responseParser == null) {
         this.responseParser = http2SolrClient.parser;
       }
@@ -1193,8 +1201,7 @@ public class Http2SolrClient extends HttpSolrClientBase {
         this.defaultCollection = http2SolrClient.defaultCollection;
       }
       if (this.listenerFactory == null) {
-        this.listenerFactory = new ArrayList<HttpListenerFactory>();
-        http2SolrClient.listenerFactory.forEach(this.listenerFactory::add);
+        this.listenerFactory = new 
ArrayList<>(http2SolrClient.listenerFactory);
       }
       if (this.executor == null) {
         this.executor = http2SolrClient.executor;
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
index c0b3ce90e1a..8d5858bab2e 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
@@ -88,12 +88,18 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
 
   protected HttpJdkSolrClient(String serverBaseUrl, HttpJdkSolrClient.Builder 
builder) {
     super(serverBaseUrl, builder);
+    HttpClient.Builder b = HttpClient.newBuilder();
 
     HttpClient.Redirect followRedirects =
         Boolean.TRUE.equals(builder.followRedirects)
             ? HttpClient.Redirect.NORMAL
             : HttpClient.Redirect.NEVER;
-    HttpClient.Builder b = 
HttpClient.newBuilder().followRedirects(followRedirects);
+    b.followRedirects(followRedirects);
+
+    b.connectTimeout(Duration.of(builder.getConnectionTimeoutMillis(), 
ChronoUnit.MILLIS));
+    // note: idle timeout isn't used for the JDK client
+    // note: request timeout is set per request
+
     if (builder.sslContext != null) {
       b.sslContext(builder.sslContext);
     }
@@ -429,11 +435,7 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
   }
 
   private void decorateRequest(HttpRequest.Builder reqb, SolrRequest<?> 
solrRequest) {
-    if (requestTimeoutMillis > 0) {
-      reqb.timeout(Duration.of(requestTimeoutMillis, ChronoUnit.MILLIS));
-    } else if (idleTimeoutMillis > 0) {
-      reqb.timeout(Duration.of(idleTimeoutMillis, ChronoUnit.MILLIS));
-    }
+    reqb.timeout(Duration.of(requestTimeoutMillis, ChronoUnit.MILLIS));
     reqb.header("User-Agent", USER_AGENT);
     setBasicAuthHeader(solrRequest, reqb);
     Map<String, String> headers = solrRequest.getHeaders();
@@ -580,12 +582,6 @@ public class HttpJdkSolrClient extends HttpSolrClientBase {
 
     @Override
     public HttpJdkSolrClient build() {
-      if (idleTimeoutMillis == null || idleTimeoutMillis <= 0) {
-        idleTimeoutMillis = (long) HttpClientUtil.DEFAULT_SO_TIMEOUT;
-      }
-      if (connectionTimeoutMillis == null) {
-        connectionTimeoutMillis = (long) 
HttpClientUtil.DEFAULT_CONNECT_TIMEOUT;
-      }
       return new HttpJdkSolrClient(baseSolrUrl, this);
     }
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
index 6bc1cd17f62..b29195f3d9e 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBase.java
@@ -59,8 +59,6 @@ public abstract class HttpSolrClientBase extends SolrClient {
   /** The URL of the Solr server. */
   protected final String serverBaseUrl;
 
-  protected final long idleTimeoutMillis;
-
   protected final long requestTimeoutMillis;
 
   protected final Set<String> urlParamNames;
@@ -87,11 +85,7 @@ public abstract class HttpSolrClientBase extends SolrClient {
     } else {
       this.serverBaseUrl = null;
     }
-    if (builder.idleTimeoutMillis != null) {
-      this.idleTimeoutMillis = builder.idleTimeoutMillis;
-    } else {
-      this.idleTimeoutMillis = -1;
-    }
+    this.requestTimeoutMillis = builder.getRequestTimeoutMillis();
     this.basicAuthAuthorizationStr = builder.basicAuthAuthorizationStr;
     if (builder.requestWriter != null) {
       this.requestWriter = builder.requestWriter;
@@ -100,11 +94,6 @@ public abstract class HttpSolrClientBase extends SolrClient 
{
       this.parser = builder.responseParser;
     }
     this.defaultCollection = builder.defaultCollection;
-    if (builder.requestTimeoutMillis != null) {
-      this.requestTimeoutMillis = builder.requestTimeoutMillis;
-    } else {
-      this.requestTimeoutMillis = -1;
-    }
     if (builder.urlParamNames != null) {
       this.urlParamNames = builder.urlParamNames;
     } else {
@@ -411,10 +400,6 @@ public abstract class HttpSolrClientBase extends 
SolrClient {
     return parser;
   }
 
-  public long getIdleTimeout() {
-    return idleTimeoutMillis;
-  }
-
   public Set<String> getUrlParamNames() {
     return urlParamNames;
   }
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBuilderBase.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBuilderBase.java
index 19025c9b6ec..453f296cffc 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBuilderBase.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClientBuilderBase.java
@@ -25,6 +25,7 @@ import org.apache.solr.client.solrj.request.RequestWriter;
 
 public abstract class HttpSolrClientBuilderBase<
     B extends HttpSolrClientBuilderBase<?, ?>, C extends HttpSolrClientBase> {
+
   protected Long idleTimeoutMillis;
   protected Long connectionTimeoutMillis;
   protected Long requestTimeoutMillis;
@@ -115,38 +116,48 @@ public abstract class HttpSolrClientBuilderBase<
     return (B) this;
   }
 
+  /**
+   * The max time a connection can be idle (that is, without traffic of bytes 
in either direction).
+   * Sometimes called a "socket timeout". Note: not applicable to the JDK 
HttpClient.
+   */
   @SuppressWarnings("unchecked")
   public B withIdleTimeout(long idleConnectionTimeout, TimeUnit unit) {
     this.idleTimeoutMillis = 
TimeUnit.MILLISECONDS.convert(idleConnectionTimeout, unit);
     return (B) this;
   }
 
-  public Long getIdleTimeoutMillis() {
-    return idleTimeoutMillis;
+  public long getIdleTimeoutMillis() {
+    return idleTimeoutMillis != null && idleTimeoutMillis > 0
+        ? idleTimeoutMillis
+        : HttpClientUtil.DEFAULT_SO_TIMEOUT;
   }
 
+  /** The max time a connection can take to connect to destinations. */
   @SuppressWarnings("unchecked")
   public B withConnectionTimeout(long connectionTimeout, TimeUnit unit) {
     this.connectionTimeoutMillis = 
TimeUnit.MILLISECONDS.convert(connectionTimeout, unit);
     return (B) this;
   }
 
-  public Long getConnectionTimeout() {
-    return connectionTimeoutMillis;
+  public long getConnectionTimeoutMillis() {
+    return connectionTimeoutMillis != null && connectionTimeoutMillis > 0
+        ? connectionTimeoutMillis
+        : HttpClientUtil.DEFAULT_CONNECT_TIMEOUT;
   }
 
-  /**
-   * Set a timeout in milliseconds for requests issued by this client.
-   *
-   * @param requestTimeout The timeout in milliseconds
-   * @return this Builder.
-   */
+  /** Set a timeout for requests to receive a response. */
   @SuppressWarnings("unchecked")
   public B withRequestTimeout(long requestTimeout, TimeUnit unit) {
     this.requestTimeoutMillis = TimeUnit.MILLISECONDS.convert(requestTimeout, 
unit);
     return (B) this;
   }
 
+  public long getRequestTimeoutMillis() {
+    return requestTimeoutMillis != null && requestTimeoutMillis > 0
+        ? requestTimeoutMillis
+        : getIdleTimeoutMillis();
+  }
+
   /**
    * If true, prefer http1.1 over http2. If not set, the default is determined 
by system property
    * 'solr.http1'. Otherwise, false.
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java
index 0e36df1bbd5..6a8456310f2 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/BasicHttpSolrClientTest.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -30,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -100,6 +102,24 @@ public class BasicHttpSolrClientTest extends 
SolrJettyTestBase {
     }
   }
 
+  public static class SlowStreamServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws IOException {
+      IntStream.range(0, 10)
+          .forEach(
+              i -> {
+                try {
+                  Thread.sleep(500);
+                  
resp.getOutputStream().write(String.valueOf(i).getBytes(StandardCharsets.UTF_8));
+                  resp.getOutputStream().flush();
+                } catch (IOException | InterruptedException e) {
+                  throw new RuntimeException(e);
+                }
+              });
+    }
+  }
+
   public static class DebugServlet extends HttpServlet {
     public static void clear() {
       lastMethod = null;
@@ -201,6 +221,7 @@ public class BasicHttpSolrClientTest extends 
SolrJettyTestBase {
             .withServlet(new ServletHolder(RedirectServlet.class), 
"/redirect/*")
             .withServlet(new ServletHolder(SlowServlet.class), "/slow/*")
             .withServlet(new ServletHolder(DebugServlet.class), "/debug/*")
+            .withServlet(new ServletHolder(SlowStreamServlet.class), 
"/slowStream/*")
             .build();
     createAndStartJetty(legacyExampleCollection1SolrHome(), jettyConfig);
   }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
index 24825bf33c3..8453269c2aa 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/Http2SolrClientTest.java
@@ -17,11 +17,15 @@
 
 package org.apache.solr.client.solrj.impl;
 
+import static org.apache.solr.handler.admin.api.ReplicationAPIBase.FILE_STREAM;
+
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.client.solrj.ResponseParser;
@@ -35,6 +39,7 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.eclipse.jetty.client.WWWAuthenticationProtocolHandler;
 import org.eclipse.jetty.http.HttpStatus;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -69,7 +74,7 @@ public class Http2SolrClientTest extends 
HttpSolrClientTestBase {
       client.query(q, SolrRequest.METHOD.GET);
       fail("No exception thrown.");
     } catch (SolrServerException e) {
-      assertTrue(e.getMessage().contains("timeout") || 
e.getMessage().contains("Timeout"));
+      assertTrue(isTimeout(e));
     }
   }
 
@@ -100,7 +105,7 @@ public class Http2SolrClientTest extends 
HttpSolrClientTestBase {
       client.query(q, SolrRequest.METHOD.GET);
       fail("No exception thrown.");
     } catch (SolrServerException e) {
-      assertTrue(e.getMessage().contains("timeout") || 
e.getMessage().contains("Timeout"));
+      assertTrue(isTimeout(e));
     }
   }
 
@@ -687,26 +692,73 @@ public class Http2SolrClientTest extends 
HttpSolrClientTestBase {
   }
 
   @Test
-  public void testIdleTimeoutWithHttpClient() {
+  public void testIdleTimeoutWithHttpClient() throws Exception {
+    String url = getBaseUrl() + SLOW_STREAM_SERVLET_PATH;
     try (Http2SolrClient oldClient =
-        new Http2SolrClient.Builder("baseSolrUrl")
-            .withIdleTimeout(5000, TimeUnit.MILLISECONDS)
+        new Http2SolrClient.Builder(url)
+            .withRequestTimeout(Long.MAX_VALUE, TimeUnit.MILLISECONDS)
+            .withIdleTimeout(100, TimeUnit.MILLISECONDS)
             .build()) {
+
       try (Http2SolrClient onlyBaseUrlChangedClient =
-          new 
Http2SolrClient.Builder("newBaseSolrUrl").withHttpClient(oldClient).build()) {
+          new Http2SolrClient.Builder(url).withHttpClient(oldClient).build()) {
         assertEquals(oldClient.getIdleTimeout(), 
onlyBaseUrlChangedClient.getIdleTimeout());
         assertEquals(oldClient.getHttpClient(), 
onlyBaseUrlChangedClient.getHttpClient());
       }
+
+      // too little time to succeed
+      QueryRequest req = new QueryRequest();
+      req.setResponseParser(new InputStreamResponseParser(FILE_STREAM));
+      assertExceptionThrownWithMessageContaining(
+          SolrServerException.class, List.of("Timeout"), () -> 
oldClient.request(req));
+
+      int newIdleTimeoutMs = 5 * 1000; // enough time to succeed
       try (Http2SolrClient idleTimeoutChangedClient =
-          new Http2SolrClient.Builder("baseSolrUrl")
+          new Http2SolrClient.Builder(url)
               .withHttpClient(oldClient)
-              .withIdleTimeout(3000, TimeUnit.MILLISECONDS)
+              .withIdleTimeout(newIdleTimeoutMs, TimeUnit.MILLISECONDS)
               .build()) {
-        assertFalse(oldClient.getIdleTimeout() == 
idleTimeoutChangedClient.getIdleTimeout());
-        assertEquals(3000, idleTimeoutChangedClient.getIdleTimeout());
+        assertNotEquals(oldClient.getIdleTimeout(), 
idleTimeoutChangedClient.getIdleTimeout());
+        assertEquals(newIdleTimeoutMs, 
idleTimeoutChangedClient.getIdleTimeout());
+        NamedList<Object> response = idleTimeoutChangedClient.request(req);
+        try (InputStream is = (InputStream) response.get("stream")) {
+          assertEquals("0123456789", new String(is.readAllBytes(), 
StandardCharsets.UTF_8));
+        }
       }
     }
   }
 
+  @Test
+  public void testRequestTimeoutWithHttpClient() throws Exception {
+    String url = getBaseUrl() + SLOW_STREAM_SERVLET_PATH;
+    try (Http2SolrClient oldClient =
+        new Http2SolrClient.Builder(url)
+            .withIdleTimeout(1000, TimeUnit.MILLISECONDS)
+            .withRequestTimeout(10, TimeUnit.SECONDS)
+            .build()) {
+      QueryRequest req = new QueryRequest();
+      req.setResponseParser(new InputStreamResponseParser(FILE_STREAM));
+      int newRequestTimeoutMs = 2000;
+      try (Http2SolrClient requestTimeoutChangedClient =
+          new Http2SolrClient.Builder(url)
+              .withHttpClient(oldClient)
+              .withRequestTimeout(newRequestTimeoutMs, TimeUnit.MILLISECONDS)
+              .build()) {
+        NamedList<Object> response = requestTimeoutChangedClient.request(req);
+        try (InputStream is = (InputStream) response.get("stream")) {
+          assertExceptionThrownWithMessageContaining(
+              IOException.class, List.of("Total timeout"), is::readAllBytes);
+        }
+      }
+      NamedList<Object> response = oldClient.request(req);
+      try (InputStream is = (InputStream) response.get("stream")) {
+        assertEquals("0123456789", new String(is.readAllBytes(), 
StandardCharsets.UTF_8));
+      }
+    }
+  }
+
+  private static boolean isTimeout(SolrServerException e) {
+    return e.getMessage().contains("timeout") || 
e.getMessage().contains("Timeout");
+  }
   /* Missed tests : - set cookies via interceptor - invariant params - 
compression */
 }
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpSolrClientTestBase.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpSolrClientTestBase.java
index 4e43356b7dd..3bcbd9dc0ae 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpSolrClientTestBase.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/HttpSolrClientTestBase.java
@@ -63,6 +63,8 @@ public abstract class HttpSolrClientTestBase extends 
SolrJettyTestBase {
   protected static final String DEBUG_SERVLET_REGEX = DEBUG_SERVLET_PATH + 
"/*";
   protected static final String REDIRECT_SERVLET_PATH = "/redirect";
   protected static final String REDIRECT_SERVLET_REGEX = REDIRECT_SERVLET_PATH 
+ "/*";
+  protected static final String SLOW_STREAM_SERVLET_PATH = "/slowStream";
+  protected static final String SLOW_STREAM_SERVLET_REGEX = 
SLOW_STREAM_SERVLET_PATH + "/*";
   protected static final String COLLECTION_1 = "collection1";
   // example chars that must be URI encoded - non-ASCII and curly quote
   protected static final String MUST_ENCODE = "\u1234\u007B";
@@ -77,6 +79,9 @@ public abstract class HttpSolrClientTestBase extends 
SolrJettyTestBase {
             .withServlet(
                 new ServletHolder(BasicHttpSolrClientTest.SlowServlet.class), 
SLOW_SERVLET_REGEX)
             .withServlet(new ServletHolder(DebugServlet.class), 
DEBUG_SERVLET_REGEX)
+            .withServlet(
+                new 
ServletHolder(BasicHttpSolrClientTest.SlowStreamServlet.class),
+                SLOW_STREAM_SERVLET_REGEX)
             .withSSLConfig(sslConfig.buildServerSSLConfig())
             .build();
     createAndStartJetty(legacyExampleCollection1SolrHome(), jettyConfig);


Reply via email to