all repos — homestead @ b16c1c7338333ad5f9c968f7fa579015d350672a

Code for my website

extract HTTP logic to shared module

Alan Pearce
commit

b16c1c7338333ad5f9c968f7fa579015d350672a

parent

1ac299018401dc48caeb862b4dcc117a7a65e091

M domain/content/publisher/app.godomain/content/publisher/app.go
@@ -7,10 +7,10 @@ "github.com/Southclaws/fault"
"github.com/Southclaws/fault/fmsg" "go.hacdias.com/indielib/indieauth" - ihttp "alin.ovh/homestead/domain/web/middleware" "alin.ovh/homestead/domain/web/server" "alin.ovh/homestead/domain/web/templates" "alin.ovh/homestead/shared/config" + ihttp "alin.ovh/homestead/shared/http" "alin.ovh/x/log" )
M domain/content/publisher/mux.godomain/content/publisher/mux.go
@@ -4,8 +4,8 @@ import (
"net/http" pubtpl "alin.ovh/homestead/domain/content/publisher/templates" - ihttp "alin.ovh/homestead/domain/web/middleware" "alin.ovh/homestead/domain/web/templates" + ihttp "alin.ovh/homestead/shared/http" ) func (app *App) Index(w http.ResponseWriter, _ *http.Request) *ihttp.Error {
D domain/web/middleware/error.go
@@ -1,20 +0,0 @@
-package http - -import ( - "fmt" - "net/http" -) - -type Error struct { - Code int - Message string - Cause error -} - -func (e *Error) Error() string { - if e.Message == "" { - e.Message = http.StatusText(e.Code) - } - - return fmt.Sprintf("%d %s", e.Code, e.Message) -}
D domain/web/middleware/mux.go
@@ -1,50 +0,0 @@
-package http - -import ( - "net/http" - - "alin.ovh/x/log" -) - -type HandleFunc func(http.ResponseWriter, *http.Request) *Error -type Handler interface { - ServeHTTP(http.ResponseWriter, *http.Request) *Error -} -type ErrorHandler func(*Error, http.ResponseWriter, *http.Request) - -type ServeMux struct { - log *log.Logger - errorHandler ErrorHandler - *http.ServeMux -} - -func NewServeMux() *ServeMux { - return &ServeMux{ - ServeMux: http.NewServeMux(), - errorHandler: func(err *Error, w http.ResponseWriter, _ *http.Request) { - http.Error(w, err.Message, err.Code) - }, - } -} - -func (sm *ServeMux) Handle(pattern string, handler Handler) { - sm.HandleFunc(pattern, handler.ServeHTTP) -} - -func (sm *ServeMux) HandleFunc(pattern string, handler HandleFunc) { - sm.ServeMux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { - defer func() { - if fail := recover(); fail != nil { - w.WriteHeader(http.StatusInternalServerError) - sm.log.Error("runtime panic!", "error", fail) - } - }() - if err := handler(w, r); err != nil { - sm.errorHandler(err, w, r) - } - }) -} - -func (sm *ServeMux) HandleError(fn ErrorHandler) { - sm.errorHandler = fn -}
D domain/web/middleware/redirect.go
@@ -1,14 +0,0 @@
-package http - -import ( - "net/http" - "net/url" -) - -func Redirect(w http.ResponseWriter, r *http.Request, url *url.URL, code int) { - http.Redirect(w, r, url.String(), code) -} - -func RedirectHandler(url *url.URL, code int) http.Handler { - return http.RedirectHandler(url.String(), code) -}
M domain/web/mux.godomain/web/mux.go
@@ -9,9 +9,9 @@ "slices"
"strings" calendar "alin.ovh/homestead/domain/calendar/templates" - ihttp "alin.ovh/homestead/domain/web/middleware" "alin.ovh/homestead/domain/web/server" "alin.ovh/homestead/domain/web/templates" + ihttp "alin.ovh/homestead/shared/http" "github.com/kevinpollet/nego" )
@@ -21,18 +21,13 @@ if r.URL.Query().Get("resource") == website.acctResource {
w.Header().Add("Content-Type", "application/jrd+json") w.Header().Add("Access-Control-Allow-Origin", "*") if err := json.NewEncoder(w).Encode(website.me); err != nil { - return &ihttp.Error{ - Code: http.StatusInternalServerError, - Cause: err, - } + return ihttp.InternalServerError("Failed to encode webfinger response", err) } return nil } - return &ihttp.Error{ - Code: http.StatusNotFound, - } + return ihttp.NotFound("Resource not found") } func (website *Website) ErrorHandler(err *ihttp.Error, w http.ResponseWriter, r *http.Request) {
@@ -63,18 +58,12 @@ file, err := website.reader.GetFile(urlPath)
if err != nil { website.log.Warn("Error reading file", "error", err) - return &ihttp.Error{ - Message: "Error reading file", - Code: http.StatusInternalServerError, - } + return ihttp.InternalServerError("Error reading file", err) } if file == nil { website.counter.Count(r, "404") - return &ihttp.Error{ - Message: "File not found", - Code: http.StatusNotFound, - } + return ihttp.NotFound("File not found") } website.counter.Count(r, file.Title) w.Header().Add("ETag", file.Etag)
@@ -133,16 +122,11 @@ case r.URL.Query().Has("go-get") && r.URL.Query().Get("go-get") == "1":
return website.ServeHTTP(w, r) case slices.Contains(website.config.Domains, r.Host): path, _ := website.reader.CanonicalisePath(r.URL.Path) - ihttp.Redirect( - w, - r, - website.config.BaseURL.JoinPath(path), - http.StatusMovedPermanently, - ) + ihttp.PermanentRedirect(w, r, website.config.BaseURL.JoinPath(path)) case re.MatchString(r.Host): url := website.config.BaseURL.JoinPath() url.Host = re.ReplaceAllString(r.Host, replace) - ihttp.Redirect(w, r, url, http.StatusTemporaryRedirect) + ihttp.TemporaryRedirect(w, r, url) case true: http.NotFound(w, r) }
M domain/web/templates/error.godomain/web/templates/error.go
@@ -5,7 +5,7 @@ "strconv"
g "alin.ovh/gomponents" . "alin.ovh/gomponents/html" - http "alin.ovh/homestead/domain/web/middleware" + http "alin.ovh/homestead/shared/http" ) func Error(site SiteSettings, err *http.Error) g.Node {
M domain/web/website.godomain/web/website.go
@@ -14,12 +14,12 @@ "alin.ovh/homestead/domain/analytics/goatcounter"
"alin.ovh/homestead/domain/analytics/nullcounter" "alin.ovh/homestead/domain/calendar" "alin.ovh/homestead/domain/content/fetcher" - ihttp "alin.ovh/homestead/domain/web/middleware" "alin.ovh/homestead/domain/web/server" "alin.ovh/homestead/domain/web/templates" "alin.ovh/homestead/shared/config" "alin.ovh/homestead/shared/events" "alin.ovh/homestead/shared/file" + ihttp "alin.ovh/homestead/shared/http" "alin.ovh/homestead/shared/storage" "alin.ovh/homestead/shared/storage/sqlite" "alin.ovh/x/log"
A shared/http/error.go
@@ -0,0 +1,67 @@
+package http + +import ( + "fmt" + "net/http" +) + +type Error struct { + Code int + Message string + Cause error +} + +func (e *Error) Error() string { + if e.Message == "" { + e.Message = http.StatusText(e.Code) + } + + return fmt.Sprintf("%d %s", e.Code, e.Message) +} + +func NewError(code int, message string, cause error) *Error { + return &Error{ + Code: code, + Message: message, + Cause: cause, + } +} + +func NotFound(message string) *Error { + return &Error{ + Code: http.StatusNotFound, + Message: message, + } +} + +func BadRequest(message string, cause error) *Error { + return &Error{ + Code: http.StatusBadRequest, + Message: message, + Cause: cause, + } +} + +func InternalServerError(message string, cause error) *Error { + return &Error{ + Code: http.StatusInternalServerError, + Message: message, + Cause: cause, + } +} + +func Unauthorized(message string, cause error) *Error { + return &Error{ + Code: http.StatusUnauthorized, + Message: message, + Cause: cause, + } +} + +func Forbidden(message string, cause error) *Error { + return &Error{ + Code: http.StatusForbidden, + Message: message, + Cause: cause, + } +}
A shared/http/mux.go
@@ -0,0 +1,74 @@
+package http + +import ( + "net/http" + + "alin.ovh/x/log" +) + +// HandleFunc is a function that handles an HTTP request and may return an Error. +type HandleFunc func(http.ResponseWriter, *http.Request) *Error + +// Handler is an interface for types that can serve HTTP requests and may return an Error. +type Handler interface { + ServeHTTP(http.ResponseWriter, *http.Request) *Error +} + +// ErrorHandler is a function that handles HTTP errors. +type ErrorHandler func(*Error, http.ResponseWriter, *http.Request) + +// ServeMux is an HTTP request multiplexer with error handling support. +// It matches the URL of each incoming request against a list of registered +// patterns and calls the handler for the pattern that most closely matches the URL. +type ServeMux struct { + log *log.Logger + errorHandler ErrorHandler + *http.ServeMux +} + +// NewServeMux creates a new ServeMux with a default error handler. +func NewServeMux() *ServeMux { + return &ServeMux{ + ServeMux: http.NewServeMux(), + errorHandler: func(err *Error, w http.ResponseWriter, _ *http.Request) { + http.Error(w, err.Message, err.Code) + }, + } +} + +// WithLogger sets the logger for this ServeMux and returns the ServeMux. +func (sm *ServeMux) WithLogger(logger *log.Logger) *ServeMux { + sm.log = logger + + return sm +} + +// Handle registers the handler for the given pattern. +// If the handler returns an error, the ServeMux's error handler will be invoked. +func (sm *ServeMux) Handle(pattern string, handler Handler) { + sm.HandleFunc(pattern, handler.ServeHTTP) +} + +// HandleFunc registers the handler function for the given pattern. +// If the handler returns an error, the ServeMux's error handler will be invoked. +func (sm *ServeMux) HandleFunc(pattern string, handler HandleFunc) { + sm.ServeMux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { + defer func() { + if fail := recover(); fail != nil { + w.WriteHeader(http.StatusInternalServerError) + if sm.log != nil { + sm.log.Error("runtime panic!", "error", fail) + } + } + }() + if err := handler(w, r); err != nil { + sm.errorHandler(err, w, r) + } + }) +} + +// HandleError sets the error handler for this ServeMux. +// The error handler is called whenever a handler returns a non-nil error. +func (sm *ServeMux) HandleError(fn ErrorHandler) { + sm.errorHandler = fn +}
A shared/http/redirect.go
@@ -0,0 +1,22 @@
+package http + +import ( + "net/http" + "net/url" +) + +func Redirect(w http.ResponseWriter, r *http.Request, url *url.URL, code int) { + http.Redirect(w, r, url.String(), code) +} + +func RedirectHandler(url *url.URL, code int) http.Handler { + return http.RedirectHandler(url.String(), code) +} + +func PermanentRedirect(w http.ResponseWriter, r *http.Request, url *url.URL) { + Redirect(w, r, url, http.StatusMovedPermanently) +} + +func TemporaryRedirect(w http.ResponseWriter, r *http.Request, url *url.URL) { + Redirect(w, r, url, http.StatusFound) +}