|
|
package web
|
|
|
|
|
|
import (
|
|
|
"log"
|
|
|
"net/http"
|
|
|
"regexp"
|
|
|
"sort"
|
|
|
"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
|
|
|
)
|
|
|
|
|
|
// The key used to communicate to the NotFound handler what methods would have
|
|
|
// been allowed if they'd been provided.
|
|
|
const ValidMethodsKey = "goji.web.validMethods"
|
|
|
|
|
|
var validMethodsMap = map[string]method{
|
|
|
"CONNECT": mCONNECT,
|
|
|
"DELETE": mDELETE,
|
|
|
"GET": mGET,
|
|
|
"HEAD": mHEAD,
|
|
|
"OPTIONS": mOPTIONS,
|
|
|
"PATCH": mPATCH,
|
|
|
"POST": mPOST,
|
|
|
"PUT": mPUT,
|
|
|
"TRACE": mTRACE,
|
|
|
}
|
|
|
|
|
|
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, unless dryrun is
|
|
|
// set.
|
|
|
Match(r *http.Request, c *C, dryrun bool) bool
|
|
|
}
|
|
|
|
|
|
func parsePattern(p interface{}) Pattern {
|
|
|
switch p.(type) {
|
|
|
case Pattern:
|
|
|
return p.(Pattern)
|
|
|
case *regexp.Regexp:
|
|
|
return parseRegexpPattern(p.(*regexp.Regexp))
|
|
|
case string:
|
|
|
return parseStringPattern(p.(string))
|
|
|
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 {
|
|
|
if method, ok := validMethodsMap[mname]; ok {
|
|
|
return method
|
|
|
}
|
|
|
return mIDK
|
|
|
}
|
|
|
|
|
|
func (rt *router) route(c C, w http.ResponseWriter, r *http.Request) {
|
|
|
m := httpMethod(r.Method)
|
|
|
var methods method
|
|
|
for _, route := range rt.routes {
|
|
|
if !strings.HasPrefix(r.URL.Path, route.prefix) ||
|
|
|
!route.pattern.Match(r, &c, false) {
|
|
|
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
if route.method&m != 0 {
|
|
|
route.handler.ServeHTTPC(c, w, r)
|
|
|
return
|
|
|
} else if route.pattern.Match(r, &c, true) {
|
|
|
methods |= route.method
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if methods == 0 {
|
|
|
rt.notFound.ServeHTTPC(c, w, r)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
var methodsList = make([]string, 0)
|
|
|
for mname, meth := range validMethodsMap {
|
|
|
if methods&meth != 0 {
|
|
|
methodsList = append(methodsList, mname)
|
|
|
}
|
|
|
}
|
|
|
sort.Strings(methodsList)
|
|
|
|
|
|
if c.Env == nil {
|
|
|
c.Env = map[string]interface{}{
|
|
|
ValidMethodsKey: methodsList,
|
|
|
}
|
|
|
} else {
|
|
|
c.Env[ValidMethodsKey] = methodsList
|
|
|
}
|
|
|
rt.notFound.ServeHTTPC(c, w, r)
|
|
|
}
|
|
|
|
|
|
func (rt *router) handleUntyped(p interface{}, m method, h interface{}) {
|
|
|
pat := parsePattern(p)
|
|
|
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.
|
|
|
|
|
|
This method is commonly used to implement sub-routing: an admin application, for
|
|
|
instance, can expose a single handler that is attached to the main Mux by
|
|
|
calling Handle("/admin*", adminHandler) or similar. Note that this function
|
|
|
doesn't strip this prefix from the path before forwarding it on (e.g., the
|
|
|
handler will see the full path, including the "/admin" part), but this
|
|
|
functionality can easily be performed by an extra middleware layer.
|
|
|
*/
|
|
|
func (rt *router) Handle(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Connect(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Delete(pattern interface{}, handler interface{}) {
|
|
|
rt.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.
|
|
|
//
|
|
|
// All GET handlers also transparently serve HEAD requests, since net/http will
|
|
|
// take care of all the fiddly bits for you. If you wish to provide an alternate
|
|
|
// implementation of HEAD, you should add a handler explicitly and place it
|
|
|
// above your GET handler.
|
|
|
func (rt *router) Get(pattern interface{}, handler interface{}) {
|
|
|
rt.handleUntyped(pattern, mGET|mHEAD, 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 (rt *router) Head(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Options(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Patch(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Post(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Put(pattern interface{}, handler interface{}) {
|
|
|
rt.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 (rt *router) Trace(pattern interface{}, handler interface{}) {
|
|
|
rt.handleUntyped(pattern, mTRACE, 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.
|
|
|
//
|
|
|
// As a convenience, the context environment variable "goji.web.validMethods"
|
|
|
// (also available as the constant ValidMethodsKey) will be set to the list of
|
|
|
// HTTP methods that could have been routed had they been provided on an
|
|
|
// otherwise identical request.
|
|
|
func (rt *router) NotFound(handler interface{}) {
|
|
|
rt.notFound = parseHandler(handler)
|
|
|
}
|