handle http2 GOAWAY

Handle http2 GOAWAY error
This commit is contained in:
Ganey 2025-06-22 21:53:40 +01:00
parent f4bea397ab
commit ee02a8bf27
2 changed files with 75 additions and 14 deletions

View File

@ -213,16 +213,6 @@ func (p *Proxy) proxyHTTPRequest(
roundTripReq.Header.Set("Connection", "keep-alive") roundTripReq.Header.Set("Connection", "keep-alive")
} }
// Handle GOAWAY frame to correctly retry a request
if roundTripReq.Body != nil {
roundTripReq.GetBody = func() (io.ReadCloser, err error) {
if err.Error() == "http2: Transport received Server's graceful shutdown GOAWAY" {
return roundTripReq.Body, nil
}
return nil, err
}
}
// Set the User-Agent as an empty string if not provided to avoid inserting golang default UA // Set the User-Agent as an empty string if not provided to avoid inserting golang default UA
if roundTripReq.Header.Get("User-Agent") == "" { if roundTripReq.Header.Get("User-Agent") == "" {
roundTripReq.Header.Set("User-Agent", "") roundTripReq.Header.Set("User-Agent", "")
@ -230,6 +220,17 @@ func (p *Proxy) proxyHTTPRequest(
_, ttfbSpan := tr.Tracer().Start(tr.Context(), "ttfb_origin") _, ttfbSpan := tr.Tracer().Start(tr.Context(), "ttfb_origin")
resp, err := httpService.RoundTrip(roundTripReq) resp, err := httpService.RoundTrip(roundTripReq)
if err != nil {
// Check for GOAWAY error and retry once if applicable
const goawayMsg = "http2: Transport received Server's graceful shutdown GOAWAY"
if err.Error() == goawayMsg && roundTripReq.GetBody != nil {
// Reset the body for retry
newBody, getBodyErr := roundTripReq.GetBody()
if getBodyErr == nil {
roundTripReq.Body = newBody
resp, err = httpService.RoundTrip(roundTripReq)
}
}
if err != nil { if err != nil {
tracing.EndWithErrorStatus(ttfbSpan, err) tracing.EndWithErrorStatus(ttfbSpan, err)
if err := roundTripReq.Context().Err(); err != nil { if err := roundTripReq.Context().Err(); err != nil {
@ -237,6 +238,7 @@ func (p *Proxy) proxyHTTPRequest(
} }
return errors.Wrap(err, "Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared") return errors.Wrap(err, "Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared")
} }
}
tracing.EndWithStatusCode(ttfbSpan, resp.StatusCode) tracing.EndWithStatusCode(ttfbSpan, resp.StatusCode)
defer resp.Body.Close() defer resp.Body.Close()

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -1018,3 +1019,61 @@ func runEchoWSService(t *testing.T, l net.Listener) {
} }
}() }()
} }
func TestHandleGOAWAYRetry(t *testing.T) {
// Simulate a request body
bodyContent := "test body content"
body := io.NopCloser(strings.NewReader(bodyContent))
// Create a mock request with a body
roundTripReq := &http.Request{
Body: body,
}
// Simulate the GOAWAY error
goawayError := errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
// Assign the GetBody function
roundTripReq.GetBody = func() (io.ReadCloser, error) {
if goawayError.Error() == "http2: Transport received Server's graceful shutdown GOAWAY" {
return roundTripReq.Body, nil
}
return nil, goawayError
}
// Test the GetBody function
retriedBody, err := roundTripReq.GetBody()
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}
// Verify the retried body content
retriedContent, _ := io.ReadAll(retriedBody)
if string(retriedContent) != bodyContent {
t.Fatalf("Expected body content '%s', got '%s'", bodyContent, string(retriedContent))
}
}
func TestHandleGOAWAYRetryError(t *testing.T) {
// Simulate a request body
bodyContent := "test body content"
body := io.NopCloser(strings.NewReader(bodyContent))
// Create a mock request with a body
roundTripReq := &http.Request{
Body: body,
}
// Simulate the GOAWAY error
goawayError := errors.New("http2: Transport received Server's graceful shutdown GOAWAY")
// Assign the GetBody function to return an error
roundTripReq.GetBody = func() (io.ReadCloser, error) {
return nil, goawayError
}
// Test the GetBody function
retriedBody, err := roundTripReq.GetBody()
if retriedBody != nil || err == nil {
t.Fatalf("Expected error, got body: %v, error: %v", retriedBody, err)
}
}