Created
January 4, 2017 21:59
-
-
Save pkieltyka/ac40ab917470e6d0c8e951b49b22d497 to your computer and use it in GitHub Desktop.
Example of acl mux on top of chi router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package acl | |
import ( | |
"net/http" | |
"reflect" | |
"regexp" | |
"runtime" | |
"strings" | |
"github.com/pressly/chi" | |
) | |
var nonalphanum = regexp.MustCompile(`[^a-zA-Z0-9_-]+`) | |
type Mux struct { | |
Mux *chi.Mux | |
} | |
type Router interface { | |
chi.Router | |
} | |
var _ Router = &Mux{} | |
func NewRouter(route string) *Mux { | |
r := &Mux{chi.NewRouter()} | |
if route != "" { | |
r.Mux.Use(Route(route)) | |
} | |
return r | |
} | |
func (r *Mux) Use(middlewares ...func(http.Handler) http.Handler) { | |
r.Mux.Use(middlewares...) | |
} | |
func (r *Mux) Handle(pattern string, handler http.Handler) { | |
r.with().Handle(pattern, handler) | |
} | |
func (r *Mux) HandleFunc(pattern string, handler http.HandlerFunc) { | |
r.with().HandleFunc(pattern, handler) | |
} | |
func (r *Mux) Connect(pattern string, handler http.HandlerFunc) { | |
r.with().Connect(pattern, handler) | |
} | |
func (r *Mux) Head(pattern string, handler http.HandlerFunc) { | |
r.with().Head(pattern, handler) | |
} | |
func (r *Mux) Get(pattern string, handler http.HandlerFunc) { | |
r.with().Get(pattern, handler) | |
} | |
func (r *Mux) Post(pattern string, handler http.HandlerFunc) { | |
r.with().Post(pattern, handler) | |
} | |
func (r *Mux) Put(pattern string, handler http.HandlerFunc) { | |
r.with().Put(pattern, handler) | |
} | |
func (r *Mux) Patch(pattern string, handler http.HandlerFunc) { | |
r.with().Patch(pattern, handler) | |
} | |
func (r *Mux) Delete(pattern string, handler http.HandlerFunc) { | |
r.with().Delete(pattern, handler) | |
} | |
func (r *Mux) Trace(pattern string, handler http.HandlerFunc) { | |
r.with().Trace(pattern, handler) | |
} | |
func (r *Mux) Options(pattern string, handler http.HandlerFunc) { | |
r.with().Options(pattern, handler) | |
} | |
func (r *Mux) NotFound(handlerFn http.HandlerFunc) { | |
r.Mux.NotFound(handlerFn) | |
} | |
func (r *Mux) ServeHTTP(w http.ResponseWriter, req *http.Request) { | |
r.Mux.ServeHTTP(w, req) | |
} | |
// This method will inject ResourceControl at the top of the middleware chain, | |
// but it will also check existing Middlewares for a ResourceControl from a | |
// previous With() call or Group(). | |
// | |
// TODO: perhaps there is a cleaner way to search for a function in a slice..? | |
// but I don't know, so I used reflection. | |
func (r *Mux) with(middlewares ...func(http.Handler) http.Handler) chi.Router { | |
rcFn := runtime.FuncForPC(reflect.ValueOf(ResourceControl).Pointer()).Name() | |
var found bool | |
for _, mw := range r.Mux.Middlewares() { | |
mwFn := runtime.FuncForPC(reflect.ValueOf(mw).Pointer()).Name() | |
if rcFn == mwFn { | |
found = true | |
break | |
} | |
} | |
var mws chi.Middlewares | |
if found { | |
mws = chi.Middlewares{} | |
} else { | |
mws = chi.Middlewares{ResourceControl} | |
} | |
mws = append(mws, middlewares...) | |
mx := r.Mux.With(mws...).(*chi.Mux) | |
return mx | |
} | |
func (r *Mux) With(middlewares ...func(http.Handler) http.Handler) chi.Router { | |
mx := r.with(middlewares...).(*chi.Mux) | |
return &Mux{mx} | |
} | |
// TODO/NOTE: this will call Group() on the chi.Mux, and not our own | |
// mux. As a result, the routes defined on a Group will not have ResourceControl | |
// middleware set so you'll have to do it yourself. The Group() method below | |
// fixes it, and adds ResourceControl automatically to each route, but, it's | |
// overkill and causes some missing role errors, or something. | |
// So for now, just set r.Use(ResourceControl) in a group manually | |
func (r *Mux) Group(fn func(r chi.Router)) chi.Router { | |
mx := r.Mux.With().(*chi.Mux) | |
if fn != nil { | |
fn(mx) | |
} | |
return mx | |
} | |
// NOTE: the following method will add a ResourceControl() for anytime a route is | |
// used with Group(), but in fact, we may not always want this.. it requires | |
// turning on and going through the API to ensure the acl logic and flow makes sense. | |
// func (r *Mux) Group(fn func(r chi.Router)) chi.Router { | |
// mx := r.Mux.With().(*chi.Mux) | |
// im := &Mux{mx} | |
// if fn != nil { | |
// fn(im) | |
// } | |
// return im | |
// } | |
func (r *Mux) Route(pattern string, fn func(r chi.Router)) chi.Router { | |
subRouter := NewRouter("") | |
if fn != nil { | |
fn(subRouter) | |
} | |
r.Mount(pattern, subRouter) | |
return subRouter | |
} | |
func (r *Mux) Mount(pattern string, handler http.Handler) { | |
r.Mux.Mount(pattern, handler) | |
} | |
func (r *Mux) Middlewares() chi.Middlewares { | |
return r.Mux.Middlewares() | |
} | |
func (r *Mux) Routes() []chi.Route { | |
return r.Mux.Routes() | |
} | |
// pathToACLRoute parses route fragment to generate ACL route fragment to be appended | |
// this is only used for .Get, .Post, .Put, etc methods to add the last part of ACL route | |
// the remaining part of the route is built when subrouters are created | |
// Example: .Get("/user_list") will add ".user_list" to ACL route for that path. | |
func pathToACLRoute(s string) string { | |
s = nonalphanum.ReplaceAllString(s, ".") | |
if strings.HasPrefix(s, ".") { | |
return s[1:len(s)] | |
} | |
return s | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment