From 9aedb4d05a70937533ff8cf657b365a80257406d Mon Sep 17 00:00:00 2001 From: eyalpost Date: Fri, 21 Apr 2017 15:26:41 +0300 Subject: [PATCH] phase #1 --- controller.go | 2 + param/methodparams.go | 101 ++++++++++++++++++++++++++++++++++++++++++ param/options.go | 15 +++++++ param/parsers.go | 41 +++++++++++++++++ response/renderer.go | 27 +++++++++++ response/responses.go | 44 ++++++++++++++++++ router.go | 42 ++++++++++++++---- 7 files changed, 264 insertions(+), 8 deletions(-) create mode 100644 param/methodparams.go create mode 100644 param/options.go create mode 100644 param/parsers.go create mode 100644 response/renderer.go create mode 100644 response/responses.go diff --git a/controller.go b/controller.go index c2a327b3..02e45738 100644 --- a/controller.go +++ b/controller.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/astaxie/beego/context" + "github.com/astaxie/beego/param" "github.com/astaxie/beego/session" ) @@ -51,6 +52,7 @@ type ControllerComments struct { Router string AllowHTTPMethods []string Params []map[string]string + MethodParams []*param.MethodParam } // Controller defines some basic http request handler operations, such as diff --git a/param/methodparams.go b/param/methodparams.go new file mode 100644 index 00000000..2e6e6840 --- /dev/null +++ b/param/methodparams.go @@ -0,0 +1,101 @@ +package param + +import ( + "fmt" + "reflect" + + beecontext "github.com/astaxie/beego/context" +) + +//Keeps param information to be auto passed to controller methods +type MethodParam struct { + name string + parser paramParser + location paramLocation + required bool + defValue interface{} +} + +type paramLocation byte + +const ( + param paramLocation = iota + body + header +) + +type MethodParamOption func(*MethodParam) + +func Bool(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, boolParser{}, opts) +} + +func String(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, stringParser{}, opts) +} + +func Int(name string, opts ...MethodParamOption) *MethodParam { + return newParam(name, intParser{}, opts) +} + +func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) { + param = &MethodParam{name: name, parser: parser} + for _, option := range opts { + option(param) + } + return +} + +func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) { + result = make([]reflect.Value, 0, len(methodParams)) + i := 0 + for _, p := range methodParams { + var strValue string + var value interface{} + switch p.location { + case body: + strValue = string(ctx.Input.RequestBody) + case header: + strValue = ctx.Input.Header(p.name) + default: + strValue = ctx.Input.Query(p.name) + } + + if strValue == "" { + if p.required { + ctx.Abort(400, "Missing argument "+p.name) + } else if p.defValue != nil { + value = p.defValue + } else { + value = p.parser.zeroValue() + } + } else { + var err error + value, err = p.parser.parse(strValue) + if err != nil { + //TODO: handle err + } + } + reflectValue, err := safeConvert(reflect.ValueOf(value), methodType.In(i)) + if err != nil { + //TODO: handle err + } + result = append(result, reflectValue) + i++ + } + return +} + +func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + err, ok = r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + } + }() + result = value.Convert(t) + return +} diff --git a/param/options.go b/param/options.go new file mode 100644 index 00000000..91d01ac3 --- /dev/null +++ b/param/options.go @@ -0,0 +1,15 @@ +package param + +var InHeader MethodParamOption = func(p *MethodParam) { + p.location = header +} + +var IsRequired MethodParamOption = func(p *MethodParam) { + p.required = true +} + +func Default(defValue interface{}) MethodParamOption { + return func(p *MethodParam) { + p.defValue = defValue + } +} diff --git a/param/parsers.go b/param/parsers.go new file mode 100644 index 00000000..eec0bb79 --- /dev/null +++ b/param/parsers.go @@ -0,0 +1,41 @@ +package param + +import "strconv" + +type paramParser interface { + parse(value string) (interface{}, error) + zeroValue() interface{} +} + +type boolParser struct { +} + +func (p boolParser) parse(value string) (interface{}, error) { + return strconv.ParseBool(value) +} + +func (p boolParser) zeroValue() interface{} { + return false +} + +type stringParser struct { +} + +func (p stringParser) parse(value string) (interface{}, error) { + return value, nil +} + +func (p stringParser) zeroValue() interface{} { + return "" +} + +type intParser struct { +} + +func (p intParser) parse(value string) (interface{}, error) { + return strconv.Atoi(value) +} + +func (p intParser) zeroValue() interface{} { + return 0 +} diff --git a/response/renderer.go b/response/renderer.go new file mode 100644 index 00000000..ca5936db --- /dev/null +++ b/response/renderer.go @@ -0,0 +1,27 @@ +package response + +import ( + "strconv" + + beecontext "github.com/astaxie/beego/context" +) + +type Renderer interface { + Render(ctx *beecontext.Context) +} + +type rendererFunc func(ctx *beecontext.Context) + +func (f rendererFunc) Render(ctx *beecontext.Context) { + f(ctx) +} + +type StatusCode int + +func (s StatusCode) Error() string { + return strconv.Itoa(int(s)) +} + +func (s StatusCode) Render(ctx *beecontext.Context) { + ctx.Output.SetStatus(int(s)) +} diff --git a/response/responses.go b/response/responses.go new file mode 100644 index 00000000..aa042a35 --- /dev/null +++ b/response/responses.go @@ -0,0 +1,44 @@ +package response + +import ( + beecontext "github.com/astaxie/beego/context" +) + +func Json(value interface{}, encoding ...bool) Renderer { + return rendererFunc(func(ctx *beecontext.Context) { + var ( + hasIndent = true + hasEncoding = false + ) + //TODO: need access to BConfig :( + // if BConfig.RunMode == PROD { + // hasIndent = false + // } + if len(encoding) > 0 && encoding[0] { + hasEncoding = true + } + ctx.Output.JSON(value, hasIndent, hasEncoding) + }) +} + +func Error(err error) Renderer { + return rendererFunc(func(ctx *beecontext.Context) { + ctx.Output.SetStatus(500) + ctx.WriteString(err.Error()) + }) +} + +func RenderMethodResult(result interface{}, ctx *beecontext.Context) { + if result != nil { + renderer, ok := result.(Renderer) + if !ok { + err, ok := result.(error) + if ok { + renderer = Error(err) + } else { + renderer = Json(result) + } + } + renderer.Render(ctx) + } +} diff --git a/router.go b/router.go index 9f573f26..4937354a 100644 --- a/router.go +++ b/router.go @@ -29,6 +29,8 @@ import ( beecontext "github.com/astaxie/beego/context" "github.com/astaxie/beego/logs" + "github.com/astaxie/beego/param" + "github.com/astaxie/beego/response" "github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/utils" ) @@ -116,6 +118,7 @@ type controllerInfo struct { handler http.Handler runFunction FilterFunc routerType int + methodParams []*param.MethodParam } // ControllerRegister containers registered router rules, controller handlers and filters. @@ -151,6 +154,10 @@ func NewControllerRegister() *ControllerRegister { // Add("/api",&RestController{},"get,post:ApiFunc" // Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc") func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingMethods ...string) { + p.addWithMethodParams(pattern, c, nil, mappingMethods...) +} + +func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInterface, methodParams []*param.MethodParam, mappingMethods ...string) { reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() methods := make(map[string]string) @@ -181,6 +188,7 @@ func (p *ControllerRegister) Add(pattern string, c ControllerInterface, mappingM route.methods = methods route.routerType = routerTypeBeego route.controllerType = t + route.methodParams = methodParams if len(methods) == 0 { for _, m := range HTTPMETHOD { p.addToRouter(m, pattern, route) @@ -247,7 +255,7 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) { key := t.PkgPath() + ":" + t.Name() if comm, ok := GlobalControllerRouter[key]; ok { for _, a := range comm { - p.Add(a.Router, c, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) + p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) } } } @@ -626,11 +634,12 @@ func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath str func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) { startTime := time.Now() var ( - runRouter reflect.Type - findRouter bool - runMethod string - routerInfo *controllerInfo - isRunnable bool + runRouter reflect.Type + findRouter bool + runMethod string + methodParams []*param.MethodParam + routerInfo *controllerInfo + isRunnable bool ) context := p.pool.Get().(*beecontext.Context) context.Reset(rw, r) @@ -742,6 +751,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) routerInfo.handler.ServeHTTP(rw, r) } else { runRouter = routerInfo.controllerType + methodParams = routerInfo.methodParams method := r.Method if r.Method == "POST" && context.Input.Query("_method") == "PUT" { method = "PUT" @@ -804,9 +814,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) execController.Options() default: if !execController.HandlerFunc(runMethod) { - var in []reflect.Value method := vc.MethodByName(runMethod) - method.Call(in) + var in []reflect.Value = param.ConvertParams(methodParams, method.Type(), context) + out := method.Call(in) + + //For backward compatibility we only handle response if we had incoming methodParams + if methodParams != nil { + p.handleParamResponse(context, execController, out) + } } } @@ -886,6 +901,17 @@ Admin: } } +func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, execController ControllerInterface, results []reflect.Value) { + //looping in reverse order for the case when both error and value are returned and error sets the response status code + for i := len(results) - 1; i >= 0; i-- { + result := results[i] + if !result.IsNil() { + resultValue := result.Interface() + response.RenderMethodResult(resultValue, context) + } + } +} + // FindRouter Find Router info for URL func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *controllerInfo, isFind bool) { var urlPath = context.Input.URL()