diff --git a/admin.go b/admin.go index 95895fb8..dada6523 100644 --- a/admin.go +++ b/admin.go @@ -121,40 +121,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) { fmt.Fprintln(rw, "AdminHttpPort:", AdminHttpPort) case "router": fmt.Fprintln(rw, "Print all router infomation:") - for _, router := range BeeApp.Handlers.fixrouters { - if router.routerType == routerTypeBeego { - if router.hasMethod { - fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name()) - } else { - fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name()) - } - } else if router.routerType == routerTypeRESTFul { - fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.runfunction) - } else if router.routerType == routerTypeHandler { - fmt.Fprintln(rw, router.pattern, "----", router.handler) - } - } - for _, router := range BeeApp.Handlers.routers { - if router.routerType == routerTypeBeego { - if router.hasMethod { - fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name()) - } else { - fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name()) - } - } else if router.routerType == routerTypeRESTFul { - fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.runfunction) - } else if router.routerType == routerTypeHandler { - fmt.Fprintln(rw, router.pattern, "----", router.handler) - } - } - if BeeApp.Handlers.enableAuto { - for controllerName, methodObj := range BeeApp.Handlers.autoRouter { - fmt.Fprintln(rw, controllerName, "----") - for methodName, obj := range methodObj { - fmt.Fprintln(rw, " ", methodName, "-----", obj.Name()) - } - } - } + // @todo print routers case "filter": fmt.Fprintln(rw, "Print all filter infomation:") if BeeApp.Handlers.enableFilter { @@ -164,12 +131,6 @@ func listConf(rw http.ResponseWriter, r *http.Request) { fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) } } - fmt.Fprintln(rw, "AfterStatic:") - if bf, ok := BeeApp.Handlers.filters[AfterStatic]; ok { - for _, f := range bf { - fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc)) - } - } fmt.Fprintln(rw, "BeforeExec:") if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok { for _, f := range bf { diff --git a/beego.go b/beego.go index 20ab234b..c9794bfa 100644 --- a/beego.go +++ b/beego.go @@ -100,7 +100,7 @@ func AddGroupRouter(prefix string, groups GroupRouters) *App { // // regex router // -// beego.Router(“/api/:id([0-9]+)“, &controllers.RController{}) +// beego.Router("/api/:id([0-9]+)", &controllers.RController{}) // // custom rules // beego.Router("/api/list",&RestController{},"*:ListFood") @@ -112,6 +112,38 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A return BeeApp } +// Router add list from +// usage: +// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) +// type BankAccount struct{ +// beego.Controller +// } +// +// register the function +// func (b *BankAccount)Mapping(){ +// b.Mapping("ShowAccount" , b.ShowAccount) +// b.Mapping("ModifyAccount", b.ModifyAccount) +//} +// +// //@router /account/:id [get] +// func (b *BankAccount) ShowAccount(){ +// //logic +// } +// +// +// //@router /account/:id [post] +// func (b *BankAccount) ModifyAccount(){ +// //logic +// } +// +// the comments @router url methodlist +// url support all the function Router's pattern +// methodlist [get post head put delete options *] +func Include(cList ...ControllerInterface) *App { + BeeApp.Handlers.Include(cList...) + return BeeApp +} + // RESTRouter adds a restful controller handler to BeeApp. // its' controller implements beego.ControllerInterface and // defines a param "pattern/:objectId" to visit each resource. @@ -261,14 +293,6 @@ func DelStaticPath(url string) *App { return BeeApp } -// [Deprecated] use InsertFilter. -// Filter adds a FilterFunc under pattern condition and named action. -// The actions contains BeforeRouter,AfterStatic,BeforeExec,AfterExec and FinishRouter. -func AddFilter(pattern, action string, filter FilterFunc) *App { - BeeApp.Handlers.AddFilter(pattern, action, filter) - return BeeApp -} - // InsertFilter adds a FilterFunc with pattern condition and action constant. // The pos means action constant including // beego.BeforeRouter, beego.AfterStatic, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. diff --git a/controller.go b/controller.go index 88823074..7dc5dde3 100644 --- a/controller.go +++ b/controller.go @@ -34,7 +34,8 @@ const ( var ( // custom error when user stop request handler manually. - USERSTOPRUN = errors.New("User stop run") + USERSTOPRUN = errors.New("User stop run") + GlobalControllerRouter map[string]map[string]*Tree //pkgpath+controller:method:routertree ) // Controller defines some basic http request handler operations, such as @@ -55,6 +56,7 @@ type Controller struct { AppController interface{} EnableRender bool EnableXSRF bool + Routers map[string]*Tree //method:routertree } // ControllerInterface is an interface to uniform all controller handler. @@ -72,6 +74,8 @@ type ControllerInterface interface { Render() error XsrfToken() string CheckXsrfCookie() bool + HandlerFunc(fn interface{}) + URLMapping() } // Init generates default values of controller operations. @@ -86,6 +90,7 @@ func (c *Controller) Init(ctx *context.Context, controllerName, actionName strin c.EnableRender = true c.EnableXSRF = true c.Data = ctx.Input.Data + c.Routers = make(map[string]*Tree) } // Prepare runs after Init before request function execution. @@ -133,6 +138,32 @@ func (c *Controller) Options() { http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405) } +// call function fn +func (c *Controller) HandlerFunc(fn interface{}) { + if v, ok := fn.(func()); ok { + v() + } +} + +// URLMapping register the internal Controller router. +func (c *Controller) URLMapping() { +} + +func (c *Controller) Mapping(method, pattern string, fn func()) { + method = strings.ToLower(method) + if !utils.InSlice(method, HTTPMETHOD) && method != "*" { + Critical("add mapping method:" + method + " is a valid method") + return + } + if t, ok := c.Routers[method]; ok { + t.AddRouter(pattern, fn) + } else { + t = NewTree() + t.AddRouter(pattern, fn) + c.Routers[method] = t + } +} + // Render sends the response with rendered template bytes as text/html type. func (c *Controller) Render() error { if !c.EnableRender { @@ -295,7 +326,6 @@ func (c *Controller) ServeXml() { } // ServeFormatted serve Xml OR Json, depending on the value of the Accept header - func (c *Controller) ServeFormatted() { accept := c.Ctx.Input.Header("Accept") switch accept { diff --git a/filter.go b/filter.go index c30d00c9..a7db36a4 100644 --- a/filter.go +++ b/filter.go @@ -6,51 +6,24 @@ package beego -import "regexp" - // FilterRouter defines filter operation before controller handler execution. // it can match patterned url and do filter function when action arrives. type FilterRouter struct { - pattern string - regex *regexp.Regexp - filterFunc FilterFunc - hasregex bool - params map[int]string - parseParams map[string]string + filterFunc FilterFunc + tree *Tree + pattern string } // ValidRouter check current request is valid for this filter. // if matched, returns parsed params in this request by defined filter router pattern. -func (mr *FilterRouter) ValidRouter(router string) (bool, map[string]string) { - if mr.pattern == "" { - return true, nil +func (f *FilterRouter) ValidRouter(router string) (bool, map[string]string) { + isok, params := f.tree.Match(router) + if isok == nil { + return false, nil } - if mr.pattern == "*" { - return true, nil + if isok, ok := isok.(bool); ok { + return isok, params + } else { + return false, nil } - if router == mr.pattern { - return true, nil - } - //pattern /admin router /admin/ match - //pattern /admin/ router /admin don't match, because url will 301 in router - if n := len(router); n > 1 && router[n-1] == '/' && router[:n-2] == mr.pattern { - return true, nil - } - - if mr.hasregex { - if !mr.regex.MatchString(router) { - return false, nil - } - matches := mr.regex.FindStringSubmatch(router) - if len(matches) > 0 { - if len(matches[0]) == len(router) { - params := make(map[string]string) - for i, match := range matches[1:] { - params[mr.params[i]] = match - } - return true, params - } - } - } - return false, nil } diff --git a/filter_test.go b/filter_test.go index a8573c34..4f0880e3 100644 --- a/filter_test.go +++ b/filter_test.go @@ -22,7 +22,7 @@ func TestFilter(t *testing.T) { r, _ := http.NewRequest("GET", "/person/asta/Xie", nil) w := httptest.NewRecorder() handler := NewControllerRegistor() - handler.AddFilter("/person/:last/:first", "AfterStatic", FilterUser) + handler.InsertFilter("/person/:last/:first", BeforeRouter, FilterUser) handler.Add("/person/:last/:first", &TestController{}) handler.ServeHTTP(w, r) if w.Body.String() != "i am astaXie" { @@ -41,7 +41,7 @@ func TestPatternTwo(t *testing.T) { r, _ := http.NewRequest("GET", "/admin/", nil) w := httptest.NewRecorder() handler := NewControllerRegistor() - handler.AddFilter("/admin/:all", "AfterStatic", FilterAdminUser) + handler.InsertFilter("/admin/?:all", BeforeRouter, FilterAdminUser) handler.ServeHTTP(w, r) if w.Body.String() != "i am admin" { t.Errorf("filter /admin/ can't run") @@ -52,7 +52,7 @@ func TestPatternThree(t *testing.T) { r, _ := http.NewRequest("GET", "/admin/astaxie", nil) w := httptest.NewRecorder() handler := NewControllerRegistor() - handler.AddFilter("/admin/:all", "AfterStatic", FilterAdminUser) + handler.InsertFilter("/admin/:all", BeforeRouter, FilterAdminUser) handler.ServeHTTP(w, r) if w.Body.String() != "i am admin" { t.Errorf("filter /admin/astaxie can't run") diff --git a/namespace.go b/namespace.go index 5eda7281..c0f10aab 100644 --- a/namespace.go +++ b/namespace.go @@ -7,18 +7,17 @@ package beego import ( "net/http" - "strings" beecontext "github.com/astaxie/beego/context" + "github.com/astaxie/beego/middleware" ) type namespaceCond func(*beecontext.Context) bool // Namespace is store all the info type Namespace struct { - prefix string - condition namespaceCond - handlers *ControllerRegistor + prefix string + handlers *ControllerRegistor } // get new Namespace @@ -39,8 +38,23 @@ func NewNamespace(prefix string) *Namespace { // } // return false // }) +// Cond as the first filter func (n *Namespace) Cond(cond namespaceCond) *Namespace { - n.condition = cond + fn := func(ctx *beecontext.Context) { + if !cond(ctx) { + middleware.Exception("405", ctx.ResponseWriter, ctx.Request, "Method not allowed") + } + } + if v, ok := n.handlers.filters[BeforeRouter]; ok { + mr := new(FilterRouter) + mr.tree = NewTree() + mr.pattern = "*" + mr.filterFunc = fn + mr.tree.AddRouter("*", true) + n.handlers.filters[BeforeRouter] = append([]*FilterRouter{mr}, v...) + } else { + n.handlers.InsertFilter("*", BeforeRouter, fn) + } return n } @@ -55,12 +69,13 @@ func (n *Namespace) Cond(cond namespaceCond) *Namespace { // } // }) func (n *Namespace) Filter(action string, filter FilterFunc) *Namespace { + var a int if action == "before" { - action = "BeforeRouter" + a = BeforeRouter } else if action == "after" { - action = "FinishRouter" + a = FinishRouter } - n.handlers.AddFilter("*", action, filter) + n.handlers.InsertFilter("*", a, filter) return n } @@ -167,39 +182,35 @@ func (n *Namespace) Handler(rootpath string, h http.Handler) *Namespace { //) func (n *Namespace) Namespace(ns ...*Namespace) *Namespace { for _, ni := range ns { - n.handlers.Handler(ni.prefix, ni, true) + n.handlers.routers.AddTree(ni.prefix, ni.handlers.routers) + if n.handlers.enableFilter { + for pos, filterList := range ni.handlers.filters { + for _, mr := range filterList { + t := NewTree() + t.AddTree(ni.prefix, mr.tree) + mr.tree = t + n.handlers.insertFilterRouter(pos, mr) + } + } + } } return n } -// Namespace implement the http.Handler -func (n *Namespace) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - //trim the preifix from URL.Path - r.URL.Path = strings.TrimPrefix(r.URL.Path, n.prefix) - // init context - context := &beecontext.Context{ - ResponseWriter: rw, - Request: r, - Input: beecontext.NewInput(r), - Output: beecontext.NewOutput(), - } - context.Output.Context = context - context.Output.EnableGzip = EnableGzip - - if context.Input.IsWebsocket() { - context.ResponseWriter = rw - } - if n.condition != nil && !n.condition(context) { - http.Error(rw, "Method Not Allowed", 405) - return - } - n.handlers.ServeHTTP(rw, r) -} - // register Namespace into beego.Handler // support multi Namespace func AddNamespace(nl ...*Namespace) { for _, n := range nl { - Handler(n.prefix, n, true) + BeeApp.Handlers.routers.AddTree(n.prefix, n.handlers.routers) + if n.handlers.enableFilter { + for pos, filterList := range n.handlers.filters { + for _, mr := range filterList { + t := NewTree() + t.AddTree(n.prefix, mr.tree) + mr.tree = t + BeeApp.Handlers.insertFilterRouter(pos, mr) + } + } + } } } diff --git a/namespace_test.go b/namespace_test.go index 48500004..2d6df77a 100644 --- a/namespace_test.go +++ b/namespace_test.go @@ -23,7 +23,8 @@ func TestNamespaceGet(t *testing.T) { ns.Get("/user", func(ctx *context.Context) { ctx.Output.Body([]byte("v1_user")) }) - ns.ServeHTTP(w, r) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) if w.Body.String() != "v1_user" { t.Errorf("TestNamespaceGet can't run, get the response is " + w.Body.String()) } @@ -37,7 +38,8 @@ func TestNamespacePost(t *testing.T) { ns.Post("/user/:id", func(ctx *context.Context) { ctx.Output.Body([]byte(ctx.Input.Param(":id"))) }) - ns.ServeHTTP(w, r) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) if w.Body.String() != "123" { t.Errorf("TestNamespacePost can't run, get the response is " + w.Body.String()) } @@ -54,7 +56,8 @@ func TestNamespaceNest(t *testing.T) { ctx.Output.Body([]byte("order")) }), ) - ns.ServeHTTP(w, r) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) if w.Body.String() != "order" { t.Errorf("TestNamespaceNest can't run, get the response is " + w.Body.String()) } @@ -71,12 +74,39 @@ func TestNamespaceNestParam(t *testing.T) { ctx.Output.Body([]byte(ctx.Input.Param(":id"))) }), ) - ns.ServeHTTP(w, r) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) if w.Body.String() != "123" { t.Errorf("TestNamespaceNestParam can't run, get the response is " + w.Body.String()) } } +func TestNamespaceRouter(t *testing.T) { + r, _ := http.NewRequest("GET", "/v1/api/list", nil) + w := httptest.NewRecorder() + + ns := NewNamespace("/v1") + ns.Router("/api/list", &TestController{}, "*:List") + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) + if w.Body.String() != "i am list" { + t.Errorf("TestNamespaceRouter can't run, get the response is " + w.Body.String()) + } +} + +func TestNamespaceAutoFunc(t *testing.T) { + r, _ := http.NewRequest("GET", "/v1/test/list", nil) + w := httptest.NewRecorder() + + ns := NewNamespace("/v1") + ns.AutoRouter(&TestController{}) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) + if w.Body.String() != "i am list" { + t.Errorf("user define func can't run") + } +} + func TestNamespaceFilter(t *testing.T) { r, _ := http.NewRequest("GET", "/v1/user/123", nil) w := httptest.NewRecorder() @@ -88,41 +118,18 @@ func TestNamespaceFilter(t *testing.T) { Get("/user/:id", func(ctx *context.Context) { ctx.Output.Body([]byte(ctx.Input.Param(":id"))) }) - ns.ServeHTTP(w, r) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) if w.Body.String() != "this is Filter" { t.Errorf("TestNamespaceFilter can't run, get the response is " + w.Body.String()) } } -func TestNamespaceRouter(t *testing.T) { - r, _ := http.NewRequest("GET", "/v1/api/list", nil) - w := httptest.NewRecorder() - - ns := NewNamespace("/v1") - ns.Router("/api/list", &TestController{}, "*:List") - ns.ServeHTTP(w, r) - if w.Body.String() != "i am list" { - t.Errorf("TestNamespaceRouter can't run, get the response is " + w.Body.String()) - } -} - -func TestNamespaceAutoFunc(t *testing.T) { - r, _ := http.NewRequest("GET", "/v1/test/list", nil) - w := httptest.NewRecorder() - - ns := NewNamespace("/v1") - ns.AutoRouter(&TestController{}) - ns.ServeHTTP(w, r) - if w.Body.String() != "i am list" { - t.Errorf("user define func can't run") - } -} - func TestNamespaceCond(t *testing.T) { - r, _ := http.NewRequest("GET", "/v1/test/list", nil) + r, _ := http.NewRequest("GET", "/v2/test/list", nil) w := httptest.NewRecorder() - ns := NewNamespace("/v1") + ns := NewNamespace("/v2") ns.Cond(func(ctx *context.Context) bool { if ctx.Input.Domain() == "beego.me" { return true @@ -130,7 +137,8 @@ func TestNamespaceCond(t *testing.T) { return false }). AutoRouter(&TestController{}) - ns.ServeHTTP(w, r) + AddNamespace(ns) + BeeApp.Handlers.ServeHTTP(w, r) if w.Code != 405 { t.Errorf("TestNamespaceCond can't run get the result " + strconv.Itoa(w.Code)) } diff --git a/parser.go b/parser.go new file mode 100644 index 00000000..f02d1188 --- /dev/null +++ b/parser.go @@ -0,0 +1,6 @@ +// Beego (http://beego.me/) +// @description beego is an open-source, high-performance web framework for the Go programming language. +// @link http://github.com/astaxie/beego for the canonical source repository +// @license http://github.com/astaxie/beego/blob/master/LICENSE +// @authors astaxie +package beego diff --git a/router.go b/router.go index a3ca9656..372fef26 100644 --- a/router.go +++ b/router.go @@ -12,9 +12,8 @@ import ( "fmt" "net" "net/http" - "net/url" + "path" "reflect" - "regexp" "runtime" "strconv" "strings" @@ -29,7 +28,6 @@ import ( const ( // default filter execution points BeforeRouter = iota - AfterStatic BeforeExec AfterExec FinishRouter @@ -60,34 +58,25 @@ func ExceptMethodAppend(action string) { } type controllerInfo struct { - pattern string - regex *regexp.Regexp - params map[int]string controllerType reflect.Type methods map[string]string - hasMethod bool handler http.Handler runfunction FilterFunc routerType int - isPrefix bool } // ControllerRegistor containers registered router rules, controller handlers and filters. type ControllerRegistor struct { - routers []*controllerInfo // regexp router storage - fixrouters []*controllerInfo // fixed router storage + routers *Tree enableFilter bool filters map[int][]*FilterRouter - enableAuto bool - autoRouter map[string]map[string]reflect.Type //key:controller key:method value:reflect.type } // NewControllerRegistor returns a new ControllerRegistor. func NewControllerRegistor() *ControllerRegistor { return &ControllerRegistor{ - routers: make([]*controllerInfo, 0), - autoRouter: make(map[string]map[string]reflect.Type), - filters: make(map[int][]*FilterRouter), + routers: NewTree(), + filters: make(map[int][]*FilterRouter), } } @@ -102,7 +91,6 @@ func NewControllerRegistor() *ControllerRegistor { // Add("/api",&RestController{},"get,post:ApiFunc") // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) { - j, params, parts := p.splitRoute(pattern) reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() methods := make(map[string]string) @@ -127,40 +115,23 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM } } } - if j == 0 { - //now create the Route - route := &controllerInfo{} - route.pattern = pattern - route.controllerType = t - route.methods = methods - route.routerType = routerTypeBeego - if len(methods) > 0 { - route.hasMethod = true - } - p.fixrouters = append(p.fixrouters, route) - } else { // add regexp routers - //recreate the url pattern, with parameters replaced - //by regular expressions. then compile the regex - pattern = strings.Join(parts, "/") - regex, regexErr := regexp.Compile(pattern) - if regexErr != nil { - //TODO add error handling here to avoid panic - panic(regexErr) - } - //now create the Route + route := &controllerInfo{} + route.methods = methods + route.routerType = routerTypeBeego + route.controllerType = t + p.routers.AddRouter(pattern, route) +} - route := &controllerInfo{} - route.regex = regex - route.params = params - route.pattern = pattern - route.methods = methods - route.routerType = routerTypeBeego - if len(methods) > 0 { - route.hasMethod = true +// only when the Runmode is dev will generate router file in the router/auto.go from the controller +// Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) +func (p *ControllerRegistor) Include(cList ...ControllerInterface) { + if RunMode == "dev" { + for _, c := range cList { + reflectVal := reflect.ValueOf(c) + t := reflect.Indirect(reflectVal).Type() + t.PkgPath() } - route.controllerType = t - p.routers = append(p.routers, route) } } @@ -257,146 +228,20 @@ func (p *ControllerRegistor) AddMethod(method, pattern string, f FilterFunc) { methods[method] = method } route.methods = methods - paramnums, params, parts := p.splitRoute(pattern) - if paramnums == 0 { - //now create the Route - route.pattern = pattern - p.fixrouters = append(p.fixrouters, route) - } else { - //recreate the url pattern, with parameters replaced - //by regular expressions. then compile the regex - pattern = strings.Join(parts, "/") - regex, regexErr := regexp.Compile(pattern) - if regexErr != nil { - panic(regexErr) - } - //now create the Route - route.regex = regex - route.params = params - route.pattern = pattern - p.routers = append(p.routers, route) - } + p.routers.AddRouter(pattern, route) } +// add user defined Handler func (p *ControllerRegistor) Handler(pattern string, h http.Handler, options ...interface{}) { - paramnums, params, parts := p.splitRoute(pattern) route := &controllerInfo{} route.routerType = routerTypeHandler route.handler = h if len(options) > 0 { - if v, ok := options[0].(bool); ok { - route.isPrefix = v + if _, ok := options[0].(bool); ok { + pattern = path.Join(pattern, "?:all") } } - if paramnums == 0 { - route.pattern = pattern - p.fixrouters = append(p.fixrouters, route) - } else { - //recreate the url pattern, with parameters replaced - //by regular expressions. then compile the regex - pattern = strings.Join(parts, "/") - regex, regexErr := regexp.Compile(pattern) - if regexErr != nil { - panic(regexErr) - } - //now create the Route - route.regex = regex - route.params = params - route.pattern = pattern - p.routers = append(p.routers, route) - } -} - -// analisys the patter to params & parts -func (p *ControllerRegistor) splitRoute(pattern string) (paramnums int, params map[int]string, parts []string) { - parts = strings.Split(pattern, "/") - j := 0 - params = make(map[int]string) - for i, part := range parts { - if strings.HasPrefix(part, ":") { - expr := "(.*)" - //a user may choose to override the defult expression - // similar to expressjs: ‘/user/:id([0-9]+)’ - if index := strings.Index(part, "("); index != -1 { - expr = part[index:] - part = part[:index] - //match /user/:id:int ([0-9]+) - //match /post/:username:string ([\w]+) - } else if lindex := strings.LastIndex(part, ":"); lindex != 0 { - switch part[lindex:] { - case ":int": - expr = "([0-9]+)" - part = part[:lindex] - case ":string": - expr = `([\w]+)` - part = part[:lindex] - } - //marth /user/:id! non-empty value - } else if part[len(part)-1] == '!' { - expr = `(.+)` - part = part[:len(part)-1] - } - params[j] = part - parts[i] = expr - j++ - } - if strings.HasPrefix(part, "*") { - expr := "(.*)" - if part == "*.*" { - params[j] = ":path" - parts[i] = `([^.]+)\.([^.]+)` - j++ - params[j] = ":ext" - j++ - } else { - params[j] = ":splat" - parts[i] = expr - j++ - } - } - //url like someprefix:id(xxx).html - if strings.Contains(part, ":") && strings.Contains(part, "(") && strings.Contains(part, ")") { - var out []rune - var start bool - var startexp bool - var param []rune - var expt []rune - for _, v := range part { - if start { - if v != '(' { - param = append(param, v) - continue - } - } - if startexp { - if v != ')' { - expt = append(expt, v) - continue - } - } - if v == ':' { - param = make([]rune, 0) - param = append(param, ':') - start = true - } else if v == '(' { - startexp = true - start = false - params[j] = string(param) - j++ - expt = make([]rune, 0) - expt = append(expt, '(') - } else if v == ')' { - startexp = false - expt = append(expt, ')') - out = append(out, expt...) - } else { - out = append(out, v) - } - } - parts[i] = string(out) - } - } - return j, params, parts + p.routers.AddRouter(pattern, route) } // Add auto router to ControllerRegistor. @@ -405,21 +250,7 @@ func (p *ControllerRegistor) splitRoute(pattern string) (paramnums int, params m // visit the url /main/list to execute List function // /main/page to execute Page function. func (p *ControllerRegistor) AddAuto(c ControllerInterface) { - p.enableAuto = true - reflectVal := reflect.ValueOf(c) - rt := reflectVal.Type() - ct := reflect.Indirect(reflectVal).Type() - firstParam := strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) - if _, ok := p.autoRouter[firstParam]; ok { - return - } else { - p.autoRouter[firstParam] = make(map[string]reflect.Type) - } - for i := 0; i < rt.NumMethod(); i++ { - if !utils.InSlice(rt.Method(i).Name, exceptMethod) { - p.autoRouter[firstParam][rt.Method(i).Name] = ct - } - } + p.AddAutoPrefix("/", c) } // Add auto router to ControllerRegistor with prefix. @@ -428,78 +259,39 @@ func (p *ControllerRegistor) AddAuto(c ControllerInterface) { // visit the url /admin/main/list to execute List function // /admin/main/page to execute Page function. func (p *ControllerRegistor) AddAutoPrefix(prefix string, c ControllerInterface) { - p.enableAuto = true reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() - firstParam := strings.Trim(prefix, "/") + "/" + strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) - if _, ok := p.autoRouter[firstParam]; ok { - return - } else { - p.autoRouter[firstParam] = make(map[string]reflect.Type) - } + controllerName := strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) for i := 0; i < rt.NumMethod(); i++ { if !utils.InSlice(rt.Method(i).Name, exceptMethod) { - p.autoRouter[firstParam][rt.Method(i).Name] = ct + route := &controllerInfo{} + route.routerType = routerTypeBeego + route.methods = map[string]string{"*": rt.Method(i).Name} + route.controllerType = ct + pattern := path.Join(prefix, controllerName, strings.ToLower(rt.Method(i).Name), "*") + p.routers.AddRouter(pattern, route) } } } -// [Deprecated] use InsertFilter. -// Add FilterFunc with pattern for action. -func (p *ControllerRegistor) AddFilter(pattern, action string, filter FilterFunc) error { - mr, err := p.buildFilter(pattern, filter) - if err != nil { - return err - } - - switch action { - case "BeforeRouter": - p.filters[BeforeRouter] = append(p.filters[BeforeRouter], mr) - case "AfterStatic": - p.filters[AfterStatic] = append(p.filters[AfterStatic], mr) - case "BeforeExec": - p.filters[BeforeExec] = append(p.filters[BeforeExec], mr) - case "AfterExec": - p.filters[AfterExec] = append(p.filters[AfterExec], mr) - case "FinishRouter": - p.filters[FinishRouter] = append(p.filters[FinishRouter], mr) - } - p.enableFilter = true - return nil -} - // Add a FilterFunc with pattern rule and action constant. func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc) error { - mr, err := p.buildFilter(pattern, filter) - if err != nil { - return err - } + mr := new(FilterRouter) + mr.tree = NewTree() + mr.pattern = pattern + mr.filterFunc = filter + mr.tree.AddRouter(pattern, true) + return p.insertFilterRouter(pos, mr) +} + +// add Filter into +func (p *ControllerRegistor) insertFilterRouter(pos int, mr *FilterRouter) error { p.filters[pos] = append(p.filters[pos], mr) p.enableFilter = true return nil } -// build the Filter by pattern -func (p *ControllerRegistor) buildFilter(pattern string, filter FilterFunc) (*FilterRouter, error) { - mr := new(FilterRouter) - mr.params = make(map[int]string) - mr.filterFunc = filter - j, params, parts := p.splitRoute(pattern) - if j != 0 { - pattern = strings.Join(parts, "/") - regex, regexErr := regexp.Compile(pattern) - if regexErr != nil { - return nil, regexErr - } - mr.regex = regex - mr.hasregex = true - } - mr.params = params - mr.pattern = pattern - return mr, nil -} - // UrlFor does another controller handler in this request function. // it can access any controller method. func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { @@ -512,170 +304,135 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { Warn("urlfor params must key-value pair") return "" } - urlv := url.Values{} + params := make(map[string]string) if len(values) > 0 { key := "" for k, v := range values { if k%2 == 0 { key = v } else { - urlv.Set(key, v) + params[key] = v } } } controllName := strings.Join(paths[:len(paths)-1], ".") methodName := paths[len(paths)-1] - for _, route := range p.routers { - if route.controllerType.Name() == controllName { - var finded bool - if utils.InSlice(strings.ToLower(methodName), HTTPMETHOD) { - if route.hasMethod { - if m, ok := route.methods[strings.ToLower(methodName)]; ok && m != methodName { - finded = false - } else if m, ok = route.methods["*"]; ok && m != methodName { - finded = false + ok, url := p.geturl(p.routers, "/", controllName, methodName, params) + if ok { + return url + } else { + return "" + } +} + +func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName string, params map[string]string) (bool, string) { + for k, subtree := range t.fixrouters { + u := path.Join(url, k) + ok, u := p.geturl(subtree, u, controllName, methodName, params) + if ok { + return ok, u + } + } + if t.wildcard != nil { + ok, u := p.geturl(t.wildcard, url, controllName, methodName, params) + if ok { + return ok, u + } + } + if t.leaf != nil { + if c, ok := t.leaf.runObject.(*controllerInfo); ok { + if c.routerType == routerTypeBeego && c.controllerType.Name() == controllName { + find := false + if utils.InSlice(strings.ToLower(methodName), HTTPMETHOD) { + if m, ok := c.methods[strings.ToLower(methodName)]; ok && m != methodName { + return false, "" + } else if m, ok = c.methods["*"]; ok && m != methodName { + return false, "" } else { - finded = true + find = true } } else { - finded = true - } - } else if route.hasMethod { - for _, md := range route.methods { - if md == methodName { - finded = true + for _, md := range c.methods { + if md == methodName { + find = true + } } } - } - if !finded { - continue - } - var returnurl string - var i int - var startreg bool - for _, v := range route.regex.String() { - if v == '(' { - startreg = true - continue - } else if v == ')' { - startreg = false - returnurl = returnurl + urlv.Get(route.params[i]) - i++ - } else if !startreg { - returnurl = string(append([]rune(returnurl), v)) - } - } - if route.regex.MatchString(returnurl) { - return returnurl - } - } - } - for _, route := range p.fixrouters { - if route.controllerType.Name() == controllName { - var finded bool - if utils.InSlice(strings.ToLower(methodName), HTTPMETHOD) { - if route.hasMethod { - if m, ok := route.methods[strings.ToLower(methodName)]; ok && m != methodName { - finded = false - } else if m, ok = route.methods["*"]; ok && m != methodName { - finded = false + if find { + if t.leaf.regexps == nil { + if len(t.leaf.wildcards) == 0 { + return true, url + } + if len(t.leaf.wildcards) == 1 { + if v, ok := params[t.leaf.wildcards[0]]; ok { + delete(params, t.leaf.wildcards[0]) + return true, url + "/" + v + tourl(params) + } + if t.leaf.wildcards[0] == ":splat" { + return true, url + tourl(params) + } + } + if len(t.leaf.wildcards) == 3 && t.leaf.wildcards[0] == "." { + if p, ok := params[":path"]; ok { + if e, isok := params[":ext"]; isok { + delete(params, ":path") + delete(params, ":ext") + return true, url + "/" + p + "." + e + tourl(params) + } + } + } + canskip := false + for _, v := range t.leaf.wildcards { + if v == ":" { + canskip = true + continue + } + if u, ok := params[v]; ok { + url += "/" + u + } else { + if canskip { + canskip = false + continue + } else { + return false, "" + } + } + } + return true, url } else { - finded = true - } - } else { - finded = true - } - } else if route.hasMethod { - for _, md := range route.methods { - if md == methodName { - finded = true - } - } - } - if !finded { - continue - } - if len(values) > 0 { - return route.pattern + "?" + urlv.Encode() - } - return route.pattern - } - } - if p.enableAuto { - for cName, methodList := range p.autoRouter { - if strings.ToLower(strings.TrimSuffix(paths[len(paths)-2], "Controller")) == cName { - if _, ok := methodList[methodName]; ok { - if len(values) > 0 { - return "/" + strings.TrimSuffix(paths[len(paths)-2], "Controller") + "/" + methodName + "?" + urlv.Encode() - } else { - return "/" + strings.TrimSuffix(paths[len(paths)-2], "Controller") + "/" + methodName + var i int + var startreg bool + url = url + "/" + for _, v := range t.leaf.regexps.String() { + if v == '(' { + startreg = true + continue + } else if v == ')' { + startreg = false + if v, ok := params[t.leaf.wildcards[i]]; ok { + url = url + v + i++ + } else { + break + } + } else if !startreg { + url = string(append([]rune(url), v)) + } + } + if t.leaf.regexps.MatchString(url) { + return true, url + } } } } } } - return "" + return false, "" } // Implement http.Handler interface. func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - if err == USERSTOPRUN { - return - } - if _, ok := err.(middleware.HTTPException); ok { - // catch intented errors, only for HTTP 4XX and 5XX - } else { - if RunMode == "dev" { - if !RecoverPanic { - panic(err) - } else { - if ErrorsShow { - if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok { - handler(rw, r) - return - } - } - var stack string - Critical("the request url is ", r.URL.Path) - Critical("Handler crashed with error", err) - for i := 1; ; i++ { - _, file, line, ok := runtime.Caller(i) - if !ok { - break - } - Critical(file, line) - stack = stack + fmt.Sprintln(file, line) - } - middleware.ShowErr(err, rw, r, stack) - } - } else { - if !RecoverPanic { - panic(err) - } else { - // in production model show all infomation - if ErrorsShow { - handler := p.getErrorHandler(fmt.Sprint(err)) - handler(rw, r) - return - } else { - Critical("the request url is ", r.URL.Path) - Critical("Handler crashed with error", err) - for i := 1; ; i++ { - _, file, line, ok := runtime.Caller(i) - if !ok { - break - } - Critical(file, line) - } - } - } - } - - } - } - }() + defer p.recoverPanic(rw, r) starttime := time.Now() requestPath := r.URL.Path @@ -683,7 +440,6 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) var findrouter bool var runMethod string var routerInfo *controllerInfo - params := make(map[string]string) w := &responseWriter{writer: rw} w.Header().Set("Server", BeegoServerName) @@ -749,140 +505,24 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) goto Admin } - if do_filter(AfterStatic) { - goto Admin - } - if context.Input.RunController != nil && context.Input.RunMethod != "" { findrouter = true runMethod = context.Input.RunMethod runrouter = context.Input.RunController } - //first find path from the fixrouters to Improve Performance if !findrouter { - for _, route := range p.fixrouters { - n := len(requestPath) - if n == 0 { - continue - } - if requestPath == route.pattern { - runMethod = p.getRunMethod(r.Method, context, route) - if runMethod != "" { - routerInfo = route - runrouter = route.controllerType - findrouter = true - break - } - } - // pattern /admin url /admin 200 /admin/ 200 - // pattern /admin/ url /admin 301 /admin/ 200 - if requestPath[n-1] != '/' && requestPath+"/" == route.pattern { - http.Redirect(w, r, requestPath+"/", 301) - goto Admin - } - if requestPath[n-1] == '/' && route.pattern+"/" == requestPath { - runMethod = p.getRunMethod(r.Method, context, route) - if runMethod != "" { - routerInfo = route - runrouter = route.controllerType - findrouter = true - break - } - } - if route.routerType == routerTypeHandler && route.isPrefix && - strings.HasPrefix(requestPath, route.pattern) { - - routerInfo = route - runrouter = route.controllerType - findrouter = true - break - } - } - } - - //find regex's router - if !findrouter { - //find a matching Route - for _, route := range p.routers { - - //check if Route pattern matches url - if !route.regex.MatchString(requestPath) { - continue - } - - //get submatches (params) - matches := route.regex.FindStringSubmatch(requestPath) - - //double check that the Route matches the URL pattern. - if len(matches[0]) != len(requestPath) { - continue - } - - if len(route.params) > 0 { - for i, match := range matches[1:] { - params[route.params[i]] = match - } - } - runMethod = p.getRunMethod(r.Method, context, route) - if runMethod != "" { - routerInfo = route - runrouter = route.controllerType - context.Input.Params = params - findrouter = true - break - } - } - } - - if !findrouter && p.enableAuto { - // deal with url with diffirent ext - // /controller/simple - // /controller/simple.html - // /controller/simple.json - // /controller/simple.rss - lastindex := strings.LastIndex(requestPath, "/") - lastsub := requestPath[lastindex+1:] - if subindex := strings.LastIndex(lastsub, "."); subindex != -1 { - context.Input.Params[":ext"] = lastsub[subindex+1:] - r.URL.Query().Add(":ext", lastsub[subindex+1:]) - r.URL.RawQuery = r.URL.Query().Encode() - requestPath = requestPath[:len(requestPath)-len(lastsub[subindex:])] - } - for cName, methodmap := range p.autoRouter { - // if prev already find the router break - if findrouter { - break - } - if strings.ToLower(requestPath) == "/"+cName { - http.Redirect(w, r, requestPath+"/", 301) - goto Admin - } - // if there's no action, set the default action to index - if strings.ToLower(requestPath) == "/"+cName+"/" { - requestPath = requestPath + "index" - } - // if the request path start with controllerName - if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/") { - for mName, controllerType := range methodmap { - if strings.ToLower(requestPath) == "/"+cName+"/"+strings.ToLower(mName) || - (strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/"+strings.ToLower(mName)) && - requestPath[len("/"+cName+"/"+strings.ToLower(mName)):len("/"+cName+"/"+strings.ToLower(mName))+1] == "/") { - runrouter = controllerType - runMethod = mName - findrouter = true - //parse params - otherurl := requestPath[len("/"+cName+"/"+strings.ToLower(mName)):] - if len(otherurl) > 1 { - plist := strings.Split(otherurl, "/") - for k, v := range plist[1:] { - context.Input.Params[strconv.Itoa(k)] = v - } - } - break - } + runObject, p := p.routers.Match(requestPath) + if r, ok := runObject.(*controllerInfo); ok { + routerInfo = r + findrouter = true + if splat, ok := p[":splat"]; ok { + splatlist := strings.Split(splat, "/") + for k, v := range splatlist { + p[strconv.Itoa(k)] = v } } + context.Input.Params = p } } @@ -910,9 +550,26 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) } else if routerInfo.routerType == routerTypeHandler { isRunable = true routerInfo.handler.ServeHTTP(rw, r) + } else { + runrouter = routerInfo.controllerType + method := strings.ToLower(r.Method) + if method == "post" && strings.ToLower(context.Input.Query("_method")) == "put" { + method = "put" + } + if method == "post" && strings.ToLower(context.Input.Query("_method")) == "delete" { + method = "delete" + } + if m, ok := routerInfo.methods[method]; ok { + runMethod = m + } else if m, ok = routerInfo.methods["*"]; ok { + runMethod = m + } else { + runMethod = strings.Title(method) + } } } + // also defined runrouter & runMethod from filter if !isRunable { //Invoke the request handler vc := reflect.New(runrouter) @@ -981,6 +638,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) } do_filter(FinishRouter) + Admin: //admin module record QPS if EnableAdmin { @@ -995,6 +653,64 @@ Admin: } } +func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Request) { + if err := recover(); err != nil { + if err == USERSTOPRUN { + return + } + if _, ok := err.(middleware.HTTPException); ok { + // catch intented errors, only for HTTP 4XX and 5XX + } else { + if RunMode == "dev" { + if !RecoverPanic { + panic(err) + } else { + if ErrorsShow { + if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok { + handler(rw, r) + return + } + } + var stack string + Critical("the request url is ", r.URL.Path) + Critical("Handler crashed with error", err) + for i := 1; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + Critical(file, line) + stack = stack + fmt.Sprintln(file, line) + } + middleware.ShowErr(err, rw, r, stack) + } + } else { + if !RecoverPanic { + panic(err) + } else { + // in production model show all infomation + if ErrorsShow { + handler := p.getErrorHandler(fmt.Sprint(err)) + handler(rw, r) + return + } else { + Critical("the request url is ", r.URL.Path) + Critical("Handler crashed with error", err) + for i := 1; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + Critical(file, line) + } + } + } + } + + } + } +} + // there always should be error handler that sets error code accordingly for all unhandled errors. // in order to have custom UI for error page it's necessary to override "500" error. func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.ResponseWriter, r *http.Request) { @@ -1013,30 +729,6 @@ func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.Resp return handler } -// returns method name from request header or form field. -// sometimes browsers can't create PUT and DELETE request. -// set a form field "_method" instead. -func (p *ControllerRegistor) getRunMethod(method string, context *beecontext.Context, router *controllerInfo) string { - method = strings.ToLower(method) - if method == "post" && strings.ToLower(context.Input.Query("_method")) == "put" { - method = "put" - } - if method == "post" && strings.ToLower(context.Input.Query("_method")) == "delete" { - method = "delete" - } - if router.hasMethod { - if m, ok := router.methods[method]; ok { - return m - } else if m, ok = router.methods["*"]; ok { - return m - } else { - return "" - } - } else { - return strings.Title(method) - } -} - //responseWriter is a wrapper for the http.ResponseWriter //started set to true if response was written to then don't execute other handler type responseWriter struct { @@ -1070,8 +762,18 @@ func (w *responseWriter) WriteHeader(code int) { func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { hj, ok := w.writer.(http.Hijacker) if !ok { - println("supported?") return nil, nil, errors.New("webserver doesn't support hijacking") } return hj.Hijack() } + +func tourl(params map[string]string) string { + if len(params) == 0 { + return "" + } + u := "?" + for k, v := range params { + u += k + "=" + v + "&" + } + return strings.TrimRight(u, "&") +} diff --git a/router_test.go b/router_test.go index ea12a577..e0fa70f5 100644 --- a/router_test.go +++ b/router_test.go @@ -10,6 +10,7 @@ import ( "net/http" "net/http/httptest" "testing" + "github.com/astaxie/beego/context" ) @@ -76,16 +77,17 @@ func TestUrlFor(t *testing.T) { handler.Add("/person/:last/:first", &TestController{}) handler.AddAuto(&TestController{}) if handler.UrlFor("TestController.List") != "/api/list" { + Info(handler.UrlFor("TestController.List")) t.Errorf("TestController.List must equal to /api/list") } if handler.UrlFor("TestController.Get", ":last", "xie", ":first", "asta") != "/person/xie/asta" { t.Errorf("TestController.Get must equal to /person/xie/asta") } - if handler.UrlFor("TestController.Myext") != "/Test/Myext" { - t.Errorf("TestController.Myext must equal to /Test/Myext") + if handler.UrlFor("TestController.Myext") != "/test/myext" { + t.Errorf("TestController.Myext must equal to /test/myext") } - if handler.UrlFor("TestController.GetUrl") != "/Test/GetUrl" { - t.Errorf("TestController.GetUrl must equal to /Test/GetUrl") + if handler.UrlFor("TestController.GetUrl") != "/test/geturl" { + t.Errorf("TestController.GetUrl must equal to /test/geturl") } } diff --git a/tree.go b/tree.go index 26023de9..bdae21a1 100644 --- a/tree.go +++ b/tree.go @@ -25,6 +25,26 @@ func NewTree() *Tree { } } +// add Tree to the exist Tree +// prefix should has no params +func (t *Tree) AddTree(prefix string, tree *Tree) { + t.addtree(splitPath(prefix), tree) +} + +func (t *Tree) addtree(segments []string, tree *Tree) { + if len(segments) == 0 { + panic("prefix should has path") + } + if len(segments) == 1 && segments[0] != "" { + t.fixrouters[segments[0]] = tree + return + } + seg := segments[0] + subTree := NewTree() + t.fixrouters[seg] = subTree + subTree.addtree(segments[1:], tree) +} + // call addseg function func (t *Tree) AddRouter(pattern string, runObject interface{}) { t.addseg(splitPath(pattern), runObject, nil, "") @@ -83,22 +103,40 @@ func (t *Tree) match(segments []string, wildcardValues []string) (runObject inte return t.leaf.runObject, pa } } + if t.wildcard != nil && t.wildcard.leaf != nil { + if ok, pa := t.wildcard.leaf.match(wildcardValues); ok { + return t.wildcard.leaf.runObject, pa + } + } return nil, nil } - var seg string - seg, segments = segments[0], segments[1:] + seg, segs := segments[0], segments[1:] subTree, ok := t.fixrouters[seg] if ok { - runObject, params = subTree.match(segments, wildcardValues) + runObject, params = subTree.match(segs, wildcardValues) + } else if len(segs) == 0 { //.json .xml + if subindex := strings.LastIndex(seg, "."); subindex != -1 { + subTree, ok = t.fixrouters[seg[:subindex]] + if ok { + runObject, params = subTree.match(segs, wildcardValues) + if runObject != nil { + if params == nil { + params = make(map[string]string) + } + params[":ext"] = seg[subindex+1:] + return runObject, params + } + } + } } if runObject == nil && t.wildcard != nil { - runObject, params = t.wildcard.match(segments, append(wildcardValues, seg)) + runObject, params = t.wildcard.match(segs, append(wildcardValues, seg)) } if runObject == nil { if t.leaf != nil { - if ok, pa := t.leaf.match(append(wildcardValues, seg)); ok { + if ok, pa := t.leaf.match(append(wildcardValues, segments...)); ok { return t.leaf.runObject, pa } } @@ -122,7 +160,21 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string // has error if len(wildcardValues) == 0 && len(leaf.wildcards) > 0 { if utils.InSlice(":", leaf.wildcards) { - return true, nil + params = make(map[string]string) + j := 0 + for _, v := range leaf.wildcards { + if v == ":" { + continue + } + params[v] = "" + j += 1 + } + return true, params + } + if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" { + params = make(map[string]string) + params[":splat"] = "" + return true, params } Error("bug of router") return false, nil @@ -155,10 +207,27 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string if v == ":" { continue } + if v == "." { + lastone := wildcardValues[len(wildcardValues)-1] + strs := strings.SplitN(lastone, ".", 2) + if len(strs) == 2 { + params[":ext"] = strs[1] + } else { + params[":ext"] = "" + } + if len(wildcardValues[j:]) == 1 { + params[":path"] = strs[0] + } else { + params[":path"] = path.Join(wildcardValues[j:]...) + "/" + strs[0] + } + return true, params + } params[v] = wildcardValues[j] j += 1 } if len(params) != len(wildcardValues) { + Info(params) + Info(wildcardValues) Error("bug of router") return false, nil } @@ -193,7 +262,7 @@ func splitPath(key string) []string { // "admin" -> false, nil, "" // ":id" -> true, [:id], "" -// "?:id" -> true, [: id], "" : meaning can empty +// "?:id" -> true, [: :id], "" : meaning can empty // ":id:int" -> true, [:id], ([0-9]+) // ":name:string" -> true, [:name], ([\w]+) // ":id([0-9]+)" -> true, [:id], ([0-9]+) diff --git a/tree_test.go b/tree_test.go index 122865ba..31a82197 100644 --- a/tree_test.go +++ b/tree_test.go @@ -13,10 +13,16 @@ var routers []testinfo func init() { routers = make([]testinfo, 0) routers = append(routers, testinfo{"/:id", "/123", map[string]string{":id": "123"}}) + routers = append(routers, testinfo{"/hello/?:id", "/hello", map[string]string{":id": ""}}) routers = append(routers, testinfo{"/", "/", nil}) routers = append(routers, testinfo{"/customer/login", "/customer/login", nil}) + routers = append(routers, testinfo{"/customer/login", "/customer/login.json", map[string]string{":ext": "json"}}) routers = append(routers, testinfo{"/*", "/customer/123", map[string]string{":splat": "customer/123"}}) + routers = append(routers, testinfo{"/customer/*", "/customer", map[string]string{":splat": ""}}) + routers = append(routers, testinfo{"/*", "/customer/2009/12/11", map[string]string{":splat": "customer/2009/12/11"}}) routers = append(routers, testinfo{"/*.*", "/nice/api.json", map[string]string{":path": "nice/api", ":ext": "json"}}) + routers = append(routers, testinfo{"/:name/*.*", "/nice/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}}) + routers = append(routers, testinfo{"/:name/test/*.*", "/nice/test/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}}) routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}}) routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}}) routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}})