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.
Related
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.
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.
My web app has URLs at three access levels:
Those accessible by anyone (login page and static assets)
Those accessible regular users and admins who are logged in
Those accessible only by admins who are logged in
I should specify the minimum access level for each URL pattern in my router, so that people below that level are blocked. (I suppose they should get HTTP error 401 or 403.)
How do I best implement these checks so that I don't have to remember to put them in every URL handler function separately (which is very easy to forget)? Ideally I'd like to do something like this:
router.Get("/someRegularPage", regularAccess(handleSomeRegularPage))
router.Get("/someAdminPage", adminAccess(handleSomeAdminPage))
router.Get("/", publicAccess(handleLoginPage))
Is there some semi-standard middleware to do this and how does that work? How hard would it be to write my own?
Additionally, it would be great if the default permission was to deny access to everybody in case I forget to specify the access level for some URL. A compiler warning or error would be ideal.
Writing your own is not hard. Assuming you store your admin token in an environment variable called ADMINTOKEN :
func AdminOnly(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization")
if r.Method == "OPTIONS" {
f(w, r)
return
}
h := r.Header.Get("Authorization")
token := strings.TrimPrefix(h, "Bearer ")
if token == os.Getenv("ADMINTOKEN") {
f(w, r)
return
}
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
OPTIONS method may have to be authorized regardless because of CORS.
The short answer is to write middleware to handle authorization. A longer answer that may help you out just as much in the long run would be to use appropriate routing paths for your endpoints alongside that middleware. For instance, you could prefix all of your admin routes with /api/admin and then wrap the router for individual routes after that with the relevant admin-auth middleware.
My first stackoverflow question, so please go easy on my naivety about stackoverflow and the question asked, beginner in golang.
I would like to know the difference between the two calls and also simple understanding of the Handle, Handler, HandleFunc, HandlerFunc.
http.Handle("/profile", Logger(profilefunc))
http.HandleFunc("/", HomeFunc)
func main() {
fmt.Println("Starting the server.")
profilefunc := http.HandlerFunc(ProfileFunc)
http.Handle("/profile", Logger(profilefunc))
http.HandleFunc("/", HomeFunc)
http.ListenAndServe("0.0.0.0:8081", nil)
}
func Logger(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
log.Println("Before serving request")
h.ServeHTTP(w, r)
log.Println("After serving request")
})
}
func ProfileFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "You are on the profile page.")
}
func HomeFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Imran Pochi")
}
I would like ... simple understanding of the Handle, Handler, HandleFunc, HandlerFunc.
Handler is an interface that can respond to an HTTP request and has a ServeHTTP(ResponseWriter, *Request) method.
http.Handle registers a Handler to handle HTTP requests matching a given pattern.
http.HandleFunc registers a handler function to handle HTTP requests matching a given pattern. The handler function should be of the form func(ResponseWriter, *Request).
HandlerFunc is an explicit function type of the form func(ResponseWriter, *Request). HandlerFunc has a method ServeHTTP that calls itself . This allows you to cast a function to a HandlerFunc and use it as a Handler.
I would to know the difference between the two calls
http.Handle("/profile", Logger(profilefunc))
http.HandleFunc("/", HomeFunc)
Logger is an example of a middleware, which is a function that takes an http.Handler and it returns another http.Handler that wraps the original handler. When called this handler may (or may not) call the nested http.Handler before and/or after performing some operation. So the first line is saying register the profileFunc Handler wrapped in the Logger middleware with the pattern "/profile". The second line is saying register the HomeFunc function with the "/" pattern.
According to what I've seen from the Go documentation examples:
A Handler is a type created to respond to an HTTP request. To make a type a Handler all one has to do is implement a ServeHTTP() method. The ServeHTTP() method does the actual request processing.
Handle() takes the route and a Handler, the type that has an instance method named ServeHttp(). Note that it just takes the type, there's no need for one to point at the actual method/function that handles the request explicitly.
HandlerFunc is a type that internally implements a ServeHTTP() method. HandlerFunc is used to cast any Go function, with the right signature, to a HandlerFunc. The newly minted HandlerFunc is then passed to the Handle() method together with the route it processes.
Note that the HandlerFunc lets one implement request handlers by just writing functions without the need for a dedicated handler type.
HandleFunc() takes a route and any function that has the right signature. HandleFunc is shorthand for first of all doing the type casting and then passing the function to the Handle() method.
The signature for a request handling function is:
func handlerName(wr http.ResponseWriter, req *http.Request)
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