blob: fa6e3dee979dbf1768b26473f885528450ac142e [file]
// 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"
"net/http"
"slices"
"testing"
"time"
_ "unsafe" // for linkname
"golang.org/x/net/internal/http3"
"golang.org/x/net/internal/testcert"
)
//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(),
}
if err := http3.RegisterServer(srv, http3.ServerOpts{}); err != nil {
t.Skipf("cannot register server: %v", err)
}
srv.Protocols = &http.Protocols{}
protocolSetHTTP3(srv.Protocols)
// We do not yet have a public API for serving on a system-chosen port
// that lets us find out what that port is. (ListenAndServeTLS will listen on
// a system-chosen port, but we can't find out what port it picked.)
// So use ServeTLS with an pro tem mechanism for passing in a PacketConn.
nc, err := net.ListenPacket("udp", srv.Addr)
if err != nil {
t.Fatal(err)
}
defer nc.Close()
go srv.ServeTLS(http3ServeConn{nc}, "", "")
tr := &http.Transport{TLSClientConfig: newTestTLSConfig()}
tr.Protocols = &http.Protocols{}
protocolSetHTTP3(tr.Protocols)
if err := http3.RegisterTransport(tr, http3.TransportOpts{}); err != nil {
// If RegisterServer above succeeded, this should as well.
t.Fatalf("cannot register transport: %v", err)
}
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,
}
for range 5 {
req, err := http.NewRequest("GET", "https://"+nc.LocalAddr().String(), 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)
}
}
type http3ServeConn struct {
conn net.PacketConn
}
func (c http3ServeConn) Accept() (net.Conn, error) { return nil, net.ErrClosed }
func (c http3ServeConn) Close() error { return nil }
func (c http3ServeConn) Addr() net.Addr { return nil }
func (c http3ServeConn) HTTP3PacketConn() net.PacketConn {
return c.conn
}