Error handling
Handlers that return an error
Section titled “Handlers that return an error”Writing the error response in every handler gets repetitive. Give handlers an error result instead, and turn errors into responses in one place.
Because Handle takes any http.Handler, a custom handler type registers like any other:
// Command app-handler uses handlers that return an error, turned into a response in one place.// Handle takes any http.Handler, so a custom handler type registers like any other.package main
import ( "encoding/json" "errors" "log" "log/slog" "net/http" "time"
"github.com/vardius/gorouter/v5")
// appHandler is a handler that can fail. Its ServeHTTP writes the error response.type appHandler func(http.ResponseWriter, *http.Request) error
func (h appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { err := h(w, r) if err == nil { return }
if he, ok := errors.AsType[*httpError](err); ok { http.Error(w, he.message, he.status) return } slog.Error("handler failed", "path", r.URL.Path, "error", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)}
// httpError is an error that carries the response the client should get.type httpError struct { status int message string}
func (e *httpError) Error() string { return e.message }
type user struct { ID string `json:"id"` Name string `json:"name"`}
var users = map[string]user{"1": {ID: "1", Name: "Gopher"}}
func newRouter() *gorouter.Router { r := gorouter.New() r.Handle(http.MethodGet, "/users/{id}", appHandler(getUser))
return r}
func getUser(w http.ResponseWriter, r *http.Request) error { u, ok := users[r.PathValue("id")] if !ok { return &httpError{status: http.StatusNotFound, message: "no such user"} }
w.Header().Set("Content-Type", "application/json") return json.NewEncoder(w).Encode(u)}
func main() { srv := &http.Server{Addr: ":8080", Handler: newRouter(), ReadHeaderTimeout: 5 * time.Second} log.Fatal(srv.ListenAndServe())}The helpers like GET take a plain handler func; use Handle with the method for custom handler types.
Recovering from panics
Section titled “Recovering from panics”A panic in a handler would otherwise end in a dropped connection. Wrap the whole router so every route is covered:
func recoverPanics(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { slog.Error("panic", "path", r.URL.Path, "error", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() next.ServeHTTP(w, r) })}
r := gorouter.New(gorouter.WithMiddleware(recoverPanics))Middleware has a complete example with logging.