about summary refs log tree commit diff stats
path: root/internal/server/server.go
diff options
context:
space:
mode:
authorAlan Pearce2025-01-30 22:16:09 +0100
committerAlan Pearce2025-01-30 22:16:09 +0100
commit99f8047ef20a64f948ac2b703c81eb49bed091c0 (patch)
treea0365a7b2e477467a91bef247db09624028e1807 /internal/server/server.go
parent4566db657dab6af43f8fce814cd0e42cbcc788bf (diff)
downloadwebsite-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.go217
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: