diff options
| author | Paul Mundt <paul.mundt@adaptant.io> | 2016-05-12 13:15:33 +0000 |
|---|---|---|
| committer | Felix Hanley <felix@userspace.com.au> | 2016-05-12 13:15:33 +0000 |
| commit | bb8476ffe78210d1a4c0b4bbee6da99da1bc15c5 (patch) | |
| tree | 5a70f855509889e02ea6df60d0b0fac208ee6f49 | |
| parent | a3a77dbd4ec45c17687d3e42dab551db59c93ca3 (diff) | |
| download | go-dict2rest-bb8476ffe78210d1a4c0b4bbee6da99da1bc15c5.tar.gz go-dict2rest-bb8476ffe78210d1a4c0b4bbee6da99da1bc15c5.tar.bz2 | |
Inhibit repeated handling of requests through context-aware middleware chains (#3)
* Squashed 'vendor/src/github.com/alexedwards/stack/' content from commit a4c0268
git-subtree-dir: vendor/src/github.com/alexedwards/stack
git-subtree-split: a4c0268505f12934376d6d8fdca232416861e271
* Use context-aware handler chains to limit repeated handling
Presently each middleware must advance through the handler chain to
reach the router endpoint. In the case of multiple compressors (gzip,
deflate) we need to be able to walk through the chain without additional
processing of already handled requests. This implements some simple
iteration logic based on request context that we pass down the chain.
Signed-off-by: Paul Mundt <paul.mundt@adaptant.io>
| -rw-r--r-- | src/dict2rest/deflate.go | 9 | ||||
| -rw-r--r-- | src/dict2rest/gzip.go | 10 | ||||
| -rw-r--r-- | src/dict2rest/main.go | 15 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/.travis.yml | 8 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/LICENSE | 20 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/README.md | 187 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/context.go | 55 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/context_test.go | 48 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/stack.go | 94 | ||||
| -rw-r--r-- | vendor/src/github.com/alexedwards/stack/stack_test.go | 126 |
10 files changed, 556 insertions, 16 deletions
diff --git a/src/dict2rest/deflate.go b/src/dict2rest/deflate.go index b8ce5d6..3bd0ed8 100644 --- a/src/dict2rest/deflate.go +++ b/src/dict2rest/deflate.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "strings" + "github.com/alexedwards/stack" ) // DEFLATE Compression @@ -17,10 +18,10 @@ func (w flateResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } -func Deflate(next http.Handler) http.Handler { +func Deflate(ctx *stack.Context, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if !strings.Contains(req.Header.Get("Accept-Encoding"), "deflate") { - // If deflate is unsupported, revert to standard handler. + if !strings.Contains(req.Header.Get("Accept-Encoding"), "deflate") || + ctx.Get("handled").(bool) == true { next.ServeHTTP(w, req) return } @@ -32,6 +33,8 @@ func Deflate(next http.Handler) http.Handler { w.Header().Set("Content-Encoding", "deflate") defer fl.Close() flw := flateResponseWriter{Writer: fl, ResponseWriter: w} + ctx.Put("handled", true) + defer ctx.Delete("handled") next.ServeHTTP(flw, req) }) } diff --git a/src/dict2rest/gzip.go b/src/dict2rest/gzip.go index d2264e2..c3bb5ed 100644 --- a/src/dict2rest/gzip.go +++ b/src/dict2rest/gzip.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "strings" + "github.com/alexedwards/stack" ) // Gzip Compression @@ -17,10 +18,11 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } -func Gzip(next http.Handler) http.Handler { +func Gzip(ctx *stack.Context, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { - // If gzip is unsupported, revert to standard handler. + if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") || + ctx.Get("handled").(bool) == true { + // move on to next handler in the chain next.ServeHTTP(w, req) return } @@ -28,6 +30,8 @@ func Gzip(next http.Handler) http.Handler { gz := gzip.NewWriter(w) defer gz.Close() gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w} + ctx.Put("handled", true) + defer ctx.Delete("handled") next.ServeHTTP(gzw, req) }) } diff --git a/src/dict2rest/main.go b/src/dict2rest/main.go index 391f97e..94e1ac2 100644 --- a/src/dict2rest/main.go +++ b/src/dict2rest/main.go @@ -3,7 +3,7 @@ package main import ( "flag" "github.com/julienschmidt/httprouter" - "github.com/justinas/alice" + "github.com/alexedwards/stack" "github.com/rs/cors" "github.com/stretchr/graceful" "log" @@ -64,23 +64,18 @@ func main() { }, }) - stdChain := alice.New(cors.Handler, Logger) + stdChain := stack.New(stack.Adapt(cors.Handler), stack.Adapt(Logger)) if *gzip { stdChain = stdChain.Append(Gzip) log.Println("Adding support for Gzip compression") } if *deflate { - // Temporary limitation until the iteration logic is fixed up - if *gzip { - log.Println("Not enabling DEFLATE, presently only one compression method can be active at a time") - } else { - stdChain = stdChain.Append(Deflate) - log.Println("Adding support for DEFLATE compression") - } + stdChain = stdChain.Append(Deflate) + log.Println("Adding support for DEFLATE compression") } - chain := stdChain.Then(router) + chain := stdChain.ThenHandler(router) listen := ":" + *port diff --git a/vendor/src/github.com/alexedwards/stack/.travis.yml b/vendor/src/github.com/alexedwards/stack/.travis.yml new file mode 100644 index 0000000..70eed36 --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/.travis.yml @@ -0,0 +1,8 @@ +sudo: false +language: go +go: + - 1.1 + - 1.2 + - 1.3 + - 1.4 + - tip
\ No newline at end of file diff --git a/vendor/src/github.com/alexedwards/stack/LICENSE b/vendor/src/github.com/alexedwards/stack/LICENSE new file mode 100644 index 0000000..f25a33b --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Alex Edwards + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/src/github.com/alexedwards/stack/README.md b/vendor/src/github.com/alexedwards/stack/README.md new file mode 100644 index 0000000..ba689f6 --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/README.md @@ -0,0 +1,187 @@ +# Stack <br> [](https://travis-ci.org/alexedwards/stack) [](http://gocover.io/github.com/alexedwards/stack) [](http://godoc.org/github.com/alexedwards/stack) + +Stack provides an easy way to chain your HTTP middleware and handlers together and to pass request-scoped context between them. It's essentially a context-aware version of [Alice](https://github.com/justinas/alice). + +[Skip to the example ›](#example) + +### Usage + +#### Making a chain + +Middleware chains are constructed with [`stack.New()`](http://godoc.org/github.com/alexedwards/stack#New): + +```go +stack.New(middlewareOne, middlewareTwo, middlewareThree) +``` + +You can also store middleware chains as variables, and then [`Append()`](http://godoc.org/github.com/alexedwards/stack#Chain.Append) to them: + +```go +stdStack := stack.New(middlewareOne, middlewareTwo) +extStack := stdStack.Append(middlewareThree, middlewareFour) +``` + +Your middleware should have the signature `func(*stack.Context, http.Handler) http.Handler`. For example: + +```go +func middlewareOne(ctx *stack.Context, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // do something middleware-ish, accessing ctx + next.ServeHTTP(w, r) + }) +} +``` + +You can also use middleware with the signature `func(http.Handler) http.Handler` by adapting it with [`stack.Adapt()`](http://godoc.org/github.com/alexedwards/stack#Adapt). For example, if you had the middleware: + +```go +func middlewareTwo(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // do something else middleware-ish + next.ServeHTTP(w, r) + }) +} +``` + +You can add it to a chain like this: + +```go +stack.New(middlewareOne, stack.Adapt(middlewareTwo), middlewareThree) +``` + +See the [codes samples](#code-samples) for real-life use of third-party middleware with Stack. + +#### Adding an application handler + +Application handlers should have the signature `func(*stack.Context, http.ResponseWriter, *http.Request)`. You add them to the end of a middleware chain with the [`Then()`](http://godoc.org/github.com/alexedwards/stack#Chain.Then) method. + +So an application handler like this: + +```go +func appHandler(ctx *stack.Context, w http.ResponseWriter, r *http.Request) { + // do something handler-ish, accessing ctx +} +``` + +Is added to the end of a middleware chain like this: + +```go +stack.New(middlewareOne, middlewareTwo).Then(appHandler) +``` + +For convenience [`ThenHandler()`](http://godoc.org/github.com/alexedwards/stack#Chain.ThenHandler) and [`ThenHandlerFunc()`](http://godoc.org/github.com/alexedwards/stack#Chain.ThenHandlerFunc) methods are also provided. These allow you to finish a chain with a standard `http.Handler` or `http.HandlerFunc` respectively. + +For example, you could use a standard `http.FileServer` as the application handler: + +```go +fs := http.FileServer(http.Dir("./static/")) +http.Handle("/", stack.New(middlewareOne, middlewareTwo).ThenHandler(fs)) +``` + +Once a chain is 'closed' with any of these methods it is converted into a [`HandlerChain`](http://godoc.org/github.com/alexedwards/stack#HandlerChain) object which satisfies the `http.Handler` interface, and can be used with the `http.DefaultServeMux` and many other routers. + +#### Using context + +Request-scoped data (or *context*) can be passed through the chain by storing it in `stack.Context`. This is implemented as a pointer to a `map[string]interface{}` and scoped to the goroutine executing the current HTTP request. Operations on `stack.Context` are protected by a mutex, so if you need to pass the context pointer to another goroutine (say for logging or completing a background process) it is safe for concurrent use. + +Data is added with [`Context.Put()`](http://godoc.org/github.com/alexedwards/stack#Context.Put). The first parameter is a string (which acts as a key) and the second is the value you need to store. For example: + +```go +func middlewareOne(ctx *stack.Context, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx.Put("token", "c9e452805dee5044ba520198628abcaa") + next.ServeHTTP(w, r) + }) +} +``` + +You retrieve data with [`Context.Get()`](http://godoc.org/github.com/alexedwards/stack#Context.Get). Remember to type assert the returned value into the type you're expecting. + +```go +func appHandler(ctx *stack.Context, w http.ResponseWriter, r *http.Request) { + token, ok := ctx.Get("token").(string) + if !ok { + http.Error(w, http.StatusText(500), 500) + return + } + fmt.Fprintf(w, "Token is: %s", token) +} +``` + +Note that `Context.Get()` will return `nil` if a key does not exist. If you need to tell the difference between a key having a `nil` value and it explicitly not existing, please check with [`Context.Exists()`](http://godoc.org/github.com/alexedwards/stack#Context.Exists). + +Keys (and their values) can be deleted with [`Context.Delete()`](http://godoc.org/github.com/alexedwards/stack#Context.Delete). + +#### Injecting context + +It's possible to inject values into `stack.Context` during a request cycle but *before* the chain starts to be executed. This is useful if you need to inject parameters from a router into the context. + +The [`Inject()`](http://godoc.org/github.com/alexedwards/stack#Inject) function returns a new copy of the chain containing the injected context. You should make sure that you use this new copy – not the original – for subsequent processing. + +Here's an example of a wrapper for injecting [httprouter](https://github.com/julienschmidt/httprouter) params into the context: + +```go +func InjectParams(hc stack.HandlerChain) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + newHandlerChain := stack.Inject(hc, "params", ps) + newHandlerChain.ServeHTTP(w, r) + } +} +``` + +A full example is available in the [code samples](#code-samples). + +### Example + +```go +package main + +import ( + "net/http" + "github.com/alexedwards/stack" + "fmt" +) + +func main() { + stk := stack.New(token, stack.Adapt(language)) + + http.Handle("/", stk.Then(final)) + + http.ListenAndServe(":3000", nil) +} + +func token(ctx *stack.Context, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx.Put("token", "c9e452805dee5044ba520198628abcaa") + next.ServeHTTP(w, r) + }) +} + +func language(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Language", "en-gb") + next.ServeHTTP(w, r) + }) +} + +func final(ctx *stack.Context, w http.ResponseWriter, r *http.Request) { + token, ok := ctx.Get("token").(string) + if !ok { + http.Error(w, http.StatusText(500), 500) + return + } + fmt.Fprintf(w, "Token is: %s", token) +} +``` + +### Code samples + +* [Integrating with httprouter](https://gist.github.com/alexedwards/4d20c505f389597c3360) +* *More to follow* + +### TODO + +- Add more code samples (using 3rd party middleware) +- Make a `chain.Merge()` method +- Mirror master in v1 branch (and mention gopkg.in in README) +- Add benchmarks diff --git a/vendor/src/github.com/alexedwards/stack/context.go b/vendor/src/github.com/alexedwards/stack/context.go new file mode 100644 index 0000000..07afe21 --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/context.go @@ -0,0 +1,55 @@ +package stack + +import ( + "sync" +) + +type Context struct { + mu sync.RWMutex + m map[string]interface{} +} + +func NewContext() *Context { + m := make(map[string]interface{}) + return &Context{m: m} +} + +func (c *Context) Get(key string) interface{} { + if !c.Exists(key) { + return nil + } + c.mu.RLock() + defer c.mu.RUnlock() + return c.m[key] +} + +func (c *Context) Put(key string, val interface{}) *Context { + c.mu.Lock() + defer c.mu.Unlock() + c.m[key] = val + return c +} + +func (c *Context) Delete(key string) *Context { + c.mu.Lock() + defer c.mu.Unlock() + delete(c.m, key) + return c +} + +func (c *Context) Exists(key string) bool { + c.mu.RLock() + defer c.mu.RUnlock() + _, ok := c.m[key] + return ok +} + +func (c *Context) copy() *Context { + nc := NewContext() + c.mu.RLock() + defer c.mu.RUnlock() + for k, v := range c.m { + nc.m[k] = v + } + return nc +} diff --git a/vendor/src/github.com/alexedwards/stack/context_test.go b/vendor/src/github.com/alexedwards/stack/context_test.go new file mode 100644 index 0000000..469200e --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/context_test.go @@ -0,0 +1,48 @@ +package stack + +import "testing" + +func TestGet(t *testing.T) { + ctx := NewContext() + ctx.m["flip"] = "flop" + ctx.m["bish"] = nil + + val := ctx.Get("flip") + assertEquals(t, "flop", val) + + val = ctx.Get("bish") + assertEquals(t, nil, val) +} + +func TestPut(t *testing.T) { + ctx := NewContext() + + ctx.Put("bish", "bash") + assertEquals(t, "bash", ctx.m["bish"]) +} + +func TestDelete(t *testing.T) { + ctx := NewContext() + ctx.m["flip"] = "flop" + + ctx.Delete("flip") + assertEquals(t, nil, ctx.m["flip"]) +} + +func TestCopy(t *testing.T) { + ctx := NewContext() + ctx.m["flip"] = "flop" + + ctx2 := ctx.copy() + ctx2.m["bish"] = "bash" + assertEquals(t, nil, ctx.m["bish"]) + assertEquals(t, "bash", ctx2.m["bish"]) +} + +func TestExists(t *testing.T) { + ctx := NewContext() + ctx.m["flip"] = "flop" + + assertEquals(t, true, ctx.Exists("flip")) + assertEquals(t, false, ctx.Exists("bash")) +} diff --git a/vendor/src/github.com/alexedwards/stack/stack.go b/vendor/src/github.com/alexedwards/stack/stack.go new file mode 100644 index 0000000..47c42b2 --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/stack.go @@ -0,0 +1,94 @@ +package stack + +import "net/http" + +type chainHandler func(*Context) http.Handler +type chainMiddleware func(*Context, http.Handler) http.Handler + +type Chain struct { + mws []chainMiddleware + h chainHandler +} + +func New(mws ...chainMiddleware) Chain { + return Chain{mws: mws} +} + +func (c Chain) Append(mws ...chainMiddleware) Chain { + newMws := make([]chainMiddleware, len(c.mws)+len(mws)) + copy(newMws[:len(c.mws)], c.mws) + copy(newMws[len(c.mws):], mws) + c.mws = newMws + return c +} + +func (c Chain) Then(chf func(ctx *Context, w http.ResponseWriter, r *http.Request)) HandlerChain { + c.h = adaptContextHandlerFunc(chf) + return newHandlerChain(c) +} + +func (c Chain) ThenHandler(h http.Handler) HandlerChain { + c.h = adaptHandler(h) + return newHandlerChain(c) +} + +func (c Chain) ThenHandlerFunc(fn func(http.ResponseWriter, *http.Request)) HandlerChain { + c.h = adaptHandlerFunc(fn) + return newHandlerChain(c) +} + +type HandlerChain struct { + context *Context + Chain +} + +func newHandlerChain(c Chain) HandlerChain { + return HandlerChain{context: NewContext(), Chain: c} +} + +func (hc HandlerChain) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Always take a copy of context (i.e. pointing to a brand new memory location) + ctx := hc.context.copy() + + final := hc.h(ctx) + for i := len(hc.mws) - 1; i >= 0; i-- { + final = hc.mws[i](ctx, final) + } + final.ServeHTTP(w, r) +} + +func Inject(hc HandlerChain, key string, val interface{}) HandlerChain { + hc.context = hc.context.copy().Put(key, val) + return hc +} + +// Adapt third party middleware with the signature +// func(http.Handler) http.Handler into chainMiddleware +func Adapt(fn func(http.Handler) http.Handler) chainMiddleware { + return func(ctx *Context, h http.Handler) http.Handler { + return fn(h) + } +} + +// Adapt http.Handler into a chainHandler +func adaptHandler(h http.Handler) chainHandler { + return func(ctx *Context) http.Handler { + return h + } +} + +// Adapt a function with the signature +// func(http.ResponseWriter, *http.Request) into a chainHandler +func adaptHandlerFunc(fn func(w http.ResponseWriter, r *http.Request)) chainHandler { + return adaptHandler(http.HandlerFunc(fn)) +} + +// Adapt a function with the signature +// func(Context, http.ResponseWriter, *http.Request) into a chainHandler +func adaptContextHandlerFunc(fn func(ctx *Context, w http.ResponseWriter, r *http.Request)) chainHandler { + return func(ctx *Context) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fn(ctx, w, r) + }) + } +} diff --git a/vendor/src/github.com/alexedwards/stack/stack_test.go b/vendor/src/github.com/alexedwards/stack/stack_test.go new file mode 100644 index 0000000..28aa0ae --- /dev/null +++ b/vendor/src/github.com/alexedwards/stack/stack_test.go @@ -0,0 +1,126 @@ +package stack + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "testing" +) + +func assertEquals(t *testing.T, e interface{}, o interface{}) { + if e != o { + t.Errorf("\n...expected = %v\n...obtained = %v", e, o) + } +} + +func serveAndRequest(h http.Handler) string { + ts := httptest.NewServer(h) + defer ts.Close() + res, err := http.Get(ts.URL) + if err != nil { + log.Fatal(err) + } + resBody, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + log.Fatal(err) + } + return string(resBody) +} + +func bishMiddleware(ctx *Context, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx.Put("bish", "bash") + fmt.Fprintf(w, "bishMiddleware>") + next.ServeHTTP(w, r) + }) +} + +func flipMiddleware(ctx *Context, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "flipMiddleware>") + next.ServeHTTP(w, r) + }) +} + +func wobbleMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "wobbleMiddleware>") + next.ServeHTTP(w, r) + }) +} + +func bishHandler(ctx *Context, w http.ResponseWriter, r *http.Request) { + val := ctx.Get("bish") + fmt.Fprintf(w, "bishHandler [bish=%v]", val) +} + +func flipHandler(ctx *Context, w http.ResponseWriter, r *http.Request) { + valb := ctx.Get("bish") + valf := ctx.Get("flip") + fmt.Fprintf(w, "flipHandler [bish=%v,flip=%v]", valb, valf) +} + +func TestNew(t *testing.T) { + st := New(bishMiddleware, flipMiddleware).Then(bishHandler) + res := serveAndRequest(st) + assertEquals(t, "bishMiddleware>flipMiddleware>bishHandler [bish=bash]", res) +} + +func TestAppend(t *testing.T) { + st := New(bishMiddleware).Append(flipMiddleware, flipMiddleware).Then(bishHandler) + res := serveAndRequest(st) + assertEquals(t, "bishMiddleware>flipMiddleware>flipMiddleware>bishHandler [bish=bash]", res) +} + +func TestAppendDoesNotMutate(t *testing.T) { + st1 := New(bishMiddleware, flipMiddleware) + st2 := st1.Append(flipMiddleware, flipMiddleware) + res := serveAndRequest(st1.Then(bishHandler)) + assertEquals(t, "bishMiddleware>flipMiddleware>bishHandler [bish=bash]", res) + res = serveAndRequest(st2.Then(bishHandler)) + assertEquals(t, "bishMiddleware>flipMiddleware>flipMiddleware>flipMiddleware>bishHandler [bish=bash]", res) +} + +func TestThen(t *testing.T) { + chf := func(ctx *Context, w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "An anonymous ContextHandlerFunc") + } + st := New().Then(chf) + res := serveAndRequest(st) + assertEquals(t, "An anonymous ContextHandlerFunc", res) +} + +func TestThenHandler(t *testing.T) { + st := New().ThenHandler(http.NotFoundHandler()) + res := serveAndRequest(st) + assertEquals(t, "404 page not found\n", res) +} + +func TestThenHandlerFunc(t *testing.T) { + hf := func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "An anonymous HandlerFunc") + } + st := New().ThenHandlerFunc(hf) + res := serveAndRequest(st) + assertEquals(t, "An anonymous HandlerFunc", res) +} + +func TestMixedMiddleware(t *testing.T) { + st := New(bishMiddleware, Adapt(wobbleMiddleware), flipMiddleware).Then(bishHandler) + res := serveAndRequest(st) + assertEquals(t, "bishMiddleware>wobbleMiddleware>flipMiddleware>bishHandler [bish=bash]", res) +} + +func TestInject(t *testing.T) { + st := New(flipMiddleware).Then(flipHandler) + st2 := Inject(st, "bish", "boop") + + res := serveAndRequest(st2) + assertEquals(t, "flipMiddleware>flipHandler [bish=boop,flip=<nil>]", res) + + res = serveAndRequest(st) + assertEquals(t, "flipMiddleware>flipHandler [bish=<nil>,flip=<nil>]", res) +} |
