package awgserver
import (
"crypto/tls"
"embed"
"fmt"
"io/fs"
"log/slog"
"net/http"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"golang.org/x/crypto/acme/autocert"
)
//go:embed static/*
var staticFiles embed.FS
// startH3Server starts the HTTP/3 server using the FilteredConn from MuxBind.
// It handles Let's Encrypt cert issuance and serves embedded static files.
func startH3Server(fc *FilteredConn, cfg *Config, logger *slog.Logger) (*http3.Server, error) {
certManager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(cfg.Domain),
Cache: autocert.DirCache(cfg.CertCache),
}
if cfg.ACMEHTTPPort > 0 {
go func() {
addr := fmt.Sprintf(":%d", cfg.ACMEHTTPPort)
logger.Info("ACME HTTP-01 challenge listener started.", "address", addr)
if err := http.ListenAndServe(addr, certManager.HTTPHandler(nil)); err != nil {
logger.Error("ACME HTTP listener failed.", "err", err)
}
}()
}
staticFS, err := fs.Sub(staticFiles, "static")
if err != nil {
return nil, fmt.Errorf("static files: %w", err)
}
handler := http.FileServer(http.FS(staticFS))
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Alt-Svc", fmt.Sprintf(`h3=":%d"; ma=86400`, cfg.ListenPort))
handler.ServeHTTP(w, r)
})
transport := &quic.Transport{Conn: fc}
h3srv := &http3.Server{
Handler: mux,
TLSConfig: &tls.Config{
GetCertificate: certManager.TLSConfig().GetCertificate,
NextProtos: []string{"h3"},
},
}
ln, err := transport.ListenEarly(h3srv.TLSConfig, &quic.Config{})
if err != nil {
return nil, fmt.Errorf("QUIC listen: %w", err)
}
logger.Info("HTTP/3 server started.", "domain", cfg.Domain, "port", cfg.ListenPort)
go func() {
if err := h3srv.ServeListener(ln); err != nil {
logger.Error("HTTP/3 server failed.", "err", err)
}
}()
return h3srv, nil
}