blob: 5bfd0afbb246787644b8bd9b2d1887e2e0b4dea9 [file] [log] [blame] [edit]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.27
package http3_test
import (
"context"
"crypto/tls"
"io"
"net/http"
"slices"
"testing"
"time"
_ "unsafe" // for linkname
"golang.org/x/net/internal/http3"
"golang.org/x/net/internal/testcert"
"golang.org/x/net/quic"
)
//go:linkname protocolSetHTTP3
func protocolSetHTTP3(p *http.Protocols)
func newTestTLSConfig() *tls.Config {
testCert := func() tls.Certificate {
cert, err := tls.X509KeyPair(testcert.LocalhostCert, testcert.LocalhostKey)
if err != nil {
panic(err)
}
return cert
}()
config := &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{testCert},
}
return config
}
func TestNetHTTPIntegration(t *testing.T) {
body := []byte("some body")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(body)
})
srv := &http.Server{
Addr: "127.0.0.1:0",
Handler: handler,
TLSConfig: newTestTLSConfig(),
}
srv.Protocols = &http.Protocols{}
protocolSetHTTP3(srv.Protocols)
var listenAddr string
listenAddrSet := make(chan any)
http3.RegisterServer(srv, http3.ServerOpts{
ListenQUIC: func(addr string, config *quic.Config) (*quic.Endpoint, error) {
e, err := quic.Listen("udp", addr, config)
listenAddr = e.LocalAddr().String()
listenAddrSet <- struct{}{}
return e, err
},
})
go func() {
if err := srv.ListenAndServeTLS("", ""); err != nil {
panic(err)
}
}()
tr := &http.Transport{TLSClientConfig: newTestTLSConfig()}
tr.Protocols = &http.Protocols{}
protocolSetHTTP3(tr.Protocols)
http3.RegisterTransport(tr)
client := &http.Client{
Transport: tr,
// Be extra generous with the timeout, to account for smaller builders
// that we use for e.g. plan9.
Timeout: 5 * time.Second,
}
<-listenAddrSet
for range 5 {
req, err := http.NewRequest("GET", "https://"+listenAddr, nil)
if err != nil {
t.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if !slices.Equal(b, body) {
t.Errorf("got %v, want %v", string(b), string(body))
}
// TestMain checks that there are no leaked goroutines after tests have
// finished running.
// Over here, we verify that closing the idle connections of a net/http
// Transport will result in HTTP/3 transport closing any UDP sockets
// after there are no longer any open connections.
// We do this in a loop to verify that CloseIdleConnections will not
// prevent transport from creating a new connection should a new dial
// be started.
tr.CloseIdleConnections()
}
// Similarly when a net/http Server shuts down, the HTTP/3 server should
// also follow.
ctx, cancel := context.WithTimeout(t.Context(), 25*time.Millisecond)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
t.Fatal(err)
}
}