Merge pull request #210 from zhaoshichen1/kratos/sync_path_param

add restful router to bm
pull/213/head
Tony 6 years ago committed by GitHub
commit d386e4d604
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      pkg/net/http/blademaster/context.go
  2. 37
      pkg/net/http/blademaster/routergroup.go
  3. 206
      pkg/net/http/blademaster/server.go
  4. 627
      pkg/net/http/blademaster/tree.go
  5. 119
      pkg/net/http/blademaster/utils.go

@ -45,6 +45,11 @@ type Context struct {
method string method string
engine *Engine engine *Engine
RoutePath string
Params Params
} }
/************************************/ /************************************/
@ -56,21 +61,13 @@ type Context struct {
// See example in godoc. // See example in godoc.
func (c *Context) Next() { func (c *Context) Next() {
c.index++ c.index++
s := int8(len(c.handlers)) for c.index < int8(len(c.handlers)) {
for ; c.index < s; c.index++ {
// only check method on last handler, otherwise middlewares
// will never be effected if request method is not matched
if c.index == s-1 && c.method != c.Request.Method {
code := http.StatusMethodNotAllowed
c.Error = ecode.MethodNotAllowed
http.Error(c.Writer, http.StatusText(code), code)
return
}
c.handlers[c.index](c) c.handlers[c.index](c)
c.index++
} }
} }
// Abort prevents pending handlers from being called. Note that this will not stop the current handler. // Abort prevents pending handlers from being called. Note that this will not stop the current handler.
// Let's say you have an authorization middleware that validates that the current request is authorized. // Let's say you have an authorization middleware that validates that the current request is authorized.
// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers // If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers

@ -99,11 +99,6 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
return group.handle(httpMethod, relativePath, handlers...) return group.handle(httpMethod, relativePath, handlers...)
} }
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("HEAD", relativePath, handlers...)
}
// GET is a shortcut for router.Handle("GET", path, handle). // GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers...) return group.handle("GET", relativePath, handlers...)
@ -124,6 +119,22 @@ func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) I
return group.handle("DELETE", relativePath, handlers...) return group.handle("DELETE", relativePath, handlers...)
} }
// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("PATCH", relativePath, handlers...)
}
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("OPTIONS", relativePath, handlers...)
}
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("HEAD", relativePath, handlers...)
}
func (group *RouterGroup) combineHandlers(handlerGroups ...[]HandlerFunc) []HandlerFunc { func (group *RouterGroup) combineHandlers(handlerGroups ...[]HandlerFunc) []HandlerFunc {
finalSize := len(group.Handlers) finalSize := len(group.Handlers)
for _, handlers := range handlerGroups { for _, handlers := range handlerGroups {
@ -164,3 +175,19 @@ func (group *RouterGroup) injections(relativePath string) []HandlerFunc {
} }
return nil return nil
} }
// Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
group.handle("GET", relativePath, handlers...)
group.handle("POST", relativePath, handlers...)
group.handle("PUT", relativePath, handlers...)
group.handle("PATCH", relativePath, handlers...)
group.handle("HEAD", relativePath, handlers...)
group.handle("OPTIONS", relativePath, handlers...)
group.handle("DELETE", relativePath, handlers...)
group.handle("CONNECT", relativePath, handlers...)
group.handle("TRACE", relativePath, handlers...)
return group.returnObj()
}

@ -32,7 +32,10 @@ var (
_ IRouter = &Engine{} _ IRouter = &Engine{}
stats = stat.HTTPServer stats = stat.HTTPServer
_httpDSN string _httpDSN string
default405Body = []byte("405 method not allowed")
default404Body = []byte("404 page not found")
) )
func init() { func init() {
@ -123,7 +126,7 @@ type Engine struct {
address string address string
mux *http.ServeMux // http mux router trees methodTrees
server atomic.Value // store *http.Server server atomic.Value // store *http.Server
metastore map[string]map[string]interface{} // metastore is the path as key and the metadata of this path as value, it export via /metadata metastore map[string]map[string]interface{} // metastore is the path as key and the metadata of this path as value, it export via /metadata
@ -131,6 +134,28 @@ type Engine struct {
methodConfigs map[string]*MethodConfig methodConfigs map[string]*MethodConfig
injections []injection injections []injection
// If enabled, the url.RawPath will be used to find parameters.
UseRawPath bool
// If true, the path value will be unescaped.
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
// as url.Path gonna be used, which is already unescaped.
UnescapePathValues bool
// If enabled, the router checks if another method is allowed for the
// current route, if the current request can not be routed.
// If this is the case, the request is answered with 'Method Not Allowed'
// and HTTP status code 405.
// If no other Method is allowed, the request is delegated to the NotFound
// handler.
HandleMethodNotAllowed bool
allNoRoute []HandlerFunc
allNoMethod []HandlerFunc
noRoute []HandlerFunc
noMethod []HandlerFunc
} }
type injection struct { type injection struct {
@ -152,18 +177,28 @@ func NewServer(conf *ServerConfig) *Engine {
basePath: "/", basePath: "/",
root: true, root: true,
}, },
address: ip.InternalIP(), conf: &ServerConfig{
mux: http.NewServeMux(), Timeout: xtime.Duration(time.Second),
metastore: make(map[string]map[string]interface{}), },
methodConfigs: make(map[string]*MethodConfig), address: ip.InternalIP(),
} trees: make(methodTrees, 0, 9),
if err := engine.SetConfig(conf); err != nil { metastore: make(map[string]map[string]interface{}),
panic(err) methodConfigs: make(map[string]*MethodConfig),
HandleMethodNotAllowed: true,
injections: make([]injection, 0),
} }
engine.RouterGroup.engine = engine engine.RouterGroup.engine = engine
// NOTE add prometheus monitor location // NOTE add prometheus monitor location
engine.addRoute("GET", "/metrics", monitor()) engine.addRoute("GET", "/metrics", monitor())
engine.addRoute("GET", "/metadata", engine.metadata()) engine.addRoute("GET", "/metadata", engine.metadata())
engine.NoRoute(func(c *Context) {
c.Bytes(404, "text/plain", default404Body)
c.Abort()
})
engine.NoMethod(func(c *Context) {
c.Bytes(405, "text/plain", []byte(http.StatusText(405)))
c.Abort()
})
startPerf() startPerf()
return engine return engine
} }
@ -196,46 +231,60 @@ func (engine *Engine) addRoute(method, path string, handlers ...HandlerFunc) {
engine.metastore[path] = make(map[string]interface{}) engine.metastore[path] = make(map[string]interface{})
} }
engine.metastore[path]["method"] = method engine.metastore[path]["method"] = method
engine.mux.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { root := engine.trees.get(method)
c := &Context{ if root == nil {
Context: nil, root = new(node)
engine: engine, engine.trees = append(engine.trees, methodTree{method: method, root: root})
index: -1, }
handlers: nil,
Keys: nil,
method: "",
Error: nil,
}
c.Request = req prelude := func(c *Context) {
c.Writer = w
c.handlers = handlers
c.method = method c.method = method
c.RoutePath = path
engine.handleContext(c) }
}) handlers = append([]HandlerFunc{prelude}, handlers...)
root.addRoute(path, handlers)
} }
// SetConfig is used to set the engine configuration. func (engine *Engine) prepareHandler(c *Context) {
// Only the valid config will be loaded. httpMethod := c.Request.Method
func (engine *Engine) SetConfig(conf *ServerConfig) (err error) { rPath := c.Request.URL.Path
if conf.Timeout <= 0 { unescape := false
return errors.New("blademaster: config timeout must greater than 0") if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
} }
if conf.Network == "" { rPath = cleanPath(rPath)
conf.Network = "tcp"
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
handlers, params, _ := root.getValue(rPath, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
return
}
break
} }
engine.lock.Lock()
engine.conf = conf
engine.lock.Unlock()
return
}
func (engine *Engine) methodConfig(path string) *MethodConfig { if engine.HandleMethodNotAllowed {
engine.pcLock.RLock() for _, tree := range engine.trees {
mc := engine.methodConfigs[path] if tree.method == httpMethod {
engine.pcLock.RUnlock() continue
return mc }
if handlers, _, _ := tree.root.getValue(rPath, nil, unescape); handlers != nil {
c.handlers = engine.allNoMethod
return
}
}
}
c.handlers = engine.allNoRoute
return
} }
func (engine *Engine) handleContext(c *Context) { func (engine *Engine) handleContext(c *Context) {
@ -274,12 +323,35 @@ func (engine *Engine) handleContext(c *Context) {
c.Context, cancel = context.WithCancel(ctx) c.Context, cancel = context.WithCancel(ctx)
} }
defer cancel() defer cancel()
engine.prepareHandler(c)
c.Next() c.Next()
} }
// SetConfig is used to set the engine configuration.
// Only the valid config will be loaded.
func (engine *Engine) SetConfig(conf *ServerConfig) (err error) {
if conf.Timeout <= 0 {
return errors.New("blademaster: config timeout must greater than 0")
}
if conf.Network == "" {
conf.Network = "tcp"
}
engine.lock.Lock()
engine.conf = conf
engine.lock.Unlock()
return
}
func (engine *Engine) methodConfig(path string) *MethodConfig {
engine.pcLock.RLock()
mc := engine.methodConfigs[path]
engine.pcLock.RUnlock()
return mc
}
// Router return a http.Handler for using http.ListenAndServe() directly. // Router return a http.Handler for using http.ListenAndServe() directly.
func (engine *Engine) Router() http.Handler { func (engine *Engine) Router() http.Handler {
return engine.mux return engine
} }
// Server is used to load stored http server. // Server is used to load stored http server.
@ -305,6 +377,8 @@ func (engine *Engine) Shutdown(ctx context.Context) error {
// For example, this is the right place for a logger or error management middleware. // For example, this is the right place for a logger or error management middleware.
func (engine *Engine) UseFunc(middleware ...HandlerFunc) IRoutes { func (engine *Engine) UseFunc(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.UseFunc(middleware...) engine.RouterGroup.UseFunc(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine return engine
} }
@ -333,7 +407,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
address := resolveAddress(addr) address := resolveAddress(addr)
server := &http.Server{ server := &http.Server{
Addr: address, Addr: address,
Handler: engine.mux, Handler: engine,
} }
engine.server.Store(server) engine.server.Store(server)
if err = server.ListenAndServe(); err != nil { if err = server.ListenAndServe(); err != nil {
@ -348,7 +422,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) { func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
server := &http.Server{ server := &http.Server{
Addr: addr, Addr: addr,
Handler: engine.mux, Handler: engine,
} }
engine.server.Store(server) engine.server.Store(server)
if err = server.ListenAndServeTLS(certFile, keyFile); err != nil { if err = server.ListenAndServeTLS(certFile, keyFile); err != nil {
@ -369,7 +443,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
} }
defer listener.Close() defer listener.Close()
server := &http.Server{ server := &http.Server{
Handler: engine.mux, Handler: engine,
} }
engine.server.Store(server) engine.server.Store(server)
if err = server.Serve(listener); err != nil { if err = server.Serve(listener); err != nil {
@ -381,7 +455,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
// RunServer will serve and start listening HTTP requests by given server and listener. // RunServer will serve and start listening HTTP requests by given server and listener.
// Note: this method will block the calling goroutine indefinitely unless an error happens. // Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) RunServer(server *http.Server, l net.Listener) (err error) { func (engine *Engine) RunServer(server *http.Server, l net.Listener) (err error) {
server.Handler = engine.mux server.Handler = engine
engine.server.Store(server) engine.server.Store(server)
if err = server.Serve(l); err != nil { if err = server.Serve(l); err != nil {
err = errors.Wrapf(err, "listen server: %+v/%+v", server, l) err = errors.Wrapf(err, "listen server: %+v/%+v", server, l)
@ -403,3 +477,41 @@ func (engine *Engine) Inject(pattern string, handlers ...HandlerFunc) {
handlers: handlers, handlers: handlers,
}) })
} }
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := &Context{
Context: nil,
engine: engine,
index: -1,
handlers: nil,
Keys: nil,
method: "",
Error: nil,
}
c.Request = req
c.Writer = w
engine.handleContext(c)
}
// NoRoute adds handlers for NoRoute. It return a 404 code by default.
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
engine.noRoute = handlers
engine.rebuild404Handlers()
}
// NoMethod sets the handlers called when... TODO.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
engine.noMethod = handlers
engine.rebuild405Handlers()
}
func (engine *Engine) rebuild404Handlers() {
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
}
func (engine *Engine) rebuild405Handlers() {
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
}

@ -0,0 +1,627 @@
// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
package blademaster
import (
"net/url"
"strings"
"unicode"
)
// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
Key string
Value string
}
// Params is a Param-slice, as returned by the router.
// The slice is ordered, the first URL parameter is also the first slice value.
// It is therefore safe to read values by the index.
type Params []Param
// Get returns the value of the first Param which key matches the given name.
// If no matching Param is found, an empty string is returned.
func (ps Params) Get(name string) (string, bool) {
for _, entry := range ps {
if entry.Key == name {
return entry.Value, true
}
}
return "", false
}
// ByName returns the value of the first Param which key matches the given name.
// If no matching Param is found, an empty string is returned.
func (ps Params) ByName(name string) (va string) {
va, _ = ps.Get(name)
return
}
type methodTree struct {
method string
root *node
}
type methodTrees []methodTree
func (trees methodTrees) get(method string) *node {
for _, tree := range trees {
if tree.method == method {
return tree.root
}
}
return nil
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func countParams(path string) uint8 {
var n uint
for i := 0; i < len(path); i++ {
if path[i] != ':' && path[i] != '*' {
continue
}
n++
}
if n >= 255 {
return 255
}
return uint8(n)
}
type nodeType uint8
const (
static nodeType = iota // default
root
param
catchAll
)
type node struct {
path string
indices string
children []*node
handlers []HandlerFunc
priority uint32
nType nodeType
maxParams uint8
wildChild bool
}
// increments priority of the given child and reorders if necessary.
func (n *node) incrementChildPrio(pos int) int {
n.children[pos].priority++
prio := n.children[pos].priority
// adjust position (move to front)
newPos := pos
for newPos > 0 && n.children[newPos-1].priority < prio {
// swap node positions
n.children[newPos-1], n.children[newPos] = n.children[newPos], n.children[newPos-1]
newPos--
}
// build new index char string
if newPos != pos {
n.indices = n.indices[:newPos] + // unchanged prefix, might be empty
n.indices[pos:pos+1] + // the index char we move
n.indices[newPos:pos] + n.indices[pos+1:] // rest without char at 'pos'
}
return newPos
}
// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handlers []HandlerFunc) {
fullPath := path
n.priority++
numParams := countParams(path)
// non-empty tree
if len(n.path) > 0 || len(n.children) > 0 {
walk:
for {
// Update maxParams of the current node
if numParams > n.maxParams {
n.maxParams = numParams
}
// Find the longest common prefix.
// This also implies that the common prefix contains no ':' or '*'
// since the existing key can't contain those chars.
i := 0
max := min(len(path), len(n.path))
for i < max && path[i] == n.path[i] {
i++
}
// Split edge
if i < len(n.path) {
child := node{
path: n.path[i:],
wildChild: n.wildChild,
indices: n.indices,
children: n.children,
handlers: n.handlers,
priority: n.priority - 1,
}
// Update maxParams (max of all children)
for i := range child.children {
if child.children[i].maxParams > child.maxParams {
child.maxParams = child.children[i].maxParams
}
}
n.children = []*node{&child}
// []byte for proper unicode char conversion, see #65
n.indices = string([]byte{n.path[i]})
n.path = path[:i]
n.handlers = nil
n.wildChild = false
}
// Make new node a child of this node
if i < len(path) {
path = path[i:]
if n.wildChild {
n = n.children[0]
n.priority++
// Update maxParams of the child node
if numParams > n.maxParams {
n.maxParams = numParams
}
numParams--
// Check if the wildcard matches
if len(path) >= len(n.path) && n.path == path[:len(n.path)] {
// check for longer wildcard, e.g. :name and :names
if len(n.path) >= len(path) || path[len(n.path)] == '/' {
continue walk
}
}
pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(path, "/", 2)[0]
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")
}
c := path[0]
// slash after param
if n.nType == param && c == '/' && len(n.children) == 1 {
n = n.children[0]
n.priority++
continue walk
}
// Check if a child with the next path byte exists
for i := 0; i < len(n.indices); i++ {
if c == n.indices[i] {
i = n.incrementChildPrio(i)
n = n.children[i]
continue walk
}
}
// Otherwise insert it
if c != ':' && c != '*' {
// []byte for proper unicode char conversion, see #65
n.indices += string([]byte{c})
child := &node{
maxParams: numParams,
}
n.children = append(n.children, child)
n.incrementChildPrio(len(n.indices) - 1)
n = child
}
n.insertChild(numParams, path, fullPath, handlers)
return
} else if i == len(path) { // Make node a (in-path) leaf
if n.handlers != nil {
panic("handlers are already registered for path '" + fullPath + "'")
}
n.handlers = handlers
}
return
}
} else { // Empty tree
n.insertChild(numParams, path, fullPath, handlers)
n.nType = root
}
}
func (n *node) insertChild(numParams uint8, path string, fullPath string, handlers []HandlerFunc) {
var offset int // already handled bytes of the path
// find prefix until first wildcard (beginning with ':' or '*')
for i, max := 0, len(path); numParams > 0; i++ {
c := path[i]
if c != ':' && c != '*' {
continue
}
// find wildcard end (either '/' or path end)
end := i + 1
for end < max && path[end] != '/' {
switch path[end] {
// the wildcard name must not contain ':' and '*'
case ':', '*':
panic("only one wildcard per path segment is allowed, has: '" +
path[i:] + "' in path '" + fullPath + "'")
default:
end++
}
}
// check if this Node existing children which would be
// unreachable if we insert the wildcard here
if len(n.children) > 0 {
panic("wildcard route '" + path[i:end] +
"' conflicts with existing children in path '" + fullPath + "'")
}
// check if the wildcard has a name
if end-i < 2 {
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
}
if c == ':' { // param
// split path at the beginning of the wildcard
if i > 0 {
n.path = path[offset:i]
offset = i
}
child := &node{
nType: param,
maxParams: numParams,
}
n.children = []*node{child}
n.wildChild = true
n = child
n.priority++
numParams--
// if the path doesn't end with the wildcard, then there
// will be another non-wildcard subpath starting with '/'
if end < max {
n.path = path[offset:end]
offset = end
child := &node{
maxParams: numParams,
priority: 1,
}
n.children = []*node{child}
n = child
}
} else { // catchAll
if end != max || numParams > 1 {
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
}
if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
panic("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'")
}
// currently fixed width 1 for '/'
i--
if path[i] != '/' {
panic("no / before catch-all in path '" + fullPath + "'")
}
n.path = path[offset:i]
// first node: catchAll node with empty path
child := &node{
wildChild: true,
nType: catchAll,
maxParams: 1,
}
n.children = []*node{child}
n.indices = string(path[i])
n = child
n.priority++
// second node: node holding the variable
child = &node{
path: path[i:],
nType: catchAll,
maxParams: 1,
handlers: handlers,
priority: 1,
}
n.children = []*node{child}
return
}
}
// insert remaining path part and handle to the leaf
n.path = path[offset:]
n.handlers = handlers
}
// getValue returns the handle registered with the given path (key). The values of
// wildcards are saved to a map.
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
// made if a handle exists with an extra (without the) trailing slash for the
// given path.
func (n *node) getValue(path string, po Params, unescape bool) (handlers []HandlerFunc, p Params, tsr bool) {
p = po
walk: // Outer loop for walking the tree
for {
if len(path) > len(n.path) {
if path[:len(n.path)] == n.path {
path = path[len(n.path):]
// If this node does not have a wildcard (param or catchAll)
// child, we can just look up the next child node and continue
// to walk down the tree
if !n.wildChild {
c := path[0]
for i := 0; i < len(n.indices); i++ {
if c == n.indices[i] {
n = n.children[i]
continue walk
}
}
// Nothing found.
// We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path.
tsr = path == "/" && n.handlers != nil
return
}
// handle wildcard child
n = n.children[0]
switch n.nType {
case param:
// find param end (either '/' or path end)
end := 0
for end < len(path) && path[end] != '/' {
end++
}
// save param value
if cap(p) < int(n.maxParams) {
p = make(Params, 0, n.maxParams)
}
i := len(p)
p = p[:i+1] // expand slice within preallocated capacity
p[i].Key = n.path[1:]
val := path[:end]
if unescape {
var err error
if p[i].Value, err = url.QueryUnescape(val); err != nil {
p[i].Value = val // fallback, in case of error
}
} else {
p[i].Value = val
}
// we need to go deeper!
if end < len(path) {
if len(n.children) > 0 {
path = path[end:]
n = n.children[0]
continue walk
}
// ... but we can't
tsr = len(path) == end+1
return
}
if handlers = n.handlers; handlers != nil {
return
}
if len(n.children) == 1 {
// No handle found. Check if a handle for this path + a
// trailing slash exists for TSR recommendation
n = n.children[0]
tsr = n.path == "/" && n.handlers != nil
}
return
case catchAll:
// save param value
if cap(p) < int(n.maxParams) {
p = make(Params, 0, n.maxParams)
}
i := len(p)
p = p[:i+1] // expand slice within preallocated capacity
p[i].Key = n.path[2:]
if unescape {
var err error
if p[i].Value, err = url.QueryUnescape(path); err != nil {
p[i].Value = path // fallback, in case of error
}
} else {
p[i].Value = path
}
handlers = n.handlers
return
default:
panic("invalid node type")
}
}
} else if path == n.path {
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
if handlers = n.handlers; handlers != nil {
return
}
if path == "/" && n.wildChild && n.nType != root {
tsr = true
return
}
// No handle found. Check if a handle for this path + a
// trailing slash exists for trailing slash recommendation
for i := 0; i < len(n.indices); i++ {
if n.indices[i] == '/' {
n = n.children[i]
tsr = (len(n.path) == 1 && n.handlers != nil) ||
(n.nType == catchAll && n.children[0].handlers != nil)
return
}
}
return
}
// Nothing found. We can recommend to redirect to the same URL with an
// extra trailing slash if a leaf exists for that path
tsr = (path == "/") ||
(len(n.path) == len(path)+1 && n.path[len(path)] == '/' &&
path == n.path[:len(n.path)-1] && n.handlers != nil)
return
}
}
// findCaseInsensitivePath makes a case-insensitive lookup of the given path and tries to find a handler.
// It can optionally also fix trailing slashes.
// It returns the case-corrected path and a bool indicating whether the lookup
// was successful.
func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) (ciPath []byte, found bool) {
ciPath = make([]byte, 0, len(path)+1) // preallocate enough memory
// Outer loop for walking the tree
for len(path) >= len(n.path) && strings.ToLower(path[:len(n.path)]) == strings.ToLower(n.path) {
path = path[len(n.path):]
ciPath = append(ciPath, n.path...)
if len(path) > 0 {
// If this node does not have a wildcard (param or catchAll) child,
// we can just look up the next child node and continue to walk down
// the tree
if !n.wildChild {
r := unicode.ToLower(rune(path[0]))
for i, index := range n.indices {
// must use recursive approach since both index and
// ToLower(index) could exist. We must check both.
if r == unicode.ToLower(index) {
out, found := n.children[i].findCaseInsensitivePath(path, fixTrailingSlash)
if found {
return append(ciPath, out...), true
}
}
}
// Nothing found. We can recommend to redirect to the same URL
// without a trailing slash if a leaf exists for that path
found = fixTrailingSlash && path == "/" && n.handlers != nil
return
}
n = n.children[0]
switch n.nType {
case param:
// find param end (either '/' or path end)
k := 0
for k < len(path) && path[k] != '/' {
k++
}
// add param value to case insensitive path
ciPath = append(ciPath, path[:k]...)
// we need to go deeper!
if k < len(path) {
if len(n.children) > 0 {
path = path[k:]
n = n.children[0]
continue
}
// ... but we can't
if fixTrailingSlash && len(path) == k+1 {
return ciPath, true
}
return
}
if n.handlers != nil {
return ciPath, true
} else if fixTrailingSlash && len(n.children) == 1 {
// No handle found. Check if a handle for this path + a
// trailing slash exists
n = n.children[0]
if n.path == "/" && n.handlers != nil {
return append(ciPath, '/'), true
}
}
return
case catchAll:
return append(ciPath, path...), true
default:
panic("invalid node type")
}
} else {
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
if n.handlers != nil {
return ciPath, true
}
// No handle found.
// Try to fix the path by adding a trailing slash
if fixTrailingSlash {
for i := 0; i < len(n.indices); i++ {
if n.indices[i] == '/' {
n = n.children[i]
if (len(n.path) == 1 && n.handlers != nil) ||
(n.nType == catchAll && n.children[0].handlers != nil) {
return append(ciPath, '/'), true
}
return
}
}
}
return
}
}
// Nothing found.
// Try to fix the path by adding / removing a trailing slash
if fixTrailingSlash {
if path == "/" {
return ciPath, true
}
if len(path)+1 == len(n.path) && n.path[len(path)] == '/' &&
strings.ToLower(path) == strings.ToLower(n.path[:len(path)]) &&
n.handlers != nil {
return append(ciPath, n.path...), true
}
}
return
}

@ -29,8 +29,10 @@ func resolveAddress(addr []string) string {
switch len(addr) { switch len(addr) {
case 0: case 0:
if port := os.Getenv("PORT"); port != "" { if port := os.Getenv("PORT"); port != "" {
//debugPrint("Environment variable PORT=\"%s\"", port)
return ":" + port return ":" + port
} }
//debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
return ":8080" return ":8080"
case 1: case 1:
return addr[0] return addr[0]
@ -38,3 +40,120 @@ func resolveAddress(addr []string) string {
panic("too much parameters") panic("too much parameters")
} }
} }
// cleanPath is the URL version of path.Clean, it returns a canonical URL path
// for p, eliminating . and .. elements.
//
// The following rules are applied iteratively until no further processing can
// be done:
// 1. Replace multiple slashes with a single slash.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path.
//
// If the result of this process is an empty string, "/" is returned.
func cleanPath(p string) string {
// Turn empty string into "/"
if p == "" {
return "/"
}
n := len(p)
var buf []byte
// Invariants:
// reading from path; r is index of next byte to process.
// writing to buf; w is index of next byte to write.
// path must start with '/'
r := 1
w := 1
if p[0] != '/' {
r = 0
buf = make([]byte, n+1)
buf[0] = '/'
}
trailing := n > 1 && p[n-1] == '/'
// A bit more clunky without a 'lazybuf' like the path package, but the loop
// gets completely inlined (bufApp). So in contrast to the path package this
// loop has no expensive function calls (except 1x make)
for r < n {
switch {
case p[r] == '/':
// empty path element, trailing slash is added after the end
r++
case p[r] == '.' && r+1 == n:
trailing = true
r++
case p[r] == '.' && p[r+1] == '/':
// . element
r += 2
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
// .. element: remove to last /
r += 3
if w > 1 {
// can backtrack
w--
if buf == nil {
for w > 1 && p[w] != '/' {
w--
}
} else {
for w > 1 && buf[w] != '/' {
w--
}
}
}
default:
// real path element.
// add slash if needed
if w > 1 {
bufApp(&buf, p, w, '/')
w++
}
// copy element
for r < n && p[r] != '/' {
bufApp(&buf, p, w, p[r])
w++
r++
}
}
}
// re-append trailing slash
if trailing && w > 1 {
bufApp(&buf, p, w, '/')
w++
}
if buf == nil {
return p[:w]
}
return string(buf[:w])
}
// internal helper to lazily create a buffer if necessary.
func bufApp(buf *[]byte, s string, w int, c byte) {
if *buf == nil {
if s[w] == c {
return
}
*buf = make([]byte, len(s))
copy(*buf, s[:w])
}
(*buf)[w] = c
}

Loading…
Cancel
Save