nikoss
nikoss

Reputation: 3698

Golang httpRouter returns the last response when used with the slice of functions

I am trying to implement expressjs like features to httprouter package . I create a struct type mounter

type Mounter struct {
    BasePath string
    Routes   []*Route
}

and a Route struct which represents subRoutes

type Route struct {
    Path   string
    Method string
    Func   Handle
}

type Handle func(http.ResponseWriter, *http.Request, Params)

type Params interface{}

i have a NewRoutes Function which is the main thing i wanted to port from expressjs new routes does the same thing as express.Router

func NewRoutes(base string) (mounter *Mounter) {
    mounter = &Mounter{
        BasePath: base,
    }
    return
}

and i have get post put delete methods under *Mounter

//GET request handler
func (mounter *Mounter) GET(path string, Func Handle) {
    mounter.Routes = append(mounter.Routes, &Route{path, "get", Func})
}

//POST request handler
func (mounter *Mounter) POST(path string, Func Handle) {
    mounter.Routes = append(mounter.Routes, &Route{path, "post", Func})
}

//PUT request handler
func (mounter *Mounter) PUT(path string, Func Handle) {
    mounter.Routes = append(mounter.Routes, &Route{path, "put", Func})
}

//DELETE request handler
func (mounter *Mounter) DELETE(path string, Func Handle) {
    mounter.Routes = append(mounter.Routes, &Route{path, "delete", Func})
}

and finally i have a Mount method which mounts the router to the actual router

func (mounter *Mounter) Mount(router *rtr.Router) {
    mounter.BasePath = strings.TrimSuffix(mounter.BasePath, "/")
    for _, route := range mounter.Routes {
        path := route.Path
        if !strings.HasSuffix(path, "/") {
            path += "/"
        }
        path = mounter.BasePath + path
        switch strings.ToLower(route.Method) {
        case "get":
            router.GET(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
                route.Func(res, req, params)
            })
        case "post":
            router.POST(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
                route.Func(res, req, params)
            })
        case "delete":
            router.DELETE(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
                route.Func(res, req, params)
            })
        case "put":
            router.PUT(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
                route.Func(res, req, params)
            })
        }
    }
}

everything works pretty nice and the methods are working fine too if i try to send a post request to a get endpoint it gives a nice 404 but the only issue is it always responds with the handler of last added member regardless of subpath so

package api
var ApiRouter = express.NewRoutes("/api/")

func init() {
    ApiRouter.GET("/", func(res http.ResponseWriter, req *http.Request, _ express.Params) {
        fmt.Fprintln(res, "testget/")
    })
    ApiRouter.GET("/pt", func(res http.ResponseWriter, req *http.Request, _ express.Params) {
        fmt.Fprintln(res, "pt")
    })
    ApiRouter.POST("/test", func(res http.ResponseWriter, req *http.Request, _ express.Params) {
        fmt.Fprintln(res, "test/post")
    })
}

package main
func main() {
    router := express.New()
    api.ApiRouter.Mount(router)
    for _, route := range api.ApiRouter.Routes {
        fmt.Println(*route)
    }
    router.ServeFiles("/public/*filepath", http.Dir("./public/"))
    http.ListenAndServe(":1024", router)
}

Will always respond test/post and the output of the range i am doing above for test purposes is So do you have any idea why it uses always the same function to respond but recognizes paths perfectly?

Upvotes: 0

Views: 464

Answers (1)

putu
putu

Reputation: 6454

The problem lies in Mount method, which is known as Iteration Variables and Closures. Declare a new variable for capturing route e.g.

thisRoute := route //capture iteration variable
switch strings.ToLower(route.Method) {
case "get":
    router.GET(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
        thisRoute.Func(res, req, params)
    })
case "post":
    router.POST(path, func(res http.ResponseWriter, req *http.Request, params rtr.Params) {
        thisRoute.Func(res, req, params)
    })
//...
}

Upvotes: 1

Related Questions