Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 127 additions & 21 deletions chain.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Package alice provides a convenient way to chain http handlers.
package alice

import "net/http"
import (
"net/http"
)

// A constructor for a piece of middleware.
// Some middleware use this constructor out of the box,
Expand All @@ -14,39 +16,65 @@ type Constructor func(http.Handler) http.Handler
// the same set of constructors in the same order.
type Chain struct {
constructors []Constructor
endwares []Endware
}

// New creates a new chain,
// memorizing the given list of middleware constructors.
// New serves no other function,
// constructors are only called upon a call to Then().
func New(constructors ...Constructor) Chain {
return Chain{append(([]Constructor)(nil), constructors...)}
return Chain{append(([]Constructor)(nil), constructors...), ([]Endware)(nil)}
}

// Then chains the middleware and returns the final http.Handler.
// New(m1, m2, m3).Then(h)
// endwareHandler represents a handler that has been modified
// to execute endwares afterwards. This is a helper for Then()
// because if we just wrap it in an anonymous
// http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)))
// there is a stack overflow
type endwareHandler struct {
handler http.Handler
endwares []Endware
}

// ServeHTTP serves the main endwareHandler's handler as well as
// calling all of the individual endwares afterwards.
func (eh endwareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
eh.handler.ServeHTTP(w, r)
for _, endware := range eh.endwares {
endware.ServeHTTP(w, r)
}
}

// Then chains the middleware and endwares and returns the final http.Handler.
// New(m1, m2, m3).Finally(e1, e2, e3).Then(h)
// is equivalent to:
// m1(m2(m3(h)))
// When the request comes in, it will be passed to m1, then m2, then m3
// and finally, the given handler
// (assuming every middleware calls the following one).
// followed by:
// e1(e2(e3()))
// When the request comes in, it will be passed to m1, then m2, then m3,
// then the given handler (who serves the response), then e1, e2, e3
// (assuming every middleware/endwares calls the following one).
//
// A chain can be safely reused by calling Then() several times.
// stdStack := alice.New(ratelimitHandler, csrfHandler)
// stdStack := alice.New(ratelimitHandler, csrfHandler).Finally(loggingHandler)
// indexPipe = stdStack.Then(indexHandler)
// authPipe = stdStack.Then(authHandler)
// Note that constructors are called on every call to Then()
// and thus several instances of the same middleware will be created
// Note that constructors and endwares are called on every call to Then()
// and thus several instances of the same middleware/endwares will be created
// when a chain is reused in this way.
// For proper middleware, this should cause no problems.
// For proper middleware/endwares, this should cause no problems.
//
// Then() treats nil as http.DefaultServeMux.
func (c Chain) Then(h http.Handler) http.Handler {
if h == nil {
h = http.DefaultServeMux
}

if len(c.endwares) > 0 {
h = endwareHandler{h, c.endwares}
}

for i := range c.constructors {
h = c.constructors[len(c.constructors)-1-i](h)
}
Expand All @@ -73,6 +101,7 @@ func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler {
// as the last ones in the request flow.
//
// Append returns a new chain, leaving the original one untouched.
// The new chain will have the original chain's endwares.
//
// stdChain := alice.New(m1, m2)
// extChain := stdChain.Append(m3, m4)
Expand All @@ -83,7 +112,7 @@ func (c Chain) Append(constructors ...Constructor) Chain {
newCons = append(newCons, c.constructors...)
newCons = append(newCons, constructors...)

return Chain{newCons}
return New(newCons...).AppendEndware(c.endwares...)
}

// Extend extends a chain by adding the specified chain
Expand All @@ -92,21 +121,98 @@ func (c Chain) Append(constructors ...Constructor) Chain {
// Extend returns a new chain, leaving the original one untouched.
//
// stdChain := alice.New(m1, m2)
// ext1Chain := alice.New(m3, m4)
// ext1Chain := alice.New(m3, m4).Finally(e1, e2)
// ext2Chain := stdChain.Extend(ext1Chain)
// // requests in stdChain go m1 -> m2
// // requests in ext1Chain go m3 -> m4
// // requests in ext2Chain go m1 -> m2 -> m3 -> m4
// // requests in stdChain go m1 -> m2 -> handler
// // requests in ext1Chain go m3 -> m4 -> handler -> e1 -> e2
// // requests in ext2Chain go m1 -> m2 -> m3 -> m4 -> handler -> e1 -> e2
//
// Another example:
// aHtmlAfterNosurf := alice.New(m2)
// logRequestChain := aHtmlAfterNosurf.Finally(e1)
// aHtml := alice.New(m1, func(h http.Handler) http.Handler {
// csrf := nosurf.New(h)
// csrf.SetFailureHandler(aHtmlAfterNosurf.ThenFunc(csrfFail))
// csrf.SetFailureHandler(logRequestChain.ThenFunc(csrfFail))
// return csrf
// }).Extend(aHtmlAfterNosurf)
// // requests to aHtml hitting nosurfs success handler go m1 -> nosurf -> m2 -> target-handler
// // requests to aHtml hitting nosurfs failure handler go m1 -> nosurf -> m2 -> csrfFail
// }).Extend(logRequestChain)
// // requests to aHtml hitting nosurfs success handler go:
// m1 -> nosurf -> m2 -> target-handler -> e1
// // requests to aHtml hitting nosurfs failure handler go:
// m1 -> nosurf -> m2 -> csrfFail -> e1
func (c Chain) Extend(chain Chain) Chain {
return c.Append(chain.constructors...)
return c.
Append(chain.constructors...).
AppendEndware(chain.endwares...)
}

// Endware is functionality executed after a the main handler is called
// and response has been sent to the requester. Like middleware,
// values from the request or response can be accessed. This will not
// let you access values from the request or the response that can no longer be used.
// e.g. re-reading a request body, re-setting the response headers, etc.
type Endware http.Handler

// Finally creates a new chain with the original chain's
// constructors and endwares, as well as the provided endwares.
// Endwares are executed after both the constructors and
// the Then() handler are called.
func (c Chain) Finally(endwares ...Endware) Chain {
newEnds := make([]Endware, 0, len(c.endwares)+len(endwares))
newEnds = append(newEnds, c.endwares...)
newEnds = append(newEnds, endwares...)

newC := New(c.constructors...)
newC.endwares = newEnds
return newC
}

// FinallyFuncs works identically to Finally, but takes HandlerFuncs
// instead of Endwares.
//
// The following two statements are equivalent:
// c.Finally(http.HandlerFunc(fn1), http.HandlerFunc(fn2))
// c.FinallyFuncs(fn1, fn2)
//
// FinallyFuncs provides all the guarantees of Finally.
func (c Chain) FinallyFuncs(fns ...func(w http.ResponseWriter, r *http.Request)) Chain {
// convert each http.HandlerFunc into an Endware
endwares := make([]Endware, len(fns))
for i, fn := range fns {
endwares[i] = http.HandlerFunc(fn)
}

return c.Finally(endwares...)
}

// AppendEndware extends a chain, adding the specified endwares
// as the last ones in the request flow.
//
// AppendEndware returns a new chain, leaving the original one untouched.
// The new chain will have the original chain's constructors.
//
// stdChain := alice.New(m1).Finally(e1, e2)
// extChain := stdChain.AppendEndware(e3, e4)
// // requests in stdHandler go m1 -> handler -> e1 -> e2
// // requests in extHandler go m1 -> handler -> e1 -> e2 -> e3 -> e4
func (c Chain) AppendEndware(endwares ...Endware) Chain {
return New(c.constructors...).Finally(append(c.endwares, endwares...)...)
}

// AppendEndwareFuncs works identically to AppendEndware, but takes HandlerFuncs
// instead of Endwares.
//
// The following two statements are equivalent:
// c.AppendEndware(http.HandlerFunc(fn1), http.HandlerFunc(fn2))
// c.AppendEndwareFuncs(fn1, fn2)
//
// AppendEndwareFuncs provides all the guarantees of AppendEndware.
func (c Chain) AppendEndwareFuncs(fns ...func(w http.ResponseWriter, r *http.Request)) Chain {
// convert each http.HandlerFunc into an Endware
endwares := make([]Endware, len(fns))
for i, fn := range fns {
endwares[i] = http.HandlerFunc(fn)
}

return c.AppendEndware(endwares...)

}
Loading