'Gorilla/mux middleware next.ServeHTTP panic

I'm having difficulty wrapping my head around a custom handler using gorilla/mux. It's actually working as intended, but fails at the very last step of calling next.ServeHTTP(r, w)

I start an http server, and attach middleware:

import (
    "fmt"
    "net/http"
    "github.com/gorilla/mux"
)

func NewServer() (*Server, error) {
    s := &Server{
        // some struct ..
    }
    r := mux.NewRouter()

    // Route all requests to /api3
    r.PathPrefix("/api3")
    r.Methods("GET", "POST", "PUT", "DELETE")

    // Attach middleware, log a thing for each request.
    r.Use(LoggingMiddleware)

    // .. Some user endpoints
    r.Handle("/users", api3.PostUser()).Methods(http.MethodPost)

    // Bind to a port and pass our router in
    err := http.ListenAndServe(":8080", r)
    if err != nil {
        // handle error.. 
    }
    return s, nil
}

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("LOGGING MIDDLEWARE")
        next.ServeHTTP(w, r) <-- panics every time
    })
}

What's strange is that the above will print LOGGING MIDDLEWARE but promptly panics thereafter, referring to the next.ServeHTTP(w, r) line.

This example is almost a 1:1 example of Gorilla/mux's own middleware examples.

I've attempted to attach the middleware at the end of the of the function, before calling http.ListenAndServe, thinking order was an issue. It did not work: The same result appears

PostUser looks like:

func PostUser() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        var req AddUserRequest
        err := json.NewDecoder(r.Body).Decode(&req)
        if err != nil {
            w.WriteHeader(http.StatusBadRequest)
            return
        }

        err = userDb.Add()
        if err != nil {
            w.WriteHeader(http.StatusBadRequest)
            return
        }

        
        w.WriteHeader(http.StatusOK)
        return
    })
}

I suspected the PostUser() endpoint might be the culprit. However, removing everything and having a basic endpoint still reproduces the error, example:

import (
    "fmt"
    "net/http"
    "github.com/gorilla/mux"
)

func NewServer() (*Server, error) {
    s := &Server{
        // some struct ..
    }
    r := mux.NewRouter()

    // Route all requests to /api3
    r.PathPrefix("/api3")
    r.Methods("GET", "POST", "PUT", "DELETE")

    // Attach middleware, log a thing for each request.
    r.Use(LoggingMiddleware)

    // health endpoint
    r.HandleFunc("/health", api3.Health)    
    // Bind to a port and pass our router in
    err := http.ListenAndServe(":8080", r)
    if err != nil {
        // handle error.. 
    }
    return s, nil
}

// Health returns 200 OK if everything is normal
func Health(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    _, _ = fmt.Fprint(w, "OK")
}

The above results in the same. I.e:

curl http://localhost:8081/api3/health
curl: (52) Empty reply from server

With a stacktrace as such:

backend-api  | LOGGING MIDDLEWARE <nil>
backend-api  | 2022/02/15 15:55:22 http: panic serving 192.168.80.1:59370: runtime error: invalid memory address or nil pointer dereference
backend-api  | goroutine 14 [running]:
backend-api  | net/http.(*conn).serve.func1(0xc0001dc0a0)
backend-api  |  /usr/local/go/src/net/http/server.go:1805 +0x153
backend-api  | panic(0x8bb2e0, 0xc40c70)
backend-api  |  /usr/local/go/src/runtime/panic.go:971 +0x499


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source