-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix: respect explicit HTTP/1.1 configuration by disabling HTTP/2 fallback #2433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
55da24f
133d9b0
fd2571a
6b90cba
20c4bc9
5cd86bd
ca0d147
f0aafb3
1e86afa
315cf27
d5a94ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -183,6 +183,10 @@ func New(options *Options) (*HTTPX, error) { | |
| CheckRedirect: redirectFunc, | ||
| }, retryablehttpOptions) | ||
|
|
||
| if httpx.Options.Protocol == "http11" && httpx.Options.DisableHTTPFallback { | ||
| httpx.client.CheckRetry = getCustomCheckRetry(httpx.Options) | ||
| } | ||
|
|
||
| transport2 := &http2.Transport{ | ||
| TLSClientConfig: &tls.Config{ | ||
| InsecureSkipVerify: true, | ||
|
|
@@ -468,4 +472,15 @@ func (httpx *HTTPX) Sanitize(respStr string, trimLine, normalizeSpaces bool) str | |
| respStr = httputilz.NormalizeSpaces(respStr) | ||
| } | ||
| return respStr | ||
| // getCustomCheckRetry returns a custom CheckRetry function that respects DisableHTTPFallback | ||
| func getCustomCheckRetry(opts *Options) retryablehttp.CheckRetryFunc { | ||
| return func(ctx context.Context, resp *http.Response, err error) (bool, error) { | ||
| if opts.Protocol == "http11" && opts.DisableHTTPFallback { | ||
| if isHTTP2FallbackError(err) { | ||
| return false, err | ||
| } | ||
| } | ||
| return retryablehttp.DefaultRetryPolicy(ctx, resp, err) | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+475
to
486
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Check if file exists and get its size
if [ -f "common/httpx/httpx.go" ]; then
wc -l common/httpx/httpx.go
# Show lines 455-510 with context around the cited lines 475-486
nl -ba common/httpx/httpx.go | sed -n '455,510p'
else
echo "File not found at common/httpx/httpx.go"
find . -name "httpx.go" -type f 2>/dev/null || echo "No httpx.go found"
fiRepository: projectdiscovery/httpx Length of output: 151 🏁 Script executed: #!/bin/bash
set -euo pipefail
# View lines 455-486 using sed for line numbers
sed -n '455,486p' common/httpx/httpx.go | awk '{printf "%4d %s\n", NR+454, $0}'Repository: projectdiscovery/httpx Length of output: 1178 Move Go does not support named function declarations inside other functions. The 🐛 Proposed fix func (httpx *HTTPX) Sanitize(respStr string, trimLine, normalizeSpaces bool) string {
respStr = httpx.htmlPolicy.Sanitize(respStr)
if trimLine {
respStr = strings.ReplaceAll(respStr, "\n", "")
}
if normalizeSpaces {
respStr = httputilz.NormalizeSpaces(respStr)
}
return respStr
- // getCustomCheckRetry returns a custom CheckRetry function that respects DisableHTTPFallback
- func getCustomCheckRetry(opts *Options) retryablehttp.CheckRetryFunc {
- return func(ctx context.Context, resp *http.Response, err error) (bool, error) {
- if opts.Protocol == "http11" && opts.DisableHTTPFallback {
- if isHTTP2FallbackError(err) {
- return false, err
- }
- }
- return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
- }
-}
}
+
+// getCustomCheckRetry returns a custom CheckRetry function that respects DisableHTTPFallback.
+func getCustomCheckRetry(opts *Options) retryablehttp.CheckRetryFunc {
+ return func(ctx context.Context, resp *http.Response, err error) (bool, error) {
+ if opts.Protocol == "http11" && opts.DisableHTTPFallback && isHTTP2FallbackError(err) {
+ return false, err
+ }
+ return retryablehttp.DefaultRetryPolicy(ctx, resp, err)
+ }
+}🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| package httpx | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| func TestDisableHTTPFallbackBlocks(t *testing.T) { | ||
| opts := &Options{ | ||
| Protocol: "http11", | ||
| DisableHTTPFallback: true, | ||
| Timeout: 5 * time.Second, | ||
| RetryMax: 3, | ||
| } | ||
|
|
||
| client, err := NewClient(opts) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create client: %v", err) | ||
| } | ||
|
|
||
| checkRetry := client.NewCheckRetryFunc() | ||
| http2Error := errors.New( | ||
| "net/http: HTTP/1.x transport connection broken: malformed HTTP version \"HTTP/2\"") | ||
|
|
||
| shouldRetry, returnedErr := checkRetry(context.Background(), nil, http2Error) | ||
|
|
||
| if shouldRetry { | ||
| t.Error("Expected shouldRetry=false when DisableHTTPFallback=true") | ||
| } | ||
| if returnedErr == nil { | ||
| t.Error("Expected error to be returned") | ||
| } | ||
| } | ||
|
|
||
| func TestDisableHTTPFallbackAllows(t *testing.T) { | ||
| opts := &Options{ | ||
| Protocol: "http11", | ||
| DisableHTTPFallback: false, | ||
| Timeout: 5 * time.Second, | ||
| RetryMax: 3, | ||
| } | ||
|
|
||
| client, err := NewClient(opts) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create client: %v", err) | ||
| } | ||
|
|
||
| checkRetry := client.NewCheckRetryFunc() | ||
| http2Error := errors.New( | ||
| "net/http: HTTP/1.x transport connection broken: malformed HTTP version \"HTTP/2\"") | ||
|
|
||
| shouldRetry, _ := checkRetry(context.Background(), nil, http2Error) | ||
|
|
||
| if !shouldRetry { | ||
| t.Error("Expected shouldRetry=true when DisableHTTPFallback=false") | ||
| } | ||
| } | ||
|
|
||
| func TestNonHTTP2ErrorsRetry(t *testing.T) { | ||
| opts := &Options{ | ||
| Protocol: "http11", | ||
| DisableHTTPFallback: true, | ||
| Timeout: 5 * time.Second, | ||
| RetryMax: 3, | ||
| } | ||
|
|
||
| client, err := NewClient(opts) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create client: %v", err) | ||
| } | ||
|
|
||
| checkRetry := client.NewCheckRetryFunc() | ||
| timeoutErr := errors.New("context deadline exceeded") | ||
|
|
||
| shouldRetry, _ := checkRetry(context.Background(), nil, timeoutErr) | ||
|
|
||
| if !shouldRetry { | ||
| t.Error("Expected timeout error to still trigger retry") | ||
| } | ||
| } | ||
|
|
||
| func TestIsHTTP2FallbackError(t *testing.T) { | ||
| testCases := []struct { | ||
| name string | ||
| errMsg string | ||
| shouldMatch bool | ||
| }{ | ||
| { | ||
| name: "Malformed HTTP/2 version", | ||
| errMsg: "malformed HTTP version \"HTTP/2\"", | ||
| shouldMatch: true, | ||
| }, | ||
| { | ||
| name: "Malformed HTTP response", | ||
| errMsg: "malformed HTTP response", | ||
| shouldMatch: true, | ||
| }, | ||
| { | ||
| name: "Generic error", | ||
| errMsg: "connection refused", | ||
| shouldMatch: false, | ||
| }, | ||
| { | ||
| name: "Timeout error", | ||
| errMsg: "context deadline exceeded", | ||
| shouldMatch: false, | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| err := errors.New(tc.errMsg) | ||
| result := isHTTP2FallbackError(err) | ||
|
|
||
| if result != tc.shouldMatch { | ||
| t.Errorf("Expected %v, got %v for: %s", tc.shouldMatch, result, tc.errMsg) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||||||||||||||||||
| package httpx | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import ( | ||||||||||||||||||||
| "context" | ||||||||||||||||||||
| "net/http" | ||||||||||||||||||||
| "strings" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| "github.com/hashicorp/go-retryablehttp" | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
Comment on lines
+8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Retry-related imports in common/httpx ==="
rg -n 'go-retryablehttp|retryablehttp-go' common/httpx
echo
echo "=== CheckRetry assignments/usages ==="
rg -n '\bCheckRetry\b|NewCheckRetryFunc|getCustomCheckRetry' common/httpx
echo
echo "=== Client/NewClient symbols in common/httpx ==="
rg -nP 'type\s+Client\s+struct|func\s+NewClient\s*\(' common/httpxRepository: projectdiscovery/httpx Length of output: 1275 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== common/httpx/retry.go (lines 1-35) ==="
cat -n common/httpx/retry.go | head -35
echo
echo "=== common/httpx/httpx.go (lines 180-195) ==="
sed -n '180,195p' common/httpx/httpx.go | cat -nRepository: projectdiscovery/httpx Length of output: 1656 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Checking where NewCheckRetryFunc is used ==="
rg -n 'NewCheckRetryFunc' --type go
echo
echo "=== getCustomCheckRetry implementation (lines 475-500) ==="
sed -n '475,500p' common/httpx/httpx.go | cat -n
echo
echo "=== Checking imports in httpx.go (first 30 lines) ==="
head -30 common/httpx/httpx.go | cat -nRepository: projectdiscovery/httpx Length of output: 2134 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Checking if isHTTP2FallbackError is in retry.go ==="
rg -n 'isHTTP2FallbackError' common/httpx
echo
echo "=== Checking DefaultRetryPolicy calls ==="
rg -n 'DefaultRetryPolicy' common/httpxRepository: projectdiscovery/httpx Length of output: 609 Unify retryablehttp import path to avoid type incompatibility. retry.go imports 🔧 Proposed fix import (
"context"
"net/http"
"strings"
- "github.com/hashicorp/go-retryablehttp"
+ retryablehttp "github.com/projectdiscovery/retryablehttp-go"
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| func isHTTP2FallbackError(err error) bool { | ||||||||||||||||||||
| if err == nil { | ||||||||||||||||||||
| return false | ||||||||||||||||||||
| } | ||||||||||||||||||||
| errorMsg := err.Error() | ||||||||||||||||||||
| return strings.Contains(errorMsg, "malformed HTTP version \"HTTP/2\"") || | ||||||||||||||||||||
| strings.Contains(errorMsg, "malformed HTTP response") | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func (c *Client) NewCheckRetryFunc() retryablehttp.CheckRetryFunc { | ||||||||||||||||||||
| return func(ctx context.Context, resp *http.Response, err error) (bool, error) { | ||||||||||||||||||||
| if c.Options.Protocol == "http11" && c.Options.DisableHTTPFallback { | ||||||||||||||||||||
| if isHTTP2FallbackError(err) { | ||||||||||||||||||||
| return false, err | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return retryablehttp.DefaultRetryPolicy(ctx, resp, err) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix heading nesting in Notes.
The
- ### Disable HTTP/2 Fallbackblock introduces an invalid heading jump and odd list/heading structure. Convert it to a normal bullet (or promote to a proper section heading outside the list).📝 Proposed markdown fix
📝 Committable suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 292-292: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🤖 Prompt for AI Agents