user693747
user693747

Reputation: 31

Get gorilla/mux router current route name from middleware

Problem: Unable to access mux.CurrentRoute(r).GetName() from middleware. (Although I had been able to access it from my middleware, I had to change the way my middleware works due to it's previous inability to access the request). So I've mucked something up and I'm not sure how to get back to a working state where I can access the route name.

Any help would be much appreciated!

Error:

runtime error: invalid memory address or nil pointer dereference 

Code:

func main() {
    var (
        err          error
        r            *mux.Router
        devRouter    *mux.Router
        usersRouter  *mux.Router
        brandsRouter *mux.Router
    )
    defer db.Close()
    defer store.Close()

    r = mux.NewRouter()
    devRouter = r.PathPrefix("/api/v1/dev").Subrouter()
    usersRouter = r.PathPrefix("/api/v1/users").Subrouter()
    brandsRouter = r.PathPrefix("/api/v1/brands").Subrouter()

    // development endpoints
    devRouter.HandleFunc("/db/seed", devDbSeed)
    ...

    // users
    usersRouter.HandleFunc("/create", usersCreateHandlerFunc).Methods("POST").Name("USERS_CREATE")
    ...

    // brands
    brandsRouter.HandleFunc("/create", brandsCreateHandlerFunc).Methods("POST").Name("BRANDS_CREATE")
    ...

    // products
    brandsRouter.HandleFunc("/{brand_id:[0-9]+}/products", brandsProductsListHandlerFunc).Methods("GET").Name("BRANDS_PRODUCTS_LIST")
    ...

    // mwAuthorize and mwAuthenticate basically work the same
    mw := []func(http.Handler) http.Handler{mwAuthenticate, mwAuthorize}
    http.Handle("/", use(r, mw...))
    err = http.ListenAndServe(":9000", nil)
    if err != nil {
         logIt(err)
    }
}

func use(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
    // exec order: mw[0],mw[1],mw[N]...
    for i := len(mw) - 1; i >= 0; i-- {
        h = mw[i](h)
    }
    return h
}

func mwAuthorize(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
         if true != authorize(r) {
            w.WriteHeader(http.StatusForbidden)
            return
         } else {
            next.ServeHTTP(w, r)
         }
     })
}

func authorize(r *http.Request) (isAuthorized bool) {
    isAuthorized = false
    /**
       This is where it's failing!
    */
    routeName := mux.CurrentRoute(r).GetName()
    switch routeName {
    case "USERS_CREATE":
        // route-specific authorization
        break
    ...
    default:
        break
    }
    return
}

Update (2015-01-04 @ 4:49PM EST): So after removing the middleware (or at least commenting out the section that's trying to read mux.CurrentRoute) I am able to retrieve the route name from the destination handlerfunc (ex: usersCreateHandlerFunc or brandsCreateHandlerFunc). This doesn't solve my problem (I'd still like to perform authentication/authorization in middleware as opposed to every handlerfunc), I have a hunch it's letting me know *mux.Router isn't available in my middleware until after the final .ServeHTTP call. (Or something along those lines...)

Update (2015-01-04 @ 5:41PM EST): Tried a different (albeit less-preferred) direction of using Negroni as the middleware component. Still getting nil-pointer error when I try to get mux.CurrentRoute.

Update (2015-01-04 @ 6:17PM EST): I am able to access the request (ex: r.URL) from the middleware func's, but still no luck on accessing the mux.Route (ex: mux.CurrentRoute(r)). After looking a bit more at the mux source, I think it's because the current mux context isn't getting set because the router hasn't executed the matcher yet (and therefore it doesn't know what route it's currently on until AFTER the middleware is complete). However, I'm still not sure how to either resolve this, or re-structure my code to handle this.

Upvotes: 3

Views: 7245

Answers (3)

Parzibyte
Parzibyte

Reputation: 1598

What about:

routeName := mux.CurrentRoute(r).GetName()

Where r is the *http.Request. Don't forget to import "github.com/gorilla/mux". Remember that in order to use this, you must give you route a name when you define it

Upvotes: 3

jayme
jayme

Reputation: 1316

From CurrentRoute godoc:

CurrentRoute returns the matched route for the current request, if any. This only works when called inside the handler of the matched route because the matched route is stored in the request context[...]

In your example, your chain of mwAuthenticate, mwAuthorize is attached to the route "/" without using gorilla mux. That means when the request passes your handlers, it has not passed gorilla mux router.

Try the following (your example stripped down):

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

var (
    err          error
    r            *mux.Router
    devRouter    *mux.Router
)

func devDbSeed(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "devDbSeed")
    return
}

func main() {
    r = mux.NewRouter()
    devRouter = r.PathPrefix("/api/v1/dev").Subrouter()

    // mwAuthorize and mwAuthenticate basically work the same
    mw := []func(http.Handler) http.Handler{mwAuthenticate, mwAuthorize}

    // development endpoints
    devRouter.Handle("/db/seed", use(http.HandlerFunc(devDbSeed), mw...)).Name("foo")

    // Send all requests into the mux router
    err = http.ListenAndServe(":9000", r)
    if err != nil {
        log.Fatal(err)
    }
}

func use(h http.Handler, mw ...func(http.Handler) http.Handler) http.Handler {
    // exec order: mw[0],mw[1],mw[N]...
    for i := len(mw) - 1; i >= 0; i-- {
        h = mw[i](h)
    }
    return h
}

func mwAuthorize(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !authorize(r) {
            w.WriteHeader(http.StatusForbidden)
            return
        }
        next.ServeHTTP(w, r)
    })
}
func mwAuthenticate(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        next.ServeHTTP(w, r)
    })
}

func authorize(r *http.Request) (isAuthorized bool) {
    isAuthorized = false

    handlerName := "UNKNOWN"
    if route := mux.CurrentRoute(r); route != nil {
        routeName := route.GetName()
        if routeName != "" {
            handlerName = routeName
        }
    }

    log.Println(handlerName)
    switch handlerName {
    case "USERS_CREATE":
        // route-specific authorization
        log.Println("USERS_CREATE")
        break
    default:
        break
    }
    return
}

Upvotes: 2

Iacob Vlad-George
Iacob Vlad-George

Reputation: 29

I had the same problem and I resolved in that way:

var match mux.RouteMatch
routeExists := s.Router.Match(r, &match)
if routeExists && match.Route.GetName(){
    routeName := match.Route.GetName()
}

And when I defined the route I added .Name("route/:param") where route/:param is my route.

Upvotes: 0

Related Questions