Skip to content

Instantly share code, notes, and snippets.

@rickcrawford
Created August 30, 2019 15:32
Show Gist options
  • Save rickcrawford/ae35a1412dfd5ad06133c502ab355710 to your computer and use it in GitHub Desktop.
Save rickcrawford/ae35a1412dfd5ad06133c502ab355710 to your computer and use it in GitHub Desktop.
HTTP3 server example.
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"os"
"sync"
"time"
"github.com/lucas-clemente/quic-go/http3"
)
// Server is the HTTP server implementation.
type Server struct {
Server *http.Server
quicServer *http3.Server
listener net.Listener
lock sync.Mutex
}
// Listen creates an active listener for s that can be
// used to serve requests.
func (s *Server) Listen() (net.Listener, error) {
if s.Server == nil {
return nil, fmt.Errorf("Server field is nil")
}
ln, err := net.Listen("tcp", s.Server.Addr)
if tcpLn, ok := ln.(*net.TCPListener); ok {
ln = tcpKeepAliveListener{TCPListener: tcpLn}
}
return tls.NewListener(ln, s.Server.TLSConfig), err
}
// ListenPacket creates udp connection for QUIC if it is enabled,
func (s *Server) ListenPacket() (net.PacketConn, error) {
udpAddr, err := net.ResolveUDPAddr("udp", s.Server.Addr)
if err != nil {
return nil, err
}
return net.ListenUDP("udp", udpAddr)
}
// Serve serves requests on ln. It blocks until ln is closed.
func (s *Server) Serve(ln net.Listener) error {
s.lock.Lock()
s.listener = ln
s.lock.Unlock()
err := s.Server.Serve(ln)
if err == http.ErrServerClosed {
err = nil
}
if s.quicServer != nil {
s.quicServer.Close()
}
return err
}
// ServePacket serves QUIC requests on pc until it is closed.
func (s *Server) ServePacket(pc net.PacketConn) error {
if s.quicServer != nil {
err := s.quicServer.Serve(pc.(*net.UDPConn))
return fmt.Errorf("serving QUIC connections: %v", err)
}
return nil
}
// ServeHTTP is the entry point of all HTTP requests.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "test")
}
func (s *Server) wrapWithSvcHeaders(previousHandler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.quicServer.SetQuicHeaders(w.Header())
previousHandler.ServeHTTP(w, r)
}
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
// Accept accepts the connection with a keep-alive enabled.
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
func (ln tcpKeepAliveListener) File() (*os.File, error) {
return ln.TCPListener.File()
}
func NewServer(tlsConfig *tls.Config, addr string) *Server {
s := &Server{
Server: &http.Server{
Addr: addr,
TLSConfig: tlsConfig,
},
}
s.quicServer = &http3.Server{Server: s.Server}
s.Server.Handler = s.wrapWithSvcHeaders(s)
// s.Server.Handler = s
return s
}
func main() {
cer, err := tls.LoadX509KeyPair("cert.pem", "priv.key")
if err != nil {
log.Println(err)
return
}
config := &tls.Config{
// MinVersion: tls.VersionTLS13,
// CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
// PreferServerCipherSuites: true,
Certificates: []tls.Certificate{cer},
NextProtos: []string{"h3-20", "h2", "http/1.1"},
}
s := NewServer(config, ":8443")
pc, err := s.ListenPacket()
ln, err := s.Listen()
go func() {
s.Serve(ln)
}()
s.ServePacket(pc)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment