gohttpserver/internal/server/server.go

179 lines
3.8 KiB
Go
Raw Normal View History

2022-11-28 03:59:20 +00:00
package server
import (
"context"
"fmt"
2022-11-28 04:09:23 +00:00
"log"
2022-11-28 03:59:20 +00:00
"net/http"
"os"
"os/signal"
"syscall"
"time"
2022-11-28 04:09:23 +00:00
"git.eeqj.de/sneak/gohttpserver/internal/config"
2022-11-28 03:59:20 +00:00
"git.eeqj.de/sneak/gohttpserver/internal/globals"
2022-11-28 04:09:23 +00:00
"git.eeqj.de/sneak/gohttpserver/internal/logger"
2022-11-28 03:59:20 +00:00
"github.com/rs/zerolog"
"github.com/spf13/viper"
"go.uber.org/fx"
"github.com/getsentry/sentry-go"
"github.com/go-chi/chi"
// spooky action at a distance!
// this populates the environment
// from a ./.env file automatically
// for development configuration.
// .env contents should be things like
// `DBURL=postgres://user:pass@.../`
// (without the backticks, of course)
_ "github.com/joho/godotenv/autoload"
)
type ServerParams struct {
fx.In
Logger logger.Logger
Globals globals.Globals
Config config.Config
}
type Server struct {
appname string
version string
buildarch string
startupTime time.Time
port int
exitCode int
sentryEnabled bool
log *zerolog.Logger
ctx context.Context
cancelFunc context.CancelFunc
httpServer *http.Server
router *chi.Mux
params ServerParams
}
func New(lc fx.Lifecycle, params ServerParams) (*Server, error) {
s := new(Server)
s.params = params
s.log = params.Logger.Get()
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
s.startupTime = time.Now()
s.version = params.Globals.Version
s.Run()
},
OnStop: func(ctx context.Context) error {
// FIXME do server shutdown here
},
})
return s, nil
}
// FIXME change this to use uber/fx DI and an Invoke()
// this is where we come in from package main.
func (s *Server) Run() {
// this does nothing if SENTRY_DSN is unset in env.
// TODO remove:
if s.sentryEnabled {
sentry.CaptureMessage("It works!")
}
s.configure()
// logging before sentry, because sentry logs
s.enableSentry()
return s.serve()
}
func (s *Server) enableSentry() {
s.sentryEnabled = false
if s.Config.SentryDSN == "" {
return
}
err := sentry.Init(sentry.ClientOptions{
Dsn: viper.GetString("SENTRY_DSN"),
Release: fmt.Sprintf("%s-%s", s.params.Globals.Appname, s.params.Globals.Version),
})
if err != nil {
log.Fatal().Err(err).Msg("sentry init failure")
return
}
s.log.Info().Msg("sentry error reporting activated")
s.sentryEnabled = true
}
func (s *Server) serve() int {
// FIXME fx will handle this for us
s.ctx, s.cancelFunc = context.WithCancel(context.Background())
// signal watcher
go func() {
c := make(chan os.Signal, 1)
signal.Ignore(syscall.SIGPIPE)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
// block and wait for signal
sig := <-c
s.log.Info().Msgf("signal received: %+v", sig)
if s.cancelFunc != nil {
// cancelling the main context will trigger a clean
// shutdown.
s.cancelFunc()
}
}()
go s.serveUntilShutdown()
for range s.ctx.Done() {
// aforementioned clean shutdown upon main context
// cancellation
}
s.cleanShutdown()
return s.exitCode
}
func (s *Server) cleanupForExit() {
s.log.Info().Msg("cleaning up")
// FIXME unimplemented
// close database connections or whatever
}
func (s *Server) cleanShutdown() {
// initiate clean shutdown
s.exitCode = 0
ctxShutdown, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
if err := s.httpServer.Shutdown(ctxShutdown); err != nil {
s.log.Error().
Err(err).
Msg("server clean shutdown failed")
}
if shutdownCancel != nil {
shutdownCancel()
}
s.cleanupForExit()
if s.sentryEnabled {
sentry.Flush(2 * time.Second)
}
}
func (s *Server) uptime() time.Duration {
return time.Since(s.startupTime)
}
func (s *Server) maintenance() bool {
return s.params.Config.MaintenanceMode
}
func (s *Server) configure() {
// FIXME move most of this to dedicated places
// if viper.GetBool("DEBUG") {
// pp.Print(viper.AllSettings())
// }
}