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())
|
|
|
|
// }
|
|
|
|
}
|