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) | |||
| } | |||