diff --git a/Makefile b/Makefile index 54bdf33..10b8c7e 100644 --- a/Makefile +++ b/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)" diff --git a/cmd/root.go b/cmd/root.go index 2a9a658..bdbfca8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,4 +14,4 @@ var rootCmd = &cobra.Command{ func Execute() error { return rootCmd.Execute() -} \ No newline at end of file +} diff --git a/cmd/serve.go b/cmd/serve.go index 4bc27e1..b4d2886 100644 --- a/cmd/serve.go +++ b/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)))) + } + } + } }, } diff --git a/config.yaml b/config.yaml index 6a7a445..5806e53 100644 --- a/config.yaml +++ b/config.yaml @@ -1,2 +1,3 @@ server: - addr: "0.0.0.0" \ No newline at end of file + address: "0.0.0.0" + port: 8089 \ No newline at end of file diff --git a/go.mod b/go.mod index 1a4794f..b8fd5c8 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 1059dff..1a4956c 100644 --- a/go.sum +++ b/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= diff --git a/internal/app/app.go b/internal/app/app.go index fd750c6..f290628 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -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 -} \ No newline at end of file + 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 +} diff --git a/internal/app/errors.go b/internal/app/errors.go index a61c6de..75c8507 100644 --- a/internal/app/errors.go +++ b/internal/app/errors.go @@ -6,4 +6,4 @@ import ( var ( ErrNilPointerWarn = errors.New("nil pointer dereference warning") -) \ No newline at end of file +) diff --git a/internal/config/config.go b/internal/config/config.go index ac000e5..21225a0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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) +} diff --git a/internal/safe/recover.go b/internal/safe/recover.go new file mode 100644 index 0000000..02d7818 --- /dev/null +++ b/internal/safe/recover.go @@ -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() + }() +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..57a2f04 --- /dev/null +++ b/internal/server/server.go @@ -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 +// } diff --git a/internal/server/status.go b/internal/server/status.go new file mode 100644 index 0000000..ce7eda4 --- /dev/null +++ b/internal/server/status.go @@ -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() +} \ No newline at end of file diff --git a/main.go b/main.go index 4f402af..3491ccd 100644 --- a/main.go +++ b/main.go @@ -6,4 +6,4 @@ import ( func main() { cmd.Execute() -} \ No newline at end of file +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..0622a04 --- /dev/null +++ b/static/index.html @@ -0,0 +1 @@ +Hiwqe \ No newline at end of file