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 31570101be8 SOLR-17286: Remote Proxy via Jetty HttpClient (#3447)
31570101be8 is described below

commit 31570101be82d52ce93daebcd2fb2af1aa794d28
Author: David Smiley <[email protected]>
AuthorDate: Wed Aug 20 09:32:26 2025 -0400

    SOLR-17286: Remote Proxy via Jetty HttpClient (#3447)
    
    No longer Apache HttpClient.
    
    * benchmark module:
    ** Docs: use ExecutorUtil.shutdownAndAwaitTermination
    ** MiniClusterBenchStateTest: do cleanup in @After
    
    (cherry picked from commit f94d30c611c336b631dbb5442b476adb3661c5ca)
---
 solr/CHANGES.txt                                   |   3 +-
 .../src/java/org/apache/solr/bench/Docs.java       |   8 +-
 .../solr/bench/MiniClusterBenchStateTest.java      |  23 ++--
 solr/core/build.gradle                             |   1 +
 .../src/java/org/apache/solr/api/V2HttpCall.java   |   4 +-
 .../apache/solr/servlet/CoreContainerProvider.java |  12 --
 .../java/org/apache/solr/servlet/HttpSolrCall.java | 142 ++-----------------
 .../org/apache/solr/servlet/HttpSolrProxy.java     | 153 +++++++++++++++++++++
 .../apache/solr/servlet/SolrDispatchFilter.java    |  17 +--
 .../client/solrj/impl/Krb5HttpClientUtils.java     |   1 +
 .../solr/client/solrj/impl/Http2SolrClient.java    |  10 +-
 .../client/solrj/impl/Krb5HttpClientBuilder.java   |   1 +
 .../PreemptiveBasicAuthClientBuilderFactory.java   |   7 +-
 13 files changed, 201 insertions(+), 181 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index c898c9bd33f..3bcb1f4385c 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -34,7 +34,8 @@ Dependency Upgrades
 
 Other Changes
 ---------------------
-(No changes)
+* SOLR-17286: When proxying requests to another node, use Jetty HttpClient not 
Apache HttpClient.
+  (David Smiley)
 
 ==================  9.9.1 ==================
 Bug Fixes
diff --git a/solr/benchmark/src/java/org/apache/solr/bench/Docs.java 
b/solr/benchmark/src/java/org/apache/solr/bench/Docs.java
index d256739acae..b3c28bc1d4b 100644
--- a/solr/benchmark/src/java/org/apache/solr/bench/Docs.java
+++ b/solr/benchmark/src/java/org/apache/solr/bench/Docs.java
@@ -26,13 +26,13 @@ import java.util.Queue;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
 import org.apache.lucene.util.RamUsageEstimator;
 import org.apache.solr.bench.generators.MultiString;
 import org.apache.solr.bench.generators.SolrGen;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.util.CollectionUtil;
+import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.common.util.SuppressForbidden;
 import org.quicktheories.core.Gen;
@@ -110,11 +110,7 @@ public class Docs {
       executorService.execute(() -> docs.add(Docs.this.inputDocument()));
     }
 
-    executorService.shutdown();
-    boolean result = executorService.awaitTermination(10, TimeUnit.MINUTES);
-    if (!result) {
-      throw new RuntimeException("Timeout waiting for doc adds to finish");
-    }
+    ExecutorUtil.shutdownAndAwaitTermination(executorService);
     log(
         "done preGenerateDocs docs="
             + docs.size()
diff --git 
a/solr/benchmark/src/test/org/apache/solr/bench/MiniClusterBenchStateTest.java 
b/solr/benchmark/src/test/org/apache/solr/bench/MiniClusterBenchStateTest.java
index af568e3bb98..82708f6a34e 100644
--- 
a/solr/benchmark/src/test/org/apache/solr/bench/MiniClusterBenchStateTest.java
+++ 
b/solr/benchmark/src/test/org/apache/solr/bench/MiniClusterBenchStateTest.java
@@ -26,15 +26,13 @@ import static 
org.apache.solr.bench.generators.SourceDSL.longs;
 import static org.apache.solr.bench.generators.SourceDSL.strings;
 
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
-import java.lang.invoke.MethodHandles;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
-import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.junit.After;
 import org.junit.Test;
 import org.openjdk.jmh.annotations.Mode;
 import org.openjdk.jmh.infra.BenchmarkParams;
@@ -42,12 +40,12 @@ import org.openjdk.jmh.infra.IterationParams;
 import org.openjdk.jmh.runner.IterationType;
 import org.openjdk.jmh.runner.WorkloadParams;
 import org.openjdk.jmh.runner.options.TimeValue;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @ThreadLeakLingering(linger = 10)
 public class MiniClusterBenchStateTest extends SolrTestCaseJ4 {
-  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private MiniClusterState.MiniClusterBenchState miniBenchState;
+  private BaseBenchState baseBenchState;
+  private BenchmarkParams benchParams;
 
   @Test
   public void testMiniClusterState() throws Exception {
@@ -55,9 +53,8 @@ public class MiniClusterBenchStateTest extends SolrTestCaseJ4 
{
     System.setProperty("workBaseDir", createTempDir("work").toString());
     System.setProperty("random.counts", "true");
 
-    MiniClusterState.MiniClusterBenchState miniBenchState =
-        new MiniClusterState.MiniClusterBenchState();
-    BenchmarkParams benchParams =
+    miniBenchState = new MiniClusterState.MiniClusterBenchState();
+    benchParams =
         new BenchmarkParams(
             "benchmark",
             "generatedTarget",
@@ -80,7 +77,7 @@ public class MiniClusterBenchStateTest extends SolrTestCaseJ4 
{
             "vmVersion",
             "jmhVersion",
             TimeValue.seconds(10));
-    BaseBenchState baseBenchState = new BaseBenchState();
+    baseBenchState = new BaseBenchState();
     baseBenchState.doSetup(benchParams);
     miniBenchState.doSetup(benchParams, baseBenchState);
 
@@ -111,7 +108,7 @@ public class MiniClusterBenchStateTest extends 
SolrTestCaseJ4 {
             .field(doubles().all());
 
     int numDocs = 50;
-    Iterator<SolrInputDocument> docIt = docs.preGenerate(numDocs);
+    docs.preGenerate(numDocs);
 
     miniBenchState.index(collection, docs, numDocs);
 
@@ -124,8 +121,12 @@ public class MiniClusterBenchStateTest extends 
SolrTestCaseJ4 {
     BaseBenchState.log("match all query result=" + result);
 
     assertEquals(numDocs, result.getResults().getNumFound());
+  }
 
+  @After
+  public void after() throws Exception {
     BaseBenchState.doTearDown(benchParams);
+
     miniBenchState.tearDown(benchParams);
     miniBenchState.shutdownMiniCluster(benchParams, baseBenchState);
   }
diff --git a/solr/core/build.gradle b/solr/core/build.gradle
index 72ac2dbe293..51db9824312 100644
--- a/solr/core/build.gradle
+++ b/solr/core/build.gradle
@@ -122,6 +122,7 @@ dependencies {
   implementation 'org.eclipse.jetty:jetty-client'
   implementation 'org.eclipse.jetty:jetty-http'
   implementation 'org.eclipse.jetty:jetty-io'
+  implementation 'org.eclipse.jetty:jetty-util'
   implementation 'org.eclipse.jetty.toolchain:jetty-servlet-api'
 
   // ZooKeeper
diff --git a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java 
b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
index 24cb696aeee..3480d8f7f5b 100644
--- a/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
+++ b/solr/core/src/java/org/apache/solr/api/V2HttpCall.java
@@ -349,7 +349,7 @@ public class V2HttpCall extends HttpSolrCall {
   }
 
   /**
-   * Differentiate between "admin" and "remotequery"-type requests; executing 
each as appropriate.
+   * Differentiate between "admin" and "remoteproxy"-type requests; executing 
each as appropriate.
    *
    * <p>The JAX-RS framework used by {@link V2HttpCall} doesn't provide any 
easy way to check in
    * advance whether a Jersey application can handle an incoming request. 
This, in turn, makes it
@@ -360,7 +360,7 @@ public class V2HttpCall extends HttpSolrCall {
    * <p>This method uses this strategy to differentiate between admin requests 
that don't require a
    * {@link SolrCore}, but whose path happen to contain a core/collection name 
(e.g.
    * ADDREPLICAPROP's path of
-   * /collections/collName/shards/shardName/replicas/replicaName/properties), 
and "REMOTEQUERY"
+   * /collections/collName/shards/shardName/replicas/replicaName/properties), 
and "REMOTEPROXY"
    * requests which do require a local SolrCore to process.
    */
   @Override
diff --git 
a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java 
b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
index 8b8bc3c927d..10ae2bff6f2 100644
--- a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
+++ b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
@@ -49,7 +49,6 @@ import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
 import javax.servlet.UnavailableException;
-import org.apache.http.client.HttpClient;
 import org.apache.lucene.store.MMapDirectory;
 import org.apache.lucene.util.VectorUtil;
 import org.apache.solr.client.api.util.SolrVersion;
@@ -84,7 +83,6 @@ public class CoreContainerProvider implements 
ServletContextListener {
   private final String metricTag = SolrMetricProducer.getUniqueMetricTag(this, 
null);
   private CoreContainer cores;
   private Properties extraProperties;
-  private HttpClient httpClient;
   private SolrMetricManager metricManager;
   private RateLimitManager rateLimitManager;
   private String registryName;
@@ -123,14 +121,6 @@ public class CoreContainerProvider implements 
ServletContextListener {
     return cores;
   }
 
-  /**
-   * @see SolrDispatchFilter#getHttpClient()
-   */
-  HttpClient getHttpClient() throws UnavailableException {
-    checkReady();
-    return httpClient;
-  }
-
   private void checkReady() throws UnavailableException {
     // TODO throw AlreadyClosedException instead?
     if (cores == null) {
@@ -176,7 +166,6 @@ public class CoreContainerProvider implements 
ServletContextListener {
       }
     } finally {
       if (cc != null) {
-        httpClient = null;
         cc.shutdown();
       }
     }
@@ -229,7 +218,6 @@ public class CoreContainerProvider implements 
ServletContextListener {
               });
 
       coresInit = createCoreContainer(computeSolrHome(servletContext), 
extraProperties);
-      this.httpClient = 
coresInit.getUpdateShardHandler().getDefaultHttpClient();
       setupJvmMetrics(coresInit, coresInit.getNodeConfig().getMetricsConfig());
 
       SolrZkClient zkClient = null;
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java 
b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index 1fd374536b5..895c00603dd 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -36,16 +36,13 @@ import io.opentracing.Span;
 import io.opentracing.tag.Tags;
 import java.io.EOFException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
-import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
@@ -62,26 +59,10 @@ import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import net.jcip.annotations.ThreadSafe;
-import org.apache.http.Header;
-import org.apache.http.HeaderIterator;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpEntityEnclosingRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.client.methods.HttpOptions;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.entity.InputStreamEntity;
 import org.apache.solr.api.ApiBag;
 import org.apache.solr.api.V2HttpCall;
 import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.Aliases;
@@ -133,6 +114,7 @@ import 
org.apache.solr.update.processor.DistributingUpdateProcessorFactory;
 import org.apache.solr.util.RTimerTree;
 import org.apache.solr.util.tracing.TraceUtils;
 import org.apache.zookeeper.KeeperException;
+import org.eclipse.jetty.client.HttpClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MarkerFactory;
@@ -142,8 +124,6 @@ import org.slf4j.MarkerFactory;
 public class HttpSolrCall {
   private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  public static final String ORIGINAL_USER_PRINCIPAL_HEADER = 
"originalUserPrincipal";
-
   public static final String INTERNAL_REQUEST_COUNT = "_forwardedCount";
 
   protected final SolrDispatchFilter solrDispatchFilter;
@@ -500,12 +480,6 @@ public class HttpSolrCall {
     }
   }
 
-  protected void sendRemoteQuery() throws IOException {
-    SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, new 
SolrQueryResponse(), action));
-    mustClearSolrRequestInfo = true;
-    remoteQuery(coreUrl + path, response);
-  }
-
   /** This method processes the request. */
   public Action call() throws IOException {
 
@@ -630,7 +604,7 @@ public class HttpSolrCall {
 
   /**
    * Handle a request whose "type" could not be discerned in advance and may 
be either "admin" or
-   * "remotequery".
+   * "remoteproxy".
    *
    * <p>Some implementations (such as {@link V2HttpCall}) may find it 
difficult to differentiate all
    * request types in advance. This method serves as a hook; allowing those 
implementations to
@@ -728,107 +702,20 @@ public class HttpSolrCall {
     }
   }
 
-  // TODO using Http2Client
-  private void remoteQuery(String coreUrl, HttpServletResponse resp) throws 
IOException {
-    HttpRequestBase method;
-    HttpEntity httpEntity = null;
-
+  protected void sendRemoteQuery() throws IOException {
     ModifiableSolrParams updatedQueryParams = new 
ModifiableSolrParams(queryParams);
     int forwardCount = queryParams.getInt(INTERNAL_REQUEST_COUNT, 0) + 1;
     updatedQueryParams.set(INTERNAL_REQUEST_COUNT, forwardCount);
     String queryStr = updatedQueryParams.toQueryString();
 
+    String coreUrlAndPath = coreUrl + path;
+    log.info("Proxying request to: {}", coreUrlAndPath);
     try {
-      String urlstr = coreUrl + queryStr;
-
-      boolean isPostOrPutRequest = "POST".equals(req.getMethod()) || 
"PUT".equals(req.getMethod());
-      if ("GET".equals(req.getMethod())) {
-        method = new HttpGet(urlstr);
-      } else if ("HEAD".equals(req.getMethod())) {
-        method = new HttpHead(urlstr);
-      } else if (isPostOrPutRequest) {
-        HttpEntityEnclosingRequestBase entityRequest =
-            "POST".equals(req.getMethod()) ? new HttpPost(urlstr) : new 
HttpPut(urlstr);
-        InputStream in = req.getInputStream();
-        HttpEntity entity = new InputStreamEntity(in, req.getContentLength());
-        entityRequest.setEntity(entity);
-        method = entityRequest;
-      } else if ("DELETE".equals(req.getMethod())) {
-        method = new HttpDelete(urlstr);
-      } else if ("OPTIONS".equals(req.getMethod())) {
-        method = new HttpOptions(urlstr);
-      } else {
-        throw new SolrException(
-            SolrException.ErrorCode.SERVER_ERROR, "Unexpected method type: " + 
req.getMethod());
-      }
-
-      for (Enumeration<String> e = req.getHeaderNames(); e.hasMoreElements(); 
) {
-        String headerName = e.nextElement();
-        if (!"host".equalsIgnoreCase(headerName)
-            && !"authorization".equalsIgnoreCase(headerName)
-            && !"accept".equalsIgnoreCase(headerName)) {
-          method.addHeader(headerName, req.getHeader(headerName));
-        }
-      }
-      // These headers not supported for HttpEntityEnclosingRequests
-      if (method instanceof HttpEntityEnclosingRequest) {
-        method.removeHeaders(TRANSFER_ENCODING_HEADER);
-        method.removeHeaders(CONTENT_LENGTH_HEADER);
-      }
-
-      // Make sure the user principal is forwarded when its exist
-      HttpClientContext httpClientRequestContext =
-          HttpClientUtil.createNewHttpClientRequestContext();
-      Principal userPrincipal = req.getUserPrincipal();
-      if (userPrincipal != null) {
-        // Normally the context contains a static userToken to enable reuse 
resources. However, if a
-        // personal Principal object exists, we use that instead, also as a 
means to transfer
-        // authentication information to Auth plugins that wish to intercept 
the request later
-        if (log.isDebugEnabled()) {
-          log.debug("Forwarding principal {}", userPrincipal);
-        }
-        httpClientRequestContext.setUserToken(userPrincipal);
-      }
-
-      // Execute the method.
-      final HttpResponse response =
-          solrDispatchFilter.getHttpClient().execute(method, 
httpClientRequestContext);
-      int httpStatus = response.getStatusLine().getStatusCode();
-      httpEntity = response.getEntity();
-
-      resp.setStatus(httpStatus);
-      for (HeaderIterator responseHeaders = response.headerIterator();
-          responseHeaders.hasNext(); ) {
-        Header header = responseHeaders.nextHeader();
-
-        // We pull out these two headers below because they can cause chunked
-        // encoding issues with Tomcat
-        if (header != null
-            && !header.getName().equalsIgnoreCase(TRANSFER_ENCODING_HEADER)
-            && !header.getName().equalsIgnoreCase(CONNECTION_HEADER)) {
-
-          // NOTE: explicitly using 'setHeader' instead of 'addHeader' so that
-          // the remote nodes values for any response headers will overide any 
that
-          // may have already been set locally (ex: by the local jetty's 
RewriteHandler config)
-          resp.setHeader(header.getName(), header.getValue());
-        }
-      }
-
-      if (httpEntity != null) {
-        if (httpEntity.getContentEncoding() != null)
-          resp.setHeader(
-              httpEntity.getContentEncoding().getName(),
-              httpEntity.getContentEncoding().getValue());
-        if (httpEntity.getContentType() != null)
-          resp.setContentType(httpEntity.getContentType().getValue());
-
-        InputStream is = httpEntity.getContent();
-        OutputStream os = resp.getOutputStream();
-
-        is.transferTo(os);
-      }
-
-    } catch (IOException e) {
+      response.reset(); // clear all headers and status
+      HttpClient httpClient = cores.getDefaultHttpSolrClient().getHttpClient();
+      HttpSolrProxy.doHttpProxy(httpClient, req, response, coreUrlAndPath + 
queryStr);
+    } catch (Exception e) {
+      // note: don't handle interruption differently; we are stopping
       sendError(
           new SolrException(
               SolrException.ErrorCode.SERVER_ERROR,
@@ -837,8 +724,10 @@ public class HttpSolrCall {
                   + " with _forwardCount: "
                   + forwardCount,
               e));
-    } finally {
-      Utils.consumeFully(httpEntity);
+    } catch (Error e) {
+      throw e;
+    } catch (Throwable e) {
+      throw new RuntimeException(e);
     }
   }
 
@@ -1317,9 +1206,6 @@ public class HttpSolrCall {
     };
   }
 
-  static final String CONNECTION_HEADER = "Connection";
-  static final String TRANSFER_ENCODING_HEADER = "Transfer-Encoding";
-  static final String CONTENT_LENGTH_HEADER = "Content-Length";
   List<CommandOperation> parsedCommands;
 
   public List<CommandOperation> getCommands(boolean validateInput) {
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrProxy.java 
b/solr/core/src/java/org/apache/solr/servlet/HttpSolrProxy.java
new file mode 100644
index 00000000000..0282f360f05
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrProxy.java
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+
+package org.apache.solr.servlet;
+
+import io.opentracing.util.GlobalTracer;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.solr.util.tracing.TraceUtils;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.InputStreamRequestContent;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+
+/** Helper class for proxying the request to another Solr node. */
+// Tried to use Jetty's ProxyServlet instead but ran into inexplicable 
difficulties:  EOF/reset.
+// Perhaps was related to its use of ServletRequest.startAsync()/AsyncContext
+class HttpSolrProxy {
+  // TODO add X-Forwarded-For and with comma delimited
+
+  private static final Set<HttpHeader> HOP_BY_HOP_HEADERS =
+      EnumSet.of(
+          HttpHeader.CONNECTION,
+          HttpHeader.KEEP_ALIVE,
+          HttpHeader.PROXY_AUTHENTICATE,
+          HttpHeader.PROXY_AUTHORIZATION,
+          HttpHeader.TE,
+          HttpHeader.TRANSFER_ENCODING,
+          HttpHeader.UPGRADE);
+
+  // Methods that shouldn't have a body according to HTTP spec
+  private static final Set<String> NO_BODY_METHODS = Set.of("GET", "HEAD", 
"DELETE");
+
+  static void doHttpProxy(
+      HttpClient httpClient,
+      HttpServletRequest servletReq,
+      HttpServletResponse servletRsp,
+      String url)
+      throws Throwable {
+    Request proxyReq = 
httpClient.newRequest(url).method(servletReq.getMethod());
+
+    // clearing them first to ensure there's no stock entries (e.g. user-agent)
+    proxyReq.headers(proxyFields -> copyRequestHeaders(servletReq, 
proxyFields.clear()));
+
+    // FYI see InstrumentedHttpListenerFactory
+    TraceUtils.injectTraceContext(proxyReq, GlobalTracer.get().activeSpan());
+    // TODO client spans.  See OTEL agent's approach:
+    // 
https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jetty-httpclient/jetty-httpclient-12.0
+
+    if (!NO_BODY_METHODS.contains(servletReq.getMethod())) {
+      proxyReq.body(
+          new InputStreamRequestContent(servletReq.getContentType(), 
servletReq.getInputStream()));
+    }
+
+    CompletableFuture<Result> resultFuture = new CompletableFuture<>();
+
+    proxyReq.send(
+        new Response.Listener() {
+          private final byte[] buffer = new byte[8192];
+
+          @Override
+          public void onBegin(Response response) {
+            servletRsp.setStatus(response.getStatus());
+          }
+
+          @Override
+          public void onHeaders(Response response) {
+            copyResponseHeaders(response, servletRsp);
+          }
+
+          @Override
+          public void onContent(Response response, ByteBuffer content) {
+            try {
+              final OutputStream clientOutputStream = 
servletRsp.getOutputStream();
+
+              // Copy content to the client's output stream in chunks using 
the existing buffer
+              int remaining = content.remaining();
+              while (remaining > 0) {
+                int chunkSize = Math.min(remaining, buffer.length);
+                content.get(buffer, 0, chunkSize);
+                clientOutputStream.write(buffer, 0, chunkSize);
+                remaining -= chunkSize;
+              }
+              clientOutputStream.flush();
+            } catch (IOException e) {
+              throw new RuntimeException(e);
+            }
+          }
+
+          @Override
+          public void onComplete(Result result) {
+            resultFuture.complete(result);
+          }
+        });
+
+    Result result = resultFuture.get(); // waits
+    var failure = result.getFailure();
+    if (failure != null) {
+      throw failure;
+    }
+  }
+
+  private static void copyRequestHeaders(
+      HttpServletRequest servletReq, HttpFields.Mutable proxyFields) {
+    servletReq
+        .getHeaderNames()
+        .asIterator()
+        .forEachRemaining(
+            headerName -> {
+              HttpHeader knownHeader = HttpHeader.CACHE.get(headerName); // 
maybe null
+              if (!HOP_BY_HOP_HEADERS.contains(knownHeader)) {
+                servletReq
+                    .getHeaders(headerName)
+                    .asIterator()
+                    .forEachRemaining(headerVal -> proxyFields.add(headerName, 
headerVal));
+              }
+            });
+  }
+
+  private static void copyResponseHeaders(Response proxyRsp, 
HttpServletResponse servletRsp) {
+    for (HttpField headerField : proxyRsp.getHeaders()) {
+      HttpHeader knownHeader = headerField.getHeader();
+      if (!HOP_BY_HOP_HEADERS.contains(knownHeader)) {
+        // HttpField: even if multiple values, it's encoded as one comma 
delimited value
+        servletRsp.addHeader(headerField.getName(), headerField.getValue());
+      }
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java 
b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index cfdac952858..7e8f5aa447e 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -40,7 +40,6 @@ import javax.servlet.UnavailableException;
 import javax.servlet.http.HttpFilter;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.apache.http.client.HttpClient;
 import org.apache.solr.api.V2HttpCall;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -91,15 +90,6 @@ public class SolrDispatchFilter extends HttpFilter 
implements PathExcluder {
 
   public final boolean isV2Enabled = V2ApiUtils.isEnabled();
 
-  public HttpClient getHttpClient() {
-    try {
-      return containerProvider.getHttpClient();
-    } catch (UnavailableException e) {
-      throw new SolrException(
-          ErrorCode.SERVER_ERROR, "Internal Http Client Unavailable, startup 
may have failed");
-    }
-  }
-
   /**
    * Enum to define action that needs to be processed. PASSTHROUGH: Pass 
through to another filter
    * via webapp. FORWARD: Forward rewritten URI (without path prefix and 
core/collection name) to
@@ -134,6 +124,7 @@ public class SolrDispatchFilter extends HttpFilter 
implements PathExcluder {
 
   @Override
   public void init(FilterConfig config) throws ServletException {
+    super.init(config);
     try {
       containerProvider = 
CoreContainerProvider.serviceForContext(config.getServletContext());
       boolean isCoordinator =
@@ -201,9 +192,11 @@ public class SolrDispatchFilter extends HttpFilter 
implements PathExcluder {
             }
           });
     } finally {
-      ServletUtils.consumeInputFully(request, response);
       SolrRequestInfo.reset();
-      SolrRequestParsers.cleanupMultipartFiles(request);
+      if (!request.isAsyncStarted()) { // jetty's proxy uses this
+        ServletUtils.consumeInputFully(request, response);
+        SolrRequestParsers.cleanupMultipartFiles(request);
+      }
     }
   }
 
diff --git 
a/solr/modules/hadoop-auth/src/test/org/apache/solr/client/solrj/impl/Krb5HttpClientUtils.java
 
b/solr/modules/hadoop-auth/src/test/org/apache/solr/client/solrj/impl/Krb5HttpClientUtils.java
index 8f78c5035fb..967c32c82ad 100644
--- 
a/solr/modules/hadoop-auth/src/test/org/apache/solr/client/solrj/impl/Krb5HttpClientUtils.java
+++ 
b/solr/modules/hadoop-auth/src/test/org/apache/solr/client/solrj/impl/Krb5HttpClientUtils.java
@@ -38,6 +38,7 @@ public class Krb5HttpClientUtils {
     HttpAuthenticationStore authenticationStore = new 
HttpAuthenticationStore();
     
authenticationStore.addAuthentication(createSPNEGOAuthentication(principalName));
     http2Client
+        .getHttpClient()
         .getProtocolHandlers()
         .put(new 
WWWAuthenticationProtocolHandler(http2Client.getHttpClient()));
     http2Client.setAuthenticationStore(authenticationStore);
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 7462798a06a..4e5761c2095 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
@@ -214,14 +214,14 @@ public class Http2SolrClient extends HttpSolrClientBase {
     this.listenerFactory.add(factory);
   }
 
-  // internal usage only
-  HttpClient getHttpClient() {
+  /** internal use only */
+  public HttpClient getHttpClient() {
     return httpClient;
   }
 
-  // internal usage only
-  ProtocolHandlers getProtocolHandlers() {
-    return httpClient.getProtocolHandlers();
+  @Deprecated(since = "9.10")
+  public ProtocolHandlers getProtocolHandlers() {
+    return getHttpClient().getProtocolHandlers();
   }
 
   private HttpClient createHttpClient(Builder builder) {
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
index 8b47d45d2f0..3e555d533e8 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Krb5HttpClientBuilder.java
@@ -131,6 +131,7 @@ public class Krb5HttpClientBuilder implements 
HttpClientBuilderFactory {
     authenticationStore.addAuthentication(createSPNEGOAuthentication());
     http2Client.setAuthenticationStore(authenticationStore);
     http2Client
+        .getHttpClient()
         .getProtocolHandlers()
         .put(new 
WWWAuthenticationProtocolHandler(http2Client.getHttpClient()));
   }
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
index 0d1231baa74..2b8c183065b 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientBuilderFactory.java
@@ -95,10 +95,9 @@ public class PreemptiveBasicAuthClientBuilderFactory 
implements HttpClientBuilde
     authenticationStore.addAuthentication(
         new SolrBasicAuthentication(basicAuthUser, basicAuthPass));
     client.setAuthenticationStore(authenticationStore);
-    client.getProtocolHandlers().put(new 
WWWAuthenticationProtocolHandler(client.getHttpClient()));
-    client
-        .getProtocolHandlers()
-        .put(new ProxyAuthenticationProtocolHandler(client.getHttpClient()));
+    var httpClient = client.getHttpClient();
+    httpClient.getProtocolHandlers().put(new 
WWWAuthenticationProtocolHandler(httpClient));
+    httpClient.getProtocolHandlers().put(new 
ProxyAuthenticationProtocolHandler(httpClient));
   }
 
   @Override

Reply via email to