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.
| @ -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 | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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, | |||||
| } | |||||
| } | |||||
| @ -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) | |||||
| } | |||||
| @ -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<ip>(?:\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) | |||||
| } | |||||