2021-11-19 18:37:42 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"net/http"
|
2022-03-24 09:16:51 +00:00
|
|
|
"net/http/pprof"
|
2021-11-19 18:37:42 +00:00
|
|
|
|
2022-06-16 22:51:27 +00:00
|
|
|
"git.sr.ht/~hamcha/containers"
|
2022-02-01 11:35:34 +00:00
|
|
|
jsoniter "github.com/json-iterator/go"
|
2022-11-30 18:15:47 +00:00
|
|
|
"github.com/strimertul/strimertul/database"
|
2022-01-27 15:49:18 +00:00
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2022-11-18 16:37:30 +00:00
|
|
|
kv "github.com/strimertul/kilovolt/v9"
|
2021-11-19 18:37:42 +00:00
|
|
|
)
|
|
|
|
|
2022-11-23 15:34:49 +00:00
|
|
|
var json = jsoniter.ConfigFastest
|
|
|
|
|
2021-11-19 18:37:42 +00:00
|
|
|
type Server struct {
|
2022-11-30 18:15:47 +00:00
|
|
|
Config ServerConfig
|
|
|
|
db *database.LocalDBClient
|
|
|
|
logger *zap.Logger
|
|
|
|
server *http.Server
|
|
|
|
frontend fs.FS
|
|
|
|
hub *kv.Hub
|
|
|
|
mux *http.ServeMux
|
2022-12-03 15:16:59 +00:00
|
|
|
requestedRoutes map[string]http.Handler
|
|
|
|
cancelConfigSub database.CancelFunc
|
2021-11-19 18:37:42 +00:00
|
|
|
}
|
|
|
|
|
2022-11-30 18:15:47 +00:00
|
|
|
func NewServer(db *database.LocalDBClient, logger *zap.Logger) (*Server, error) {
|
2021-11-19 18:37:42 +00:00
|
|
|
server := &Server{
|
2022-11-30 18:15:47 +00:00
|
|
|
logger: logger,
|
|
|
|
db: db,
|
|
|
|
server: &http.Server{},
|
2022-12-03 15:16:59 +00:00
|
|
|
requestedRoutes: make(map[string]http.Handler),
|
2021-11-19 18:37:42 +00:00
|
|
|
}
|
2022-11-30 18:15:47 +00:00
|
|
|
|
2022-11-23 21:24:32 +00:00
|
|
|
err := db.GetJSON(ServerConfigKey, &server.Config)
|
2021-11-21 21:36:48 +00:00
|
|
|
if err != nil {
|
2021-12-09 12:32:58 +00:00
|
|
|
// Initialize with default config
|
|
|
|
server.Config = ServerConfig{
|
|
|
|
Bind: "localhost:4337",
|
|
|
|
EnableStaticServer: false,
|
|
|
|
KVPassword: "",
|
|
|
|
}
|
|
|
|
// Save
|
|
|
|
err = db.PutJSON(ServerConfigKey, server.Config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-11-21 21:36:48 +00:00
|
|
|
}
|
|
|
|
|
2022-02-01 11:35:34 +00:00
|
|
|
// Set hub
|
|
|
|
server.hub = db.Hub()
|
|
|
|
|
|
|
|
// Set password
|
|
|
|
server.hub.SetOptions(kv.HubOptions{
|
2021-11-21 21:36:48 +00:00
|
|
|
Password: server.Config.KVPassword,
|
2022-02-01 11:35:34 +00:00
|
|
|
})
|
2021-11-19 18:37:42 +00:00
|
|
|
|
2021-11-21 21:36:48 +00:00
|
|
|
return server, nil
|
2021-11-19 18:37:42 +00:00
|
|
|
}
|
|
|
|
|
2022-11-18 19:28:13 +00:00
|
|
|
// StatusData contains status info for the HTTP module
|
|
|
|
type StatusData struct {
|
|
|
|
Bind string
|
|
|
|
}
|
|
|
|
|
2021-11-23 10:34:02 +00:00
|
|
|
func (s *Server) Close() error {
|
2022-12-03 15:16:59 +00:00
|
|
|
if s.cancelConfigSub != nil {
|
|
|
|
s.cancelConfigSub()
|
|
|
|
}
|
|
|
|
|
2021-11-23 10:34:02 +00:00
|
|
|
return s.server.Close()
|
|
|
|
}
|
|
|
|
|
2021-11-19 18:37:42 +00:00
|
|
|
func (s *Server) SetFrontend(files fs.FS) {
|
|
|
|
s.frontend = files
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) makeMux() *http.ServeMux {
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
2022-03-24 09:16:51 +00:00
|
|
|
// Register pprof
|
|
|
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
|
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
|
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
|
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
|
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
|
|
|
2021-11-19 18:37:42 +00:00
|
|
|
if s.frontend != nil {
|
|
|
|
mux.Handle("/ui/", http.StripPrefix("/ui/", FileServerWithDefault(http.FS(s.frontend))))
|
|
|
|
}
|
|
|
|
if s.hub != nil {
|
|
|
|
mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
kv.ServeWs(s.hub, w, r)
|
|
|
|
})
|
|
|
|
}
|
2021-11-19 18:46:48 +00:00
|
|
|
if s.Config.EnableStaticServer {
|
|
|
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(s.Config.Path))))
|
2021-11-19 18:37:42 +00:00
|
|
|
}
|
2022-11-30 18:15:47 +00:00
|
|
|
for route, handler := range s.requestedRoutes {
|
2022-12-03 15:16:59 +00:00
|
|
|
mux.Handle(route, handler)
|
2022-11-23 21:22:49 +00:00
|
|
|
}
|
2021-11-19 18:37:42 +00:00
|
|
|
|
|
|
|
return mux
|
|
|
|
}
|
|
|
|
|
2022-12-03 15:16:59 +00:00
|
|
|
func (s *Server) RegisterRoute(route string, handler http.Handler) {
|
2022-11-30 18:15:47 +00:00
|
|
|
s.requestedRoutes[route] = handler
|
2022-12-03 15:16:59 +00:00
|
|
|
s.mux = s.makeMux()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) UnregisterRoute(route string) {
|
|
|
|
delete(s.requestedRoutes, route)
|
|
|
|
s.mux = s.makeMux()
|
2022-11-30 18:15:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-19 18:37:42 +00:00
|
|
|
func (s *Server) Listen() error {
|
|
|
|
// Start HTTP server
|
2022-06-16 22:51:27 +00:00
|
|
|
restart := containers.NewRWSync(false)
|
2021-11-19 18:37:42 +00:00
|
|
|
exit := make(chan error)
|
2022-12-03 15:16:59 +00:00
|
|
|
var err error
|
|
|
|
err, s.cancelConfigSub = s.db.SubscribeKey(ServerConfigKey, func(value string) {
|
|
|
|
oldBind := s.Config.Bind
|
|
|
|
oldPassword := s.Config.KVPassword
|
|
|
|
err := json.Unmarshal([]byte(value), &s.Config)
|
|
|
|
if err != nil {
|
|
|
|
s.logger.Error("Failed to unmarshal config", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s.mux = s.makeMux()
|
|
|
|
// Restart hub if password changed
|
|
|
|
if oldPassword != s.Config.KVPassword {
|
|
|
|
s.hub.SetOptions(kv.HubOptions{
|
|
|
|
Password: s.Config.KVPassword,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
// Restart server if bind changed
|
|
|
|
if oldBind != s.Config.Bind {
|
|
|
|
restart.Set(true)
|
|
|
|
err = s.server.Shutdown(context.Background())
|
2022-11-25 13:16:20 +00:00
|
|
|
if err != nil {
|
2022-12-03 15:16:59 +00:00
|
|
|
s.logger.Error("Failed to shutdown server", zap.Error(err))
|
2022-11-25 13:16:20 +00:00
|
|
|
return
|
|
|
|
}
|
2021-11-19 18:37:42 +00:00
|
|
|
}
|
2022-12-03 15:16:59 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
exit <- fmt.Errorf("error while handling subscription to HTTP config changes: %w", err)
|
|
|
|
}
|
2021-11-19 18:37:42 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
2022-01-27 15:49:18 +00:00
|
|
|
s.logger.Info("Starting HTTP server", zap.String("bind", s.Config.Bind))
|
2021-11-19 18:37:42 +00:00
|
|
|
s.mux = s.makeMux()
|
|
|
|
s.server = &http.Server{
|
|
|
|
Handler: s,
|
|
|
|
Addr: s.Config.Bind,
|
|
|
|
}
|
2022-01-27 15:49:18 +00:00
|
|
|
s.logger.Info("HTTP server started", zap.String("bind", s.Config.Bind))
|
2021-11-19 18:37:42 +00:00
|
|
|
err := s.server.ListenAndServe()
|
2022-01-27 15:49:18 +00:00
|
|
|
s.logger.Debug("HTTP server died", zap.Error(err))
|
2021-11-19 18:37:42 +00:00
|
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
exit <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Are we trying to close or restart?
|
2022-01-27 15:49:18 +00:00
|
|
|
s.logger.Debug("HTTP server stopped", zap.Bool("restart", restart.Get()))
|
2021-11-19 18:37:42 +00:00
|
|
|
if restart.Get() {
|
|
|
|
restart.Set(false)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s.logger.Debug("HTTP server stalled")
|
|
|
|
exit <- nil
|
|
|
|
}()
|
|
|
|
|
|
|
|
return <-exit
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// Redirect to /ui/ if root
|
|
|
|
if r.URL.Path == "/" {
|
|
|
|
http.Redirect(w, r, "/ui/", http.StatusFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
s.mux.ServeHTTP(w, r)
|
|
|
|
}
|