From aef028263229d8acda28b8e657413f7e9c187833 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Wed, 22 May 2024 16:48:57 +0200 Subject: refactor: split server and mux --- internal/server/server.go | 166 ++++++++-------------------------------------- 1 file changed, 29 insertions(+), 137 deletions(-) (limited to 'internal/server/server.go') diff --git a/internal/server/server.go b/internal/server/server.go index 97851f0..4fda3dc 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,21 +2,17 @@ package server import ( "context" - "encoding/json" "fmt" - "mime" "net" "net/http" "os" - "path" "slices" - "strings" "time" cfg "website/internal/config" "website/internal/log" + "website/internal/website" - "github.com/benpate/digit" "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/pkg/errors" @@ -25,8 +21,9 @@ import ( var config *cfg.Config var ( - CommitSHA string - ShortSHA string + CommitSHA string = "local" + ShortSHA string = "local" + serverHeader string = fmt.Sprintf("website (%s)", ShortSHA) ) type Config struct { @@ -38,91 +35,23 @@ type Config struct { BaseURL cfg.URL `conf:"default:http://localhost:3000,short:b"` } -type HTTPError struct { - Error error - Message string - Code int -} - type Server struct { *http.Server } -func canonicalisePath(path string) (cPath string, differs bool) { - cPath = path - if strings.HasSuffix(path, "/index.html") { - cPath, differs = strings.CutSuffix(path, "index.html") - } else if !strings.HasSuffix(path, "/") && files[path+"/"] != (File{}) { - cPath, differs = path+"/", true - } - return cPath, differs -} - -func serveFile(w http.ResponseWriter, r *http.Request) *HTTPError { - urlPath, shouldRedirect := canonicalisePath(r.URL.Path) - if shouldRedirect { - http.Redirect(w, r, urlPath, 302) - return nil - } - file := GetFile(urlPath) - if file == (File{}) { - return &HTTPError{ - Message: "File not found", - Code: http.StatusNotFound, - } - } - w.Header().Add("ETag", file.etag) - w.Header().Add("Vary", "Accept-Encoding") - w.Header().Add("Content-Security-Policy", config.CSP.String()) - for k, v := range config.Extra.Headers { - w.Header().Add(k, v) - } - - http.ServeFile(w, r, files[urlPath].filename) - return nil -} - -type webHandler func(http.ResponseWriter, *http.Request) *HTTPError - -func (fn webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - defer func() { - if fail := recover(); fail != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Error("runtime panic!", "error", fail) - } - }() - w.Header().Set("Server", fmt.Sprintf("website (%s)", ShortSHA)) - if err := fn(w, r); err != nil { - if strings.Contains(r.Header.Get("Accept"), "text/html") { - w.WriteHeader(err.Code) - notFoundPage := "website/private/404.html" - http.ServeFile(w, r, notFoundPage) - } else { - http.Error(w, err.Message, err.Code) - } - } -} - -var newMIMEs = map[string]string{ - ".xsl": "text/xsl", -} - -func fixupMIMETypes() { - for ext, newType := range newMIMEs { - if err := mime.AddExtensionType(ext, newType); err != nil { - log.Error("could not update mime type", "ext", ext, "mime", newType) - } - } -} - func applyDevModeOverrides(config *cfg.Config) { config.CSP.ScriptSrc = slices.Insert(config.CSP.ScriptSrc, 0, "'unsafe-inline'") config.CSP.ConnectSrc = slices.Insert(config.CSP.ConnectSrc, 0, "'self'") } -func New(runtimeConfig *Config) (*Server, error) { - fixupMIMETypes() +func serverHeaderHandler(wrappedHandler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Server", serverHeader) + wrappedHandler.ServeHTTP(w, r) + }) +} +func New(runtimeConfig *Config) (*Server, error) { var err error config, err = cfg.GetConfig() if err != nil { @@ -132,13 +61,6 @@ func New(runtimeConfig *Config) (*Server, error) { applyDevModeOverrides(config) } - prefix := path.Join(runtimeConfig.Root, "public") - log.Debug("registering content files", "prefix", prefix) - err = registerContentFiles(prefix) - if err != nil { - return nil, errors.WithMessagef(err, "registering content files") - } - env := "development" if runtimeConfig.Production { env = "production" @@ -159,56 +81,20 @@ func New(runtimeConfig *Config) (*Server, error) { }) top := http.NewServeMux() - mux := http.NewServeMux() + mux, err := website.NewMux(config, runtimeConfig.Root) + if err != nil { + return nil, err + } log.Debug("binding main handler to", "host", runtimeConfig.BaseURL.Hostname()+"/") hostname := runtimeConfig.BaseURL.Hostname() - mux.Handle(hostname+"/", webHandler(serveFile)) - - var acctResource = "acct:" + config.Email - me := digit.NewResource(acctResource). - Link("http://openid.net/specs/connect/1.0/issuer", "", config.OIDCHost.String()) - mux.HandleFunc(hostname+"/.well-known/webfinger", func(w http.ResponseWriter, r *http.Request) { - if r.URL.Query().Get("resource") == acctResource { - obj, err := json.Marshal(me) - if err != nil { - http.Error( - w, - http.StatusText(http.StatusInternalServerError), - http.StatusInternalServerError, - ) - - return - } - - w.Header().Add("Content-Type", "application/jrd+json") - w.Header().Add("Access-Control-Allow-Origin", "*") - _, err = w.Write(obj) - if err != nil { - log.Warn("error writing webfinger request", "error", err) - } - } - }) - const oidcPath = "/.well-known/openid-configuration" - mux.HandleFunc( - hostname+oidcPath, - func(w http.ResponseWriter, r *http.Request) { - u := config.OIDCHost.JoinPath(oidcPath) - http.Redirect(w, r, u.String(), 302) - }) - - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - newURL := runtimeConfig.BaseURL.String() + r.URL.String() - http.Redirect(w, r, newURL, 301) + + top.Handle(hostname+"/", mux) + + top.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + newURL := runtimeConfig.BaseURL.JoinPath(r.URL.String()) + http.Redirect(w, r, newURL.String(), 301) }) - top.Handle("/", - sentryHandler.Handle( - wrapHandlerWithLogging(mux, wrappedHandlerOptions{ - defaultHostname: runtimeConfig.BaseURL.Hostname(), - }), - ), - ) - // no logging, no sentry top.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) @@ -216,8 +102,14 @@ func New(runtimeConfig *Config) (*Server, error) { listenAddress := net.JoinHostPort(runtimeConfig.ListenAddress, runtimeConfig.Port) return &Server{ &http.Server{ - Addr: listenAddress, - Handler: top, + Addr: listenAddress, + Handler: sentryHandler.Handle( + serverHeaderHandler( + wrapHandlerWithLogging(top, wrappedHandlerOptions{ + defaultHostname: runtimeConfig.BaseURL.Hostname(), + }), + ), + ), }, }, nil } -- cgit 1.4.1