I am using Goji (https://github.com/zenazn/goji) and would like to define groups of routes that have their own middleware. For example, all paths under /company
should use an LDAP authentication and have a middleware defined to do this. All paths under /external
use a different type of authentication so they have a different middleware definition. But this is a single application served on the same port, so I don't want to create separate web services altogether -- just the paths (and some specific routes) may use different middleware.
All the examples I've seen with Goji are using a single set of middleware for all routes, so I am not sure how to accomplish this in a clean way. Additionally it would be nice if I could specify a base path for all routes within a route group, similar to how I've seen in some other routing frameworks.
Am I missing this functionality in the Goji library (or net/http by extension) that allows me to group routes together and have each group use its own middleware stack?
What I would like to achieve is something like this (psedocode):
// Use an LDAP authenticator for:
// GET /company/employees
// and
// POST /company/records
companyGroup = &RouteGroup{"basePath": "/company"}
companyGroup.Use(LDAPAuthenticator)
companyGroup.Add(goji.Get("/employees", Employees.ListAll))
companyGroup.Add(goji.Post("/records", Records.Create))
// Use a special external user authenticator for: GET /external/products
externalGroup = &RouteGroup{"basePath": "/external"}
externalGroup.Use(ExternalUserAuthenticator)
externalGroup.Add(goji.Get("/products", Products.ListAll))
You should be able to solve your problem with something like this:
// Use an LDAP authenticator
companyGroup := web.New()
companyGroup.Use(LDAPAuthenticator)
companyGroup.Get("/company/employees", Employees.ListAll)
companyGroup.Post("/company/records", Records.Create)
goji.Handle("/company/*", companyGroup)
// Use a special external user authenticator for: GET /external/products
externalGroup := web.New()
externalGroup.Use(ExternalUserAuthenticator)
externalGroup.Get("/external/products", Products.ListAll)
goji.Handle("/external/*", externalGroup)
You need to give each group its own web
. Just keep in mind you need to specify the full path within the group members.
Greg R's response sums it up nicely (and is the correct answer), but I'll show you an approach that lets you 'avoid' (cheat!) having to specify the full route.
Part of why Goji's router is fast is that it compiles everything on start-up, so routes need to know their full path - but we can provide that at a higher level by writing functions that take a "prefix" and return a router.
package main
import (
"github.com/zenazn/goji/graceful"
"github.com/zenazn/goji/web"
"net/http"
)
func GetCompanyRoutes(prefix string) http.Handler {
comp := web.New()
comp.Use(SomeMiddleware)
comp.Get(prefix+"/products", Products.ListAll)
comp.Get(prefix+"/product/:id", Products.JustOne)
comp.Get(prefix+"/product/delete", Products.Delete)
return comp
}
// ... and a GetExternalRoutes with the same pattern
func main() {
r := web.New()
r.Get("/", IndexHandler)
r.Handle("/company/*", GetCompanyRoutes("/company"))
r.Handle("/external/*", GetExternalRoutes("/external"))
graceful.Serve("localhost:8000", r)
}
Since this is all compiled at startup, there's no concern about the string concatenation impacting routing performance.
I use a similar pattern as my handlers reside in a separate package - my package main just calls r.Handle("/admin/*", handlers.GetAdminRoutes("/admin")
. If I wanted to change the URL structure at a later date, I can just change it to r.Handle("/newadminlocation/*", handlers.GetAdminRoutes("/newadminlocation")
instead.
Following Goji's author suggestion on this closed issue, you can create a SubRouter
struct which extends web.Mux
, allowing you to offer the same API as web.Mux
does, and in addition strip the prefix for your subrouter using a middleware which calls go's http.StripPrefix()
.
The code above could be re-written:
func GetCompanyRoutes() http.Handler {
comp := web.New()
comp.Use(SomeMiddleware)
comp.Get("/products", Products.ListAll)
comp.Get("/product/:id", Products.JustOne)
comp.Get("/product/delete", Products.Delete)
return comp
}
func main() {
r := web.New()
r.Get("/", IndexHandler)
companySubRouter := NewSubRouter("/company", r)
companySubRouter.Handle("/*", GetCompanyRoutes())
externalSubRouter := NewSubRouter("/external", r)
externalSubrouter.Handle("/*", GetExternalRoutes())
graceful.Serve("localhost:8000", r)
}
A possible implementation for NewSubRouter()
:
type SubRouter struct {
*web.Mux
prefix string
}
func NewSubRouter(prefix string, parent *web.Mux) *SubRouter {
mux := web.New()
// we want prefix to be '/*'-suffix-free for http.StripPrefix() below.
prefix = strings.TrimRight(prefix, "/*")
// however, we bind parent to match both:
// *-free prefix (exact match)
parent.Handle(prefix, mux)
// and match with a '/*' suffix, matching "prefix/*", e.g. "prefix/subpath/a"
parent.Handle(prefix+"/*", mux)
mux.Use(func(c *web.C, handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// TODO record the prefix and possibly ancestors prefixes..
// strip the prefix from the URLs in the request for the following middleware
strippedHandler := http.StripPrefix(prefix, handler)
strippedHandler.ServeHTTP(rw, req)
})
})
return &SubRouter{
Mux: mux,
prefix: prefix,
}
}
Edit:
I've updated the
prefix
<->prefix+"/*"
approach above to be a bit sane-r. Note that callers to this function need to provide a trailing-slash and asterisk free prefix. leading slashes are ok.An alternative to the above is to return a straight
web.Mux
(i.e.return mux
instead ofreturn &SubRouter{...}
and discard theSubRouter
struct altogether). It depends on whether theprefix
string is of any value to the caller of this function.
来源:https://stackoverflow.com/questions/25298646/how-can-i-create-separate-route-groups-with-different-middleware-in-goji-golang