radiance/cmd/solrays/app.go

151 lines
4.1 KiB
Go

package main
import (
"context"
"flag"
"net"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"path"
"syscall"
"time"
"github.com/coreos/go-systemd/v22/activation"
"github.com/prometheus/client_golang/prometheus/promhttp"
metrics "github.com/slok/go-http-metrics/metrics/prometheus"
"github.com/slok/go-http-metrics/middleware"
"github.com/slok/go-http-metrics/middleware/std"
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
"k8s.io/klog/v2"
"go.firedancer.io/radiance/pkg/util"
)
var (
backendAddr = flag.String("backend", "http://127.0.0.1:8899", "Backend RPC URI to proxy to")
debugAddr = flag.String("listen", "[::1]:6060", "pprof and metrics server address")
tlsHostname = flag.String("tlsHostname", "", "When set, serve TLS using Let's Encrypt using the hostname in question")
tlsProd = flag.Bool("tlsProd", false, "Use the production Let's Encrypt environment")
cacheDir = flag.String("cacheDir", "solrays-data", "Cache directory")
)
func init() {
klog.CopyStandardLogTo("INFO")
klog.InitFlags(nil)
flag.Parse()
}
func getSDListeners() []net.Listener {
// We use systemd socket activation for (almost) zero downtime deployment -
// systemd will keep the socket open even while we restart the process
// (plus, it allows us to bind to port 80).
//
// Read more: https://vincent.bernat.ch/en/blog/2018-systemd-golang-socket-activation
listeners, err := activation.Listeners()
if err != nil {
klog.Fatalf("cannot retrieve listeners: %s", err)
}
if len(listeners) != 1 {
klog.Fatalf("unexpected number of sockets passed by systemd (%d != 1)", len(listeners))
}
return listeners
}
func shutdownHandler(server *http.Server) chan struct{} {
done := make(chan struct{})
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-quit
klog.Info("server is shutting down")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
klog.Exitf("cannot gracefully shut down the server: %s", err)
}
close(done)
}()
return done
}
func main() {
listeners := getSDListeners()
// Metrics recording middleware
mdlw := middleware.New(middleware.Config{
Recorder: metrics.NewRecorder(metrics.Config{}),
})
mux := http.NewServeMux()
mux.Handle("/", newHandler())
mux.HandleFunc("/health", func(w http.ResponseWriter, req *http.Request) {
klog.V(1).Infof("[%s] %s %s %v", req.RemoteAddr, req.Method, req.URL, req.Header)
w.Write([]byte("ok"))
})
wrapped := std.Handler("default", mdlw, mux)
server := &http.Server{
Handler: wrapped,
ReadTimeout: readTimeout,
WriteTimeout: requestTimeout,
}
// Setup TLS if an hostname has been specified.
if *tlsHostname != "" {
// Proceed only if a valid hostname has been specified.
if util.IsValidHostname(*tlsHostname) {
klog.Fatalf("tlsHostname [%s] is an invalid hostname, exiting", *tlsHostname)
}
klog.Infof("provisioning Let's Encrypt certificate for %s", *tlsHostname)
var acmeApi string
if *tlsProd {
klog.Infof("using production Let's Encrypt server")
acmeApi = autocert.DefaultACMEDirectory
} else {
klog.Infof("using staging Let's Encrypt server")
acmeApi = "https://acme-staging-v02.api.letsencrypt.org/directory"
}
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(*tlsHostname),
Cache: autocert.DirCache(path.Join(*cacheDir, "autocert")),
Client: &acme.Client{DirectoryURL: acmeApi},
}
server.TLSConfig = certManager.TLSConfig()
klog.Info("certificate provisioning configured")
}
// Graceful shutdown
done := shutdownHandler(server)
go func() {
http.Handle("/metrics", promhttp.Handler())
klog.Infof("debug server listening on %s", *debugAddr)
klog.Exit(http.ListenAndServe(*debugAddr, nil))
}()
if *tlsHostname != "" {
klog.Infof("main server listening with TLS on %s", listeners[0].Addr())
klog.Exit(server.ServeTLS(listeners[0], "", ""))
} else {
klog.Infof("main server listening on %s", listeners[0].Addr())
klog.Exit(server.Serve(listeners[0]))
}
<-done
}