Skip to content

[FEATURE] improve HTTP/3 negotiation and fallback behavior #3266

@zbchi

Description

@zbchi

⚠️ Verification

🎯 Solution Description

Problem

In cases where HTTP/3 should behave like an optional enhancement, the current implementation can still fail hard or keep retrying a broken HTTP/3 path instead of gracefully staying on HTTP/2.

Current state

now fall back to http2 use the same req.body(but req.body is a pipe)

  func (dt *dualTransport) RoundTrip(req *http.Request) (*http.Response, error) {
      if cachedAltSvc.Protocol == "h3" {
          resp, err := dt.http3Transport.RoundTrip(req)
          if err == nil {
              return resp, nil
          }
          // fallback to http2 with the same req
      }
      return dt.http2Transport.RoundTrip(req)
  }

once the http3 transport starts reading from req.Body, part or all of the body may already have been consumed.
if http3 then fails and the code falls back to http2, http2 will reuse the same req.Body, which may now contain only the remaining data or may no longer be replayable at all.

what's more:

1.once the client caches an Alt-Svc entry for h3, it effectively does one thing: try HTTP/3 first on the next request.
2.If that attempt fails, it falls back to HTTP/2 for that request only.
3.It does not currently mark that HTTP/3 path unhealthy, evict the cached alternative, or apply any cooldown before trying it again.
4.As long as the cached entry is still valid, the next request can take the same HTTP/3-first path again.
5.If the HTTP/2 response keeps advertising the same Alt-Svc, that state gets refreshed, so a broken
QUIC path can keep being retried instead of being backed off explicitly.

Alt-Svc handling:

The parser can record alternative host and port, but the client still sends requests to the original authority, so the advertised alternative endpoints are not actually used.
Staleness handling is also incomplete: expired entries are ignored during lookup, but withdrawn alternatives are not explicitly cleaned up, and ma=0 is not treated as immediate invalidation.

now if http3 fail,use the same http.Request

📋 Use Cases

Fall back smoothly to HTTP/2 when HTTP/3 transport fails.
Apply explicit backoff and cooldown to unhealthy HTTP/3 paths to avoid immediate retries.
Make the alternative service addresses declared by Alt-Svc actually work.
The server only sends Alt-Svc when HTTP/3 is actually available.
Improve test coverage for the complete HTTP/3 upgrade and fallback flow.

⚖️ Complexity & Risks

No response

🔗 External Dependencies

No response

📘 Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.3.2version 3.3.2enhancementNew feature or request
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions