finger/internal/server/server.go
Gustavo Maronato de3da93523
All checks were successful
Go / checks (push) Successful in 58s
refactor so external servers can use Finger as a handler
2023-09-20 15:13:10 -03:00

99 lines
2.8 KiB
Go

package server
import (
"context"
"fmt"
"log/slog"
"net"
"net/http"
"time"
"git.maronato.dev/maronato/finger/handler"
"git.maronato.dev/maronato/finger/internal/config"
"git.maronato.dev/maronato/finger/internal/log"
"git.maronato.dev/maronato/finger/internal/middleware"
"git.maronato.dev/maronato/finger/webfingers"
"golang.org/x/sync/errgroup"
)
const (
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
ReadTimeout = 5 * time.Second
// WriteTimeout is the maximum duration before timing out
// writes of the response.
WriteTimeout = 10 * time.Second
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled.
IdleTimeout = 30 * time.Second
// ReadHeaderTimeout is the amount of time allowed to read
// request headers.
ReadHeaderTimeout = 2 * time.Second
// RequestTimeout is the maximum duration for the entire
// request.
RequestTimeout = 7 * 24 * time.Hour
)
func StartServer(ctx context.Context, cfg *config.Config, fingers webfingers.WebFingers) error {
l := log.FromContext(ctx)
// Create the server mux
mux := http.NewServeMux()
mux.Handle("/.well-known/webfinger", handler.WebfingerHandler(fingers))
mux.Handle("/healthz", HealthCheckHandler(cfg))
// Create a new server
srv := &http.Server{
Addr: cfg.GetAddr(),
Handler: middleware.RequestLogger(
middleware.Recoverer(
http.TimeoutHandler(mux, RequestTimeout, "request timed out"),
),
),
ReadHeaderTimeout: ReadHeaderTimeout,
ReadTimeout: ReadTimeout,
WriteTimeout: WriteTimeout,
IdleTimeout: IdleTimeout,
}
// Create the errorgroup that will manage the server execution
eg, egCtx := errgroup.WithContext(ctx)
// Start the server
eg.Go(func() error {
l.Info("Starting server", slog.String("addr", srv.Addr))
// Use the global context for the server
srv.BaseContext = func(_ net.Listener) context.Context {
return egCtx
}
return srv.ListenAndServe() //nolint:wrapcheck // We wrap the error in the errgroup
})
// Gracefully shutdown the server when the context is done
eg.Go(func() error {
// Wait for the context to be done
<-egCtx.Done()
l.Info("Shutting down server")
// Disable the cancel since we don't wan't to force
// the server to shutdown if the context is canceled.
noCancelCtx := context.WithoutCancel(egCtx)
return srv.Shutdown(noCancelCtx) //nolint:wrapcheck // We wrap the error in the errgroup
})
// Log when the server is fully shutdown
srv.RegisterOnShutdown(func() {
l.Info("Server shutdown complete")
})
// Wait for the server to exit and check for errors that
// are not caused by the context being canceled.
if err := eg.Wait(); err != nil && ctx.Err() == nil {
return fmt.Errorf("server exited with error: %w", err)
}
return nil
}