1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
|
package self_test
import (
"io"
"net"
"net/http"
"strconv"
"testing"
"time"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/stretchr/testify/require"
)
func TestHTTP3ServerHotswap(t *testing.T) {
mux1 := http.NewServeMux()
mux1.HandleFunc("/hello1", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, World 1!\n") // don't check the error here. Stream may be reset.
})
mux2 := http.NewServeMux()
mux2.HandleFunc("/hello2", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hello, World 2!\n") // don't check the error here. Stream may be reset.
})
server1 := &http3.Server{
Handler: mux1,
QUICConfig: getQuicConfig(nil),
}
server2 := &http3.Server{
Handler: mux2,
QUICConfig: getQuicConfig(nil),
}
tlsConf := http3.ConfigureTLSConfig(getTLSConfig())
ln, err := quic.ListenEarly(newUDPConnLocalhost(t), tlsConf, getQuicConfig(nil))
require.NoError(t, err)
port := strconv.Itoa(ln.Addr().(*net.UDPAddr).Port)
rt := &http3.Transport{
TLSClientConfig: getTLSClientConfig(),
DisableCompression: true,
QUICConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}),
}
client := &http.Client{Transport: rt}
defer func() {
require.NoError(t, rt.Close())
require.NoError(t, ln.Close())
}()
// open first server and make single request to it
errChan1 := make(chan error, 1)
go func() { errChan1 <- server1.ServeListener(ln) }()
resp, err := client.Get("https://localhost:" + port + "/hello1")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Hello, World 1!\n", string(body))
// open second server with same underlying listener
errChan2 := make(chan error, 1)
go func() { errChan2 <- server2.ServeListener(ln) }()
time.Sleep(scaleDuration(20 * time.Millisecond))
select {
case err := <-errChan1:
t.Fatalf("server1 stopped unexpectedly: %v", err)
case err := <-errChan2:
t.Fatalf("server2 stopped unexpectedly: %v", err)
default:
}
// now close first server
require.NoError(t, server1.Close())
select {
case err := <-errChan1:
require.ErrorIs(t, err, http.ErrServerClosed)
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for server1 to stop")
}
require.NoError(t, client.Transport.(*http3.Transport).Close())
// verify that new connections are handled by the second server now
resp, err = client.Get("https://localhost:" + port + "/hello2")
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
body, err = io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, "Hello, World 2!\n", string(body))
// close the other server
require.NoError(t, server2.Close())
select {
case err := <-errChan2:
require.ErrorIs(t, err, http.ErrServerClosed)
case <-time.After(time.Second):
t.Fatal("timed out waiting for server2 to stop")
}
}
|