pip compatible server to serve Python packages out of GitHub
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

154 lines
3.0 KiB

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
}