From 55ad951bce344d7b99cf2b7fb397ca42628043cb Mon Sep 17 00:00:00 2001 From: astaxie Date: Fri, 16 May 2014 10:18:15 +0800 Subject: [PATCH] beego: support more router //design model beego.Get(router, beego.FilterFunc) beego.Post(router, beego.FilterFunc) beego.Put(router, beego.FilterFunc) beego.Head(router, beego.FilterFunc) beego.Options(router, beego.FilterFunc) beego.Delete(router, beego.FilterFunc) beego.Handler(router, http.Handler) //example beego.Get("/user", func(ctx *context.Context) { ctx.Output.Body([]byte("Get userlist")) }) beego.Post("/user", func(ctx *context.Context) { ctx.Output.Body([]byte("add userlist")) }) beego.Delete("/user/:id", func(ctx *context.Context) { ctx.Output.Body([]byte([]byte(ctx.Input.Param(":id"))) }) import ( "http" "github.com/gorilla/rpc" "github.com/gorilla/rpc/json" ) func init() { s := rpc.NewServer() s.RegisterCodec(json.NewCodec(), "application/json") s.RegisterService(new(HelloService), "") beego.Handler("/rpc", s) } --- app.go | 54 +++++++ beego.go | 54 +++++++ router.go | 393 +++++++++++++++++++++++++++++++++++-------------- router_test.go | 46 ++++++ 4 files changed, 438 insertions(+), 109 deletions(-) diff --git a/app.go b/app.go index 1bb36c4d..9175e74a 100644 --- a/app.go +++ b/app.go @@ -136,6 +136,60 @@ func (app *App) AutoRouterWithPrefix(prefix string, c ControllerInterface) *App return app } +// add router for Get method +func (app *App) Get(rootpath string, f FilterFunc) *App { + app.Handlers.Get(rootpath, f) + return app +} + +// add router for Post method +func (app *App) Post(rootpath string, f FilterFunc) *App { + app.Handlers.Post(rootpath, f) + return app +} + +// add router for Put method +func (app *App) Put(rootpath string, f FilterFunc) *App { + app.Handlers.Put(rootpath, f) + return app +} + +// add router for Delete method +func (app *App) Delete(rootpath string, f FilterFunc) *App { + app.Handlers.Delete(rootpath, f) + return app +} + +// add router for Options method +func (app *App) Options(rootpath string, f FilterFunc) *App { + app.Handlers.Options(rootpath, f) + return app +} + +// add router for Head method +func (app *App) Head(rootpath string, f FilterFunc) *App { + app.Handlers.Head(rootpath, f) + return app +} + +// add router for Patch method +func (app *App) Patch(rootpath string, f FilterFunc) *App { + app.Handlers.Patch(rootpath, f) + return app +} + +// add router for Patch method +func (app *App) Any(rootpath string, f FilterFunc) *App { + app.Handlers.Any(rootpath, f) + return app +} + +// add router for http.Handler +func (app *App) Handler(rootpath string, h http.Handler) *App { + app.Handlers.Handler(rootpath, h) + return app +} + // UrlFor creates a url with another registered controller handler with params. // The endpoint is formed as path.controller.name to defined the controller method which will run. // The values need key-pair data to assign into controller method. diff --git a/beego.go b/beego.go index c09455d5..3631a7c6 100644 --- a/beego.go +++ b/beego.go @@ -121,6 +121,60 @@ func AutoPrefix(prefix string, c ControllerInterface) *App { return BeeApp } +// register router for Get method +func Get(rootpath string, f FilterFunc) *App { + BeeApp.Get(rootpath, f) + return BeeApp +} + +// register router for Post method +func Post(rootpath string, f FilterFunc) *App { + BeeApp.Post(rootpath, f) + return BeeApp +} + +// register router for Delete method +func Delete(rootpath string, f FilterFunc) *App { + BeeApp.Delete(rootpath, f) + return BeeApp +} + +// register router for Put method +func Put(rootpath string, f FilterFunc) *App { + BeeApp.Put(rootpath, f) + return BeeApp +} + +// register router for Head method +func Head(rootpath string, f FilterFunc) *App { + BeeApp.Head(rootpath, f) + return BeeApp +} + +// register router for Options method +func Options(rootpath string, f FilterFunc) *App { + BeeApp.Options(rootpath, f) + return BeeApp +} + +// register router for Patch method +func Patch(rootpath string, f FilterFunc) *App { + BeeApp.Patch(rootpath, f) + return BeeApp +} + +// register router for all method +func Any(rootpath string, f FilterFunc) *App { + BeeApp.Any(rootpath, f) + return BeeApp +} + +// register router for own Handler +func Handler(rootpath string, h http.Handler) *App { + BeeApp.Handler(rootpath, h) + return BeeApp +} + // ErrorHandler registers http.HandlerFunc to each http err code string. // usage: // beego.ErrorHandler("404",NotFound) diff --git a/router.go b/router.go index 44963cbf..557bdc8b 100644 --- a/router.go +++ b/router.go @@ -35,6 +35,12 @@ const ( FinishRouter ) +const ( + routerTypeBeego = iota + routerTypeRESTFul + routerTypeHandler +) + var ( // supported http methods. HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head", "trace", "connect"} @@ -60,6 +66,9 @@ type controllerInfo struct { controllerType reflect.Type methods map[string]string hasMethod bool + handler http.Handler + runfunction FilterFunc + routerType int } // ControllerRegistor containers registered router rules, controller handlers and filters. @@ -92,10 +101,211 @@ 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) { - parts := strings.Split(pattern, "/") + j, params, parts := p.splitRoute(pattern) + reflectVal := reflect.ValueOf(c) + t := reflect.Indirect(reflectVal).Type() + methods := make(map[string]string) + if len(mappingMethods) > 0 { + semi := strings.Split(mappingMethods[0], ";") + for _, v := range semi { + colon := strings.Split(v, ":") + if len(colon) != 2 { + panic("method mapping format is invalid") + } + comma := strings.Split(colon[0], ",") + for _, m := range comma { + if m == "*" || utils.InSlice(strings.ToLower(m), HTTPMETHOD) { + if val := reflectVal.MethodByName(colon[1]); val.IsValid() { + methods[strings.ToLower(m)] = colon[1] + } else { + panic(colon[1] + " method doesn't exist in the controller " + t.Name()) + } + } else { + panic(v + " is an invalid method mapping. Method doesn't exist " + m) + } + } + } + } + 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.regex = regex + route.params = params + route.pattern = pattern + route.methods = methods + route.routerType = routerTypeBeego + if len(methods) > 0 { + route.hasMethod = true + } + route.controllerType = t + p.routers = append(p.routers, route) + } +} + +// add get method +// usage: +// Get("/", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Get(pattern string, f FilterFunc) { + p.AddMethod("get", pattern, f) +} + +// add post method +// usage: +// Post("/api", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Post(pattern string, f FilterFunc) { + p.AddMethod("post", pattern, f) +} + +// add put method +// usage: +// Put("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Put(pattern string, f FilterFunc) { + p.AddMethod("put", pattern, f) +} + +// add delete method +// usage: +// Delete("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Delete(pattern string, f FilterFunc) { + p.AddMethod("delete", pattern, f) +} + +// add head method +// usage: +// Head("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Head(pattern string, f FilterFunc) { + p.AddMethod("head", pattern, f) +} + +// add patch method +// usage: +// Patch("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Patch(pattern string, f FilterFunc) { + p.AddMethod("patch", pattern, f) +} + +// add options method +// usage: +// Options("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Options(pattern string, f FilterFunc) { + p.AddMethod("options", pattern, f) +} + +// add all method +// usage: +// Any("/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) Any(pattern string, f FilterFunc) { + p.AddMethod("*", pattern, f) +} + +// add http method router +// usage: +// AddMethod("get","/api/:id", func(ctx *context.Context){ +// ctx.Output.Body("hello world") +// }) +func (p *ControllerRegistor) AddMethod(method, pattern string, f FilterFunc) { + if method != "*" && !utils.InSlice(strings.ToLower(method), HTTPMETHOD) { + panic("not support http method: " + method) + } + route := &controllerInfo{} + route.routerType = routerTypeRESTFul + route.runfunction = f + methods := make(map[string]string) + if method == "*" { + for _, val := range HTTPMETHOD { + methods[val] = val + } + } else { + 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) + } +} + +func (p *ControllerRegistor) Handler(pattern string, h http.Handler) { + paramnums, params, parts := p.splitRoute(pattern) + route := &controllerInfo{} + route.routerType = routerTypeHandler + route.handler = h + 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) + params = make(map[int]string) for i, part := range parts { if strings.HasPrefix(part, ":") { expr := "(.*)" @@ -180,63 +390,7 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM parts[i] = string(out) } } - reflectVal := reflect.ValueOf(c) - t := reflect.Indirect(reflectVal).Type() - methods := make(map[string]string) - if len(mappingMethods) > 0 { - semi := strings.Split(mappingMethods[0], ";") - for _, v := range semi { - colon := strings.Split(v, ":") - if len(colon) != 2 { - panic("method mapping format is invalid") - } - comma := strings.Split(colon[0], ",") - for _, m := range comma { - if m == "*" || utils.InSlice(strings.ToLower(m), HTTPMETHOD) { - if val := reflectVal.MethodByName(colon[1]); val.IsValid() { - methods[strings.ToLower(m)] = colon[1] - } else { - panic(colon[1] + " method doesn't exist in the controller " + t.Name()) - } - } else { - panic(v + " is an invalid method mapping. Method doesn't exist " + m) - } - } - } - } - if j == 0 { - //now create the Route - route := &controllerInfo{} - route.pattern = pattern - route.controllerType = t - route.methods = methods - 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.regex = regex - route.params = params - route.pattern = pattern - route.methods = methods - if len(methods) > 0 { - route.hasMethod = true - } - route.controllerType = t - p.routers = append(p.routers, route) - } + return j, params, parts } // Add auto router to ControllerRegistor. @@ -501,6 +655,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) var runrouter reflect.Type var findrouter bool var runMethod string + var routerInfo *controllerInfo params := make(map[string]string) w := &responseWriter{writer: rw} @@ -584,6 +739,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) if requestPath == route.pattern { runMethod = p.getRunMethod(r.Method, context, route) if runMethod != "" { + routerInfo = route runrouter = route.controllerType findrouter = true break @@ -598,6 +754,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) if requestPath[n-1] == '/' && route.pattern+"/" == requestPath { runMethod = p.getRunMethod(r.Method, context, route) if runMethod != "" { + routerInfo = route runrouter = route.controllerType findrouter = true break @@ -636,6 +793,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) } runMethod = p.getRunMethod(r.Method, context, route) if runMethod != "" { + routerInfo = route runrouter = route.controllerType context.Input.Params = params findrouter = true @@ -706,65 +864,82 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) if do_filter(BeforeExec) { goto Admin } - - //Invoke the request handler - vc := reflect.New(runrouter) - execController, ok := vc.Interface().(ControllerInterface) - if !ok { - panic("controller is not ControllerInterface") - } - - //call the controller init function - execController.Init(context, runrouter.Name(), runMethod, vc.Interface()) - - //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf - if EnableXSRF { - execController.XsrfToken() - if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" || - (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) { - execController.CheckXsrfCookie() + isRunable := false + if routerInfo != nil { + if routerInfo.routerType == routerTypeRESTFul { + if _, ok := routerInfo.methods[strings.ToLower(r.Method)]; ok { + isRunable = true + routerInfo.runfunction(context) + } else { + middleware.Exception("405", rw, r, "Method Not Allowed") + goto Admin + } + } else if routerInfo.routerType == routerTypeHandler { + isRunable = true + routerInfo.handler.ServeHTTP(rw, r) } } - //call prepare function - execController.Prepare() - - if !w.started { - //exec main logic - switch runMethod { - case "Get": - execController.Get() - case "Post": - execController.Post() - case "Delete": - execController.Delete() - case "Put": - execController.Put() - case "Head": - execController.Head() - case "Patch": - execController.Patch() - case "Options": - execController.Options() - default: - in := make([]reflect.Value, 0) - method := vc.MethodByName(runMethod) - method.Call(in) + if !isRunable { + //Invoke the request handler + vc := reflect.New(runrouter) + execController, ok := vc.Interface().(ControllerInterface) + if !ok { + panic("controller is not ControllerInterface") } - //render template - if !w.started && !context.Input.IsWebsocket() { - if AutoRender { - if err := execController.Render(); err != nil { - panic(err) - } + //call the controller init function + execController.Init(context, runrouter.Name(), runMethod, vc.Interface()) + //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf + if EnableXSRF { + execController.XsrfToken() + if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" || + (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) { + execController.CheckXsrfCookie() } } - } - // finish all runrouter. release resource - execController.Finish() + //call prepare function + execController.Prepare() + + if !w.started { + //exec main logic + switch runMethod { + case "Get": + execController.Get() + case "Post": + execController.Post() + case "Delete": + execController.Delete() + case "Put": + execController.Put() + case "Head": + execController.Head() + case "Patch": + execController.Patch() + case "Options": + execController.Options() + default: + in := make([]reflect.Value, 0) + method := vc.MethodByName(runMethod) + method.Call(in) + } + + //render template + if !w.started && !context.Input.IsWebsocket() { + if AutoRender { + if err := execController.Render(); err != nil { + panic(err) + } + + } + } + } + + // finish all runrouter. release resource + execController.Finish() + } //execute middleware filters if do_filter(AfterExec) { diff --git a/router_test.go b/router_test.go index 810177cf..a5e1a23e 100644 --- a/router_test.go +++ b/router_test.go @@ -10,6 +10,8 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/astaxie/beego/context" ) type TestController struct { @@ -232,3 +234,47 @@ func TestAutoPrefix(t *testing.T) { t.Errorf("TestAutoPrefix can't run") } } + +func TestRouterGet(t *testing.T) { + r, _ := http.NewRequest("GET", "/user", nil) + w := httptest.NewRecorder() + + handler := NewControllerRegistor() + handler.Get("/user", func(ctx *context.Context) { + ctx.Output.Body([]byte("Get userlist")) + }) + handler.ServeHTTP(w, r) + if w.Body.String() != "Get userlist" { + t.Errorf("TestRouterGet can't run") + } +} + +func TestRouterPost(t *testing.T) { + r, _ := http.NewRequest("POST", "/user/123", nil) + w := httptest.NewRecorder() + + handler := NewControllerRegistor() + handler.Post("/user/:id", func(ctx *context.Context) { + ctx.Output.Body([]byte(ctx.Input.Param(":id"))) + }) + handler.ServeHTTP(w, r) + if w.Body.String() != "123" { + t.Errorf("TestRouterPost can't run") + } +} + +func sayhello(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("sayhello")) +} + +func TestRouterHandler(t *testing.T) { + r, _ := http.NewRequest("POST", "/sayhi", nil) + w := httptest.NewRecorder() + + handler := NewControllerRegistor() + handler.Handler("/sayhi", http.HandlerFunc(sayhello)) + handler.ServeHTTP(w, r) + if w.Body.String() != "sayhello" { + t.Errorf("TestRouterHandler can't run") + } +}