Commit 3aceaf88 authored by astaxie's avatar astaxie

error support controller

parent 71b9854f
...@@ -33,7 +33,6 @@ import ( ...@@ -33,7 +33,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
) )
...@@ -280,15 +279,6 @@ func Handler(rootpath string, h http.Handler, options ...interface{}) *App { ...@@ -280,15 +279,6 @@ func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
return BeeApp 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. // SetViewsPath sets view directory path in beego application.
func SetViewsPath(path string) *App { func SetViewsPath(path string) *App {
ViewsPath = path ViewsPath = path
...@@ -402,9 +392,7 @@ func initBeforeHttpRun() { ...@@ -402,9 +392,7 @@ func initBeforeHttpRun() {
} }
} }
middleware.VERSION = VERSION registerDefaultErrorHandler()
middleware.AppName = AppName
middleware.RegisterErrorHandler()
if EnableDocs { if EnableDocs {
Get("/docs", serverDocs) Get("/docs", serverDocs)
......
...@@ -81,6 +81,7 @@ var ( ...@@ -81,6 +81,7 @@ var (
AppConfigProvider string // config provider AppConfigProvider string // config provider
EnableDocs bool // enable generate docs & server docs API Swagger EnableDocs bool // enable generate docs & server docs API Swagger
RouterCaseSensitive bool // router case sensitive default is true RouterCaseSensitive bool // router case sensitive default is true
AccessLogs bool // print access logs, default is false
) )
type beegoAppConfig struct { type beegoAppConfig struct {
......
...@@ -31,7 +31,7 @@ import ( ...@@ -31,7 +31,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego/middleware" "github.com/astaxie/beego"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
...@@ -53,24 +53,16 @@ func (ctx *Context) Redirect(status int, localurl string) { ...@@ -53,24 +53,16 @@ func (ctx *Context) Redirect(status int, localurl string) {
} }
// Abort stops this request. // Abort stops this request.
// if middleware.ErrorMaps exists, panic body. // if beego.ErrorMaps exists, panic body.
// if middleware.HTTPExceptionMaps exists, panic HTTPException struct with status and body string.
func (ctx *Context) Abort(status int, body string) { func (ctx *Context) Abort(status int, body string) {
ctx.ResponseWriter.WriteHeader(status) ctx.ResponseWriter.WriteHeader(status)
// first panic from ErrorMaps, is is user defined error functions. // first panic from ErrorMaps, is is user defined error functions.
if _, ok := middleware.ErrorMaps[body]; ok { if _, ok := beego.ErrorMaps[body]; ok {
panic(body) 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 // last panic user string
ctx.ResponseWriter.Write([]byte(body)) ctx.ResponseWriter.Write([]byte(body))
panic("User stop run") panic(beego.USERSTOPRUN)
} }
// Write string to response body. // Write string to response body.
......
...@@ -12,20 +12,26 @@ ...@@ -12,20 +12,26 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package middleware package beego
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/utils"
) )
var ( const (
AppName string errorTypeHandler = iota
VERSION string errorTypeController
) )
var tpl = ` var tpl = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
...@@ -76,18 +82,18 @@ var tpl = ` ...@@ -76,18 +82,18 @@ var tpl = `
` `
// render default application error page with error and stack string. // 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) t, _ := template.New("beegoerrortemp").Parse(tpl)
data := make(map[string]string) data := make(map[string]string)
data["AppError"] = AppName + ":" + fmt.Sprint(err) data["AppError"] = AppName + ":" + fmt.Sprint(err)
data["RequestMethod"] = r.Method data["RequestMethod"] = ctx.Input.Method()
data["RequestURL"] = r.RequestURI data["RequestURL"] = ctx.Input.Uri()
data["RemoteAddr"] = r.RemoteAddr data["RemoteAddr"] = ctx.Input.IP()
data["Stack"] = Stack data["Stack"] = Stack
data["BeegoVersion"] = VERSION data["BeegoVersion"] = VERSION
data["GoVersion"] = runtime.Version() data["GoVersion"] = runtime.Version()
rw.WriteHeader(500) ctx.Output.SetStatus(500)
t.Execute(rw, data) t.Execute(ctx.ResponseWriter, data)
} }
var errtpl = ` var errtpl = `
...@@ -190,11 +196,18 @@ var errtpl = ` ...@@ -190,11 +196,18 @@ var errtpl = `
</html> </html>
` `
type errorInfo struct {
controllerType reflect.Type
handler http.HandlerFunc
method string
errorType int
}
// map of http handlers for each error string. // map of http handlers for each error string.
var ErrorMaps map[string]http.HandlerFunc var ErrorMaps map[string]*errorInfo
func init() { func init() {
ErrorMaps = make(map[string]http.HandlerFunc) ErrorMaps = make(map[string]*errorInfo)
} }
// show 404 notfound error. // show 404 notfound error.
...@@ -283,55 +296,115 @@ func SimpleServerError(rw http.ResponseWriter, r *http.Request) { ...@@ -283,55 +296,115 @@ func SimpleServerError(rw http.ResponseWriter, r *http.Request) {
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 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. // register default error http handlers, 404,401,403,500 and 503.
func RegisterErrorHandler() { func registerDefaultErrorHandler() {
if _, ok := ErrorMaps["404"]; !ok { if _, ok := ErrorMaps["404"]; !ok {
ErrorMaps["404"] = NotFound Errorhandler("404", NotFound)
} }
if _, ok := ErrorMaps["401"]; !ok { if _, ok := ErrorMaps["401"]; !ok {
ErrorMaps["401"] = Unauthorized Errorhandler("401", Unauthorized)
} }
if _, ok := ErrorMaps["403"]; !ok { if _, ok := ErrorMaps["403"]; !ok {
ErrorMaps["403"] = Forbidden Errorhandler("403", Forbidden)
} }
if _, ok := ErrorMaps["503"]; !ok { if _, ok := ErrorMaps["503"]; !ok {
ErrorMaps["503"] = ServiceUnavailable Errorhandler("503", ServiceUnavailable)
} }
if _, ok := ErrorMaps["500"]; !ok { 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. // show error string as simple text message.
// if error string is empty, show 500 error as default. // 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) {
if h, ok := ErrorMaps[errcode]; ok { code, err := strconv.Atoi(errcode)
isint, err := strconv.Atoi(errcode)
if err != nil { if err != nil {
isint = 500 code = 503
} }
w.Header().Set("Content-Type", "text/html; charset=utf-8") ctx.ResponseWriter.WriteHeader(code)
w.WriteHeader(isint) if h, ok := ErrorMaps[errcode]; ok {
h(w, r) executeError(h, ctx)
return return
} else if h, ok := ErrorMaps["503"]; ok {
executeError(h, ctx)
return
} else {
ctx.WriteString(errcode)
} }
isint, err := strconv.Atoi(errcode) }
if err != nil {
isint = 500 func executeError(err *errorInfo, ctx *context.Context) {
if err.errorType == errorTypeHandler {
err.handler(ctx.ResponseWriter, ctx.Request)
return
}
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)
} }
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) // finish all runrouter. release resource
execController.Finish()
}
} }
// 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"}
}
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"strings" "strings"
beecontext "github.com/astaxie/beego/context" beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
) )
type namespaceCond func(*beecontext.Context) bool type namespaceCond func(*beecontext.Context) bool
...@@ -57,7 +56,7 @@ func NewNamespace(prefix string, params ...innnerNamespace) *Namespace { ...@@ -57,7 +56,7 @@ func NewNamespace(prefix string, params ...innnerNamespace) *Namespace {
func (n *Namespace) Cond(cond namespaceCond) *Namespace { func (n *Namespace) Cond(cond namespaceCond) *Namespace {
fn := func(ctx *beecontext.Context) { fn := func(ctx *beecontext.Context) {
if !cond(ctx) { if !cond(ctx) {
middleware.Exception("405", ctx.ResponseWriter, ctx.Request, "Method not allowed") exception("405", ctx)
} }
} }
if v, ok := n.handlers.filters[BeforeRouter]; ok { if v, ok := n.handlers.filters[BeforeRouter]; ok {
......
...@@ -30,7 +30,6 @@ import ( ...@@ -30,7 +30,6 @@ import (
"time" "time"
beecontext "github.com/astaxie/beego/context" beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/toolbox"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
...@@ -577,7 +576,6 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin ...@@ -577,7 +576,6 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin
// Implement http.Handler interface. // Implement http.Handler interface.
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
defer p.recoverPanic(rw, r)
starttime := time.Now() starttime := time.Now()
var runrouter reflect.Type var runrouter reflect.Type
var findrouter bool var findrouter bool
...@@ -600,6 +598,8 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -600,6 +598,8 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
context.Output.Context = context context.Output.Context = context
context.Output.EnableGzip = EnableGzip context.Output.EnableGzip = EnableGzip
defer p.recoverPanic(context)
var urlPath string var urlPath string
if !RouterCaseSensitive { if !RouterCaseSensitive {
urlPath = strings.ToLower(r.URL.Path) urlPath = strings.ToLower(r.URL.Path)
...@@ -648,7 +648,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -648,7 +648,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
context.Input.CruSession, err = GlobalSessions.SessionStart(w, r) context.Input.CruSession, err = GlobalSessions.SessionStart(w, r)
if err != nil { if err != nil {
Error(err) Error(err)
middleware.Exception("503", rw, r, "") exception("503", context)
return return
} }
defer func() { defer func() {
...@@ -703,7 +703,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -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 no matches to url, throw a not found exception
if !findrouter { if !findrouter {
middleware.Exception("404", rw, r, "") exception("404", context)
goto Admin goto Admin
} }
...@@ -719,7 +719,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) ...@@ -719,7 +719,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
isRunable = true isRunable = true
routerInfo.runfunction(context) routerInfo.runfunction(context)
} else { } else {
middleware.Exception("405", rw, r, "Method Not Allowed") exception("405", context)
goto Admin goto Admin
} }
} else if routerInfo.routerType == routerTypeHandler { } else if routerInfo.routerType == routerTypeHandler {
...@@ -830,7 +830,7 @@ Admin: ...@@ -830,7 +830,7 @@ Admin:
} }
} }
if RunMode == "dev" { if RunMode == "dev" || AccessLogs {
var devinfo string var devinfo string
if findrouter { if findrouter {
if routerInfo != nil { if routerInfo != nil {
...@@ -852,27 +852,23 @@ Admin: ...@@ -852,27 +852,23 @@ 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 := recover(); err != nil {
if err == USERSTOPRUN { if err == USERSTOPRUN {
return 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 RunMode == "dev" {
if !RecoverPanic { if !RecoverPanic {
panic(err) panic(err)
} else { } else {
if ErrorsShow { if ErrorsShow {
if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok { if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok {
handler(rw, r) executeError(handler, context)
return return
} }
} }
var stack string var stack string
Critical("the request url is ", r.URL.Path) Critical("the request url is ", context.Input.Url())
Critical("Handler crashed with error", err) Critical("Handler crashed with error", err)
for i := 1; ; i++ { for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i) _, file, line, ok := runtime.Caller(i)
...@@ -882,7 +878,7 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques ...@@ -882,7 +878,7 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques
Critical(fmt.Sprintf("%s:%d", file, line)) Critical(fmt.Sprintf("%s:%d", file, line))
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
} }
middleware.ShowErr(err, rw, r, stack) showErr(err, context, stack)
} }
} else { } else {
if !RecoverPanic { if !RecoverPanic {
...@@ -890,17 +886,17 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques ...@@ -890,17 +886,17 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques
} else { } else {
// in production model show all infomation // in production model show all infomation
if ErrorsShow { if ErrorsShow {
if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok { if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok {
handler(rw, r) executeError(handler, context)
return return
} else if handler, ok := middleware.ErrorMaps["503"]; ok { } else if handler, ok := ErrorMaps["503"]; ok {
handler(rw, r) executeError(handler, context)
return return
} else { } else {
rw.Write([]byte(fmt.Sprint(err))) context.WriteString(fmt.Sprint(err))
} }
} else { } else {
Critical("the request url is ", r.URL.Path) Critical("the request url is ", context.Input.Url())
Critical("Handler crashed with error", err) Critical("Handler crashed with error", err)
for i := 1; ; i++ { for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i) _, file, line, ok := runtime.Caller(i)
...@@ -912,8 +908,6 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques ...@@ -912,8 +908,6 @@ func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Reques
} }
} }
} }
}
} }
} }
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"strings" "strings"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
...@@ -67,7 +66,7 @@ func serverStaticRouter(ctx *context.Context) { ...@@ -67,7 +66,7 @@ func serverStaticRouter(ctx *context.Context) {
//if the request is dir and DirectoryIndex is false then //if the request is dir and DirectoryIndex is false then
if finfo.IsDir() { if finfo.IsDir() {
if !DirectoryIndex { if !DirectoryIndex {
middleware.Exception("403", ctx.ResponseWriter, ctx.Request, "403 Forbidden") exception("403", ctx)
return return
} else if ctx.Input.Request.URL.Path[len(ctx.Input.Request.URL.Path)-1] != '/' { } 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) http.Redirect(ctx.ResponseWriter, ctx.Request, ctx.Input.Request.URL.Path+"/", 302)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment