Skip to content

Comments

fix(httpx): honor -pr http11 by disabling retryablehttp h2 fallback#2414

Open
cococlaw wants to merge 2 commits intoprojectdiscovery:devfrom
cococlaw:fix/algora-2240-http11-no-h2-fallback
Open

fix(httpx): honor -pr http11 by disabling retryablehttp h2 fallback#2414
cococlaw wants to merge 2 commits intoprojectdiscovery:devfrom
cococlaw:fix/algora-2240-http11-no-h2-fallback

Conversation

@cococlaw
Copy link

@cococlaw cococlaw commented Feb 20, 2026

Fixes #2240

/claim #2240

Summary

  • Prevent retryablehttp from silently falling back to HTTP/2 when -pr http11 is set.
  • Keep normal fallback behavior for non-http11 mode.

Why

When users explicitly force HTTP/1.1, protocol fallback to HTTP/2 violates expectation and can bypass transport policy differences.

Validation

  • GOTOOLCHAIN=auto go test ./common/httpx

Summary by CodeRabbit

  • Bug Fixes

    • Ensure HTTP/1.1 mode fully disables HTTP/2 fallback so both client paths use the same HTTP/1.1 behavior, avoiding unintended HTTP/2 transport usage.
  • Tests

    • Added tests confirming HTTP/1.1 enforcement prevents HTTP/2 fallback and that non-HTTP/1.1 protocols retain HTTP/2 fallback behavior.

@neo-by-projectdiscovery-dev
Copy link

neo-by-projectdiscovery-dev bot commented Feb 20, 2026

Neo Security Audit

No security issues found

Hardening Notes
  • Semgrep flagged InsecureSkipVerify and TLS 1.0 in the HTTP/2 transport configuration (lines 193-196), but these are intentional design choices for a security scanning tool that needs to connect to servers with invalid certificates and legacy TLS versions. These patterns exist throughout the codebase and are not introduced by this PR.
  • Minor code quality note: Line 156 uses string literal "http11" while line 188 uses constant HTTP11 for the same comparison. Both work correctly since HTTP11 = "http11", but using the constant consistently would improve maintainability. This is not a security issue.
  • The validation logic in runner/options.go (line 799) restricts the Protocol field to only UNKNOWN (empty) or HTTP11 ("http11"), preventing any protocol bypass attempts.
  • The test coverage is comprehensive, verifying both that HTTP/1.1 mode disables HTTP/2 fallback and that non-HTTP/1.1 modes preserve the HTTP/2 client.

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

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

When Protocol is set to HTTP11, HTTPX now reuses the primary HTTP/1.1 client for HTTPClient2 instead of creating a separate HTTP/2 probing client; for other protocols the prior behavior (creating a separate http2.Transport and client2) remains unchanged.

Changes

Cohort / File(s) Summary
HTTP Protocol Initialization
common/httpx/httpx.go
Adds a conditional path: when Options.Protocol == "http11", assign HTTPClient2/client2 to the same retryable HTTP/1.1 client as HTTPClient, skipping creation of a separate http2.Transport.
HTTP Protocol Tests
common/httpx/httpx_protocol_test.go
New tests: verify HTTPClient2 equals HTTPClient and is not using an http2.Transport when Protocol is HTTP11; verify distinct clients and an http2.Transport for non-HTTP11 (e.g., HTTP2).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant HTTPX_Init as HTTPX Init
    participant Client1 as PrimaryClient (retryable)
    participant Client2 as SecondaryClient
    participant Transport2 as http2.Transport

    rect rgba(0,128,0,0.5)
    User->>HTTPX_Init: create with Protocol=="http11"
    HTTPX_Init->>Client1: create primary HTTP/1.1 client
    HTTPX_Init->>Client2: assign Client2 = Client1 (reuse)
    Client2-->>User: returns clients (same underlying transport)
    end

    rect rgba(0,0,128,0.5)
    User->>HTTPX_Init: create with Protocol!="http11"
    HTTPX_Init->>Client1: create primary client (retryable)
    HTTPX_Init->>Transport2: build http2.Transport with TLS config
    HTTPX_Init->>Client2: create secondary client using Transport2
    Client1-->>User: returns primary client
    Client2-->>User: returns secondary (http2-capable) client
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 I nudge the clients, hop and bind,
One hop for HTTP/1.1, no fallback to find.
Two clients once danced, now share a way,
Cleaner steps for requests each day.
Tests cheer softly — hop, hooray! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 accurately reflects the main change: adding HTTP/1.1 mode support by disabling HTTP/2 fallback in the retry client.
Linked Issues check ✅ Passed The PR successfully addresses issue #2240 by preventing retryablehttp's HTTP/2 fallback when HTTP/1.1 mode is configured, and ensures both retry and probe clients use the same HTTP/1.1 client.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #2240: implementing HTTP/1.1 mode enforcement and adding validation tests for both HTTP/1.1 and HTTP/2 protocol paths.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
common/httpx/httpx.go (1)

191-204: ⚠️ Potential issue | 🟠 Major

httpx.client2 is still initialized with http2.Transport even when Protocol == HTTP11 — incomplete fix.

The PR correctly patches retryablehttp.Client.HTTPClient2 (line 185–188), setting it to use HTTP/1.1 when the protocol is forced. However, httpx.client2 — the HTTPX struct's own *http.Client field — is created unconditionally with http2.Transport (lines 191–204) without checking httpx.Options.Protocol.

When SupportHTTP2(protocol, method, targetURL) is called during HTTP/2 probing on HTTPS targets, it receives the scheme ("https") and dispatches through h.client2.Do(req) (http2.go:55), which uses HTTP/2. This violates the user's explicit HTTP/1.1 constraint.

Fix: Apply the same conditional logic to client2 initialization:

if httpx.Options.Protocol != HTTP11 {
    httpx.client2 = &http.Client{
        Transport: transport2,
        Timeout:   httpx.Options.Timeout,
    }
}

Or, when Protocol == HTTP11, initialize client2 with the same HTTP/1.1-only transport used for the main client.

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

In `@common/httpx/httpx.go` around lines 191 - 204, The httpx.client2 is always
built with an http2.Transport which ignores the user's HTTP/1.1 override; update
the client2 initialization to honor httpx.Options.Protocol by either (a) only
assigning an http2.Transport when httpx.Options.Protocol != HTTP11, or (b) when
httpx.Options.Protocol == HTTP11, initialize httpx.client2 with the same
HTTP/1.1-only transport used for the main client (i.e., mirror the logic applied
to retryablehttp.Client.HTTPClient2). Locate the client2 construction around the
http2.Transport block and modify it to check httpx.Options.Protocol (compare
against HTTP11) so SupportHTTP2 and h.client2.Do(req) will not force HTTP/2 when
the user requested HTTP/1.1.
🧹 Nitpick comments (1)
common/httpx/httpx_protocol_test.go (1)

9-17: LGTM — assertion and test logic are correct.

require.Same correctly validates pointer identity after HTTPClient2 = HTTPClient. One minor note: New() allocates a fastdialer.Dialer (and other resources) that are never released. Consider adding a t.Cleanup to close h.Dialer if the dialer exposes a Close() method, to avoid resource leaks in the test binary.

🧹 Suggested cleanup
 h, err := New(&options)
 require.NoError(t, err)
+t.Cleanup(func() { _ = h.Dialer.Close() })
 require.NotNil(t, h.client)
 require.Same(t, h.client.HTTPClient, h.client.HTTPClient2)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/httpx/httpx_protocol_test.go` around lines 9 - 17, Test allocates
resources (fastdialer.Dialer) that aren't released; add a cleanup to close the
dialer. In the TestNewHTTP11DisablesRetryableHTTP2FallbackClient test, after
successfully creating h via New(&options) register t.Cleanup to call Close on
h.Dialer (or the appropriate Close/CloseIdleConnections method exposed by the
fastdialer.Dialer) so the dialer and any associated resources are released when
the test finishes; reference the New function, the returned h value and its
h.Dialer field when adding the cleanup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@common/httpx/httpx.go`:
- Around line 191-204: The httpx.client2 is always built with an http2.Transport
which ignores the user's HTTP/1.1 override; update the client2 initialization to
honor httpx.Options.Protocol by either (a) only assigning an http2.Transport
when httpx.Options.Protocol != HTTP11, or (b) when httpx.Options.Protocol ==
HTTP11, initialize httpx.client2 with the same HTTP/1.1-only transport used for
the main client (i.e., mirror the logic applied to
retryablehttp.Client.HTTPClient2). Locate the client2 construction around the
http2.Transport block and modify it to check httpx.Options.Protocol (compare
against HTTP11) so SupportHTTP2 and h.client2.Do(req) will not force HTTP/2 when
the user requested HTTP/1.1.

---

Nitpick comments:
In `@common/httpx/httpx_protocol_test.go`:
- Around line 9-17: Test allocates resources (fastdialer.Dialer) that aren't
released; add a cleanup to close the dialer. In the
TestNewHTTP11DisablesRetryableHTTP2FallbackClient test, after successfully
creating h via New(&options) register t.Cleanup to call Close on h.Dialer (or
the appropriate Close/CloseIdleConnections method exposed by the
fastdialer.Dialer) so the dialer and any associated resources are released when
the test finishes; reference the New function, the returned h value and its
h.Dialer field when adding the cleanup.

@wh0amibjm
Copy link

Implemented the follow-up for the CodeRabbit major feedback (plus cleanup in tests) in commit a4701ad.\n\nI can’t push directly to cococlaw/httpx from this account, so I opened a helper PR that can be merged/cherry-picked:\n- https://github.com/cococlaw/httpx/pull/1\n- branch: wh0amibjm:fix/algora-2240-http11-no-h2-fallback-followup\n\nWhat it fixes:\n- in http11 mode, both retry fallback and HTTP/2 probe clients now use HTTP/1.1 client:\n - HTTPClient2 = HTTPClient\n - httpx.client2 = httpx.client.HTTPClient\n- keeps dedicated http2.Transport client only for non-http11 modes\n- extends tests to assert both paths and adds dialer cleanup via t.Cleanup\n\nProof (local):\n- before (origin/dev + same test):\n - go test ./common/httpx -run TestNew_HTTP11DisablesRetryableHTTP2Fallback -count=1 -> FAIL\n- after:\n - go test ./common/httpx -run TestNew_ -count=1 -> PASS

@wh0amibjm
Copy link

Quick merge-readiness follow-up:\n\n- CodeRabbit + Neo are both green\n- the follow-up fix for http11 fallback/probe consistency is ready\n- helper PR for upstream-fork apply/cherry-pick remains available: https://github.com/cococlaw/httpx/pull/1\n\nIf this looks good, could a maintainer take a final pass and merge/cherry-pick? 🙏

@cococlaw
Copy link
Author

cococlaw commented Feb 22, 2026

Implemented the follow-up from the CodeRabbit major feedback in commit a4701aded0963ed1e0621c6b1eb02ea4c0833bbf (also reflected on branch head).\n\nWhat changed:\n- In HTTP11 mode, both retry fallback and probe clients are aligned on the same HTTP/1.1 transport (HTTPClient2 = HTTPClient, client2 = HTTPClient).\n- Added tests to assert HTTP11 disables HTTP/2 fallback/probe transport and non-HTTP11 keeps dedicated HTTP/2 probing behavior.\n- Added dialer cleanup in tests.\n\nCould you please take a quick look when you get a moment? 🙏

@cococlaw
Copy link
Author

cococlaw commented Feb 22, 2026

gentle nudge for the follow-up fix 🙏\n\nI implemented the follow-up addressing the CodeRabbit feedback (HTTP/1.1 path now aligns fallback/probe clients and added protocol coverage tests) and pushed it on #2414 (branch fix/algora-2240-http11-no-h2-fallback). Could you please give this a quick review when you have a moment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

-pr http11 flag is ignored on retryablehttp-go due to HTTP/2 fallback

3 participants