Add HTTP3 support (experimental)

Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
This commit is contained in:
Julien Salleyron 2021-01-07 14:48:04 +01:00 committed by GitHub
parent 0509b6fdb9
commit e5a01c7cc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 298 additions and 20 deletions

View file

@ -125,6 +125,8 @@ type TCPEntryPoint struct {
tracker *connectionTracker
httpServer *httpServer
httpsServer *httpServer
http3Server *http3server
}
// NewTCPEntryPoint creates a new TCPEntryPoint.
@ -136,24 +138,29 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T
return nil, fmt.Errorf("error preparing server: %w", err)
}
router := &tcp.Router{}
rt := &tcp.Router{}
httpServer, err := createHTTPServer(ctx, listener, configuration, true)
if err != nil {
return nil, fmt.Errorf("error preparing httpServer: %w", err)
}
router.HTTPForwarder(httpServer.Forwarder)
rt.HTTPForwarder(httpServer.Forwarder)
httpsServer, err := createHTTPServer(ctx, listener, configuration, false)
if err != nil {
return nil, fmt.Errorf("error preparing httpsServer: %w", err)
}
router.HTTPSForwarder(httpsServer.Forwarder)
h3server, err := newHTTP3Server(ctx, configuration, httpsServer)
if err != nil {
return nil, err
}
rt.HTTPSForwarder(httpsServer.Forwarder)
tcpSwitcher := &tcp.HandlerSwitcher{}
tcpSwitcher.Switch(router)
tcpSwitcher.Switch(rt)
return &TCPEntryPoint{
listener: listener,
@ -162,6 +169,7 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint) (*T
tracker: tracker,
httpServer: httpServer,
httpsServer: httpsServer,
http3Server: h3server,
}, nil
}
@ -170,6 +178,10 @@ func (e *TCPEntryPoint) Start(ctx context.Context) {
logger := log.FromContext(ctx)
logger.Debugf("Start TCP Server")
if e.http3Server != nil {
go func() { _ = e.http3Server.Start() }()
}
for {
conn, err := e.listener.Accept()
if err != nil {
@ -230,7 +242,7 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
var wg sync.WaitGroup
shutdownServer := func(server stoppableServer) {
shutdownServer := func(server stoppable) {
defer wg.Done()
err := server.Shutdown(ctx)
if err == nil {
@ -257,6 +269,11 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
if e.httpsServer.Server != nil {
wg.Add(1)
go shutdownServer(e.httpsServer.Server)
if e.http3Server != nil {
wg.Add(1)
go shutdownServer(e.http3Server)
}
}
if e.tracker != nil {
@ -299,6 +316,10 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcp.Router) {
e.httpsServer.Switcher.UpdateHandler(httpsHandler)
e.switcher.Switch(rt)
if e.http3Server != nil {
e.http3Server.Switch(rt)
}
}
// writeCloserWrapper wraps together a connection, and the concrete underlying
@ -463,9 +484,13 @@ func (c *connectionTracker) Close() {
}
}
type stoppableServer interface {
type stoppable interface {
Shutdown(context.Context) error
Close() error
}
type stoppableServer interface {
stoppable
Serve(listener net.Listener) error
}
@ -504,7 +529,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
listener := newHTTPForwarder(ln)
go func() {
err := serverHTTP.Serve(listener)
if err != nil {
if err != nil && !errors.Is(err, http.ErrServerClosed) {
log.FromContext(ctx).Errorf("Error while starting server: %v", err)
}
}()

View file

@ -0,0 +1,87 @@
package server
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"sync"
"time"
"github.com/lucas-clemente/quic-go/http3"
"github.com/traefik/traefik/v2/pkg/config/static"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/tcp"
)
type http3server struct {
*http3.Server
http3conn net.PacketConn
lock sync.RWMutex
getter func(info *tls.ClientHelloInfo) (*tls.Config, error)
}
func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, httpsServer *httpServer) (*http3server, error) {
if !configuration.EnableHTTP3 {
return nil, nil
}
conn, err := net.ListenPacket("udp", configuration.GetAddress())
if err != nil {
return nil, fmt.Errorf("error while starting http3 listener: %w", err)
}
h3 := &http3server{
http3conn: conn,
getter: func(info *tls.ClientHelloInfo) (*tls.Config, error) {
return nil, errors.New("no tls config")
},
}
h3.Server = &http3.Server{
Server: &http.Server{
Addr: configuration.GetAddress(),
Handler: httpsServer.Server.(*http.Server).Handler,
ErrorLog: httpServerLogger,
ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout),
WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout),
IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
TLSConfig: &tls.Config{GetConfigForClient: h3.getGetConfigForClient},
},
}
previousHandler := httpsServer.Server.(*http.Server).Handler
httpsServer.Server.(*http.Server).Handler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
err := h3.Server.SetQuicHeaders(rw.Header())
if err != nil {
log.FromContext(ctx).Errorf("failed to set HTTP3 headers: %v", err)
}
previousHandler.ServeHTTP(rw, req)
})
return h3, nil
}
func (e *http3server) Start() error {
return e.Serve(e.http3conn)
}
func (e *http3server) Switch(rt *tcp.Router) {
e.lock.Lock()
defer e.lock.Unlock()
e.getter = rt.GetTLSGetClientInfo()
}
func (e *http3server) getGetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) {
e.lock.RLock()
defer e.lock.RUnlock()
return e.getter(info)
}