Skip to content

Commit

Permalink
add 'smart' middleware helpers, see _examples/6_middleware for more
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Oct 17, 2018
1 parent 1fc2d0e commit e68107d
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 22 deletions.
93 changes: 93 additions & 0 deletions _examples/6_middleware/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Package main will explore the helpers for middleware(s) that Muxie has to offer,
// but they are totally optional, you can still use your favourite pattern
// to wrap route handlers.
//
// Example of usage of an external net/http common middleware:
//
// import "github.com/rs/cors"
//
// mux := muxie.New()
// mux.Use(cors.Default().Handler)
//
//
// To wrap a specific route or even if for some reason you want to wrap the entire router
// use the `Pre(middlewares...).For(mainHandler)` as :
//
// wrapped := muxie.Pre(cors.Default().Handler, ...).For(mux)
// http.ListenAndServe(..., wrapped)
package main

import (
"log"
"net/http"

"github.com/kataras/muxie"
)

func main() {
mux := muxie.NewMux()
// Globally, will be inherited by all sub muxes as well unless `Of(...).Unlink()` called.
mux.Use(myGlobalMiddleware)

// Per Route.
mux.Handle("/", muxie.Pre(myFirstRouteMiddleware, mySecondRouteMiddleware).ForFunc(myMainRouteHandler))

// Per Group.
inheritor := mux.Of("/inheritor")
inheritor.Use(myMiddlewareForSubmux)
inheritor.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("execute: my inheritor's main index route's handler")
w.Write([]byte("Hello from /inheritor\n"))
})

// Per Group, without its parents' middlewares.
// Unlink will clear all middlewares for this sub mux.
orphan := mux.Of("/orphan").Unlink()
orphan.Use(myMiddlewareForSubmux)
orphan.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("execute: orphan's main index route's handler")
w.Write([]byte("Hello from /orphan\n"))
})

// Open your web browser or any other HTTP Client
// and navigate through the below endpoinds, one by one,
// and check the console output of your webserver.
//
// http://localhost:8080
// http://localhost:8080/inheritor
// http://localhost:8080/orphan
http.ListenAndServe(":8080", mux)
}

func myGlobalMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("execute: my global and first of all middleware for all following mux' routes and sub muxes")
next.ServeHTTP(w, r)
})
}

func myMiddlewareForSubmux(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("execute: my submux' routes middleware")
next.ServeHTTP(w, r)
})
}

func myFirstRouteMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("execute: my first specific route's middleware")
next.ServeHTTP(w, r)
})
}

func mySecondRouteMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("execute: my second specific route's middleware")
next.ServeHTTP(w, r)
})
}

func myMainRouteHandler(w http.ResponseWriter, r *http.Request) {
log.Println("execute: my main route's handler")
w.Write([]byte("Hello World!\n"))
}
107 changes: 103 additions & 4 deletions mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ type Mux struct {
Routes *Trie

paramsPool *sync.Pool
root string

// per mux
root string
beginHandlers []Wrapper
}

// NewMux returns a new HTTP multiplexer which uses a fast, if not the fastest
Expand All @@ -44,9 +47,69 @@ func NewMux() *Mux {
}
}

// Use adds middleware that should be called before each mux route's main handler.
// Should be called before `Handle/HandleFunc`. Order matters.
//
// A Wrapper is just a type of `func(http.Handler) http.Handler`
// which is a common type definition for net/http middlewares.
//
// To add a middleware for a specific route and not in the whole mux
// use the `Handle/HandleFunc` with the package-level `muxie.Pre` function instead.
// Functionality of `Use` is pretty self-explained but new gophers should
// take a look of the examples for further details.
func (m *Mux) Use(middlewares ...Wrapper) {
m.beginHandlers = append(m.beginHandlers, middlewares...)
}

type (
// Wrapper is just a type of `func(http.Handler) http.Handler`
// which is a common type definition for net/http middlewares.
Wrapper func(http.Handler) http.Handler

// Wrappers contains `Wrapper`s that can be registered and used by a "main route handler".
// Look the `Pre` and `For/ForFunc` functions too.
Wrappers struct {
pre []Wrapper
}
)

// For registers the wrappers for a specific handler and returns a handler
// that can be passed via the `Handle` function.
func (w Wrappers) For(main http.Handler) http.Handler {
if len(w.pre) > 0 {
for i, lidx := 0, len(w.pre)-1; i <= lidx; i++ {
main = w.pre[lidx-i](main)
}
}

// keep note that if no middlewares then
// it will return the main one untouched,
// the check of length of w.pre is because we may add "done handlers" as well in the future, if community asks for that.
return main
}

// ForFunc registers the wrappers for a specific raw handler function
// and returns a handler that can be passed via the `Handle` function.
func (w Wrappers) ForFunc(mainFunc func(http.ResponseWriter, *http.Request)) http.Handler {
return w.For(http.HandlerFunc(mainFunc))
}

// Pre starts a chain of handlers for wrapping a "main route handler"
// the registered "middleware" will run before the main handler(see `Wrappers#For/ForFunc`).
//
// Usage:
// mux := muxie.NewMux()
// myMiddlewares := muxie.Pre(myFirstMiddleware, mySecondMiddleware)
// mux.Handle("/", myMiddlewares.ForFunc(myMainRouteHandler))
func Pre(middleware ...Wrapper) Wrappers {
return Wrappers{pre: middleware}
}

// Handle registers a route handler for a path pattern.
func (m *Mux) Handle(pattern string, handler http.Handler) {
m.Routes.Insert(m.root+pattern, WithHandler(handler))
m.Routes.Insert(m.root+pattern,
WithHandler(
Pre(m.beginHandlers...).For(handler)))
}

// HandleFunc registers a route handler function for a path pattern.
Expand Down Expand Up @@ -113,9 +176,11 @@ func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// SubMux is the child of a main Mux.
type SubMux interface {
Of(prefix string) SubMux
Unlink() SubMux
Use(middlewares ...Wrapper)
Handle(pattern string, handler http.Handler)
HandleFunc(pattern string, handlerFunc func(http.ResponseWriter, *http.Request))
Of(prefix string) SubMux
}

// Of returns a new Mux which its Handle and HandleFunc will register the path based on given "prefix", i.e:
Expand Down Expand Up @@ -147,6 +212,40 @@ func (m *Mux) Of(prefix string) SubMux {

return &Mux{
Routes: m.Routes,
root: prefix,

root: prefix,
beginHandlers: append([]Wrapper{}, m.beginHandlers...),
}
}

/* Notes:
Four options to solve optionally "inherition" of parent's middlewares but dismissed:
- I could add options for "inherition" of middlewares inside the `Mux#Use` itself.
But this is a problem because the end-dev will have to use a specific muxie's constant even if he doesn't care about the other option.
- Create a new function like `UseOnly` or `UseExplicit`
which will remove any previous middlewares and use only the new one.
But this has a problem of making the `Use` func to act differently and debugging will be a bit difficult if big app if called after the `UseOnly`.
- Add a new func for creating new groups to remove any inherited middlewares from the parent.
But with this, we will have two functions for the same thing and users may be confused about this API design.
- Put the options to the existing `Of` function, and make them optionally by functional design of options.
But this will make things ugly and may confuse users as well, there is a better way.
Solution: just add a function like `Unlink`
to remove any inherited fields (now and future feature requests), so we don't have
breaking changes and etc. This `Unlink`, which will return the same SubMux, it can be used like `v1 := mux.Of(..).Unlink()`
*/

// Unlink will remove any inheritance fields from the parent mux (and its parent)
// that are inherited with the `Of` function.
// Returns the current SubMux. Usage:
//
// mux := NewMux()
// mux.Use(myLoggerMiddleware)
// v1 := mux.Of("/v1").Unlink() // v1 will no longer have the "myLoggerMiddleware".
// v1.HandleFunc("/users", myHandler)
func (m *Mux) Unlink() SubMux {
m.beginHandlers = m.beginHandlers[0:0]
return m
}
3 changes: 2 additions & 1 deletion params_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ func GetParams(w http.ResponseWriter) []ParamEntry {
}

// SetParam sets manually a parameter to the "w" http.ResponseWriter which should be a *paramsWriter.
// This is not commonly used by the end-developers.
// This is not commonly used by the end-developers,
// unless sharing values(string messages only) between handlers is absolutely necessary.
func SetParam(w http.ResponseWriter, key, value string) bool {
if store, ok := w.(*paramsWriter); ok {
store.Set(key, value)
Expand Down
23 changes: 9 additions & 14 deletions trie.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ type InsertOption func(*Node)

// WithHandler sets the node's `Handler` field (useful for HTTP).
func WithHandler(handler http.Handler) InsertOption {
if handler == nil {
panic("muxie/WithHandler: empty handler")
}

return func(n *Node) {
if n.Handler == nil {
n.Handler = handler
Expand All @@ -71,24 +75,15 @@ func WithData(data interface{}) InsertOption {
}

// Insert adds a node to the trie.
func (t *Trie) Insert(key string, options ...InsertOption) {
n := t.insert(key, "", nil, nil)
for _, opt := range options {
opt(n)
}
}

// InsertRoute adds a route node to the trie, handler and pattern are required.
func (t *Trie) InsertRoute(pattern, routeName string, handler http.Handler) {
func (t *Trie) Insert(pattern string, options ...InsertOption) {
if pattern == "" {
panic("muxie/trie#InsertRoute: empty pattern")
panic("muxie/trie#Insert: empty pattern")
}

if handler == nil {
panic("muxie/trie#InsertRoute: empty handler")
n := t.insert(pattern, "", nil, nil)
for _, opt := range options {
opt(n)
}

t.insert(pattern, routeName, nil, handler)
}

const (
Expand Down
2 changes: 1 addition & 1 deletion trie_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

func initTree(tree *Trie) {
for _, tt := range tests {
tree.Insert(tt.key, WithTag(tt.routeName))
tree.insert(tt.key, tt.routeName, nil, nil)
}
}

Expand Down
4 changes: 2 additions & 2 deletions trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func testTrie(t *testing.T, oneByOne bool) {
// insert.
for idx, tt := range tests {
if !oneByOne {
tree.Insert(tt.key, WithTag(tt.routeName))
tree.insert(tt.key, tt.routeName, nil, nil)
}

for reqIdx, req := range tt.requests {
Expand All @@ -131,7 +131,7 @@ func testTrie(t *testing.T, oneByOne bool) {
// run.
for idx, tt := range tests {
if oneByOne {
tree.Insert(tt.key, WithTag(tt.routeName))
tree.insert(tt.key, tt.routeName, nil, nil)
}
params := new(paramsWriter)
for reqIdx, req := range tt.requests {
Expand Down

0 comments on commit e68107d

Please sign in to comment.