server with basic hot reload
This commit is contained in:
4
Makefile
4
Makefile
@@ -27,9 +27,11 @@ run: build
|
||||
@echo "-- running $(NAME)"
|
||||
@$(BINARY)
|
||||
|
||||
#BUILD_PARAMS = -trimpath
|
||||
|
||||
build:
|
||||
@echo "-- building $(NAME)"
|
||||
@go build -o $(BINARY) $(ENTRY)
|
||||
@go build $(BUILD_PARAMS) -o $(BINARY) $(ENTRY)
|
||||
|
||||
test:
|
||||
@echo "-- testing $(NAME)"
|
||||
|
||||
@@ -14,4 +14,4 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
func Execute() error {
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
}
|
||||
|
||||
123
cmd/serve.go
123
cmd/serve.go
@@ -1,36 +1,149 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
application "git.oblat.lv/alex/triggerssmith/internal/app"
|
||||
"git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var opts = struct {
|
||||
ConfigPath *string
|
||||
Debug *bool
|
||||
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)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
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))
|
||||
// }
|
||||
// }()
|
||||
// configure logger
|
||||
if *opts.Debug {
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(cmd.OutOrStdout(), &slog.HandlerOptions{Level: slog.LevelDebug})))
|
||||
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})))
|
||||
}
|
||||
|
||||
slog.Debug("Starting server")
|
||||
|
||||
// load config
|
||||
slog.Debug("Reading configuration", slog.String("path", *opts.ConfigPath))
|
||||
config, err := config.LoadConfig(*opts.ConfigPath)
|
||||
cfg, err := config.LoadConfig(*opts.ConfigPath)
|
||||
if err != nil {
|
||||
slog.Error("Failed to load configuration", slog.String("path", *opts.ConfigPath), slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
slog.Debug("Configuration loaded", slog.Any("config", config))
|
||||
slog.Info("Server started", slog.Int("port", config.Server.Port), slog.String("address", config.Server.Addr))
|
||||
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)
|
||||
|
||||
/*
|
||||
// setup handlers
|
||||
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)
|
||||
*/
|
||||
|
||||
// start server
|
||||
/*addr := fmt.Sprintf("%s:%d", cfg.Server.Addr, cfg.Server.Port)
|
||||
slog.Debug("Binding listener", slog.String("address", addr))
|
||||
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
slog.Error("Failed to start listener", slog.String("address", addr), slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
slog.Info("Server started", slog.String("address", addr))
|
||||
|
||||
if err := srv.Serve(ln); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("HTTP server stopped with error", slog.String("error", err.Error()))
|
||||
}*/
|
||||
|
||||
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))))
|
||||
}
|
||||
|
||||
for true {
|
||||
fmt.Scanln()
|
||||
if err := config.ReloadConfig(cfg); err != nil {
|
||||
slog.Error("Failed to reload configuration", slog.String("error", err.Error()))
|
||||
} else {
|
||||
slog.Info("Configuration reloaded", slog.Any("config", cfg))
|
||||
var addr = net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))
|
||||
err = server.Reload(addr)
|
||||
if err != nil {
|
||||
slog.Error("Failed to restart server with new configuration", slog.String("error", err.Error()))
|
||||
} else {
|
||||
slog.Info("Server restarted with new configuration", slog.String("address", net.JoinHostPort(cfg.Server.Addr, fmt.Sprintf("%d", cfg.Server.Port))))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
server:
|
||||
addr: "0.0.0.0"
|
||||
address: "0.0.0.0"
|
||||
port: 8089
|
||||
5
go.mod
5
go.mod
@@ -11,6 +11,9 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
@@ -22,4 +25,6 @@ require (
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
gorm.io/driver/sqlite v1.6.0 // indirect
|
||||
gorm.io/gorm v1.31.1 // indirect
|
||||
)
|
||||
|
||||
10
go.sum
10
go.sum
@@ -13,10 +13,16 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -54,3 +60,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
|
||||
@@ -1,6 +1,38 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
"git.oblat.lv/alex/triggerssmith/internal/server"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
// Application state and configurations can be added here
|
||||
}
|
||||
configuration atomic.Value // *config.Config
|
||||
ls *server.LiveServer
|
||||
}
|
||||
|
||||
// NewApp creates a new instance of the App struct. Always returns ErrNilPointerWarn.
|
||||
func NewApp() (*App, error) {
|
||||
app := &App{}
|
||||
return app, ErrNilPointerWarn
|
||||
}
|
||||
|
||||
func (a *App) LoadConfiguration(cfg *config.Config) {
|
||||
a.configuration.Store(cfg)
|
||||
}
|
||||
|
||||
func (a *App) Configuration() *config.Config {
|
||||
cfg := a.configuration.Load()
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
return cfg.(*config.Config)
|
||||
}
|
||||
|
||||
func (a *App) Server() *server.LiveServer {
|
||||
if a.ls == nil {
|
||||
a.ls, _ = server.Create("main_server")
|
||||
}
|
||||
return a.ls
|
||||
}
|
||||
|
||||
@@ -6,4 +6,4 @@ import (
|
||||
|
||||
var (
|
||||
ErrNilPointerWarn = errors.New("nil pointer dereference warning")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/akyaiy/GSfass/core/config"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Addr string `mapstructure:"address"`
|
||||
Port int `mapstructure:"port"`
|
||||
Addr string `mapstructure:"address"`
|
||||
StaticFilesPath string `mapstructure:"static_dir"`
|
||||
LogPath string `mapstructure:"log_path"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
}
|
||||
|
||||
var configPath atomic.Value // string
|
||||
var defaults = map[string]any{
|
||||
"server.port": 8080,
|
||||
"server.address": "127.0.0.0",
|
||||
"server.static_dir": "./static",
|
||||
}
|
||||
|
||||
func read(cfg *Config) error {
|
||||
return config.Read().Config().FilePath(configPath.Load().(string)).SetBy(cfg).SetDefaults(defaults).End()
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
configPath.Store(path)
|
||||
var cfg Config
|
||||
err := config.Read().Config().FilePath(path).SetBy(&cfg).SetDefaults(map[string]any{
|
||||
"server.port": 8080,
|
||||
"server.address": "127.0.0.0",
|
||||
}).End()
|
||||
err := read(&cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func ReloadConfig(cfg *Config) error {
|
||||
return read(cfg)
|
||||
}
|
||||
|
||||
16
internal/safe/recover.go
Normal file
16
internal/safe/recover.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package safe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func SafeGO(fn func(), errs chan<- error) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errs <- fmt.Errorf("panic: %v", r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
465
internal/server/server.go
Normal file
465
internal/server/server.go
Normal file
@@ -0,0 +1,465 @@
|
||||
// package server
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "fmt"
|
||||
// "log/slog"
|
||||
// "net"
|
||||
// "net/http"
|
||||
// "sync/atomic"
|
||||
// "time"
|
||||
|
||||
// "git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
// "git.oblat.lv/alex/triggerssmith/internal/safe"
|
||||
// )
|
||||
|
||||
// type LiveServer struct {
|
||||
// current atomic.Value // *Server
|
||||
// }
|
||||
|
||||
// type Server struct {
|
||||
// generalLogger *slog.Logger
|
||||
// cfg *config.ServerConfig
|
||||
|
||||
// srv *http.Server
|
||||
// ln net.Listener
|
||||
// }
|
||||
|
||||
// func (s *Server) GetConfig() *config.ServerConfig {
|
||||
// return s.cfg
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) Start(cfg *config.ServerConfig, handler http.Handler) error {
|
||||
// slog.Debug("Starting new server", slog.Any("config", *cfg))
|
||||
// addr := fmt.Sprintf("%s:%d", cfg.Addr, cfg.Port)
|
||||
// ln, err := net.Listen("tcp", addr)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// srv := &http.Server{
|
||||
// Handler: handler,
|
||||
// }
|
||||
// hs := &Server{
|
||||
// cfg: cfg,
|
||||
// ln: ln,
|
||||
// srv: srv,
|
||||
// }
|
||||
// started := make(chan error, 1)
|
||||
// go func() {
|
||||
// err := srv.Serve(ln)
|
||||
// started <- err
|
||||
// }()
|
||||
|
||||
// select {
|
||||
// case err := <-started:
|
||||
// return fmt.Errorf("cannot start server: %w", err)
|
||||
// case <-time.After(1 * time.Millisecond):
|
||||
// }
|
||||
|
||||
// old := ls.current.Load()
|
||||
// ls.current.Store(hs)
|
||||
// if old != nil {
|
||||
// errorChan := make(chan error, 1)
|
||||
// safe.SafeGO(func() {
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
// defer cancel()
|
||||
// old.(*Server).srv.Shutdown(ctx)
|
||||
// }, errorChan)
|
||||
// select {
|
||||
// case err := <-errorChan:
|
||||
// return err
|
||||
// case <-time.After(4 * time.Second):
|
||||
// return fmt.Errorf("timeout while shutting down old server")
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type LiveServer struct {
|
||||
name string
|
||||
data any // config, etc
|
||||
|
||||
handler http.Handler
|
||||
|
||||
active atomic.Value // *instance
|
||||
mu sync.Mutex
|
||||
|
||||
statusMu sync.Mutex
|
||||
status Status
|
||||
|
||||
initDone bool
|
||||
}
|
||||
|
||||
type instance struct {
|
||||
srv *http.Server
|
||||
ln net.Listener
|
||||
|
||||
addr string
|
||||
}
|
||||
|
||||
func Create(name string) (*LiveServer, error) {
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("server name is empty")
|
||||
}
|
||||
|
||||
ls := &LiveServer{name: name}
|
||||
ls.setStatus(Status{ID: StatusStopped})
|
||||
return ls, nil
|
||||
}
|
||||
|
||||
func (ls *LiveServer) setStatus(st Status) {
|
||||
ls.statusMu.Lock()
|
||||
defer ls.statusMu.Unlock()
|
||||
|
||||
ls.status = st
|
||||
}
|
||||
|
||||
func (ls *LiveServer) Status() Status {
|
||||
ls.statusMu.Lock()
|
||||
defer ls.statusMu.Unlock()
|
||||
|
||||
return ls.status
|
||||
}
|
||||
|
||||
func (ls *LiveServer) SetHandler(h http.Handler) {
|
||||
ls.mu.Lock()
|
||||
defer ls.mu.Unlock()
|
||||
|
||||
ls.handler = h
|
||||
}
|
||||
|
||||
func (ls *LiveServer) listen(addr string) (net.Listener, error) {
|
||||
slog.Debug("listening", slog.String("addr", addr))
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
slog.Debug("listening failed", slog.String("err", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (ls *LiveServer) serve(inst *instance) (chan error, error) {
|
||||
slog.Debug("serving", slog.Any("instance", *inst))
|
||||
errChan := make(chan error, 1)
|
||||
if inst == nil {
|
||||
err := fmt.Errorf("instance is nil")
|
||||
slog.Debug("serving failed", slog.String("err", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
srv := inst.srv
|
||||
ln := inst.ln
|
||||
go func() {
|
||||
err := srv.Serve(ln)
|
||||
errChan <- err
|
||||
}()
|
||||
return errChan, nil
|
||||
}
|
||||
|
||||
func (ls *LiveServer) Init() error {
|
||||
slog.Debug("initializating live server", slog.Any("liveserver", *ls))
|
||||
ls.mu.Lock()
|
||||
defer ls.mu.Unlock()
|
||||
ls.setStatus(Status{ID: StatusInitializing})
|
||||
|
||||
if ls.handler == nil {
|
||||
err := fmt.Errorf("handler not set")
|
||||
slog.Debug("initializating failed", slog.String("err", err.Error()))
|
||||
return err
|
||||
}
|
||||
ls.initDone = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LiveServer) Start(addr string) error {
|
||||
slog.Debug("starting server", slog.String("addr", addr))
|
||||
|
||||
if !ls.initDone {
|
||||
err := fmt.Errorf("server is not initialized")
|
||||
slog.Debug("starting failed", slog.String("err", err.Error()))
|
||||
return err
|
||||
}
|
||||
ls.setStatus(Status{ID: StatusStarting})
|
||||
|
||||
ln, err := ls.listen(addr)
|
||||
if err != nil {
|
||||
ls.setStatus(Status{ID: StatusError, Err: err})
|
||||
return err
|
||||
}
|
||||
srv := &http.Server{Handler: ls.handler}
|
||||
|
||||
ls.active.Store(&instance{
|
||||
srv: srv,
|
||||
ln: ln,
|
||||
addr: addr,
|
||||
})
|
||||
|
||||
_, err = ls.serve(ls.active.Load().(*instance))
|
||||
if err != nil {
|
||||
ls.setStatus(Status{ID: StatusError, Err: err})
|
||||
return err
|
||||
}
|
||||
ls.setStatus(Status{ID: StatusOK})
|
||||
|
||||
// go func() {
|
||||
// err := <-errChan
|
||||
// if err != nil && err != http.ErrServerClosed {
|
||||
// ls.setStatus(Status{ID: StatusError, Err: err})
|
||||
// slog.Error("Server stopped with error", slog.String("name", ls.name), slog.String("error", err.Error()))
|
||||
// return
|
||||
// }
|
||||
// }()
|
||||
|
||||
slog.Debug("Server started", slog.String("name", ls.name), slog.String("address", addr))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LiveServer) stop(inst *instance) error {
|
||||
slog.Debug("stopping server")
|
||||
|
||||
instAny := ls.active.Load()
|
||||
if instAny == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
inst.ln.Close()
|
||||
err := inst.srv.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
slog.Debug("shutdown", slog.String("err", err.Error()))
|
||||
}
|
||||
ls.setStatus(Status{ID: StatusStopped})
|
||||
|
||||
if err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls *LiveServer) Stop() error {
|
||||
return ls.stop(ls.active.Load().(*instance))
|
||||
}
|
||||
|
||||
|
||||
func (ls *LiveServer) Reload(newAddr string) error {
|
||||
ls.mu.Lock()
|
||||
oldInstAny := ls.active.Load()
|
||||
var oldAddr string
|
||||
if oldInstAny != nil {
|
||||
oldAddr = oldInstAny.(*instance).addr
|
||||
}
|
||||
ls.mu.Unlock()
|
||||
|
||||
println("Old addr:", oldAddr, "New addr:", newAddr)
|
||||
if oldAddr == newAddr {
|
||||
return nil
|
||||
}
|
||||
slog.Debug("Reloading server", slog.String("name", ls.name), slog.String("new_address", newAddr))
|
||||
|
||||
ls.setStatus(Status{ID: StatusStarting})
|
||||
|
||||
slog.Debug("starting new server")
|
||||
err := ls.Start(newAddr)
|
||||
if err != nil {
|
||||
ls.active.Store(oldInstAny)
|
||||
return fmt.Errorf("cannot start new server: %w", err)
|
||||
}
|
||||
|
||||
if err := ls.stop(oldInstAny.(*instance)); err != nil {
|
||||
slog.Debug("stopping failed", slog.String("err", err.Error()))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// package server
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "errors"
|
||||
// "fmt"
|
||||
// "log/slog"
|
||||
// "net"
|
||||
// "net/http"
|
||||
// "sync"
|
||||
// "sync/atomic"
|
||||
// "time"
|
||||
|
||||
// "git.oblat.lv/alex/triggerssmith/internal/config"
|
||||
// )
|
||||
|
||||
// type LiveServer struct {
|
||||
// name string
|
||||
// handler http.Handler
|
||||
// cfg *config.Config
|
||||
|
||||
// active atomic.Value // *instance
|
||||
// mu sync.Mutex // защищает операции Start/Stop/Reload
|
||||
|
||||
// statusMu sync.Mutex
|
||||
// status Status
|
||||
// }
|
||||
|
||||
// type instance struct {
|
||||
// srv *http.Server
|
||||
// ln net.Listener
|
||||
// cfg *config.ServerConfig
|
||||
// }
|
||||
|
||||
// func Create(name string) (*LiveServer, error) {
|
||||
// if name == "" {
|
||||
// return nil, errors.New("server name is empty")
|
||||
// }
|
||||
|
||||
// ls := &LiveServer{name: name}
|
||||
// ls.setStatus(Status{ID: StatusStopped})
|
||||
// return ls, nil
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) setStatus(st Status) {
|
||||
// ls.statusMu.Lock()
|
||||
// ls.status = st
|
||||
// ls.statusMu.Unlock()
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) Status() Status {
|
||||
// ls.statusMu.Lock()
|
||||
// s := ls.status
|
||||
// ls.statusMu.Unlock()
|
||||
// return s
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) LoadConfiguration(cfg *config.Config) {
|
||||
// ls.mu.Lock()
|
||||
// defer ls.mu.Unlock()
|
||||
|
||||
// ls.cfg = cfg
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) SetHandler(h http.Handler) {
|
||||
// ls.mu.Lock()
|
||||
// defer ls.mu.Unlock()
|
||||
|
||||
// ls.handler = h
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) Start() error {
|
||||
// ls.mu.Lock()
|
||||
// defer ls.mu.Unlock()
|
||||
|
||||
// if ls.cfg == nil {
|
||||
// return errors.New("configuration not loaded")
|
||||
// }
|
||||
// if ls.handler == nil {
|
||||
// return errors.New("handler not set")
|
||||
// }
|
||||
|
||||
// ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ls.cfg.Server.Addr, ls.cfg.Server.Port))
|
||||
// if err != nil {
|
||||
// ls.setStatus(Status{ID: StatusError, Err: err})
|
||||
// return err
|
||||
// }
|
||||
|
||||
// srv := &http.Server{Handler: ls.handler}
|
||||
|
||||
// inst := &instance{
|
||||
// srv: srv,
|
||||
// ln: ln,
|
||||
// cfg: &ls.cfg.Server,
|
||||
// }
|
||||
|
||||
// ls.setStatus(Status{ID: StatusStarting})
|
||||
// ls.active.Store(inst)
|
||||
|
||||
// go func() {
|
||||
// err := srv.Serve(ln)
|
||||
// if err != nil && err != http.ErrServerClosed {
|
||||
// ls.setStatus(Status{ID: StatusError, Err: err})
|
||||
// return
|
||||
// }
|
||||
// }()
|
||||
|
||||
// // даём серверу время забиндиться
|
||||
// time.Sleep(5 * time.Millisecond)
|
||||
|
||||
// ls.setStatus(Status{ID: StatusOK})
|
||||
// slog.Info("Server started", slog.String("name", ls.name), slog.String("address", fmt.Sprintf("%s:%d", ls.cfg.Server.Addr, ls.cfg.Server.Port)))
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) Stop() error {
|
||||
// ls.mu.Lock()
|
||||
// defer ls.mu.Unlock()
|
||||
|
||||
// instAny := ls.active.Load()
|
||||
// if instAny == nil {
|
||||
// return nil
|
||||
// }
|
||||
// inst := instAny.(*instance)
|
||||
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
// err := inst.srv.Shutdown(ctx)
|
||||
// inst.ln.Close()
|
||||
|
||||
// ls.setStatus(Status{ID: StatusStopped})
|
||||
// return err
|
||||
// }
|
||||
|
||||
// func (ls *LiveServer) Reload(newCfg *config.Config) error {
|
||||
// ls.mu.Lock()
|
||||
// defer ls.mu.Unlock()
|
||||
|
||||
// oldInstAny := ls.active.Load()
|
||||
// var oldCfg *config.ServerConfig
|
||||
|
||||
// if oldInstAny != nil {
|
||||
// oldCfg = oldInstAny.(*instance).cfg
|
||||
// }
|
||||
|
||||
// if oldCfg != nil &&
|
||||
// oldCfg.Addr == newCfg.Server.Addr &&
|
||||
// oldCfg.Port == newCfg.Server.Port {
|
||||
// ls.cfg = newCfg
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", newCfg.Server.Addr, newCfg.Server.Port))
|
||||
// // if err != nil {
|
||||
// // return fmt.Errorf("cannot bind new address: %w", err)
|
||||
// // }
|
||||
// // srv := &http.Server{Handler: ls.handler}
|
||||
|
||||
// // newInst := &instance{
|
||||
// // srv: srv,
|
||||
// // ln: ln,
|
||||
// // cfg: &newCfg.Server,
|
||||
// // }
|
||||
|
||||
// ls.setStatus(Status{ID: StatusStarting})
|
||||
|
||||
|
||||
// err := ls.Start()
|
||||
// if err != nil {
|
||||
// ls.active.Store(oldInstAny)
|
||||
// return fmt.Errorf("cannot start new server: %w", err)
|
||||
// }
|
||||
|
||||
// if err := ls.Stop(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
20
internal/server/status.go
Normal file
20
internal/server/status.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package server
|
||||
|
||||
type StatusID int
|
||||
|
||||
const (
|
||||
StatusStopped StatusID = iota
|
||||
StatusStarting
|
||||
StatusOK
|
||||
StatusError
|
||||
StatusInitializing
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
ID StatusID
|
||||
Err error
|
||||
}
|
||||
|
||||
func (s Status) Error() string {
|
||||
return s.Err.Error()
|
||||
}
|
||||
1
static/index.html
Normal file
1
static/index.html
Normal file
@@ -0,0 +1 @@
|
||||
Hiwqe
|
||||
Reference in New Issue
Block a user