mirror of
https://github.com/refraction-networking/uquic.git
synced 2025-04-02 19:57:35 +03:00
uTLS is not yet bumped to the new version, so this commit breaks the dependencies relationship by getting rid of the local replace.
167 lines
4.7 KiB
Go
167 lines
4.7 KiB
Go
package self_test
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/refraction-networking/uquic"
|
|
"github.com/refraction-networking/uquic/http3"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
)
|
|
|
|
type listenerWrapper struct {
|
|
http3.QUICEarlyListener
|
|
listenerClosed bool
|
|
count int32
|
|
}
|
|
|
|
func (ln *listenerWrapper) Close() error {
|
|
ln.listenerClosed = true
|
|
return ln.QUICEarlyListener.Close()
|
|
}
|
|
|
|
func (ln *listenerWrapper) Faker() *fakeClosingListener {
|
|
atomic.AddInt32(&ln.count, 1)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
return &fakeClosingListener{ln, 0, ctx, cancel}
|
|
}
|
|
|
|
type fakeClosingListener struct {
|
|
*listenerWrapper
|
|
closed int32
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func (ln *fakeClosingListener) Accept(ctx context.Context) (quic.EarlyConnection, error) {
|
|
Expect(ctx).To(Equal(context.Background()))
|
|
return ln.listenerWrapper.Accept(ln.ctx)
|
|
}
|
|
|
|
func (ln *fakeClosingListener) Close() error {
|
|
if atomic.CompareAndSwapInt32(&ln.closed, 0, 1) {
|
|
ln.cancel()
|
|
if atomic.AddInt32(&ln.listenerWrapper.count, -1) == 0 {
|
|
ln.listenerWrapper.Close()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var _ = Describe("HTTP3 Server hotswap test", func() {
|
|
var (
|
|
mux1 *http.ServeMux
|
|
mux2 *http.ServeMux
|
|
client *http.Client
|
|
rt *http3.RoundTripper
|
|
server1 *http3.Server
|
|
server2 *http3.Server
|
|
ln *listenerWrapper
|
|
port string
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
mux1 = http.NewServeMux()
|
|
mux1.HandleFunc("/hello1", func(w http.ResponseWriter, r *http.Request) {
|
|
defer GinkgoRecover()
|
|
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) {
|
|
defer GinkgoRecover()
|
|
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())
|
|
quicln, err := quic.ListenAddrEarly("0.0.0.0:0", tlsConf, getQuicConfig(nil))
|
|
ln = &listenerWrapper{QUICEarlyListener: quicln}
|
|
Expect(err).NotTo(HaveOccurred())
|
|
port = strconv.Itoa(ln.Addr().(*net.UDPAddr).Port)
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(rt.Close()).NotTo(HaveOccurred())
|
|
Expect(ln.Close()).NotTo(HaveOccurred())
|
|
})
|
|
|
|
BeforeEach(func() {
|
|
rt = &http3.RoundTripper{
|
|
TLSClientConfig: getTLSClientConfig(),
|
|
DisableCompression: true,
|
|
QuicConfig: getQuicConfig(&quic.Config{MaxIdleTimeout: 10 * time.Second}),
|
|
}
|
|
client = &http.Client{Transport: rt}
|
|
})
|
|
|
|
It("hotswap works", func() {
|
|
// open first server and make single request to it
|
|
fake1 := ln.Faker()
|
|
stoppedServing1 := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
server1.ServeListener(fake1)
|
|
close(stoppedServing1)
|
|
}()
|
|
|
|
resp, err := client.Get("https://localhost:" + port + "/hello1")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(resp.StatusCode).To(Equal(200))
|
|
body, err := io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(string(body)).To(Equal("Hello, World 1!\n"))
|
|
|
|
// open second server with same underlying listener,
|
|
// make sure it opened and both servers are currently running
|
|
fake2 := ln.Faker()
|
|
stoppedServing2 := make(chan struct{})
|
|
go func() {
|
|
defer GinkgoRecover()
|
|
server2.ServeListener(fake2)
|
|
close(stoppedServing2)
|
|
}()
|
|
|
|
Consistently(stoppedServing1).ShouldNot(BeClosed())
|
|
Consistently(stoppedServing2).ShouldNot(BeClosed())
|
|
|
|
// now close first server, no errors should occur here
|
|
// and only the fake listener should be closed
|
|
Expect(server1.Close()).NotTo(HaveOccurred())
|
|
Eventually(stoppedServing1).Should(BeClosed())
|
|
Expect(fake1.closed).To(Equal(int32(1)))
|
|
Expect(fake2.closed).To(Equal(int32(0)))
|
|
Expect(ln.listenerClosed).ToNot(BeTrue())
|
|
Expect(client.Transport.(*http3.RoundTripper).Close()).NotTo(HaveOccurred())
|
|
|
|
// verify that new connections are being initiated from the second server now
|
|
resp, err = client.Get("https://localhost:" + port + "/hello2")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(resp.StatusCode).To(Equal(200))
|
|
body, err = io.ReadAll(gbytes.TimeoutReader(resp.Body, 3*time.Second))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(string(body)).To(Equal("Hello, World 2!\n"))
|
|
|
|
// close the other server - both the fake and the actual listeners must close now
|
|
Expect(server2.Close()).NotTo(HaveOccurred())
|
|
Eventually(stoppedServing2).Should(BeClosed())
|
|
Expect(fake2.closed).To(Equal(int32(1)))
|
|
Expect(ln.listenerClosed).To(BeTrue())
|
|
})
|
|
})
|