gorilla / mux

Package gorilla/mux is a powerful HTTP router and URL matcher for building Go web servers with 🦍
https://gorilla.github.io
BSD 3-Clause "New" or "Revised" License
20.93k stars 1.85k forks source link

[BUG] handler and middleware running twice with middleware defined on pointer reciever #756

Closed flipkickmedia closed 7 months ago

flipkickmedia commented 7 months ago

Is there an existing issue for this?

Current Behavior

Add middleware and a handler to a route as per the readme, call the route and the handler and middleware run twice, the output is a concat of the two handlers' output.

Expected Behavior

Middleware should run once, handler should run once

Steps To Reproduce

Create the simple example in the readme and then call a router, the middleware and handler will run twice.

Anything else?

This works as expected:

package main

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

func MiddlewareA(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        referer := r.Header.Get("Origin")
        w.Header().Set("Access-Control-Allow-Origin", referer)
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Cache-Control, Authorization, X-Requested-With")
        w.Header().Set("Access-Control-Allow-Method", "GET, POST,OPTIONS")
        next.ServeHTTP(w, r)
    })
}

func MiddlewareB(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        referer := r.Header.Get("Origin")
        w.Header().Set("Access-Control-Allow-Origin", referer)
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Cache-Control, Authorization, X-Requested-With")
        w.Header().Set("Access-Control-Allow-Method", "GET, POST,OPTIONS")
        next.ServeHTTP(w, r)
    })
}

func HandlerA(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "TestingA")
}

func HandlerB(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "TestingB")
}

func main() {
    router := mux.NewRouter()
    router.Use(MiddlewareA)
    router.Use(MiddlewareB)
    router.HandleFunc("/", HandlerA)
    router.HandleFunc("/test", HandlerB)
    log.Fatal(http.ListenAndServe(":9000", router))
}
tshaw@tshaw-ThinkPad-P14s-Gen-3:~$ curl http://localhost:9000
TestingA
tshaw@tshaw-ThinkPad-P14s-Gen-3:~$ curl http://localhost:9000/
TestingA
tshaw@tshaw-ThinkPad-P14s-Gen-3:~$ curl http://localhost:9000/test
TestingB
tshaw@tshaw-ThinkPad-P14s-Gen-3:~$ curl http://localhost:9000/test/
404 page not found
tshaw@tshaw-ThinkPad-P14s-Gen-3:~$ 

This causes the failure:

func (m *Cors) Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        referer := r.Header.Get("Origin")
        w.Header().Set("Access-Control-Allow-Origin", referer)
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Cache-Control, Authorization, X-Requested-With")
        w.Header().Set("Access-Control-Allow-Method", "GET, POST,OPTIONS")
        next.ServeHTTP(w, r)
    })
}

This problem is caused by passing a function which has a pointer reciever. The fix is to return an anonymous function:

// GetHandler
// Sets the content type to application/json
func (m *Cors) GetHandler() func(next http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            referer := r.Header.Get("Origin")
            w.Header().Set("Access-Control-Allow-Origin", referer)
            w.Header().Set("Access-Control-Allow-Credentials", "true")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, Access-Control-Allow-Origin, Cache-Control, Authorization, X-Requested-With")
            w.Header().Set("Access-Control-Allow-Method", "GET, POST,OPTIONS")
            next.ServeHTTP(w, r)
        })
    }
}

However, I want to access specific struct data in the handler context. Looking at the source code:

type MiddlewareFunc func(http.Handler) http.Handler

I'm implementing that interface so it should work. Looking at the readme https://github.com/gorilla/mux?tab=readme-ov-file#middleware what Im doing is documented but this causes a double response.