I am familiar with the Go middleware pattern like this:
// Pattern for writing HTTP middleware.
func middlewareHandler(next http.Handler) http.Handler {
The most flexible solution would be like this:
First define a type that matches your handler signature and implement ServeHTTP
to satisfy the http.Handler interface. By doing so, ServeHTTP
will be able to call the handler function and process the error if it fails. Something like:
type httpHandlerWithError func(http.ResponseWriter, *http.Request) error
func (fn httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
http.Error(w, err.Message, err.StatusCode)
}
}
Now create the middleware as usual. The middleware should create a function which returns an error if it fails or calls the next in the chain on success. Then convert the function to the defined type something like:
func AuthMiddleware(next http.Handler) http.Handler {
// create handler which returns error
fn := func(w http.ResponseWriter, r *http.Request) error {
//a custom error value
unauthorizedError := &httpError{Code: http.StatusUnauthorized, Message: http.StatusText(http.StatusUnauthorized)}
auth := r.Header.Get("authorization")
creds := credentialsFromHeader(auth)
if creds != nil {
return unauthorizedError
}
user, err := db.ReadUser(creds.username)
if err != nil {
return &httpError{Code: http.StatusInternalServerError, Message: http.StatusText(http.StatusInternalServerError)}
}
err = checkPassword(creds.password+user.Salt, user.Hash)
if err != nil {
return unauthorizedError
}
ctx := r.Context()
userCtx := UserToCtx(ctx, user)
// we got here so there was no error
next.ServeHTTP(w, r.WithContext(userCtx))
return nil
}
// convert function
return httpHandlerWithError(fn)
}
Now you can use the middleware as you would use any regular middleware.