http3: only retry requests for which it is safe to do so (#5141)

* add an integration test for HTTP/3 redialing logic

* http3: only retry requests for which it is safe to do so
This commit is contained in:
Marten Seemann
2025-05-16 11:22:13 +08:00
committed by GitHub
parent d7ca54c602
commit 77c401dcbb
5 changed files with 317 additions and 96 deletions

View File

@@ -2,6 +2,7 @@ package self_test
import (
"context"
"crypto/tls"
"fmt"
"io"
"net"
@@ -142,7 +143,16 @@ func TestGracefulShutdownPendingStreams(t *testing.T) {
})
var server *http3.Server
port := startHTTPServer(t, mux, func(s *http3.Server) { server = s })
client := newHTTP3Client(t)
connChan := make(chan quic.EarlyConnection, 1)
tr := &http3.Transport{
TLSClientConfig: getTLSClientConfigWithoutServerName(),
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
conn, err := quic.DialAddrEarly(ctx, addr, tlsCfg, cfg)
connChan <- conn
return conn, err
},
}
cl := &http.Client{Transport: tr}
proxy := quicproxy.Proxy{
Conn: newUDPConnLocalhost(t),
@@ -159,7 +169,7 @@ func TestGracefulShutdownPendingStreams(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/helloworld", proxyPort), nil)
require.NoError(t, err)
go func() {
resp, err := client.Do(req)
resp, err := cl.Do(req)
if err != nil {
errChan <- err
return
@@ -180,14 +190,27 @@ func TestGracefulShutdownPendingStreams(t *testing.T) {
go func() { shutdownChan <- server.Shutdown(ctx) }()
time.Sleep(rtt / 2) // wait for the server to start shutting down
var conn quic.EarlyConnection
select {
case conn = <-connChan:
case <-time.After(time.Second):
t.Fatal("connection was not opened")
}
// make sure that the server rejects further requests
for range 3 {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:%d/helloworld", proxyPort), nil)
str, err := conn.OpenStreamSync(ctx)
require.NoError(t, err)
_, err = client.Do(req)
var h3err *http3.Error
require.ErrorAs(t, err, &h3err)
require.Equal(t, http3.ErrCodeRequestRejected, h3err.ErrorCode)
str.Write([]byte("foobar"))
select {
case <-str.Context().Done():
case <-time.After(time.Second):
t.Fatal("stream was not rejected")
}
_, err = str.Read(make([]byte, 10))
var serr *quic.StreamError
require.ErrorAs(t, err, &serr)
require.Equal(t, quic.StreamErrorCode(http3.ErrCodeRequestRejected), serr.ErrorCode)
}
cancel()