go-chi / chi

lightweight, idiomatic and composable router for building Go HTTP services
https://go-chi.io
MIT License
18.52k stars 988 forks source link

URL params are not available inside middleware on the root router #892

Open dennisvanderweide opened 10 months ago

dennisvanderweide commented 10 months ago

When I create a middleware to log the id URL parameter, which exists in the route I request, the value is always empty. I do get a value inside the handler itself

r := chi.NewRouter()

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

r.Get("/test/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := router.Param(r, "id").String()
    io.WriteString(w, id)
})

When I move this code to a Group, the URL param will become available inside the middleware.

r := chi.NewRouter()

r.Group(func(r chi.Router) {
    r.Use(func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            id := chi.URLParam(r, "id")
            log.Println(id)
            h.ServeHTTP(w, r)
        })
    })

    r.Get("/test/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := router.Param(r, "id").String()
        io.WriteString(w, id)
    })
})

I think this behavior is quite strange and prone to errors

Tested with the latest version (5.0.11)

erwinschaap commented 9 months ago

I have the same issue

sakthi-lucia0567 commented 7 months ago

This behavior is due to the way the chi router handles middleware and route patterns. When you register a middleware using r.Use() at the root level, the middleware is applied to all routes, including routes that do not have any URL parameters. As a result, when the middleware tries to extract the "id" parameter using chi.URLParam(r, "id"), it doesn't find it because the current route doesn't have an "id" parameter.

dennisvanderweide commented 6 months ago

The behavior you describe would be what I would expect to be the behavior, but the problem is that when I execute an HTTP request for a route which does have an id parameter, it won't be able to find the parameter even though the route does contain a parameter.

ning-kang commented 5 months ago

mx.URLParams gets populated only at the routeHTTP function which will not get the id param if your middleware is at the top level (* route). Try this instead:

v1Router := chi.NewRouter()
v1Router.Route("/test/{id}", func(r chi.Router) {
    r.Use(Middleware)
    r.Get("/", handler.getHandler)
    r.Patch("/", handler.updateHandler)
    r.Delete("/", handler.deleteHandler)
})
jfcdigitalventures commented 2 months ago

Thanks! That works but is super optimal. I need to repeat the middleware call for every endpoint group.

Example:

v1Router := chi.NewRouter()
v1Router.Route("/test/{id}", func(r chi.Router) {
    r.Use(Middleware)
    r.Get("/", handler.getHandler)
    r.Patch("/", handler.updateHandler)
    r.Delete("/", handler.deleteHandler)
})

v1Router := chi.NewRouter()
v1Router.Route("/another-test/{id}", func(r chi.Router) {
    r.Use(Middleware)
    r.Get("/", handler.getHandler)
    r.Patch("/", handler.updateHandler)
    r.Delete("/", handler.deleteHandler)
})

That is a problem because I want to ensure that the middleware (authZ) is called on every request. Do I miss anything here or is there a better way to get the URL params in the middleware (I need the params to make a call on the authZ).

dennisvanderweide commented 2 months ago

When I create a middleware to log the id URL parameter, which exists in the route I request, the value is always empty. I do get a value inside the handler itself

r := chi.NewRouter()

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

r.Get("/test/{id}", func(w http.ResponseWriter, r *http.Request) {
  id := router.Param(r, "id").String()
  io.WriteString(w, id)
})

When I move this code to a Group, the URL param will become available inside the middleware.

r := chi.NewRouter()

r.Group(func(r chi.Router) {
  r.Use(func(h http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          id := chi.URLParam(r, "id")
          log.Println(id)
          h.ServeHTTP(w, r)
      })
  })

  r.Get("/test/{id}", func(w http.ResponseWriter, r *http.Request) {
      id := router.Param(r, "id").String()
      io.WriteString(w, id)
  })
})

I think this behavior is quite strange and prone to errors

Tested with the latest version (5.0.11)

@jfcdigitalventures You could wrap everything in a Group just like in my example here, but it's a workaround, not a fix. I still think it's weird that a middleware on the root behaves different than middleware on every other level. My suggestion would be to fix this, so this behaviour will be the same whenever you use a middleware.