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)
}
This commit is contained in:
astaxie 2014-05-16 10:18:15 +08:00
parent ef815bf5fc
commit 55ad951bce
4 changed files with 438 additions and 109 deletions

54
app.go
View File

@ -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.

View File

@ -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)

393
router.go
View File

@ -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) {

View File

@ -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")
}
}