package web
|
|
|
|
import (
|
|
"net/http"
|
|
"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 {
|
|
prefix string
|
|
method method
|
|
pattern Pattern
|
|
handler Handler
|
|
}
|
|
|
|
type router struct {
|
|
lock sync.Mutex
|
|
routes []route
|
|
notFound Handler
|
|
machine *routeMachine
|
|
}
|
|
|
|
func httpMethod(mname string) method {
|
|
if method, ok := validMethodsMap[mname]; ok {
|
|
return method
|
|
}
|
|
return mIDK
|
|
}
|
|
|
|
func (rt *router) compile() *routeMachine {
|
|
rt.lock.Lock()
|
|
defer rt.lock.Unlock()
|
|
sm := routeMachine{
|
|
sm: compile(rt.routes),
|
|
routes: rt.routes,
|
|
}
|
|
rt.setMachine(&sm)
|
|
return &sm
|
|
}
|
|
|
|
func (rt *router) getMatch(c *C, w http.ResponseWriter, r *http.Request) Match {
|
|
rm := rt.getMachine()
|
|
if rm == nil {
|
|
rm = rt.compile()
|
|
}
|
|
|
|
methods, route := rm.route(c, w, r)
|
|
if route != nil {
|
|
return Match{
|
|
Pattern: route.pattern,
|
|
Handler: route.handler,
|
|
}
|
|
}
|
|
|
|
if methods == 0 {
|
|
return Match{Handler: rt.notFound}
|
|
}
|
|
|
|
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[interface{}]interface{}{
|
|
ValidMethodsKey: methodsList,
|
|
}
|
|
} else {
|
|
c.Env[ValidMethodsKey] = methodsList
|
|
}
|
|
return Match{Handler: rt.notFound}
|
|
}
|
|
|
|
func (rt *router) route(c *C, w http.ResponseWriter, r *http.Request) {
|
|
match := GetMatch(*c)
|
|
if match.Handler == nil {
|
|
match = rt.getMatch(c, w, r)
|
|
}
|
|
match.Handler.ServeHTTPC(*c, w, r)
|
|
}
|
|
|
|
func (rt *router) handleUntyped(p PatternType, m method, h HandlerType) {
|
|
rt.handle(ParsePattern(p), m, parseHandler(h))
|
|
}
|
|
|
|
func (rt *router) handle(p Pattern, m method, h Handler) {
|
|
rt.lock.Lock()
|
|
defer rt.lock.Unlock()
|
|
|
|
// Calculate the sorted insertion point, because there's no reason to do
|
|
// swapping hijinks if we're already making a copy. We need to use
|
|
// bubble sort because we can only compare adjacent elements.
|
|
pp := p.Prefix()
|
|
var i int
|
|
for i = len(rt.routes); i > 0; i-- {
|
|
rip := rt.routes[i-1].prefix
|
|
if rip <= pp || strings.HasPrefix(rip, pp) {
|
|
break
|
|
}
|
|
}
|
|
|
|
newRoutes := make([]route, len(rt.routes)+1)
|
|
copy(newRoutes, rt.routes[:i])
|
|
newRoutes[i] = route{
|
|
prefix: pp,
|
|
method: m,
|
|
pattern: p,
|
|
handler: h,
|
|
}
|
|
copy(newRoutes[i+1:], rt.routes[i:])
|
|
|
|
rt.setMachine(nil)
|
|
rt.routes = newRoutes
|
|
}
|