179 lines
3.8 KiB
Go
179 lines
3.8 KiB
Go
|
package server
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"os/signal"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
|
||
|
"git.eeqj.de/sneak/gohttpserver/internal/globals"
|
||
|
"github.com/docker/docker/daemon/logger"
|
||
|
"github.com/rs/zerolog"
|
||
|
"github.com/rs/zerolog/log"
|
||
|
"github.com/spf13/viper"
|
||
|
"go.uber.org/fx"
|
||
|
"honnef.co/go/tools/config"
|
||
|
|
||
|
"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())
|
||
|
// }
|
||
|
}
|