about summary refs log tree commit diff stats
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/server/mime.go22
-rw-r--r--internal/server/server.go166
-rw-r--r--internal/website/filemap.go (renamed from internal/server/filemap.go)2
-rw-r--r--internal/website/mux.go119
4 files changed, 171 insertions, 138 deletions
diff --git a/internal/server/mime.go b/internal/server/mime.go
new file mode 100644
index 0000000..696a0ad
--- /dev/null
+++ b/internal/server/mime.go
@@ -0,0 +1,22 @@
+package server
+
+import (
+	"mime"
+	"website/internal/log"
+)
+
+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 init() {
+	fixupMIMETypes()
+}
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
 }
diff --git a/internal/server/filemap.go b/internal/website/filemap.go
index 6130e65..6daeb18 100644
--- a/internal/server/filemap.go
+++ b/internal/website/filemap.go
@@ -1,4 +1,4 @@
-package server
+package website
 
 import (
 	"fmt"
diff --git a/internal/website/mux.go b/internal/website/mux.go
new file mode 100644
index 0000000..0335f97
--- /dev/null
+++ b/internal/website/mux.go
@@ -0,0 +1,119 @@
+package website
+
+import (
+	"encoding/json"
+	"net/http"
+	"path"
+	"strings"
+	"website/internal/config"
+	"website/internal/log"
+
+	"github.com/benpate/digit"
+	"github.com/pkg/errors"
+)
+
+type HTTPError struct {
+	Error   error
+	Message string
+	Code    int
+}
+
+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
+}
+
+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)
+		}
+	}()
+	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)
+		}
+	}
+}
+
+func NewMux(cfg *config.Config, root string) (mux *http.ServeMux, err error) {
+	mux = &http.ServeMux{}
+
+	prefix := path.Join(root, "public")
+	log.Debug("registering content files", "prefix", prefix)
+	err = registerContentFiles(prefix)
+	if err != nil {
+		return nil, errors.WithMessagef(err, "registering content files")
+	}
+
+	mux.Handle("/", webHandler(func(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", cfg.CSP.String())
+		for k, v := range cfg.Extra.Headers {
+			w.Header().Add(k, v)
+		}
+
+		http.ServeFile(w, r, files[urlPath].filename)
+		return nil
+	}))
+
+	var acctResource = "acct:" + cfg.Email
+	me := digit.NewResource(acctResource).
+		Link("http://openid.net/specs/connect/1.0/issuer", "", cfg.OIDCHost.String())
+
+	mux.HandleFunc("/.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(
+		oidcPath,
+		func(w http.ResponseWriter, r *http.Request) {
+			u := cfg.OIDCHost.JoinPath(oidcPath)
+			http.Redirect(w, r, u.String(), 302)
+		})
+
+	return mux, nil
+}