From ed438ca532172ee07a9ffccba9328e7baf858b5f Mon Sep 17 00:00:00 2001 From: Carl Jackson Date: Mon, 17 Mar 2014 00:00:23 -0700 Subject: [PATCH] A wild web framework appears They say that every programmer builds a web framework at some point. This one is mine. The basic idea behind this one is that I wanted a Sinatra for Go, and I couldn't find one anywhere. Furthermore, net/http is in many ways really close to what I want out of a Sinatra-in-Go, and many of the frameworks I did find seemed to reinvent too much, or were incompatible with net/http in weird ways, or used too much questionable reflection magic. So long story short, I wrote my own. This implementation is only half-baked, and among other things it's missing a whole lot of tests. --- web/middleware.go | 184 ++++++++++++++++++++++++++++++ web/mux.go | 78 +++++++++++++ web/pattern.go | 121 ++++++++++++++++++++ web/router.go | 277 ++++++++++++++++++++++++++++++++++++++++++++++ web/web.go | 122 ++++++++++++++++++++ 5 files changed, 782 insertions(+) create mode 100644 web/middleware.go create mode 100644 web/mux.go create mode 100644 web/pattern.go create mode 100644 web/router.go create mode 100644 web/web.go diff --git a/web/middleware.go b/web/middleware.go new file mode 100644 index 0000000..fe8da8c --- /dev/null +++ b/web/middleware.go @@ -0,0 +1,184 @@ +package web + +import ( + "fmt" + "log" + "net/http" + "sync" +) + +// Maximum size of the pool of spare middleware stacks +const mPoolSize = 32 + +type mLayer struct { + fn func(*C, http.Handler) http.Handler + name string +} + +type mStack struct { + lock sync.Mutex + stack []mLayer + pool chan *cStack + router Handler +} + +// Constructing a middleware stack involves a lot of allocations: at the very +// least each layer will have to close over the layer after (inside) it, and +// perhaps a context object. Instead of doing this on every request, let's cache +// fully assembled middleware stacks (the "c" stands for "cached"). +type cStack struct { + C + m http.Handler +} + +func (s *cStack) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.C = C{} + s.m.ServeHTTP(w, r) +} +func (s *cStack) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + s.C = c + s.m.ServeHTTP(w, r) +} + +func (m *mStack) appendLayer(name string, fn interface{}) { + var ml mLayer + ml.name = name + switch fn.(type) { + case func(http.Handler) http.Handler: + unwrapped := fn.(func(http.Handler) http.Handler) + ml.fn = func(c *C, h http.Handler) http.Handler { + return unwrapped(h) + } + case func(*C, http.Handler) http.Handler: + ml.fn = fn.(func(*C, http.Handler) http.Handler) + default: + log.Fatalf(`Unknown middleware type %v. Expected a function `+ + `with signature "func(http.Handler) http.Handler" or `+ + `"func(*web.C, http.Handler) http.Handler".`, fn) + } + m.stack = append(m.stack, ml) +} + +func (m *mStack) findLayer(name string) int { + for i, middleware := range m.stack { + if middleware.name == name { + return i + } + } + return -1 +} + +func (m *mStack) invalidate() { + old := m.pool + m.pool = make(chan *cStack, mPoolSize) + close(old) + for _ = range old { + } +} + +func (m *mStack) newStack() *cStack { + m.lock.Lock() + defer m.lock.Unlock() + + cs := cStack{} + router := m.router + + cs.m = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + router.ServeHTTPC(cs.C, w, r) + }) + for i := len(m.stack) - 1; i >= 0; i-- { + cs.m = m.stack[i].fn(&cs.C, cs.m) + } + + return &cs +} + +func (m *mStack) alloc() *cStack { + select { + case cs := <-m.pool: + if cs == nil { + return m.alloc() + } + return cs + default: + return m.newStack() + } +} + +func (m *mStack) release(cs *cStack) { + // It's possible that the pool has been invalidated and therefore + // closed, in which case we'll start panicing, which is dumb. I'm not + // sure this is actually better than just grabbing a lock, but whatever. + defer func() { + recover() + }() + select { + case m.pool <- cs: + default: + } +} + +// Append the given middleware to the middleware stack. See the documentation +// for type Mux for a list of valid middleware types. +// +// No attempt is made to enforce the uniqueness of middleware names. +func (m *mStack) Use(name string, middleware interface{}) { + m.lock.Lock() + defer m.lock.Unlock() + m.appendLayer(name, middleware) + m.invalidate() +} + +// Insert the given middleware immediately before a given existing middleware in +// the stack. See the documentation for type Mux for a list of valid middleware +// types. Returns an error if no middleware has the name given by "before." +// +// No attempt is made to enforce the uniqueness of names. If the insertion point +// is ambiguous, the first (outermost) one is chosen. +func (m *mStack) Insert(name string, middleware interface{}, before string) error { + m.lock.Lock() + defer m.lock.Unlock() + i := m.findLayer(before) + if i < 0 { + return fmt.Errorf("web: unknown middleware %v", before) + } + + m.appendLayer(name, middleware) + inserted := m.stack[len(m.stack)-1] + copy(m.stack[i+1:], m.stack[i:]) + m.stack[i] = inserted + + m.invalidate() + return nil +} + +// Remove the given named middleware from the middleware stack. Returns an error +// if there is no middleware by that name. +// +// If the name of the middleware to delete is ambiguous, the first (outermost) +// one is chosen. +func (m *mStack) Abandon(name string) error { + m.lock.Lock() + defer m.lock.Unlock() + i := m.findLayer(name) + if i < 0 { + return fmt.Errorf("web: unknown middleware %v", name) + } + + copy(m.stack[i:], m.stack[i+1:]) + m.stack = m.stack[:len(m.stack)-1 : len(m.stack)] + + m.invalidate() + return nil +} + +// Returns a list of middleware currently in use. +func (m *mStack) Middleware() []string { + m.lock.Lock() + defer m.lock.Unlock() + stack := make([]string, len(m.stack)) + for i, ml := range m.stack { + stack[i] = ml.name + } + return stack +} diff --git a/web/mux.go b/web/mux.go new file mode 100644 index 0000000..d95e929 --- /dev/null +++ b/web/mux.go @@ -0,0 +1,78 @@ +package web + +import ( + "net/http" +) + +/* +An HTTP multiplexer, much like net/http's ServeMux. + +Routes may be added using any of the various HTTP-method-specific functions. +When processing a request, when iterating in insertion order the first route +that matches both the request's path and method is used. + +There are two other differences worth mentioning between web.Mux and +http.ServeMux. First, string patterns (i.e., Sinatra-like patterns) must match +exactly: the "rooted subtree" behavior of ServeMux is not implemented. Secondly, +unlike ServeMux, Mux does not support Host-specific patterns. + +If you require any of these features, remember that you are free to mix and +match muxes at any part of the stack. + +In order to provide a sane API, many functions on Mux take interface{}'s. This +is obviously not a very satisfying solution, but it's probably the best we can +do for now. Instead of duplicating documentation on each method, the types +accepted by those functions are documented here. + +A middleware (the untyped parameter in Use() and Insert()) must be one of the +following types: + - func(http.Handler) http.Handler + - func(c *web.C, http.Handler) http.Handler +All of the route-adding functions on Mux take two untyped parameters: pattern +and handler. Pattern must be one of the following types: + - string (interpreted as a Sinatra pattern) + - regexp.Regexp + - web.Pattern +Handler must be one of the following types: + - http.Handler + - web.Handler + - func(w http.ResponseWriter, r *http.Request) + - func(c web.C, w http.ResponseWriter, r *http.Request) +*/ +type Mux struct { + mStack + router +} + +// Create a new Mux without any routes or middleware. +func New() *Mux { + mux := Mux{ + mStack: mStack{ + stack: make([]mLayer, 0), + pool: make(chan *cStack), + }, + router: router{ + routes: make([]route, 0), + notFound: parseHandler(http.NotFound), + }, + } + mux.mStack.router = HandlerFunc(mux.router.route) + return &mux +} + +// Serve a request with the given Mux. Satisfies the http.Handler interface. +func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + stack := m.mStack.alloc() + defer m.mStack.release(stack) + + stack.ServeHTTP(w, r) +} + +// Serve a context dependent request with the given Mux. Satisfies the +// web.Handler interface. +func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + stack := m.mStack.alloc() + defer m.mStack.release(stack) + + stack.ServeHTTPC(c, w, r) +} diff --git a/web/pattern.go b/web/pattern.go new file mode 100644 index 0000000..3733765 --- /dev/null +++ b/web/pattern.go @@ -0,0 +1,121 @@ +package web + +import ( + "fmt" + "net/http" + "regexp" + "strings" +) + +type regexpPattern struct { + re *regexp.Regexp + names []string +} + +func (p regexpPattern) Prefix() string { + prefix, _ := p.re.LiteralPrefix() + return prefix +} +func (p regexpPattern) Match(r *http.Request, c *C) bool { + matches := p.re.FindStringSubmatch(r.URL.Path) + if matches == nil { + return false + } + + if c.UrlParams == nil && len(matches) > 0 { + c.UrlParams = make(map[string]string, len(matches)-1) + } + for i := 1; i < len(matches); i++ { + c.UrlParams[p.names[i]] = matches[i] + } + return true +} + +func parseRegexpPattern(re *regexp.Regexp) regexpPattern { + rnames := re.SubexpNames() + // We have to make our own copy since package regexp forbids us + // from scribbling over the slice returned by SubexpNames(). + names := make([]string, len(rnames)) + for i, rname := range rnames { + if rname == "" { + rname = fmt.Sprintf("$%d", i) + } + names[i] = rname + } + return regexpPattern{ + re: re, + names: names, + } +} + +type stringPattern struct { + raw string + pats []string + literals []string + isPrefix bool +} + +func (s stringPattern) Prefix() string { + return s.literals[0] +} + +func (s stringPattern) Match(r *http.Request, c *C) bool { + path := r.URL.Path + matches := make([]string, len(s.pats)) + for i := 0; i < len(s.pats); i++ { + if !strings.HasPrefix(path, s.literals[i]) { + return false + } + path = path[len(s.literals[i]):] + + m := strings.IndexRune(path, '/') + if m == -1 { + m = len(path) + } + if m == 0 { + // Empty strings are not matches, otherwise routes like + // "/:foo" would match the path "/" + return false + } + matches[i] = path[:m] + path = path[m:] + } + // There's exactly one more literal than pat. + if s.isPrefix { + if strings.HasPrefix(path, s.literals[len(s.pats)]) { + return false + } + } else { + if path != s.literals[len(s.pats)] { + return false + } + } + + if c.UrlParams == nil && len(matches) > 0 { + c.UrlParams = make(map[string]string, len(matches)-1) + } + for i, match := range matches { + c.UrlParams[s.pats[i]] = match + } + return true +} + +func parseStringPattern(s string, isPrefix bool) stringPattern { + matches := patternRe.FindAllStringSubmatchIndex(s, -1) + pats := make([]string, len(matches)) + literals := make([]string, len(matches)+1) + n := 0 + for i, match := range matches { + a, b := match[2], match[3] + literals[i] = s[n : a-1] // Need to leave off the colon + pats[i] = s[a:b] + n = b + } + literals[len(matches)] = s[n:] + return stringPattern{ + raw: s, + pats: pats, + literals: literals, + isPrefix: isPrefix, + } +} diff --git a/web/router.go b/web/router.go new file mode 100644 index 0000000..0db15c4 --- /dev/null +++ b/web/router.go @@ -0,0 +1,277 @@ +package web + +import ( + "log" + "net/http" + "regexp" + "strings" + "sync" +) + +type method int + +const ( + mCONNECT method = 1 << iota + mDELETE + mGET + mHEAD + mOPTIONS + mPATCH + mPOST + mPUT + mTRACE + // We only natively support the methods above, but we pass through other + // methods. This constant pretty much only exists for the sake of mALL. + mIDK + + mALL method = mCONNECT | mDELETE | mGET | mHEAD | mOPTIONS | mPATCH | + mPOST | mPUT | mTRACE | mIDK +) + +type route struct { + // Theory: most real world routes have a string prefix which is both + // cheap(-ish) to test against and pretty selective. And, conveniently, + // both regexes and string patterns give us this out-of-box. + prefix string + method method + pattern Pattern + handler Handler +} + +type router struct { + lock sync.Mutex + routes []route + notFound Handler +} + +// A pattern determines whether or not a given request matches some criteria. +// They are often used in routes, which are essentially (pattern, methodSet, +// handler) tuples. If the method and pattern match, the given handler is used. +// +// Built-in implementations of this interface are used to implement regular +// expression and string matching. +type Pattern interface { + // In practice, most real-world routes have a string prefix that can be + // used to quickly determine if a pattern is an eligible match. The + // router uses the result of this function to optimize away calls to the + // full Match function, which is likely much more expensive to compute. + // If your Pattern does not support prefixes, this function should + // return the empty string. + Prefix() string + // Returns true if the request satisfies the pattern. This function is + // free to examine both the request and the context to make this + // decision. After it is certain that the request matches, this function + // should mutate or create c.UrlParams if necessary. + Match(r *http.Request, c *C) bool +} + +var patternRe = regexp.MustCompile(`/:([^/]+)`) + +func parsePattern(p interface{}, isPrefix bool) Pattern { + switch p.(type) { + case Pattern: + return p.(Pattern) + case *regexp.Regexp: + return parseRegexpPattern(p.(*regexp.Regexp)) + case string: + return parseStringPattern(p.(string), isPrefix) + default: + log.Fatalf("Unknown pattern type %v. Expected a web.Pattern, "+ + "regexp.Regexp, or a string.", p) + } + panic("log.Fatalf does not return") +} + +type netHttpWrap struct { + http.Handler +} + +func (h netHttpWrap) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.Handler.ServeHTTP(w, r) +} +func (h netHttpWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + h.Handler.ServeHTTP(w, r) +} + +func parseHandler(h interface{}) Handler { + switch h.(type) { + case Handler: + return h.(Handler) + case http.Handler: + return netHttpWrap{h.(http.Handler)} + case func(c C, w http.ResponseWriter, r *http.Request): + f := h.(func(c C, w http.ResponseWriter, r *http.Request)) + return HandlerFunc(f) + case func(w http.ResponseWriter, r *http.Request): + f := h.(func(w http.ResponseWriter, r *http.Request)) + return netHttpWrap{http.HandlerFunc(f)} + default: + log.Fatalf("Unknown handler type %v. Expected a web.Handler, "+ + "a http.Handler, or a function with signature func(C, "+ + "http.ResponseWriter, *http.Request) or "+ + "func(http.ResponseWriter, http.Request)", h) + } + panic("log.Fatalf does not return") +} + +func httpMethod(mname string) method { + switch mname { + case "CONNECT": + return mCONNECT + case "DELETE": + return mDELETE + case "GET": + return mGET + case "HEAD": + return mHEAD + case "OPTIONS": + return mOPTIONS + case "PATCH": + return mPATCH + case "POST": + return mPOST + case "PUT": + return mPUT + case "TRACE": + return mTRACE + default: + return mIDK + } +} + +func (rt *router) route(c C, w http.ResponseWriter, r *http.Request) { + m := httpMethod(r.Method) + for _, route := range rt.routes { + if route.method&m == 0 || + !strings.HasPrefix(r.URL.Path, route.prefix) || + !route.pattern.Match(r, &c) { + continue + } + route.handler.ServeHTTPC(c, w, r) + return + } + + rt.notFound.ServeHTTPC(c, w, r) +} + +func (rt *router) handleUntyped(p interface{}, m method, h interface{}) { + pat := parsePattern(p, false) + rt.handle(pat, m, parseHandler(h)) +} + +func (rt *router) handle(p Pattern, m method, h Handler) { + // We're being a little sloppy here: we assume that pointer assignments + // are atomic, and that there is no way a locked append here can affect + // another goroutine which looked at rt.routes without a lock. + rt.lock.Lock() + defer rt.lock.Unlock() + rt.routes = append(rt.routes, route{ + prefix: p.Prefix(), + method: m, + pattern: p, + handler: h, + }) +} + +// This is a bit silly, but I've renamed the method receivers in the public +// functions here "m" instead of the standard "rt", since they will eventually +// be shown on the documentation for the Mux that they are included in. + +// Dispatch to the given handler when the pattern matches, regardless of HTTP +// method. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *router) Handle(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mALL, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// CONNECT. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *router) Connect(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mCONNECT, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// DELETE. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *router) Delete(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mDELETE, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// GET. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *router) Get(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mGET, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// HEAD. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *router) Head(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mHEAD, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// OPTIONS. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *router) Options(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mOPTIONS, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// PATCH. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *router) Patch(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mPATCH, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// POST. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *router) Post(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mPOST, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// PUT. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *router) Put(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mPUT, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// TRACE. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *router) Trace(pattern interface{}, handler interface{}) { + m.handleUntyped(pattern, mTRACE, handler) +} + +/* +Dispatch to the given handler when the given (Sinatra-like) pattern matches a +prefix of the path. This function explicitly takes a string parameter since you +can implement this behavior with a regular expression using the standard +Handle() function. + +This function is probably most helpful when implementing sub-routing: an admin +application, for instance, can expose a single handler, and can be hooked up all +at once by attaching a sub-route at "/admin". + +Notably, this function does not strip the matched prefix from downstream +handlers, so in the above example the handler would recieve requests with path +"/admin/foo/bar", for example, instead of just "/foo/bar". Luckily, this is a +problem easily surmountable by middleware. + +See the documentation for type Mux for a description of what types are accepted +for handler. +*/ +func (m *router) Sub(pattern string, handler interface{}) { + pat := parsePattern(pattern, true) + m.handle(pat, mALL, parseHandler(handler)) +} + +// Set the fallback (i.e., 404) handler for this mux. See the documentation for +// type Mux for a description of what types are accepted for handler. +func (m *router) NotFound(handler interface{}) { + m.notFound = parseHandler(handler) +} diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..60d17bb --- /dev/null +++ b/web/web.go @@ -0,0 +1,122 @@ +/* +Package web is a microframework inspired by Sinatra. + +The underlying philosophy behind this package is that net/http is a very good +HTTP library which is only missing a few features. If you disagree with this +statement (e.g., you think that the interfaces it exposes are not especially +good, or if you're looking for a comprehensive "batteries included" feature +list), you're likely not going to have a good time using this library. In that +spirit, we have attempted wherever possible to be compatible with net/http. You +should be able to insert any net/http compliant handler into this library, or +use this library with any other net/http compliant mux. + +This package attempts to solve three problems that net/http does not. First, it +allows you to specify URL patterns with Sinatra-like named wildcards and +regexps. Second, it allows you to write reconfigurable middleware stacks. And +finally, it allows you to attach additional context to requests, in a manner +that can be manipulated by both compliant middleware and handlers. + +A usage example: + + m := web.New() + +Use your favorite HTTP verbs: + + var legacyFooHttpHandler http.Handler // From elsewhere + m.Get("/foo", legacyFooHttpHandler) + m.Post("/bar", func(w http.ResponseWriter, r *http.Request) { + w.Write("Hello world!") + }) + +Bind parameters using either Sinatra-like patterns or regular expressions: + + m.Get("/hello/:name", func(c *web.C, w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %s!", c.UrlParams["name"]) + }) + pattern := regexp.MustCompile(`^/ip/(?P(?:\d{1,3}\.){3}\d{1,3})$`) + m.Get(pattern, func(c *web.C, w http.ResponseWriter, r *http.Request) { + fmt.Printf(w, "Info for IP address %s:", c.UrlParams["ip"]) + }) + +Middleware are functions that wrap http.Handlers, just like you'd use with raw +net/http. Middleware functions can optionally take a context parameter, which +will be threaded throughout the middleware stack and to the final handler, even +if not all of these things do not support contexts. Middleware are encouraged to +use the Env parameter to pass data to other middleware and to the final handler: + + m.Use(func(h http.Handler) http.Handler { + handler := func(w http.ResponseWriter, r *http.Request) { + log.Println("Before request") + h.ServeHTTP(w, r) + log.Println("After request") + } + return http.HandlerFunc(handler) + }) + m.Use(func(c *web.C, h http.Handler) http.Handler { + handler := func(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("user") + if err == nil { + c.Env["user"] = cookie.Raw + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(handler) + }) + + m.Get("/baz", func(c web.C, w http.ResponseWriter, r *http.Request) { + if user, ok := c.Env["user"], ok { + w.Write("Hello " + string(user)) + } else { + w.Write("Hello Stranger!") + } + }) +*/ +package web + +import ( + "net/http" +) + +/* +Per-request context object. Threaded through all compliant middleware layers and +to the final request handler. + +As an implementation detail, references to these structs are reused between +requests to reduce allocation churn, but the maps they contain are created fresh +on every request. If you are closing over a context (especially relevant for +middleware), you should not close over either the UrlParams or Env objects, +instead accessing them through the context whenever they are required. +*/ +type C struct { + // The parameters parsed by the mux from the URL itself. In most cases, + // will contain a map from programmer-specified identifiers to the + // strings that matched those identifiers, but if a unnamed regex + // capture is used, it will be assigned to the special identifiers "$1", + // "$2", etc. + UrlParams map[string]string + // A free-form environment, similar to Rack or PEP 333's environments. + // Middleware layers are encouraged to pass data to downstream layers + // and other handlers using this map, and are even more strongly + // encouraged to document and maybe namespace they keys they use. + Env map[string]interface{} +} + +// A superset of net/http's http.Handler, which also includes a mechanism for +// serving requests with a context. If your handler does not support the use of +// contexts, we encourage you to use http.Handler instead. +type Handler interface { + http.Handler + ServeHTTPC(C, http.ResponseWriter, *http.Request) +} + +// Like net/http's http.HandlerFunc, but supports a context object. Implements +// both http.Handler and web.Handler free of charge. +type HandlerFunc func(C, http.ResponseWriter, *http.Request) + +func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h(C{}, w, r) +} + +func (h HandlerFunc) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + h(c, w, r) +}