122 lines
2.4 KiB
Go
122 lines
2.4 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
type Config struct {
|
|
Addr string
|
|
TLSKeyFile string
|
|
TLSCertFile string
|
|
}
|
|
type Server struct {
|
|
srv *http.Server
|
|
Log *slog.Logger
|
|
Router chi.Router
|
|
|
|
readTimeout time.Duration
|
|
writeTimeout time.Duration
|
|
idleTimeout time.Duration
|
|
shutdownTimeout time.Duration
|
|
}
|
|
|
|
func New(opts ...Option) *Server {
|
|
s := &Server{
|
|
Log: slog.New(slog.NewJSONHandler(os.Stdout, nil)),
|
|
Router: chi.NewRouter(),
|
|
shutdownTimeout: 10 * time.Second,
|
|
readTimeout: 5 * time.Second,
|
|
writeTimeout: 10 * time.Second,
|
|
idleTimeout: 30 * time.Second,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(s)
|
|
}
|
|
|
|
s.Router.Use(
|
|
MiddlewareRecovery(s.Log),
|
|
MiddlewareRequestID(),
|
|
MiddlewareLogging(s.Log),
|
|
)
|
|
|
|
s.Router.Get("/healthz", HealthHandler)
|
|
s.Router.Get("/readyz", ReadinessHandler)
|
|
|
|
s.srv = &http.Server{
|
|
Addr: getAddr(),
|
|
Handler: s.Router,
|
|
ReadTimeout: s.readTimeout,
|
|
WriteTimeout: s.writeTimeout,
|
|
IdleTimeout: s.idleTimeout,
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func getAddr() string {
|
|
addr := os.Getenv("APP_SERVER_ADDR")
|
|
if addr == "" {
|
|
addr = ":8080"
|
|
}
|
|
return addr
|
|
}
|
|
|
|
func getTLSKey() string { return os.Getenv("APP_SERVER_TLS_KEY_FILE") }
|
|
func getTLSCert() string { return os.Getenv("APP_SERVER_TLS_CERT_FILE") }
|
|
|
|
func isTLSEnabled() bool {
|
|
return getTLSKey() != "" && getTLSCert() != ""
|
|
}
|
|
|
|
func (s *Server) Start() error {
|
|
s.Log.Info("starting server", "addr", s.srv.Addr)
|
|
|
|
go func() {
|
|
var err error
|
|
if isTLSEnabled() {
|
|
err = s.srv.ListenAndServeTLS(getTLSCert(), getTLSKey())
|
|
} else {
|
|
err = s.srv.ListenAndServe()
|
|
}
|
|
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
s.Log.Error("server failed to start", "error", err)
|
|
}
|
|
}()
|
|
|
|
return s.waitForShutdown()
|
|
}
|
|
|
|
func (s *Server) LoggerWithContext(ctx context.Context) *slog.Logger {
|
|
return LoggerFromContext(ctx)
|
|
}
|
|
|
|
func (s *Server) waitForShutdown() error {
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
|
<-quit
|
|
|
|
s.Log.Info("shutting down server")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
|
|
defer cancel()
|
|
|
|
if err := s.srv.Shutdown(ctx); err != nil {
|
|
s.Log.Error("server failed to shutdown", "error", err)
|
|
return err
|
|
}
|
|
|
|
s.Log.Info("server shut down successfully")
|
|
return nil
|
|
}
|