all repos — homestead @ 728423eaeb39d3db2fdab31f65a3dc503542762d

Code for my website

refactor: split server and mux

Alan Pearce
commit

728423eaeb39d3db2fdab31f65a3dc503542762d

parent

9814f620a81f44a80af2017d8dee2343c6dd4f22

1 file changed, 119 insertions(+), 0 deletions(-)

changed files
A 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 +}