diff options
author | Alan Pearce | 2025-01-30 22:16:09 +0100 |
---|---|---|
committer | Alan Pearce | 2025-01-30 22:16:09 +0100 |
commit | 99f8047ef20a64f948ac2b703c81eb49bed091c0 (patch) | |
tree | a0365a7b2e477467a91bef247db09624028e1807 /internal/server/server.go | |
parent | 4566db657dab6af43f8fce814cd0e42cbcc788bf (diff) | |
download | website-99f8047ef20a64f948ac2b703c81eb49bed091c0.tar.lz website-99f8047ef20a64f948ac2b703c81eb49bed091c0.tar.zst website-99f8047ef20a64f948ac2b703c81eb49bed091c0.zip |
re-organise everything sqlite
Diffstat (limited to 'internal/server/server.go')
-rw-r--r-- | internal/server/server.go | 217 |
1 files changed, 48 insertions, 169 deletions
diff --git a/internal/server/server.go b/internal/server/server.go index 0f5e22f..b3161fb 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,22 +3,12 @@ package server import ( "context" "fmt" - "net" "net/http" - "net/url" - "regexp" - "slices" - "strconv" - "strings" "time" - "go.alanpearce.eu/website/internal/builder" cfg "go.alanpearce.eu/website/internal/config" - "go.alanpearce.eu/website/internal/storage/sqlite" - "go.alanpearce.eu/website/internal/website" "go.alanpearce.eu/x/log" - "github.com/osdevisnot/sorvor/pkg/livereload" "gitlab.com/tozd/go/errors" ) @@ -26,55 +16,31 @@ var ( CommitSHA = "local" ShortSHA = "local" serverHeader = fmt.Sprintf("website (%s)", ShortSHA) + + ReadHeaderTimeout = 10 * time.Second + ReadTimeout = 1 * time.Minute + WriteTimeout = 2 * time.Minute + IdleTimeout = 10 * time.Minute ) -type Config struct { - DBPath string `conf:"default:site.db"` - Redirect bool `conf:"default:true"` - ListenAddress string `conf:"default:localhost"` - Port int `conf:"default:8080,short:p"` - TLSPort int `conf:"default:8443"` - TLS bool `conf:"default:false"` - - Development bool `conf:"default:false,flag:dev"` - ACMECA string `conf:"env:ACME_CA"` - ACMECACert string `conf:"env:ACME_CA_CERT"` - Domains string -} +type Options struct { + Development bool + ListenAddress string + Port int + TLSPort int + TLS bool -type Server struct { - *http.Server - runtimeConfig *Config - config *cfg.Config - log *log.Logger -} + ACMEIssuer string + ACMEIssuerCert string -func applyDevModeOverrides(config *cfg.Config, runtimeConfig *Config) { - config.CSP.ScriptSrc = slices.Insert(config.CSP.ScriptSrc, 0, "'unsafe-inline'") - config.CSP.ConnectSrc = slices.Insert(config.CSP.ConnectSrc, 0, "'self'") - if runtimeConfig.Domains != "" { - config.Domains = strings.Split(runtimeConfig.Domains, ",") - } else { - config.Domains = []string{runtimeConfig.ListenAddress} - } - scheme := "http" - port := runtimeConfig.Port - if runtimeConfig.TLS { - scheme = "https" - port = runtimeConfig.TLSPort - } - config.BaseURL = cfg.URL{ - URL: &url.URL{ - Scheme: scheme, - Host: net.JoinHostPort(config.Domains[0], strconv.Itoa(port)), - }, - } + Config *cfg.Config } -func updateCSPHashes(config *cfg.Config, r *builder.Result) { - for i, h := range r.Hashes { - config.CSP.StyleSrc[i] = fmt.Sprintf("'%s'", h) - } +type Server struct { + mux *http.ServeMux + options *Options + log *log.Logger + server *http.Server } func serverHeaderHandler(wrappedHandler http.Handler) http.Handler { @@ -84,109 +50,37 @@ func serverHeaderHandler(wrappedHandler http.Handler) http.Handler { }) } -func rebuild(builderConfig *builder.IOConfig, config *cfg.Config, log *log.Logger) error { - r, err := builder.BuildSite(builderConfig, config, log.Named("builder")) - if err != nil { - return errors.WithMessage(err, "could not build site") - } - updateCSPHashes(config, r) - - return nil -} - -func New(runtimeConfig *Config, log *log.Logger) (*Server, error) { - builderConfig := &builder.IOConfig{ - Development: runtimeConfig.Development, - } - - config, err := cfg.GetConfig(builderConfig.Source, log.Named("config")) - if err != nil { - return nil, errors.WithMessage(err, "error parsing configuration file") - } - if runtimeConfig.Development { - applyDevModeOverrides(config, runtimeConfig) - } - - top := http.NewServeMux() - - err = rebuild(builderConfig, config, log) - if err != nil { - return nil, err - } - +func New(options *Options, log *log.Logger) (*Server, error) { fixupMIMETypes(log) - if runtimeConfig.Development { - liveReload := livereload.New() - top.Handle("/_/reload", liveReload) - liveReload.Start() - fw, err := NewFileWatcher(log.Named("watcher")) - if err != nil { - return nil, errors.WithMessage(err, "could not create file watcher") - } - for _, dir := range []string{"content", "static", "templates", "internal/builder"} { - err := fw.AddRecursive(dir) - if err != nil { - return nil, errors.WithMessagef( - err, - "could not add directory %s to file watcher", - dir, - ) - } - } - go fw.Start(func(filename string) { - log.Info("rebuilding site", "changed_file", filename) - err := rebuild(builderConfig, config, log) - if err != nil { - log.Error("error rebuilding site", "error", err) - } - }) - } + return &Server{ + mux: http.NewServeMux(), + log: log, + options: options, + }, nil +} - loggingMux := http.NewServeMux() +func (s *Server) HostApp(app *App) { + s.mux.Handle(app.Domain+"/", app.Handler) +} - log.Debug("creating reader") - reader, err := sqlite.NewReader(runtimeConfig.DBPath, log.Named("sqlite")) - if err != nil { - return nil, errors.WithMessage(err, "could not create sqlite reader") - } +func (s *Server) HostFallbackApp(app *App) { + s.mux.Handle("/", app.Handler) +} - mux, err := website.NewMux(config, reader, log.Named("website")) - if err != nil { - return nil, errors.WithMessage(err, "could not create website mux") +func (s *Server) serve(tls bool) error { + if tls { + return s.serveTLS() } - if runtimeConfig.Redirect { - re := regexp.MustCompile( - "^(.*)\\." + strings.ReplaceAll(config.WildcardDomain, ".", `\.`) + "$", - ) - replace := "${1}." + config.Domains[0] - loggingMux.Handle(config.BaseURL.Hostname()+"/", mux) - loggingMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - switch { - case slices.Contains(config.Domains, r.Host): - path, _ := reader.CanonicalisePath(r.URL.Path) - http.Redirect( - w, - r, - config.BaseURL.JoinPath(path).String(), - http.StatusMovedPermanently, - ) - case re.MatchString(r.Host): - url := config.BaseURL.JoinPath() - url.Host = re.ReplaceAllString(r.Host, replace) - http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect) - case true: - http.NotFound(w, r) - } - }) - } else { - loggingMux.Handle("/", mux) - } + return s.serveTCP() +} +func (s *Server) Start() error { + top := http.NewServeMux() top.Handle("/", serverHeaderHandler( - wrapHandlerWithLogging(loggingMux, log), + wrapHandlerWithLogging(s.mux, s.log), ), ) @@ -194,30 +88,15 @@ func New(runtimeConfig *Config, log *log.Logger) (*Server, error) { w.WriteHeader(http.StatusNoContent) }) - return &Server{ - Server: &http.Server{ - ReadHeaderTimeout: 10 * time.Second, - ReadTimeout: 1 * time.Minute, - WriteTimeout: 2 * time.Minute, - IdleTimeout: 10 * time.Minute, - Handler: top, - }, - log: log, - config: config, - runtimeConfig: runtimeConfig, - }, nil -} - -func (s *Server) serve(tls bool) error { - if tls { - return s.serveTLS() + s.server = &http.Server{ + ReadHeaderTimeout: ReadHeaderTimeout, + ReadTimeout: ReadTimeout, + WriteTimeout: WriteTimeout, + IdleTimeout: IdleTimeout, + Handler: s.mux, } - return s.serveTCP() -} - -func (s *Server) Start() error { - if err := s.serve(s.runtimeConfig.TLS); err != http.ErrServerClosed { + if err := s.serve(s.options.TLS); err != http.ErrServerClosed { return errors.WithMessage(err, "error creating/closing server") } @@ -233,7 +112,7 @@ func (s *Server) Stop() chan struct{} { s.log.Debug("shutting down server") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - err := s.Server.Shutdown(ctx) + err := s.server.Shutdown(ctx) s.log.Debug("server shut down") if err != nil { // Error from closing listeners, or context timeout: |