From 3aceaf88389ec677fa170f19806f0eb9143ebace Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 26 Feb 2015 23:34:43 +0800 Subject: [PATCH] error support controller --- beego.go | 14 +-- config.go | 1 + context/context.go | 16 +--- middleware/error.go => error.go | 153 +++++++++++++++++++++++--------- middleware/exceptions.go | 49 ---------- namespace.go | 3 +- router.go | 98 ++++++++++---------- staticfile.go | 3 +- 8 files changed, 167 insertions(+), 170 deletions(-) rename middleware/error.go => error.go (71%) delete mode 100644 middleware/exceptions.go diff --git a/beego.go b/beego.go index ab70a182..ba28855d 100644 --- a/beego.go +++ b/beego.go @@ -33,7 +33,6 @@ import ( "strconv" "strings" - "github.com/astaxie/beego/middleware" "github.com/astaxie/beego/session" ) @@ -280,15 +279,6 @@ func Handler(rootpath string, h http.Handler, options ...interface{}) *App { return BeeApp } -// ErrorHandler registers http.HandlerFunc to each http err code string. -// usage: -// beego.ErrorHandler("404",NotFound) -// beego.ErrorHandler("500",InternalServerError) -func Errorhandler(err string, h http.HandlerFunc) *App { - middleware.Errorhandler(err, h) - return BeeApp -} - // SetViewsPath sets view directory path in beego application. func SetViewsPath(path string) *App { ViewsPath = path @@ -402,9 +392,7 @@ func initBeforeHttpRun() { } } - middleware.VERSION = VERSION - middleware.AppName = AppName - middleware.RegisterErrorHandler() + registerDefaultErrorHandler() if EnableDocs { Get("/docs", serverDocs) diff --git a/config.go b/config.go index 5210ea93..f326ad22 100644 --- a/config.go +++ b/config.go @@ -81,6 +81,7 @@ var ( AppConfigProvider string // config provider EnableDocs bool // enable generate docs & server docs API Swagger RouterCaseSensitive bool // router case sensitive default is true + AccessLogs bool // print access logs, default is false ) type beegoAppConfig struct { diff --git a/context/context.go b/context/context.go index b60650a8..0cb8df6d 100644 --- a/context/context.go +++ b/context/context.go @@ -31,7 +31,7 @@ import ( "strings" "time" - "github.com/astaxie/beego/middleware" + "github.com/astaxie/beego" "github.com/astaxie/beego/utils" ) @@ -53,24 +53,16 @@ func (ctx *Context) Redirect(status int, localurl string) { } // Abort stops this request. -// if middleware.ErrorMaps exists, panic body. -// if middleware.HTTPExceptionMaps exists, panic HTTPException struct with status and body string. +// if beego.ErrorMaps exists, panic body. func (ctx *Context) Abort(status int, body string) { ctx.ResponseWriter.WriteHeader(status) // first panic from ErrorMaps, is is user defined error functions. - if _, ok := middleware.ErrorMaps[body]; ok { + if _, ok := beego.ErrorMaps[body]; ok { panic(body) } - // second panic from HTTPExceptionMaps, it is system defined functions. - if e, ok := middleware.HTTPExceptionMaps[status]; ok { - if len(body) >= 1 { - e.Description = body - } - panic(e) - } // last panic user string ctx.ResponseWriter.Write([]byte(body)) - panic("User stop run") + panic(beego.USERSTOPRUN) } // Write string to response body. diff --git a/middleware/error.go b/error.go similarity index 71% rename from middleware/error.go rename to error.go index 00cf9de5..211c54e4 100644 --- a/middleware/error.go +++ b/error.go @@ -12,20 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package middleware +package beego import ( "fmt" "html/template" "net/http" + "reflect" "runtime" "strconv" + "strings" + + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/utils" ) -var ( - AppName string - VERSION string +const ( + errorTypeHandler = iota + errorTypeController ) + var tpl = ` @@ -76,18 +82,18 @@ var tpl = ` ` // render default application error page with error and stack string. -func ShowErr(err interface{}, rw http.ResponseWriter, r *http.Request, Stack string) { +func showErr(err interface{}, ctx *context.Context, Stack string) { t, _ := template.New("beegoerrortemp").Parse(tpl) data := make(map[string]string) data["AppError"] = AppName + ":" + fmt.Sprint(err) - data["RequestMethod"] = r.Method - data["RequestURL"] = r.RequestURI - data["RemoteAddr"] = r.RemoteAddr + data["RequestMethod"] = ctx.Input.Method() + data["RequestURL"] = ctx.Input.Uri() + data["RemoteAddr"] = ctx.Input.IP() data["Stack"] = Stack data["BeegoVersion"] = VERSION data["GoVersion"] = runtime.Version() - rw.WriteHeader(500) - t.Execute(rw, data) + ctx.Output.SetStatus(500) + t.Execute(ctx.ResponseWriter, data) } var errtpl = ` @@ -190,11 +196,18 @@ var errtpl = ` ` +type errorInfo struct { + controllerType reflect.Type + handler http.HandlerFunc + method string + errorType int +} + // map of http handlers for each error string. -var ErrorMaps map[string]http.HandlerFunc +var ErrorMaps map[string]*errorInfo func init() { - ErrorMaps = make(map[string]http.HandlerFunc) + ErrorMaps = make(map[string]*errorInfo) } // show 404 notfound error. @@ -283,55 +296,115 @@ func SimpleServerError(rw http.ResponseWriter, r *http.Request) { http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } -// add http handler for given error string. -func Errorhandler(err string, h http.HandlerFunc) { - ErrorMaps[err] = h -} - // register default error http handlers, 404,401,403,500 and 503. -func RegisterErrorHandler() { +func registerDefaultErrorHandler() { if _, ok := ErrorMaps["404"]; !ok { - ErrorMaps["404"] = NotFound + Errorhandler("404", NotFound) } if _, ok := ErrorMaps["401"]; !ok { - ErrorMaps["401"] = Unauthorized + Errorhandler("401", Unauthorized) } if _, ok := ErrorMaps["403"]; !ok { - ErrorMaps["403"] = Forbidden + Errorhandler("403", Forbidden) } if _, ok := ErrorMaps["503"]; !ok { - ErrorMaps["503"] = ServiceUnavailable + Errorhandler("503", ServiceUnavailable) } if _, ok := ErrorMaps["500"]; !ok { - ErrorMaps["500"] = InternalServerError + Errorhandler("500", InternalServerError) } } +// ErrorHandler registers http.HandlerFunc to each http err code string. +// usage: +// beego.ErrorHandler("404",NotFound) +// beego.ErrorHandler("500",InternalServerError) +func Errorhandler(code string, h http.HandlerFunc) *App { + errinfo := &errorInfo{} + errinfo.errorType = errorTypeHandler + errinfo.handler = h + errinfo.method = code + ErrorMaps[code] = errinfo + return BeeApp +} + +// ErrorController registers ControllerInterface to each http err code string. +// usage: +// beego.ErrorHandler(&controllers.ErrorController{}) +func ErrorController(c ControllerInterface) *App { + reflectVal := reflect.ValueOf(c) + rt := reflectVal.Type() + ct := reflect.Indirect(reflectVal).Type() + for i := 0; i < rt.NumMethod(); i++ { + if !utils.InSlice(rt.Method(i).Name, exceptMethod) && strings.HasPrefix(rt.Method(i).Name, "Error") { + errinfo := &errorInfo{} + errinfo.errorType = errorTypeController + errinfo.controllerType = ct + errinfo.method = rt.Method(i).Name + errname := strings.TrimPrefix(rt.Method(i).Name, "Error") + ErrorMaps[errname] = errinfo + } + } + return BeeApp +} + // show error string as simple text message. // if error string is empty, show 500 error as default. -func Exception(errcode string, w http.ResponseWriter, r *http.Request, msg string) { +func exception(errcode string, ctx *context.Context) { + code, err := strconv.Atoi(errcode) + if err != nil { + code = 503 + } + ctx.ResponseWriter.WriteHeader(code) if h, ok := ErrorMaps[errcode]; ok { - isint, err := strconv.Atoi(errcode) - if err != nil { - isint = 500 - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(isint) - h(w, r) + executeError(h, ctx) + return + } else if h, ok := ErrorMaps["503"]; ok { + executeError(h, ctx) + return + } else { + ctx.WriteString(errcode) + } +} + +func executeError(err *errorInfo, ctx *context.Context) { + if err.errorType == errorTypeHandler { + err.handler(ctx.ResponseWriter, ctx.Request) return } - isint, err := strconv.Atoi(errcode) - if err != nil { - isint = 500 + if err.errorType == errorTypeController { + //Invoke the request handler + vc := reflect.New(err.controllerType) + execController, ok := vc.Interface().(ControllerInterface) + if !ok { + panic("controller is not ControllerInterface") + } + //call the controller init function + execController.Init(ctx, err.controllerType.Name(), err.method, vc.Interface()) + + //call prepare function + execController.Prepare() + + execController.URLMapping() + + in := make([]reflect.Value, 0) + method := vc.MethodByName(err.method) + method.Call(in) + + //render template + if ctx.Output.Status == 0 { + if AutoRender { + if err := execController.Render(); err != nil { + panic(err) + } + } + } + + // finish all runrouter. release resource + execController.Finish() } - if isint == 404 { - msg = "404 page not found" - } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(isint) - fmt.Fprintln(w, msg) } diff --git a/middleware/exceptions.go b/middleware/exceptions.go deleted file mode 100644 index a08a7358..00000000 --- a/middleware/exceptions.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2014 beego Author. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package middleware - -import "fmt" - -// http exceptions -type HTTPException struct { - StatusCode int // http status code 4xx, 5xx - Description string -} - -// return http exception error string, e.g. "400 Bad Request". -func (e *HTTPException) Error() string { - return fmt.Sprintf("%d %s", e.StatusCode, e.Description) -} - -// map of http exceptions for each http status code int. -// defined 400,401,403,404,405,500,502,503 and 504 default. -var HTTPExceptionMaps map[int]HTTPException - -func init() { - HTTPExceptionMaps = make(map[int]HTTPException) - - // Normal 4XX HTTP Status - HTTPExceptionMaps[400] = HTTPException{400, "Bad Request"} - HTTPExceptionMaps[401] = HTTPException{401, "Unauthorized"} - HTTPExceptionMaps[403] = HTTPException{403, "Forbidden"} - HTTPExceptionMaps[404] = HTTPException{404, "Not Found"} - HTTPExceptionMaps[405] = HTTPException{405, "Method Not Allowed"} - - // Normal 5XX HTTP Status - HTTPExceptionMaps[500] = HTTPException{500, "Internal Server Error"} - HTTPExceptionMaps[502] = HTTPException{502, "Bad Gateway"} - HTTPExceptionMaps[503] = HTTPException{503, "Service Unavailable"} - HTTPExceptionMaps[504] = HTTPException{504, "Gateway Timeout"} -} diff --git a/namespace.go b/namespace.go index 4e2632e5..ebb7c14f 100644 --- a/namespace.go +++ b/namespace.go @@ -19,7 +19,6 @@ import ( "strings" beecontext "github.com/astaxie/beego/context" - "github.com/astaxie/beego/middleware" ) type namespaceCond func(*beecontext.Context) bool @@ -57,7 +56,7 @@ func NewNamespace(prefix string, params ...innnerNamespace) *Namespace { func (n *Namespace) Cond(cond namespaceCond) *Namespace { fn := func(ctx *beecontext.Context) { if !cond(ctx) { - middleware.Exception("405", ctx.ResponseWriter, ctx.Request, "Method not allowed") + exception("405", ctx) } } if v, ok := n.handlers.filters[BeforeRouter]; ok { diff --git a/router.go b/router.go index fadf2ec3..b9d649a2 100644 --- a/router.go +++ b/router.go @@ -30,7 +30,6 @@ import ( "time" beecontext "github.com/astaxie/beego/context" - "github.com/astaxie/beego/middleware" "github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/utils" ) @@ -577,7 +576,6 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin // Implement http.Handler interface. func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - defer p.recoverPanic(rw, r) starttime := time.Now() var runrouter reflect.Type var findrouter bool @@ -600,6 +598,8 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) context.Output.Context = context context.Output.EnableGzip = EnableGzip + defer p.recoverPanic(context) + var urlPath string if !RouterCaseSensitive { urlPath = strings.ToLower(r.URL.Path) @@ -648,7 +648,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) context.Input.CruSession, err = GlobalSessions.SessionStart(w, r) if err != nil { Error(err) - middleware.Exception("503", rw, r, "") + exception("503", context) return } defer func() { @@ -703,7 +703,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) //if no matches to url, throw a not found exception if !findrouter { - middleware.Exception("404", rw, r, "") + exception("404", context) goto Admin } @@ -719,7 +719,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) isRunable = true routerInfo.runfunction(context) } else { - middleware.Exception("405", rw, r, "Method Not Allowed") + exception("405", context) goto Admin } } else if routerInfo.routerType == routerTypeHandler { @@ -830,7 +830,7 @@ Admin: } } - if RunMode == "dev" { + if RunMode == "dev" || AccessLogs { var devinfo string if findrouter { if routerInfo != nil { @@ -852,27 +852,51 @@ Admin: } } -func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Request) { +func (p *ControllerRegistor) recoverPanic(context *beecontext.Context) { if err := recover(); err != nil { if err == USERSTOPRUN { return } - if he, ok := err.(middleware.HTTPException); ok { - rw.Write([]byte(he.Description)) - // 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 - } + if RunMode == "dev" { + if !RecoverPanic { + panic(err) + } else { + if ErrorsShow { + if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok { + executeError(handler, context) + return } - var stack string - Critical("the request url is ", r.URL.Path) + } + var stack string + Critical("the request url is ", context.Input.Url()) + Critical("Handler crashed with error", err) + for i := 1; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + Critical(fmt.Sprintf("%s:%d", file, line)) + stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) + } + showErr(err, context, stack) + } + } else { + if !RecoverPanic { + panic(err) + } else { + // in production model show all infomation + if ErrorsShow { + if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok { + executeError(handler, context) + return + } else if handler, ok := ErrorMaps["503"]; ok { + executeError(handler, context) + return + } else { + context.WriteString(fmt.Sprint(err)) + } + } else { + Critical("the request url is ", context.Input.Url()) Critical("Handler crashed with error", err) for i := 1; ; i++ { _, file, line, ok := runtime.Caller(i) @@ -880,39 +904,9 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques break } Critical(fmt.Sprintf("%s:%d", file, line)) - stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) - } - middleware.ShowErr(err, rw, r, stack) - } - } else { - if !RecoverPanic { - panic(err) - } else { - // in production model show all infomation - if ErrorsShow { - if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok { - handler(rw, r) - return - } else if handler, ok := middleware.ErrorMaps["503"]; ok { - handler(rw, r) - return - } else { - rw.Write([]byte(fmt.Sprint(err))) - } - } 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(fmt.Sprintf("%s:%d", file, line)) - } } } } - } } } diff --git a/staticfile.go b/staticfile.go index d9855064..5ab853a3 100644 --- a/staticfile.go +++ b/staticfile.go @@ -22,7 +22,6 @@ import ( "strings" "github.com/astaxie/beego/context" - "github.com/astaxie/beego/middleware" "github.com/astaxie/beego/utils" ) @@ -67,7 +66,7 @@ func serverStaticRouter(ctx *context.Context) { //if the request is dir and DirectoryIndex is false then if finfo.IsDir() { if !DirectoryIndex { - middleware.Exception("403", ctx.ResponseWriter, ctx.Request, "403 Forbidden") + exception("403", ctx) return } else if ctx.Input.Request.URL.Path[len(ctx.Input.Request.URL.Path)-1] != '/' { http.Redirect(ctx.ResponseWriter, ctx.Request, ctx.Input.Request.URL.Path+"/", 302)