Files
triggerssmith/cmd/serve.go

169 lines
4.7 KiB
Go

package cmd
import (
"fmt"
"log/slog"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"
application "git.oblat.lv/alex/triggerssmith/internal/app"
"git.oblat.lv/alex/triggerssmith/internal/config"
"git.oblat.lv/alex/triggerssmith/internal/vars"
"github.com/spf13/cobra"
)
var optsServeCmd = struct {
ConfigPath *string
Debug *bool
}{}
// simple middleware for request logging
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
slog.Info("HTTP request",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.String("remote", r.RemoteAddr),
)
next.ServeHTTP(w, r)
slog.Debug("HTTP request finished",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.Duration("latency", time.Since(start)),
)
})
}
func writePID(path string) error {
pid := os.Getpid()
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = fmt.Fprintf(f, "%d\n", pid)
return err
}
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the server",
Run: func(cmd *cobra.Command, args []string) {
defer func() {
if r := recover(); r != nil {
slog.Error("Application panicked", slog.Any("error", r))
os.Exit(-1)
}
}()
// configure logger
if *optsServeCmd.Debug {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})))
} else {
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelInfo})))
}
pid := os.Getpid()
slog.Debug("Starting server", slog.Int("pid", pid))
if err := writePID(vars.PID_PATH); err != nil {
panic(err)
}
slog.Debug("created pid file", slog.String("path", vars.PID_PATH))
defer os.Remove(vars.PID_PATH)
// load config
slog.Debug("Reading configuration", slog.String("path", *optsServeCmd.ConfigPath))
cfg, err := config.LoadConfig(*optsServeCmd.ConfigPath)
if err != nil {
slog.Error("Failed to load configuration", slog.String("path", *optsServeCmd.ConfigPath), slog.String("error", err.Error()))
return
}
slog.Debug("Configuration loaded", slog.Any("config", cfg))
// init app
app, err := application.NewApp()
if err != application.ErrNilPointerWarn && err != nil {
slog.Error("Failed to create app instance", slog.String("error", err.Error()))
return
}
app.LoadConfiguration(cfg)
server := app.Server()
mux := http.NewServeMux()
// static files
staticPath := cfg.Server.StaticFilesPath
slog.Debug("Setting up static file server", slog.String("path", staticPath))
fs := http.FileServer(http.Dir(staticPath))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
handler := loggingMiddleware(mux)
server.SetHandler(handler)
server.Init()
var addr = net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))
slog.Debug("Binding listener", slog.String("address", addr))
err = server.Start(addr)
if err != nil {
slog.Error("Failed to start server", slog.String("error", err.Error()))
return
} else {
slog.Info("Server started", slog.String("address", net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))))
}
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
for true {
sig := <-sigch
slog.Debug("got signal", slog.Any("os.Signal", sig))
switch sig {
case syscall.SIGHUP:
if err := config.ReloadConfig(cfg); err != nil {
slog.Error("Failed to reload configuration", slog.String("error", err.Error()))
} else {
slog.Info("Configuration reloaded")
var addr = net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))
slog.Debug("New configuration", slog.Any("config", cfg))
err = server.Reload(addr)
if err != nil {
slog.Error("Failed to restart server with new configuration", slog.String("error", err.Error()))
}
}
case syscall.SIGINT:
slog.Info("Stopping server by SIGINT")
err := server.Stop()
if err != nil {
slog.Error("Failed to stop server", slog.String("err", err.Error()))
os.Exit(1)
}
os.Remove(vars.PID_PATH)
return
case syscall.SIGTERM:
slog.Info("Stopping server by SIGTERM")
err := server.Stop()
if err != nil {
slog.Error("Failed to stop server", slog.String("err", err.Error()))
os.Exit(1)
}
os.Remove(vars.PID_PATH)
return
}
}
},
}
func init() {
optsServeCmd.Debug = serveCmd.Flags().BoolP("debug", "d", false, "Enable debug logs")
optsServeCmd.ConfigPath = serveCmd.Flags().StringP("config", "c", "config.yaml", "Path to configuration file")
rootCmd.AddCommand(serveCmd)
}