Different middleware for different routes in negroni - go

I want to have different middleware for different path. My current implementation is from this link
UserRouter := mux.NewRouter().StrictSlash(true)
AdminRouter := mux.NewRouter().StrictSlash(true)
Router.HandleFunc("/apps/{app_name}/xyz", Handler).Methods("GET")
I created three different routers, so that I can assosiate them with different path and middleware
nUserPath := negroni.New(middleware.NewAuthMiddleWare())
nUserPath.UseHandler(UserRouter)
nAdminPath := negroni.New()
nAdminPath.UseHandler(AdminRouter)
I created two different negroni instances and passed them the respective routers. As I wanted all this to run part of the same application on the same port so I created a Wrapper Router and negroni instance and associated them with the existing like below
BaseRouter := mux.NewRouter().StrictSlash(true)
BaseRouter.Handle(UserBasePath,nUserPath) // UserBasePath is `/apps`
BaseRouter.Handle(HealthCheck,nUserPath) // HealthCheck is `/health`
BaseRouter.Handle(AdminBasePath,nAdminPath) // AdminBasePath is `/Admin`
n := negroni.New(middleware.NewLogger()) // attached other common middleware here
n.UseHandler(router.BaseRouter)
n.Run(":8080")
Issues faced in this approach:
When I run /health it runs properly but when I run /apps/{app_name}/something I get a 404: Not Found
Note : I went through other approaches mentioned in below link but they don't satisfy my need.
- Route-specific Middlewares with Negroni

So, the issue with the above implementation is that BaseRouter.Handle() method take a path and not a path_matcher/template so all the url's which has path_length more than one were not working.
I figured out two ways to achieve what I needed:
First approach
// Create a rootRouter
var rootRouter *mux.Router = mux.NewRouter()
// Create as many subRouter you want with some prefix
var appsBasePath string = "/apps"
var adminBasePath string = "/admin"
upRouter := rootRouter.PathPrefix(appsBasePath).Subrouter()
apRouter := rootRouter.PathPrefix(adminBasePath).Subrouter()
// Register all the paths and mention middleware specifically for all of them
// Here middleware is a method with signature as
// func middleware( http.Handler) http.HandlerFunc {}
upRouter.Path("/test").Methods("POST").Handler(middleware(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
fmt.Fprintf(w, "Welcome to the home page!")
})))
n := negroni.New(middleware.NewLogger()) // attached other common middleware here
n.UseHandler(rootRouter)
n.Run(":8080")
Second approach
This is extension/solution of the original issue in the question
// Replace BaseRouter.handle() as below
// as PathPrefix takes a template so it won't have issue that we were facing
BaseRouter.PathPrefix(UserBasePath).Handler(nUserPath)
Thing to remember here is that within negroni nUserPath RequestContext of the middleware attached will be different from that of the actual router's HandlerMethod
Note:
By path length I mean something like this -- /abc or /abc/ has path_length=1 and /abc/xyz has path_length=2

Related

How to nest routers in Go julienschmidt/httprouter?

I want to expose the following URLs from my service:
GET /api/foo
GET /api/bar
I also want to structure it as a router nested inside another. The toplevel router will match all requests to /api and serve them with the nested router which will match requests to /foo and /bar. Basically, namespacing.
I could just have a single router and give the /api prefix to both the routes:
router.GET("/api/foo", apiFoo)
router.GET("/api/bar", apiBar)
But I would like the convenience of having a single router for all routes inside the /api prefix so that I can add appropriate middlewares to them all with a single function call.
Here's what I tried:
package main
import (
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func apiFoo(w http.ResponseWriter, r *http.Request) {}
func apiBar(w http.ResponseWriter, r *http.Request) {}
func main() {
api := httprouter.New()
api.HandlerFunc(http.MethodGet, "/foo", apiFoo)
api.HandlerFunc(http.MethodGet, "/bar", apiBar)
router := httprouter.New()
router.Handler(http.MethodGet, "/api", api)
log.Fatal(http.ListenAndServe(":8080", router))
}
However, getting 404 not found on going to http://localhost:8080/api/foo or http://localhost:8080/api/bar
I had thought that nested routers would work because routers implement the http.Handler interface. What am I missing?
httprouter does not concatenate the path, so you can't do it that way. Both routers will just inspect the request path and act accordingly.
There is an open pull request for 7 years, that would implement it. You could have a look there and implement similar logic yourself, the PR is based on concatenating the path. Maybe you can write a small helper function for that.
If you are willing to switch the router package, you could look into alternatives such as chi, which support router groups.

Difference between middleware chi.Use vs chi.With

What is the difference between chi.Use and chi.With when setting up a middleware with Chi router.
Use must be declared before all routes under the same group, whereas r.With allows you to "inline" middlewares.
As a matter of fact, the function signatures are different. Use returns nothing, With returns a chi.Router.
Let's say you have a route and want to add a middleware only to one of them, you would use r.With:
r.Route("/myroute", func(r chi.Router) {
r.Use(someMiddleware) // can declare it here
r.Get("/bar", handlerBar)
r.Put("/baz", handlerBaz)
// r.Use(someMiddleware) // can NOT declare it here
}
r.Route("/other-route", func(r chi.Router) {
r.Get("/alpha", handlerBar)
r.Put("/beta", handlerBaz)
r.With(someMiddleware).Get("/gamma", handlerQuux)
}
In the first example, someMiddleware is declared for all sub-routes, whereas in the second example r.With allows you to add a middleware only for the /other-route/gamma route.
According to the documentation of chi.Use and chi.With.
Use appends a middleware handler to the Mux middleware stack.
The middleware stack for any Mux will execute before searching for a matching route to a specific handler, which provides opportunity to respond early, change the course of the request execution, or set request-scoped values for the next http.Handler.
With adds inline middlewares for an endpoint handler.
Let see how chi.Use and chi.With example
The use case is pretty straight forward with chi.Use the registered middleware will run before all the routes handler which are register with the Router
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
For eg: here Logger middleware will be called before all the register routes handler.
whereas with chi.With you are returned new route on which the middleware would be ran so if any routes is registered on the returned Router the registered middleware will run. Here the use case is very specific suppose if you want to run a specific middleware for a group of routes or want to perform some operation for specific routes then for the case you can use chi.Use
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", listArticles) // GET /articles
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017
r.Post("/", createArticle) // POST /articles
r.Get("/search", searchArticles) // GET /articles/search
// Regexp url parameters:
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
// Subrouters:
r.Route("/{articleID}", func(r chi.Router) {
r.Use(ArticleCtx)
r.Get("/", getArticle) // GET /articles/123
r.Put("/", updateArticle) // PUT /articles/123
r.Delete("/", deleteArticle) // DELETE /articles/123
})
})
In the above example the paginate middleware will only be called for all the articles with /articles/ and /{month}-{day}-{year} day wise route for other routes chi.With won't be called if there any middlware registered with chi.Use over main route then that would be called.

Attach middleware to routes mounted to the router

This might just be because of my inexperience, but I've taken over a webserver solution written in Go and I have some issues.
The routing is set up in such a way that each "top" route is mounted to the router with router.Mount() with a handler attached to each mounted route. Example:
router.Mount("/group", (handler.NewGroupHandler(groupSrv, render)).Router())
So, within this mounted route I want to add a middleware to get a specific URL parameter. I've tried around, and followed this guide https://medium.com/#szablowska.patrycja/chi-and-missing-urlparam-in-middleware-9435c48a063b which seems to have worked for other. However, this haven't worked for me, which I now suspect is because of the approach with mounting.
The idea of the middleware is quite simple, just check the URL param against some requirements. I could of course do this manually in every path, but a middleware would be better both for readability and future development. How the middleware is written:
func GroupMiddleware() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
groupIDStr := chi.URLParam(r, "group_id")
if checkAgainstGroupID {
// Do something, raise error
} else {
next.ServeHTTP(w, r)
}
}
return http.HandlerFunc(fn)
}
}
And then applied like this:
r.With(subhandler.GroupMiddleware).Route("/{group_id}", func(r chi.Router) {
r.Get("/", h.get)
})
Which obviously doesn't work. Any help would be appreciated because I can't believe that mounting routes makes it impossible to add middleware to subgroups.
Edit: This worked perfectly, it was just me that didn't register that I called an unaffected route. So in case someone else should end up with this same question, know that this actually is a valid approach.

Golang request middleware library?

I'm using gorrilla mux for my mux for my routing setup.
Is there any open source library that will provide some sort of request middleware?
router.HandleFunc("/products", GetProducts).Methods("GET")
So currently I have the GetProducts function that will return the products etc.
But this is a REST api that I am building, so I have to handle things like loading the user, verifying the 'api token' for the request etc.
I don't want to do this for each and every method so I was hoping I there was some request middleware when I can do this before/after execution, along with adding things like User, Permissions to the context in each middleware function.
You can use Go Gin HTTP web framework that supports middlewares as well as you want:
Using middleware:
func main() {
// Creates a router without any middleware by default
r := gin.New()
// Global middleware
// Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release.
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire.
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// Authorization group
// authorized := r.Group("/", AuthRequired())
// exactly the same as:
authorized := r.Group("/")
// per group middleware! in this case we use the custom created
// AuthRequired() middleware just in the "authorized" group.
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
// nested group
testing := authorized.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}

Testing handlers that use Gorilla/context

I'm using gorilla/context in a web app. The example in the docs looks like:
func MyHandler(w http.ResponseWriter, r *http.Request) {
//...
val := context.Get(r, foo.MyKey)
//...
}
How can I unit test a handler that works like this? The only way I've managed so far is to use the context package inside my tests. I'm thinking at the moment that I could inject a context parameter into the handler but then I'm not conforming to the HandlerFunc interface.
This is a classic cross-cutting concerns example.
You are using a 3rd party to magically handle input params for your unit under test. By that very definition, you are going to have to do some extra setup to prep the context for the state you want.
When it comes to Go http handlers (which the convention is to KISS it), you shouldn't need to "reach out of context" of your func to get extra data: keep all the data you need within your single func.
Personally, I try to avoid corrupting my handlers like this. I think I've only used gorilla's context once out of the dozens of big sites I've built. And that was basically to get around a cached response, just to keep the data refesh to the end user. Of which I simply ignored in my unit tests, as it was out-of-scrope of what I was testing.
Instead, I use middle-ware wrappers to setup the basics I want in my handles and modify the handler's signature accordingly.
caching
logging
authentication and authorization
json marshaling
context (e.g. expected User{} object loaded from DB)
...etc. I would create a middle-ware that wraps your handler when you register it with mux that uses gorilla/context to lookup your cookie or userid or something, hidrates that user object from cache, DB, redis, etc, and then calls your handler that would have a signature like:
func MyHandler(u User, p Page, w http.ResponseWriter, r *http.Request) {
// u is the User object loaded from middle-ware
// p is your global Page object... etc
}
That way, your unit tests inject only the items you need under test.
And you can integration test your middle-ware on a QA server with expected User and Page objects in a datastore.
The way my team does it is to add a name to the route handler and then in the tests we call that route by name.
This is how to add a route:
r.HandleFunc("/<route>", MyHandler).Methods("GET").Name("MyHandlerByName")
Then this is how to test it
r.Get("MyHandlerByName")
One way to test handles is modify the way in which they are created. for example, Creating a function that return a http.HandlerFunc, this function can have parameters. You can mock the values that you send to the function
Without parameters
func State() http.HandlerFunc {
return http.HandlerFunc(func(pResponse http.ResponseWriter, r *http.Request) {
// your code
})
}
With Parameters
func State(pParam1,pParam2,pParam3 ...) http.HandlerFunc {
return http.HandlerFunc(func(pResponse http.ResponseWriter, r *http.Request) {
// your code using pParam1,pParam2,pParam3
})
}
The mapping will be
http.HandleFunc("/State", State())
or
http.HandleFunc("/State", State(value1,value2,value3 ....))

Resources