diff --git a/handler_17.go b/handler_17.go index 4cde993..7f51e3b 100644 --- a/handler_17.go +++ b/handler_17.go @@ -1,4 +1,4 @@ -// +build go1.7 +// +build go1.7,!go1.9 package kami @@ -10,33 +10,11 @@ import ( netcontext "golang.org/x/net/context" ) -// HandlerType is the type of Handlers and types that kami internally converts to -// ContextHandler. In order to provide an expressive API, this type is an alias for -// interface{} that is named for the purposes of documentation, however only the -// following concrete types are accepted: -// - types that implement http.Handler -// - types that implement ContextHandler -// - func(http.ResponseWriter, *http.Request) -// - func(context.Context, http.ResponseWriter, *http.Request) -type HandlerType interface{} - -// ContextHandler is like http.Handler but supports context. -type ContextHandler interface { - ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) -} - // OldContextHandler is like ContextHandler but uses the old x/net/context. type OldContextHandler interface { ServeHTTPContext(netcontext.Context, http.ResponseWriter, *http.Request) } -// HandlerFunc is like http.HandlerFunc with context. -type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request) - -func (h HandlerFunc) ServeHTTPContext(ctx context.Context, w http.ResponseWriter, r *http.Request) { - h(ctx, w, r) -} - // wrap tries to turn a HandlerType into a ContextHandler func wrap(h HandlerType) ContextHandler { switch x := h.(type) { diff --git a/handler_19.go b/handler_19.go new file mode 100644 index 0000000..342b21b --- /dev/null +++ b/handler_19.go @@ -0,0 +1,28 @@ +// +build go1.9 + +package kami + +import ( + "context" + "fmt" + "net/http" +) + +// wrap tries to turn a HandlerType into a ContextHandler +func wrap(h HandlerType) ContextHandler { + switch x := h.(type) { + case ContextHandler: + return x + case func(context.Context, http.ResponseWriter, *http.Request): + return HandlerFunc(x) + case http.Handler: + return HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) { + x.ServeHTTP(w, r) + }) + case func(http.ResponseWriter, *http.Request): + return HandlerFunc(func(_ context.Context, w http.ResponseWriter, r *http.Request) { + x(w, r) + }) + } + panic(fmt.Errorf("unsupported HandlerType: %T", h)) +} diff --git a/handler_new.go b/handler_new.go new file mode 100644 index 0000000..6d0ff46 --- /dev/null +++ b/handler_new.go @@ -0,0 +1,30 @@ +// +build go1.7 + +package kami + +import ( + "context" + "net/http" +) + +// HandlerType is the type of Handlers and types that kami internally converts to +// ContextHandler. In order to provide an expressive API, this type is an alias for +// interface{} that is named for the purposes of documentation, however only the +// following concrete types are accepted: +// - types that implement http.Handler +// - types that implement ContextHandler +// - func(http.ResponseWriter, *http.Request) +// - func(context.Context, http.ResponseWriter, *http.Request) +type HandlerType interface{} + +// ContextHandler is like http.Handler but supports context. +type ContextHandler interface { + ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) +} + +// HandlerFunc is like http.HandlerFunc with context. +type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request) + +func (h HandlerFunc) ServeHTTPContext(ctx context.Context, w http.ResponseWriter, r *http.Request) { + h(ctx, w, r) +} diff --git a/middleware_17.go b/middleware_17.go index 1e7942a..fcf280c 100644 --- a/middleware_17.go +++ b/middleware_17.go @@ -1,4 +1,4 @@ -// +build go1.7 +// +build go1.7,!go1.9 package kami @@ -6,141 +6,11 @@ import ( "context" "fmt" "net/http" - "unicode/utf8" "github.com/zenazn/goji/web/mutil" netcontext "golang.org/x/net/context" ) -// Middleware is a function that takes the current request context and returns a new request context. -// You can use middleware to build your context before your handler handles a request. -// As a special case, middleware that returns nil will halt middleware and handler execution (LogHandler will still run). -type Middleware func(context.Context, http.ResponseWriter, *http.Request) context.Context - -// MiddlewareType represents types that kami can convert to Middleware. -// kami will try its best to convert standard, non-context middleware. -// See the Use function for important information about how kami middleware is run. -// The following concrete types are accepted: -// - Middleware -// - func(context.Context, http.ResponseWriter, *http.Request) context.Context -// - func(http.ResponseWriter, *http.Request) context.Context -// - func(http.Handler) http.Handler [* see Use docs] -// - func(http.ContextHandler) http.ContextHandler [* see Use docs] -// - http.Handler [read only] -// - func(http.ResponseWriter, *http.Request) [read only] -// The old x/net/context is also supported. -type MiddlewareType interface{} - -// Afterware is a function that will run after middleware and the request. -// Afterware takes the request context and returns a new context, but unlike middleware, -// returning nil won't halt execution of other afterware. -type Afterware func(context.Context, mutil.WriterProxy, *http.Request) context.Context - -// Afterware represents types that kami can convert to Afterware. -// The following concrete types are accepted: -// - Afterware -// - func(context.Context, mutil.WriterProxy, *http.Request) context.Context -// - func(context.Context, http.ResponseWriter, *http.Request) context.Context -// - func(context.Context, *http.Request) context.Context -// - func(context.Context) context.Context -// - Middleware types -// The old x/net/context is also supported. -type AfterwareType interface{} - -// run runs the middleware chain for a particular request. -// run returns false if it should stop early. -func (m *wares) run(ctx context.Context, w http.ResponseWriter, r *http.Request) (*http.Request, context.Context, bool) { - if m.middleware != nil { - // hierarchical middleware - for i, c := range r.URL.Path { - if c == '/' || i == len(r.URL.Path)-1 { - mws, ok := m.middleware[r.URL.Path[:i+1]] - if !ok { - continue - } - for _, mw := range mws { - // return nil context to stop - result := mw(ctx, w, r) - if result == nil { - return r, ctx, false - } - if result != ctx { - r = r.WithContext(result) - } - ctx = result - } - } - } - } - - if m.wildcards != nil { - // wildcard middleware - if wild, params := m.wildcards.Get(r.URL.Path); wild != nil { - if mws, ok := wild.(*[]Middleware); ok { - ctx = mergeParams(ctx, params) - r = r.WithContext(ctx) - for _, mw := range *mws { - result := mw(ctx, w, r) - if result == nil { - return r, ctx, false - } - if result != ctx { - r = r.WithContext(result) - } - ctx = result - } - } - } - } - - return r, ctx, true -} - -// after runs the afterware chain for a particular request. -// after can't stop early -func (m *wares) after(ctx context.Context, w mutil.WriterProxy, r *http.Request) (*http.Request, context.Context) { - if m.afterWildcards != nil { - // wildcard afterware - if wild, params := m.afterWildcards.Get(r.URL.Path); wild != nil { - if aws, ok := wild.(*[]Afterware); ok { - ctx = mergeParams(ctx, params) - r = r.WithContext(ctx) - for _, aw := range *aws { - result := aw(ctx, w, r) - if result != nil { - if result != ctx { - r = r.WithContext(result) - } - ctx = result - } - } - } - } - } - - if m.afterware != nil { - // hierarchical afterware, like middleware in reverse - path := r.URL.Path - for len(path) > 0 { - chr, size := utf8.DecodeLastRuneInString(path) - if chr == '/' || len(path) == len(r.URL.Path) { - for _, aw := range m.afterware[path] { - result := aw(ctx, w, r) - if result != nil { - if result != ctx { - r = r.WithContext(result) - } - ctx = result - } - } - } - path = path[:len(path)-size] - } - } - - return r, ctx -} - // convert turns standard http middleware into kami Middleware if needed. func convert(mw MiddlewareType) Middleware { switch x := mw.(type) { @@ -263,17 +133,6 @@ func convertAW(aw AfterwareType) Afterware { panic(fmt.Errorf("unsupported AfterwareType: %T", aw)) } -// dummyHandler is used to keep track of whether the next middleware was called or not. -type dummyHandler bool - -func (dh *dummyHandler) ServeHTTP(http.ResponseWriter, *http.Request) { - *dh = true -} - -func (dh *dummyHandler) ServeHTTPContext(_ context.Context, _ http.ResponseWriter, _ *http.Request) { - *dh = true -} - // oldDummyHandler is dummyHandler compatible with the old context type. type oldDummyHandler bool diff --git a/middleware_19.go b/middleware_19.go new file mode 100644 index 0000000..10729cb --- /dev/null +++ b/middleware_19.go @@ -0,0 +1,104 @@ +// +build go1.9 + +package kami + +import ( + "context" + "fmt" + "net/http" + + "github.com/zenazn/goji/web/mutil" +) + +// convert turns standard http middleware into kami Middleware if needed. +func convert(mw MiddlewareType) Middleware { + switch x := mw.(type) { + case Middleware: + return x + case func(context.Context, http.ResponseWriter, *http.Request) context.Context: + return Middleware(x) + case func(ContextHandler) ContextHandler: + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context { + var dh dummyHandler + x(&dh).ServeHTTPContext(ctx, w, r) + if !dh { + return nil + } + return ctx + } + case func(http.Handler) http.Handler: + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context { + var dh dummyHandler + x(&dh).ServeHTTP(w, r) + if !dh { + return nil + } + return ctx + } + case http.Handler: + return Middleware(func(_ context.Context, w http.ResponseWriter, r *http.Request) context.Context { + x.ServeHTTP(w, r) + return r.Context() + }) + case func(w http.ResponseWriter, r *http.Request): + return Middleware(func(_ context.Context, w http.ResponseWriter, r *http.Request) context.Context { + x(w, r) + return r.Context() + }) + case func(w http.ResponseWriter, r *http.Request) context.Context: + return Middleware(func(_ context.Context, w http.ResponseWriter, r *http.Request) context.Context { + return x(w, r) + }) + } + panic(fmt.Errorf("unsupported MiddlewareType: %T", mw)) +} + +// convertAW +func convertAW(aw AfterwareType) Afterware { + switch x := aw.(type) { + case Afterware: + return x + case func(context.Context, mutil.WriterProxy, *http.Request) context.Context: + return Afterware(x) + case func(context.Context, *http.Request) context.Context: + return func(ctx context.Context, _ mutil.WriterProxy, r *http.Request) context.Context { + return x(ctx, r) + } + case func(context.Context) context.Context: + return func(ctx context.Context, _ mutil.WriterProxy, _ *http.Request) context.Context { + return x(ctx) + } + case Middleware: + return func(ctx context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + return x(ctx, w, r) + } + case func(context.Context, http.ResponseWriter, *http.Request) context.Context: + return func(ctx context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + return x(ctx, w, r) + } + case func(w http.ResponseWriter, r *http.Request) context.Context: + return Afterware(func(_ context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + return x(w, r) + }) + case func(w mutil.WriterProxy, r *http.Request) context.Context: + return Afterware(func(_ context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + return x(w, r) + }) + case http.Handler: + return Afterware(func(_ context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + x.ServeHTTP(w, r) + return r.Context() + }) + case func(w http.ResponseWriter, r *http.Request): + return Afterware(func(_ context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + x(w, r) + return r.Context() + }) + case func(w mutil.WriterProxy, r *http.Request): + return Afterware(func(_ context.Context, w mutil.WriterProxy, r *http.Request) context.Context { + x(w, r) + return r.Context() + }) + } + panic(fmt.Errorf("unsupported AfterwareType: %T", aw)) +} diff --git a/middleware_new.go b/middleware_new.go new file mode 100644 index 0000000..1121684 --- /dev/null +++ b/middleware_new.go @@ -0,0 +1,151 @@ +// +build go1.7 + +package kami + +import ( + "context" + "net/http" + "unicode/utf8" + + "github.com/zenazn/goji/web/mutil" +) + +// Middleware is a function that takes the current request context and returns a new request context. +// You can use middleware to build your context before your handler handles a request. +// As a special case, middleware that returns nil will halt middleware and handler execution (LogHandler will still run). +type Middleware func(context.Context, http.ResponseWriter, *http.Request) context.Context + +// MiddlewareType represents types that kami can convert to Middleware. +// kami will try its best to convert standard, non-context middleware. +// See the Use function for important information about how kami middleware is run. +// The following concrete types are accepted: +// - Middleware +// - func(context.Context, http.ResponseWriter, *http.Request) context.Context +// - func(http.ResponseWriter, *http.Request) context.Context +// - func(http.Handler) http.Handler [* see Use docs] +// - func(http.ContextHandler) http.ContextHandler [* see Use docs] +// - http.Handler [read only] +// - func(http.ResponseWriter, *http.Request) [read only] +// The old x/net/context is also supported. +type MiddlewareType interface{} + +// Afterware is a function that will run after middleware and the request. +// Afterware takes the request context and returns a new context, but unlike middleware, +// returning nil won't halt execution of other afterware. +type Afterware func(context.Context, mutil.WriterProxy, *http.Request) context.Context + +// Afterware represents types that kami can convert to Afterware. +// The following concrete types are accepted: +// - Afterware +// - func(context.Context, mutil.WriterProxy, *http.Request) context.Context +// - func(context.Context, http.ResponseWriter, *http.Request) context.Context +// - func(context.Context, *http.Request) context.Context +// - func(context.Context) context.Context +// - Middleware types +// The old x/net/context is also supported. +type AfterwareType interface{} + +// run runs the middleware chain for a particular request. +// run returns false if it should stop early. +func (m *wares) run(ctx context.Context, w http.ResponseWriter, r *http.Request) (*http.Request, context.Context, bool) { + if m.middleware != nil { + // hierarchical middleware + for i, c := range r.URL.Path { + if c == '/' || i == len(r.URL.Path)-1 { + mws, ok := m.middleware[r.URL.Path[:i+1]] + if !ok { + continue + } + for _, mw := range mws { + // return nil context to stop + result := mw(ctx, w, r) + if result == nil { + return r, ctx, false + } + if result != ctx { + r = r.WithContext(result) + } + ctx = result + } + } + } + } + + if m.wildcards != nil { + // wildcard middleware + if wild, params := m.wildcards.Get(r.URL.Path); wild != nil { + if mws, ok := wild.(*[]Middleware); ok { + ctx = mergeParams(ctx, params) + r = r.WithContext(ctx) + for _, mw := range *mws { + result := mw(ctx, w, r) + if result == nil { + return r, ctx, false + } + if result != ctx { + r = r.WithContext(result) + } + ctx = result + } + } + } + } + + return r, ctx, true +} + +// after runs the afterware chain for a particular request. +// after can't stop early +func (m *wares) after(ctx context.Context, w mutil.WriterProxy, r *http.Request) (*http.Request, context.Context) { + if m.afterWildcards != nil { + // wildcard afterware + if wild, params := m.afterWildcards.Get(r.URL.Path); wild != nil { + if aws, ok := wild.(*[]Afterware); ok { + ctx = mergeParams(ctx, params) + r = r.WithContext(ctx) + for _, aw := range *aws { + result := aw(ctx, w, r) + if result != nil { + if result != ctx { + r = r.WithContext(result) + } + ctx = result + } + } + } + } + } + + if m.afterware != nil { + // hierarchical afterware, like middleware in reverse + path := r.URL.Path + for len(path) > 0 { + chr, size := utf8.DecodeLastRuneInString(path) + if chr == '/' || len(path) == len(r.URL.Path) { + for _, aw := range m.afterware[path] { + result := aw(ctx, w, r) + if result != nil { + if result != ctx { + r = r.WithContext(result) + } + ctx = result + } + } + } + path = path[:len(path)-size] + } + } + + return r, ctx +} + +// dummyHandler is used to keep track of whether the next middleware was called or not. +type dummyHandler bool + +func (dh *dummyHandler) ServeHTTP(http.ResponseWriter, *http.Request) { + *dh = true +} + +func (dh *dummyHandler) ServeHTTPContext(_ context.Context, _ http.ResponseWriter, _ *http.Request) { + *dh = true +}