Skip to content

per-host http client pool - #7301

Merged
ehsandeep merged 13 commits into
devfrom
5453-per-host-http-client-pool
Jun 16, 2026
Merged

per-host http client pool#7301
ehsandeep merged 13 commits into
devfrom
5453-per-host-http-client-pool

Conversation

@Mzack9999

@Mzack9999 Mzack9999 commented Mar 25, 2026

Copy link
Copy Markdown
Member

Proposed changes

Close #5453

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • Performance

    • Per-host HTTP client pooling with much more aggressive idle-eviction for faster, lower-latency repeated requests.
  • Bug Fixes

    • Keep-alive enabled by default; connections are reused unless explicitly disabled.
    • Request execution now selects per-host clients to improve connection reuse correctness and stability.
  • Chores

    • Connection-reuse metrics and runtime info logging added.
    • Template execution now always cleans up context on exit.
  • Tests

    • New benchmark/test suite validating connection reuse, TLS, and concurrency.

@auto-assign
auto-assign Bot requested a review from dogancanbakir March 25, 2026 15:02
@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Per-host HTTP client pooling and connection-tracking were added: client Get now accepts a host, transports collect new vs reused connection counts, pool eviction windows shortened, keep-alive disabling by scan-strategy removed, client acquisition moved to request execution, shutdown logs and benchmark suite added.

Changes

Cohort / File(s) Summary
HTTP client pool core
pkg/protocols/http/httpclientpool/clientpool.go
Added per-host scoping to Get(..., host); introduced atomic ConnectionStats and connTrackingTransport using httptrace.GotConn; changed pool keying, transport defaults (IdleConnTimeout 30s, TLS session cache 1024→128), connection limits, cookie-jar caching, and added GetConnectionStats().
Per-request / per-host client usage
pkg/protocols/http/request.go, pkg/protocols/http/http.go
Removed Request.httpClient field; added getHTTPClientForHost(host); switched to fetching client at execution time via httpclientpool.Get(..., host); analyzer calls now receive the per-host client.
Keep-alive & request build
pkg/protocols/http/build_request.go, pkg/protocols/utils/http/requtils.go
Removed scan-strategy-based keep-alive disabling and ShouldDisableKeepAlive; req.Close now set only when DisableKeepAlive is explicitly configured and no explicit Connection header exists.
Pool eviction & lifecycle / shutdown
pkg/protocols/common/protocolstate/state.go, internal/runner/runner.go, lib/sdk_private.go, pkg/protocols/common/automaticscan/automaticscan.go
Shortened per-host eviction windows (90s inactivity, 30s refresh); Close now iterates cached clients to call CloseIdleConnections() and clears pool before closing dialer; updated call sites to pass trailing "" host when none; Runner.Close() logs connection stats (new/reused + reuse %).
Benchmarks & tests
pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go
Added comprehensive benchmark/test suite validating per-host vs template-spray behaviors, connection reuse counts, transport propagation, and goroutine/leak checks.
Template context cleanup
pkg/tmplexec/exec.go
ExecuteWithResults() now defers RemoveTemplateCtx(...) to ensure template context cleanup.
Minor call-site updates
internal/runner/runner.go, lib/sdk_private.go, pkg/protocols/common/automaticscan/automaticscan.go, pkg/protocols/http/build_request.go
Adjusted httpclientpool.Get call sites to the new signature (added trailing "" host where applicable); removed scanstrategy imports/references; simplified connection configuration invocations.

Sequence Diagram(s)

sequenceDiagram
    participant R as Request
    participant P as HTTPClientPool
    participant C as retryablehttp.Client
    participant T as Transport
    participant S as TargetServer
    participant A as Analyzer

    R->>P: Get(options, config, host)
    note right of P: select/create per-host client\nwrap transport with connTrackingTransport
    P-->>C: return client
    R->>C: Do(req)
    C->>T: RoundTrip(req)
    T->>S: TCP/TLS connect & send
    S-->>T: response
    T->>T: httptrace.GotConn -> increment New/Reused
    T-->>C: response
    C-->>R: response
    R->>A: analyze(response, client=C)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 I hopped through pools both near and far,
Per-host I planted a tiny jar,
Ninety seconds tidy, keep-alive in sight,
Connections counted — new and reused by night,
Benchmarks hum and the runner logs delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'per-host http client pool' directly and clearly describes the main change: implementing per-host HTTP client pooling to address the memory issue.
Linked Issues check ✅ Passed The PR successfully implements per-host HTTP client pooling [7301] with aggressive connection eviction [state.go], per-host client lookup [request.go], connection stats tracking [clientpool.go], and comprehensive benchmarks validating reuse behavior [clientpool_benchmark_test.go], directly addressing the host-spray memory growth issue [#5453].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing per-host HTTP client pooling and managing connection lifecycle; no unrelated modifications detected beyond the core objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 5453-per-host-http-client-pool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
pkg/protocols/common/automaticscan/automaticscan.go (1)

97-97: Disable cookie storage on the shared Wappalyzer client.

This client is reused across every target, and httpclientpool.Get(..., &Configuration{}, "") will give it a default jar. getTagsUsingWappalyzer only does a stateless fingerprinting GET, so those cookies just retain cross-target state/memory for no benefit. DisableCookie: true looks like the safer default here.

Proposed change
-	httpclient, err := httpclientpool.Get(opts.ExecuterOpts.Options, &httpclientpool.Configuration{}, "")
+	httpclient, err := httpclientpool.Get(opts.ExecuterOpts.Options, &httpclientpool.Configuration{
+		DisableCookie: true,
+	}, "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/common/automaticscan/automaticscan.go` at line 97, The shared
Wappalyzer HTTP client is created via httpclientpool.Get in
getTagsUsingWappalyzer and currently receives a default jar; update the
Configuration passed to httpclientpool.Get (the second argument) to set
DisableCookie: true so the returned httpclient does not store cookies across
targets (use opts.ExecuterOpts.Options and the existing Configuration struct but
set DisableCookie to true) to ensure stateless fingerprinting and avoid
cross-target cookie retention.
pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go (1)

101-118: Add at least one test that goes through Get().

tracedClient builds standalone http.Clients, so this suite never exercises pkg/protocols/http/httpclientpool.Get, the host-keyed cache path, or the explicit-jar cache bypass added in this PR. As written, these are useful microbenchmarks, but they do not lock down the actual regression surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go` around lines
101 - 118, Add a unit test that actually calls the httpclientpool Get method so
the host-keyed cache and explicit-jar bypass are exercised: create a pool (using
the same factory/hooks your code exposes), use tracedClient (the
connTrackingRoundTripper with newConns/reusedConns counters) as the underlying
client factory, call pool.Get(host) twice and assert the second call exercises
the cache (no newConns, increased reusedConns or same client pointer), then call
pool.Get(host, explicitJar=true) (or the equivalent API) and assert it bypasses
the cache (newConns increments or returns a different client). Reference
tracedClient, connTrackingRoundTripper, newConns, reusedConns and Get in your
test so it validates both the host-keyed cache path and the explicit-jar cache
bypass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/runner/runner.go`:
- Around line 430-434: GetConnectionStats() returns package-global cumulative
counters, so per-scan logs in runner.go show aggregated totals across in-process
runs; update the httpclientpool usage to produce per-execution stats by either
calling a reset or using a scoped API: add/ call
httpclientpool.ResetConnectionStats() at the start (or end) of a run inside the
runner (around where GetConnectionStats() is invoked) or change to an API like
httpclientpool.GetConnectionStatsForExecution(executionId) /
httpclientpool.ScopedStats(executionId) and pass the run's ExecutionId so the
logged totals (from GetConnectionStats / new scoped method) reflect only the
current scan. Ensure you reference and modify the call site in runner.go where
GetConnectionStats() is used and wire in ExecutionId from the current Runner
context.

In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go`:
- Around line 215-218: The test currently enforces a hard performance threshold
by asserting require.Greater(t, speedup, 1.5) on the local speedup variable;
remove that flaky assertion and instead record the observed speedup (e.g.,
t.Logf or similar) so the test no longer fails on noisy CI—locate the speedup
variable and the require.Greater call in clientpool_benchmark_test.go and
replace the failing assertion with a non-fatal log of the speedup, relying on
the existing deterministic connection-count assertions to validate behavior.

In `@pkg/protocols/http/httpclientpool/clientpool.go`:
- Around line 220-244: wrappedGet currently keys the pool by
configuration.Hash() plus host but configuration.Hash() doesn't include
Connection.DisableKeepAlive, so clients with different DisableKeepAlive values
can collide; update the pool key construction in clientpool.go (the code around
wrappedGet / the local hash variable) to append the effective DisableKeepAlive
flag (e.g., strconv.FormatBool(disableKeepAlives) or "keepalive=off"/"on") to
the hash before calling dialers.HTTPClientPool.Get, ensuring the same unique
symbols (configuration.Hash(), host, disableKeepAlives) are used when creating
and retrieving the client so keep-alive behavior is correctly honored.
- Around line 51-67: The connTrackingTransport wrapper only implements
RoundTrip, so http.Client.CloseIdleConnections() doesn't reach the underlying
transport; add a CloseIdleConnections method on connTrackingTransport that
forwards the call to the wrapped transport when it supports it (assert t.base to
an interface with CloseIdleConnections and call it), ensuring no-op if the base
doesn't implement that method; reference connTrackingTransport, RoundTrip,
CloseIdleConnections and the base http.RoundTripper in your change.

In `@pkg/protocols/http/request.go`:
- Around line 1010-1015: The analyzer is being given a new client via
request.getHTTPClientForHost(hostname), which can drop per-request overrides
(cookie jar, WithCustomTimeout) applied when the request was executed; instead
pass the same HTTP client instance that executed the request (the local
httpclient/httpClient variable created earlier when cloning connConfig and
resolving the client) into analyzer.Analyze so follow-up requests use the same
session/timeout; replace the getHTTPClientForHost(hostname) call with the
executing client variable when invoking analyzer.Analyze.
- Around line 847-850: The pool key currently passed to httpclientpool.Get uses
the Host header override (hostname / generatedRequest.request.Host), which
collapses distinct connection targets; change the key to use the actual
connection target generatedRequest.request.URL.Host (or concatenate URL.Host +
generatedRequest.request.Host if you need both host-target and Host-header
isolation) when calling httpclientpool.Get (the call with
request.options.Options, connConfig, hostname) so each distinct target gets its
own pool entry and transports are not incorrectly reused across vhost/IP scans.

---

Nitpick comments:
In `@pkg/protocols/common/automaticscan/automaticscan.go`:
- Line 97: The shared Wappalyzer HTTP client is created via httpclientpool.Get
in getTagsUsingWappalyzer and currently receives a default jar; update the
Configuration passed to httpclientpool.Get (the second argument) to set
DisableCookie: true so the returned httpclient does not store cookies across
targets (use opts.ExecuterOpts.Options and the existing Configuration struct but
set DisableCookie to true) to ensure stateless fingerprinting and avoid
cross-target cookie retention.

In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go`:
- Around line 101-118: Add a unit test that actually calls the httpclientpool
Get method so the host-keyed cache and explicit-jar bypass are exercised: create
a pool (using the same factory/hooks your code exposes), use tracedClient (the
connTrackingRoundTripper with newConns/reusedConns counters) as the underlying
client factory, call pool.Get(host) twice and assert the second call exercises
the cache (no newConns, increased reusedConns or same client pointer), then call
pool.Get(host, explicitJar=true) (or the equivalent API) and assert it bypasses
the cache (newConns increments or returns a different client). Reference
tracedClient, connTrackingRoundTripper, newConns, reusedConns and Get in your
test so it validates both the host-keyed cache path and the explicit-jar cache
bypass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 753306ea-3133-400d-8336-97eb7846a7b9

📥 Commits

Reviewing files that changed from the base of the PR and between 4177a48 and 95739a2.

📒 Files selected for processing (11)
  • internal/runner/runner.go
  • lib/sdk_private.go
  • pkg/protocols/common/automaticscan/automaticscan.go
  • pkg/protocols/common/protocolstate/state.go
  • pkg/protocols/http/build_request.go
  • pkg/protocols/http/http.go
  • pkg/protocols/http/httpclientpool/clientpool.go
  • pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go
  • pkg/protocols/http/request.go
  • pkg/protocols/utils/http/requtils.go
  • pkg/tmplexec/exec.go

Comment thread internal/runner/runner.go
Comment thread pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go Outdated
Comment thread pkg/protocols/http/httpclientpool/clientpool.go
Comment thread pkg/protocols/http/httpclientpool/clientpool.go Outdated
Comment thread pkg/protocols/http/request.go
Comment thread pkg/protocols/http/request.go Outdated
@neo-by-projectdiscovery-dev

neo-by-projectdiscovery-dev Bot commented Mar 25, 2026

Copy link
Copy Markdown

Neo - PR Security Review

No security issues found

Highlights

  • Added CloseIdleConnections() method to connTrackingTransport for proper connection cleanup delegation
  • Updated benchmark tests for connection leak detection

Comment @pdneo help for available commands. · Open in Neo

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/protocols/common/protocolstate/state.go (1)

283-289: LGTM! Proper cleanup sequence for idle connections.

The order of operations is correct—closing idle connections and clearing the pool before closing the fastdialer ensures transports don't attempt to use an already-closed dialer.

One minor defensive improvement: consider adding a nil check for client to guard against unexpected nil entries in the pool.

,

🛡️ Optional: Add defensive nil check
 _ = dialersInstance.HTTPClientPool.Iterate(func(_ string, client *retryablehttp.Client) error {
+    if client != nil && client.HTTPClient != nil {
         client.HTTPClient.CloseIdleConnections()
+    }
     return nil
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/common/protocolstate/state.go` around lines 283 - 289, Add a
defensive nil check inside the Iterate callback to skip any nil entries before
calling CloseIdleConnections: in the block using
dialersInstance.HTTPClientPool.Iterate(func(_ string, client
*retryablehttp.Client) error { ... }), verify client != nil and also
client.HTTPClient != nil before calling
client.HTTPClient.CloseIdleConnections(), then return nil as before; keep the
subsequent dialersInstance.HTTPClientPool.Clear() unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/protocols/common/protocolstate/state.go`:
- Around line 283-289: Add a defensive nil check inside the Iterate callback to
skip any nil entries before calling CloseIdleConnections: in the block using
dialersInstance.HTTPClientPool.Iterate(func(_ string, client
*retryablehttp.Client) error { ... }), verify client != nil and also
client.HTTPClient != nil before calling
client.HTTPClient.CloseIdleConnections(), then return nil as before; keep the
subsequent dialersInstance.HTTPClientPool.Clear() unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 39974cdc-8191-45db-988f-7fd5cd2d5e67

📥 Commits

Reviewing files that changed from the base of the PR and between 95739a2 and fa6ee67.

📒 Files selected for processing (1)
  • pkg/protocols/common/protocolstate/state.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go (1)

216-219: ⚠️ Potential issue | 🟡 Minor

Avoid hard performance thresholds in unit tests.

The require.Greater(t, speedup, 1.5) assertion is hardware- and load-dependent, making it prone to flakiness on noisy CI runners. The deterministic connection-count assertions (lines 205-214) already validate the behavior correctly.

🩹 Proposed fix
 	// speedup sanity check (at least 1.5x on localhost)
 	speedup := float64(old.Duration) / float64(new.Duration)
-	require.Greater(t, speedup, 1.5,
-		"expected at least 1.5x speedup with connection reuse")
+	t.Logf("observed speedup with connection reuse: %.2fx", speedup)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go` around lines
216 - 219, The test currently computes speedup := float64(old.Duration) /
float64(new.Duration) and asserts require.Greater(t, speedup, 1.5), which is a
flaky, environment-dependent performance threshold; remove the speedup
calculation and the require.Greater assertion (the speedup variable,
old.Duration/new.Duration usage, and the require.Greater call) and rely on the
existing deterministic connection-count assertions in this test to validate
behavior instead.
🧹 Nitpick comments (1)
pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go (1)

81-100: Consider adding CloseIdleConnections() for consistency with production wrapper.

The test's connTrackingRoundTripper doesn't implement CloseIdleConnections(), unlike the production connTrackingTransport in clientpool.go. While this doesn't break current tests (since they don't call it), adding the method would maintain consistency and prevent issues if tests are extended.

🔧 Proposed addition
 func (rt *connTrackingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
 	trace := &httptrace.ClientTrace{
 		GotConn: func(info httptrace.GotConnInfo) {
 			if info.Reused {
 				rt.reused.Add(1)
 			} else {
 				rt.newConns.Add(1)
 			}
 		},
 	}
 	req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
 	return rt.base.RoundTrip(req)
 }
+
+func (rt *connTrackingRoundTripper) CloseIdleConnections() {
+	type closeIdler interface{ CloseIdleConnections() }
+	if ci, ok := rt.base.(closeIdler); ok {
+		ci.CloseIdleConnections()
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go` around lines
81 - 100, The test's connTrackingRoundTripper is missing a CloseIdleConnections
method (unlike production connTrackingTransport in clientpool.go); add a
CloseIdleConnections receiver on connTrackingRoundTripper that delegates to the
underlying base transport when available (use a type assertion to an interface
with CloseIdleConnections or assert *http.Transport) so idle connections are
closed consistently with production behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go`:
- Around line 136-186: The scan runners (runTemplateSpray, runHostSpray,
runConcurrentHostSpray) currently ignore the error returned by doRequest which
can hide failures and corrupt connection counts; update each loop to capture the
error (err := doRequest(...)) and fail fast on non-nil errors by aborting (e.g.,
panic or log.Fatalf) with a descriptive message that includes the target URL and
the error; apply this change in the bodies of runTemplateSpray, runHostSpray,
and the goroutine inside runConcurrentHostSpray (use the same failure mechanism
so the benchmark exits immediately when doRequest fails).

---

Duplicate comments:
In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go`:
- Around line 216-219: The test currently computes speedup :=
float64(old.Duration) / float64(new.Duration) and asserts require.Greater(t,
speedup, 1.5), which is a flaky, environment-dependent performance threshold;
remove the speedup calculation and the require.Greater assertion (the speedup
variable, old.Duration/new.Duration usage, and the require.Greater call) and
rely on the existing deterministic connection-count assertions in this test to
validate behavior instead.

---

Nitpick comments:
In `@pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go`:
- Around line 81-100: The test's connTrackingRoundTripper is missing a
CloseIdleConnections method (unlike production connTrackingTransport in
clientpool.go); add a CloseIdleConnections receiver on connTrackingRoundTripper
that delegates to the underlying base transport when available (use a type
assertion to an interface with CloseIdleConnections or assert *http.Transport)
so idle connections are closed consistently with production behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d561d91-8fd3-475f-82f2-656d84f3accc

📥 Commits

Reviewing files that changed from the base of the PR and between 5516e1c and ae6b21f.

📒 Files selected for processing (1)
  • pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go

Comment thread pkg/protocols/http/httpclientpool/clientpool_benchmark_test.go
Resolve conflict in clientpool.go: keep ConnectionStats and
connTrackingTransport from this branch, adopt dev's removal of
Init()/forceMaxRedirects and use ShouldFollowHTTPRedirects().
@Mzack9999
Mzack9999 marked this pull request as draft March 25, 2026 19:22
@Mzack9999

Copy link
Copy Markdown
Member Author

Added an end-to-end benchmark (BenchmarkPR_* in clientpool_pr_perf_test.go) to measure what this PR actually buys us. Same workload — 10 hosts × 20 requests — run two ways:

  • before: single shared http.Client, keep-alive off (what nuclei effectively did)
  • after: httpclientpool.Get(opts, cfg, host), keep-alive on (what request.go does now)

Apple M1, -benchtime=5x:

HTTPS  before                   562 ms   27.3 MB   182k allocs
HTTPS  after                     82 ms    1.8 MB    21k allocs     ~7x faster
HTTPS  after, 1 goroutine/host   7.6 ms   1.8 MB    21k allocs    ~74x faster
HTTP   before                    28 ms    3.6 MB    26k allocs
HTTP   after                     13 ms    1.6 MB    19k allocs     ~2x faster

HTTPS is where it really shows: skipping the TLS handshake on every request drops wall time ~7x and memory ~15x. Under concurrency the win compounds, since hosts no longer fight over a single transport's idle pool.

The existing `TestConnectionCount_*` already locks down that with keep-alive on we open exactly one connection per host regardless of request count, so this is real reuse, not measurement noise.

@Mzack9999 Mzack9999 self-assigned this Apr 22, 2026
Comment thread pkg/protocols/http/httpclientpool/clientpool.go Dismissed
Comment thread pkg/protocols/http/httpclientpool/clientpool.go Dismissed
@Mzack9999
Mzack9999 marked this pull request as ready for review June 10, 2026 20:23
@ehsandeep
ehsandeep requested a review from Ice3man543 June 16, 2026 14:47

@Ice3man543 Ice3man543 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@ehsandeep
ehsandeep merged commit 63124ef into dev Jun 16, 2026
19 checks passed
@ehsandeep
ehsandeep deleted the 5453-per-host-http-client-pool branch June 16, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host-spray intensive memory usage

4 participants