3

I use a specific middleware for specific set of routes

r.Route("/platform", func(r chi.Router) {
    r.Use(authService.AuthMiddleware)
    r.Get("/{id}/latest", RequestPlatformVersion)
})

Now how can I access id url param inside this AuthMiddleware middleware

func (s *Service) AuthMiddleware(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(chi.URLParam(r, "id"))
        id := chi.URLParam(r, "id")
        
        if id > 100 {
          http.Error(w, errors.New("Error").Error(), http.StatusUnauthorized)
          return
        }
    }
    return http.HandlerFunc(fn)
}

However, the id param prints as an empty string even though the middleware is being ran and a specific route is being called

Thidasa Pankaja
  • 930
  • 8
  • 25
  • 44

1 Answers1

6

You put your chi.URLParam before the path param {id} and you forgot to put .ServeHTTP(w, r) at the middleware. If you don't put that thing, your request will not go inside the path inside the route.

this is the working example:

package main

import (
    "fmt"
    "net/http"

    "github.com/go-chi/chi"
)

func AuthMiddleware(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        fmt.Println(chi.URLParam(r, "id"))
        h.ServeHTTP(w, r)
    }
    return http.HandlerFunc(fn)
}

func main() {
    r := chi.NewRouter()

    r.Route("/platform/{id}", func(r chi.Router) {
        r.Use(AuthMiddleware)
        r.Get("/latest", func(rw http.ResponseWriter, r *http.Request) {
            fmt.Println("here ", chi.URLParam(r, "id")) // <- here
        })
    })

    http.ListenAndServe(":8080", r)
}

I move the {id} to platform/{id} so the middleware got the id path value, and add h.ServeHTTP(w, r) inside the middleware.

try to access http://localhost:8080/platform/1/latest

the output will be:

1
here  1

UPDATE

It is not good to run the validation after the code, you must fix the way you define the path, and move the .ServeHTTP after the validation.

This is the example:

package main

import (
    "errors"
    "fmt"
    "net/http"
    "strconv"

    "github.com/go-chi/chi"
)

func AuthMiddleware(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("Middleware First, id: %+v\n", chi.URLParam(r, "id"))
        id, _ := strconv.Atoi(chi.URLParam(r, "id"))

        if id > 100 {
            http.Error(w, errors.New("Error").Error(), http.StatusUnauthorized)
            return
        }
        h.ServeHTTP(w, r)
    }
    return http.HandlerFunc(fn)
}

func main() {
    r := chi.NewRouter()

    // This works too ()
    // r.Route("/platform/{id}", func(r chi.Router) {
    //  r.Use(AuthMiddleware)
    //  r.Get("/latest", func(rw http.ResponseWriter, r *http.Request) {
    //      fmt.Println("second: ", chi.URLParam(r, "id")) // <- here
    //  })
    // })

    // Other Solution (Wrapping Middleware)
    r.Route("/platform", func(r chi.Router) {
        r.Get("/{id}/latest", AuthMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
            fmt.Println("second: ", chi.URLParam(r, "id")) // <- here
        })).ServeHTTP)
    })

    http.ListenAndServe(":8080", r)
}
Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
David Yappeter
  • 1,434
  • 3
  • 15
  • I had used ``h.ServeHTTP(w, r)`` inside the middleware. But it's only after trying to retrieve url parameters. So, that's why it didn't work. So, the answer should be also using ``h.ServeHTTP(w, r)`` before the ``fmt.Println(chi.URLParam(r, "id"))`` no ? – Thidasa Pankaja Dec 29 '21 at 08:24
  • yes, that one will works. – David Yappeter Dec 29 '21 at 08:28
  • Thanks. But this always returns success, right ? I updated the code block in question, there I need to check the id conditionally and return an error. So, that can't be done, in this case, can it ? – Thidasa Pankaja Dec 29 '21 at 08:41
  • no that can't be done if you write your h.ServeHTTP the `id` check. It is not a good thing, to run the code first then check for validation. To work around it, I have other solution, wrap your `RequestPlatformVersion` with your middleware in the `r.Get` instead of `r.Use` (see the UPDATE part), or you can just update your route to `r.Route("/platform/{id}", ...)` so you can include the middleware, and `h.ServeHTTP()` after the validation – David Yappeter Dec 29 '21 at 09:27
  • 1
    The reason I tried to use middleware was the check should go through for all the paths in ``/platform/{id}`` . But running that validation inside each routes means, I have to add the same logic in multiple routes, no ? – Thidasa Pankaja Dec 30 '21 at 01:13
  • yes, middleware validation inside r.Route(), apply to all route inside r.Route(), if you want to have different validation value validation for each route, I think you can validate it manually inside each route. Personally, I like to use gin Framework instead of Chi, because of validation feature from it. example: https://pastebin.com/0fYZLB35 under the hood of the validation, gin use https://github.com/go-playground/validator – David Yappeter Dec 30 '21 at 03:14
  • Thank you. I ended up adding the validation separately for each route rather than a middleware as you mentioned. Switching from chi to gin is not possible since what I'm working on now is already created api, which I'm adding some features. – Thidasa Pankaja Jan 03 '22 at 02:07
  • @ThidasaPankaja you can also use chi's Group function to apply the same middleware to a set of routes – Goldie Jun 11 '23 at 03:43