diff --git a/admin.go b/admin.go index d918b595..90142c55 100644 --- a/admin.go +++ b/admin.go @@ -397,7 +397,7 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) { if err != nil { data["Message"] = []string{"error", fmt.Sprintf("%s", err)} } - data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is %s", taskname, t.GetStatus())} + data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is
%s", taskname, t.GetStatus())} } else { data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)} } @@ -410,12 +410,14 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) { var fields = []string{ fmt.Sprintf("Task Name"), fmt.Sprintf("Task Spec"), - fmt.Sprintf("Task Function"), + fmt.Sprintf("Task Status"), + fmt.Sprintf("Last Time"), fmt.Sprintf(""), } for tname, tk := range toolbox.AdminTaskList { result = []string{ fmt.Sprintf("%s", tname), + fmt.Sprintf("%s", tk.GetSpec()), fmt.Sprintf("%s", tk.GetStatus()), fmt.Sprintf("%s", tk.GetPrev().String()), } diff --git a/adminui.go b/adminui.go index 77b5cd33..7bb32b34 100644 --- a/adminui.go +++ b/adminui.go @@ -186,7 +186,7 @@ bg-warning {{end}} - Run + Run {{end}} diff --git a/app.go b/app.go index d155c531..35040f33 100644 --- a/app.go +++ b/app.go @@ -19,7 +19,10 @@ import ( "net" "net/http" "net/http/fcgi" + "os" "time" + + "github.com/astaxie/beego/utils" ) // App defines beego application with a new PatternServeMux. @@ -59,6 +62,10 @@ func (app *App) Run() { } } else { if HttpPort == 0 { + // remove the Socket file before start + if utils.FileExists(addr) { + os.Remove(addr) + } l, err = net.Listen("unix", addr) } else { l, err = net.Listen("tcp", addr) diff --git a/beego.go b/beego.go index ab70a182..9c34c9f9 100644 --- a/beego.go +++ b/beego.go @@ -33,83 +33,15 @@ import ( "strconv" "strings" - "github.com/astaxie/beego/middleware" "github.com/astaxie/beego/session" ) // beego web framework version. -const VERSION = "1.4.2" +const VERSION = "1.4.3" type hookfunc func() error //hook function to run var hooks []hookfunc //hook function slice to store the hookfunc -type groupRouter struct { - pattern string - controller ControllerInterface - mappingMethods string -} - -// RouterGroups which will store routers -type GroupRouters []groupRouter - -// Get a new GroupRouters -func NewGroupRouters() GroupRouters { - return make(GroupRouters, 0) -} - -// Add Router in the GroupRouters -// it is for plugin or module to register router -func (gr *GroupRouters) AddRouter(pattern string, c ControllerInterface, mappingMethod ...string) { - var newRG groupRouter - if len(mappingMethod) > 0 { - newRG = groupRouter{ - pattern, - c, - mappingMethod[0], - } - } else { - newRG = groupRouter{ - pattern, - c, - "", - } - } - *gr = append(*gr, newRG) -} - -func (gr *GroupRouters) AddAuto(c ControllerInterface) { - newRG := groupRouter{ - "", - c, - "", - } - *gr = append(*gr, newRG) -} - -// AddGroupRouter with the prefix -// it will register the router in BeeApp -// the follow code is write in modules: -// GR:=NewGroupRouters() -// GR.AddRouter("/login",&UserController,"get:Login") -// GR.AddRouter("/logout",&UserController,"get:Logout") -// GR.AddRouter("/register",&UserController,"get:Reg") -// the follow code is write in app: -// import "github.com/beego/modules/auth" -// AddRouterGroup("/admin", auth.GR) -func AddGroupRouter(prefix string, groups GroupRouters) *App { - for _, v := range groups { - if v.pattern == "" { - BeeApp.Handlers.AddAutoPrefix(prefix, v.controller) - } else if v.mappingMethods != "" { - BeeApp.Handlers.Add(prefix+v.pattern, v.controller, v.mappingMethods) - } else { - BeeApp.Handlers.Add(prefix+v.pattern, v.controller) - } - - } - return BeeApp -} - // Router adds a patterned controller handler to BeeApp. // it's an alias method of App.Router. // usage: @@ -280,15 +212,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 +325,7 @@ func initBeforeHttpRun() { } } - middleware.VERSION = VERSION - middleware.AppName = AppName - middleware.RegisterErrorHandler() + registerDefaultErrorHandler() if EnableDocs { Get("/docs", serverDocs) diff --git a/cache/cache_test.go b/cache/cache_test.go index bf9e79c2..7c43e539 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -15,6 +15,7 @@ package cache import ( + "os" "testing" "time" ) @@ -67,7 +68,7 @@ func TestCache(t *testing.T) { } func TestFileCache(t *testing.T) { - bm, err := NewCache("file", `{"CachePath":"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`) + bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`) if err != nil { t.Error("init err") } @@ -112,4 +113,5 @@ func TestFileCache(t *testing.T) { if v := bm.Get("astaxie"); v.(string) != "author" { t.Error("get err") } + os.RemoveAll("cache") } diff --git a/cache/file.go b/cache/file.go index 6ecf6568..bbbbbad2 100644 --- a/cache/file.go +++ b/cache/file.go @@ -92,8 +92,6 @@ func (fc *FileCache) StartAndGC(config string) error { // Init will make new dir for file cache if not exist. func (fc *FileCache) Init() { - app := filepath.Dir(os.Args[0]) - fc.CachePath = filepath.Join(app, fc.CachePath) if ok, _ := exists(fc.CachePath); !ok { // todo : error handle _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle } diff --git a/cache/redis/redis.go b/cache/redis/redis.go index b205545d..0e07eaed 100644 --- a/cache/redis/redis.go +++ b/cache/redis/redis.go @@ -32,6 +32,7 @@ package redis import ( "encoding/json" "errors" + "strconv" "time" "github.com/garyburd/redigo/redis" @@ -48,6 +49,7 @@ var ( type RedisCache struct { p *redis.Pool // redis connection pool conninfo string + dbNum int key string } @@ -137,7 +139,7 @@ func (rc *RedisCache) ClearAll() error { } // start redis cache adapter. -// config is like {"key":"collection key","conn":"connection info"} +// config is like {"key":"collection key","conn":"connection info","dbNum":"0"} // the cache item in redis are stored forever, // so no gc operation. func (rc *RedisCache) StartAndGC(config string) error { @@ -151,9 +153,12 @@ func (rc *RedisCache) StartAndGC(config string) error { if _, ok := cf["conn"]; !ok { return errors.New("config has no conn key") } - + if _, ok := cf["dbNum"]; !ok { + cf["dbNum"] = "0" + } rc.key = cf["key"] rc.conninfo = cf["conn"] + rc.dbNum, _ = strconv.Atoi(cf["dbNum"]) rc.connectInit() c := rc.p.Get() @@ -166,6 +171,11 @@ func (rc *RedisCache) StartAndGC(config string) error { func (rc *RedisCache) connectInit() { dialFunc := func() (c redis.Conn, err error) { c, err = redis.Dial("tcp", rc.conninfo) + _, selecterr := c.Do("SELECT", rc.dbNum) + if selecterr != nil { + c.Close() + return nil, selecterr + } return } // initialize a new pool diff --git a/config.go b/config.go index 7d2a94de..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 { @@ -110,7 +111,7 @@ func (b *beegoAppConfig) String(key string) string { func (b *beegoAppConfig) Strings(key string) []string { v := b.innerConfig.Strings(RunMode + "::" + key) - if len(v) == 0 { + if v[0] == "" { return b.innerConfig.Strings(key) } return v diff --git a/config/json.go b/config/json.go index ae86ea53..e2b53793 100644 --- a/config/json.go +++ b/config/json.go @@ -17,13 +17,10 @@ package config import ( "encoding/json" "errors" - "fmt" "io/ioutil" "os" - "path" "strings" "sync" - "time" ) // JsonConfig is a json config parser and implements Config interface. @@ -41,13 +38,19 @@ func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) { if err != nil { return nil, err } + + return js.ParseData(content) +} + +// ParseData returns a ConfigContainer with json string +func (js *JsonConfig) ParseData(data []byte) (ConfigContainer, error) { x := &JsonConfigContainer{ data: make(map[string]interface{}), } - err = json.Unmarshal(content, &x.data) + err := json.Unmarshal(data, &x.data) if err != nil { var wrappingArray []interface{} - err2 := json.Unmarshal(content, &wrappingArray) + err2 := json.Unmarshal(data, &wrappingArray) if err2 != nil { return nil, err } @@ -56,16 +59,6 @@ func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) { return x, nil } -func (js *JsonConfig) ParseData(data []byte) (ConfigContainer, error) { - // Save memory data to temporary file - tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) - os.MkdirAll(path.Dir(tmpName), os.ModePerm) - if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { - return nil, err - } - return js.Parse(tmpName) -} - // A Config represents the json configuration. // Only when get value, support key as section:name type. type JsonConfigContainer struct { @@ -88,11 +81,10 @@ func (c *JsonConfigContainer) Bool(key string) (bool, error) { // DefaultBool return the bool value if has no error // otherwise return the defaultval func (c *JsonConfigContainer) DefaultBool(key string, defaultval bool) bool { - if v, err := c.Bool(key); err != nil { - return defaultval - } else { + if v, err := c.Bool(key); err == nil { return v } + return defaultval } // Int returns the integer value for a given key. @@ -110,11 +102,10 @@ func (c *JsonConfigContainer) Int(key string) (int, error) { // DefaultInt returns the integer value for a given key. // if err != nil return defaltval func (c *JsonConfigContainer) DefaultInt(key string, defaultval int) int { - if v, err := c.Int(key); err != nil { - return defaultval - } else { + if v, err := c.Int(key); err == nil { return v } + return defaultval } // Int64 returns the int64 value for a given key. @@ -132,11 +123,10 @@ func (c *JsonConfigContainer) Int64(key string) (int64, error) { // DefaultInt64 returns the int64 value for a given key. // if err != nil return defaltval func (c *JsonConfigContainer) DefaultInt64(key string, defaultval int64) int64 { - if v, err := c.Int64(key); err != nil { - return defaultval - } else { + if v, err := c.Int64(key); err == nil { return v } + return defaultval } // Float returns the float value for a given key. @@ -154,11 +144,10 @@ func (c *JsonConfigContainer) Float(key string) (float64, error) { // DefaultFloat returns the float64 value for a given key. // if err != nil return defaltval func (c *JsonConfigContainer) DefaultFloat(key string, defaultval float64) float64 { - if v, err := c.Float(key); err != nil { - return defaultval - } else { + if v, err := c.Float(key); err == nil { return v } + return defaultval } // String returns the string value for a given key. @@ -175,35 +164,37 @@ func (c *JsonConfigContainer) String(key string) string { // DefaultString returns the string value for a given key. // if err != nil return defaltval func (c *JsonConfigContainer) DefaultString(key string, defaultval string) string { - if v := c.String(key); v == "" { - return defaultval - } else { + // TODO FIXME should not use "" to replace non existance + if v := c.String(key); v != "" { return v } + return defaultval } // Strings returns the []string value for a given key. func (c *JsonConfigContainer) Strings(key string) []string { + stringVal := c.String(key) + if stringVal == "" { + return []string{} + } return strings.Split(c.String(key), ";") } // DefaultStrings returns the []string value for a given key. // if err != nil return defaltval func (c *JsonConfigContainer) DefaultStrings(key string, defaultval []string) []string { - if v := c.Strings(key); len(v) == 0 { - return defaultval - } else { + if v := c.Strings(key); len(v) > 0 { return v } + return defaultval } // GetSection returns map for the given section func (c *JsonConfigContainer) GetSection(section string) (map[string]string, error) { if v, ok := c.data[section]; ok { return v.(map[string]string), nil - } else { - return nil, errors.New("not exist setction") } + return nil, errors.New("nonexist section " + section) } // SaveConfigFile save the config into file @@ -222,7 +213,7 @@ func (c *JsonConfigContainer) SaveConfigFile(filename string) (err error) { return err } -// WriteValue writes a new value for key. +// Set writes a new value for key. func (c *JsonConfigContainer) Set(key, val string) error { c.Lock() defer c.Unlock() @@ -241,18 +232,20 @@ func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) { // section.key or key func (c *JsonConfigContainer) getData(key string) interface{} { - c.RLock() - defer c.RUnlock() if len(key) == 0 { return nil } - sectionKey := strings.Split(key, "::") - if len(sectionKey) >= 2 { - curValue, ok := c.data[sectionKey[0]] + + c.RLock() + defer c.RUnlock() + + sectionKeys := strings.Split(key, "::") + if len(sectionKeys) >= 2 { + curValue, ok := c.data[sectionKeys[0]] if !ok { return nil } - for _, key := range sectionKey[1:] { + for _, key := range sectionKeys[1:] { if v, ok := curValue.(map[string]interface{}); ok { if curValue, ok = v[key]; !ok { return nil diff --git a/config/json_test.go b/config/json_test.go index 409e2c12..5aedae36 100644 --- a/config/json_test.go +++ b/config/json_test.go @@ -21,6 +21,7 @@ import ( var jsoncontext = `{ "appname": "beeapi", +"testnames": "foo;bar", "httpport": 8080, "mysqlport": 3600, "PI": 3.1415976, @@ -28,8 +29,8 @@ var jsoncontext = `{ "autorender": false, "copyrequestbody": true, "database": { - "host": "host", - "port": "port", + "host": "host", + "port": "port", "database": "database", "username": "username", "password": "password", @@ -122,6 +123,12 @@ func TestJson(t *testing.T) { if jsonconf.String("runmode") != "dev" { t.Fatal("runmode not equal to dev") } + if v := jsonconf.Strings("unknown"); len(v) > 0 { + t.Fatal("unknown strings, the length should be 0") + } + if v := jsonconf.Strings("testnames"); len(v) != 2 { + t.Fatal("testnames length should be 2") + } if v, err := jsonconf.Bool("autorender"); err != nil || v != false { t.Error(v) t.Fatal(err) @@ -179,4 +186,8 @@ func TestJson(t *testing.T) { if _, err := jsonconf.Bool("unknown"); err == nil { t.Error("unknown keys should return an error when expecting a Bool") } + + if !jsonconf.DefaultBool("unknow", true) { + t.Error("unknown keys with default value wrong") + } } diff --git a/context/context.go b/context/context.go index 89b5ffe4..f6aa85d6 100644 --- a/context/context.go +++ b/context/context.go @@ -31,7 +31,6 @@ import ( "strings" "time" - "github.com/astaxie/beego/middleware" "github.com/astaxie/beego/utils" ) @@ -53,24 +52,10 @@ 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 { - 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(body) } // Write string to response body. @@ -155,8 +140,11 @@ func (ctx *Context) CheckXsrfCookie() bool { } if token == "" { ctx.Abort(403, "'_xsrf' argument missing from POST") - } else if ctx._xsrf_token != token { + return false + } + if ctx._xsrf_token != token { ctx.Abort(403, "XSRF cookie does not match POST argument") + return false } return true } diff --git a/context/input.go b/context/input.go index 6c662820..f535e6a2 100644 --- a/context/input.go +++ b/context/input.go @@ -27,7 +27,7 @@ import ( "github.com/astaxie/beego/session" ) -// BeegoInput operates the http request header ,data ,cookie and body. +// BeegoInput operates the http request header, data, cookie and body. // it also contains router params and current session. type BeegoInput struct { CruSession session.SessionStore @@ -72,11 +72,11 @@ func (input *BeegoInput) Site() string { func (input *BeegoInput) Scheme() string { if input.Request.URL.Scheme != "" { return input.Request.URL.Scheme - } else if input.Request.TLS == nil { - return "http" - } else { - return "https" } + if input.Request.TLS == nil { + return "http" + } + return "https" } // Domain returns host name. @@ -153,12 +153,12 @@ func (input *BeegoInput) IsSecure() bool { return input.Scheme() == "https" } -// IsSecure returns boolean of this request is in webSocket. +// IsWebsocket returns boolean of this request is in webSocket. func (input *BeegoInput) IsWebsocket() bool { return input.Header("Upgrade") == "websocket" } -// IsSecure returns boolean of whether file uploads in this request or not.. +// IsUpload returns boolean of whether file uploads in this request or not.. func (input *BeegoInput) IsUpload() bool { return strings.Contains(input.Header("Content-Type"), "multipart/form-data") } @@ -189,16 +189,24 @@ func (input *BeegoInput) Proxy() []string { return []string{} } +// Referer returns http referer header. +func (input *BeegoInput) Referer() string { + return input.Header("Referer") +} + // Refer returns http referer header. func (input *BeegoInput) Refer() string { - return input.Header("Referer") + return input.Referer() } // SubDomains returns sub domain string. // if aa.bb.domain.com, returns aa.bb . func (input *BeegoInput) SubDomains() string { parts := strings.Split(input.Host(), ".") - return strings.Join(parts[len(parts)-2:], ".") + if len(parts) >= 3 { + return strings.Join(parts[:len(parts)-2], ".") + } + return "" } // Port returns request client port. @@ -237,6 +245,7 @@ func (input *BeegoInput) Query(key string) string { } // Header returns request header item string by a given string. +// if non-existed, return empty string. func (input *BeegoInput) Header(key string) string { return input.Request.Header.Get(key) } @@ -252,11 +261,12 @@ func (input *BeegoInput) Cookie(key string) string { } // Session returns current session item value by a given key. +// if non-existed, return empty string. func (input *BeegoInput) Session(key interface{}) interface{} { return input.CruSession.Get(key) } -// Body returns the raw request body data as bytes. +// CopyBody returns the raw request body data as bytes. func (input *BeegoInput) CopyBody() []byte { requestbody, _ := ioutil.ReadAll(input.Request.Body) input.Request.Body.Close() diff --git a/context/input_test.go b/context/input_test.go index ddd1a056..4566f6d6 100644 --- a/context/input_test.go +++ b/context/input_test.go @@ -70,3 +70,45 @@ func TestParse(t *testing.T) { } fmt.Println(user) } + +func TestSubDomain(t *testing.T) { + r, _ := http.NewRequest("GET", "http://www.example.com/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil) + beegoInput := NewInput(r) + + subdomain := beegoInput.SubDomains() + if subdomain != "www" { + t.Fatal("Subdomain parse error, got" + subdomain) + } + + r, _ = http.NewRequest("GET", "http://localhost/", nil) + beegoInput.Request = r + if beegoInput.SubDomains() != "" { + t.Fatal("Subdomain parse error, should be empty, got " + beegoInput.SubDomains()) + } + + r, _ = http.NewRequest("GET", "http://aa.bb.example.com/", nil) + beegoInput.Request = r + if beegoInput.SubDomains() != "aa.bb" { + t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) + } + + /* TODO Fix this + r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil) + beegoInput.Request = r + if beegoInput.SubDomains() != "" { + t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) + } + */ + + r, _ = http.NewRequest("GET", "http://example.com/", nil) + beegoInput.Request = r + if beegoInput.SubDomains() != "" { + t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) + } + + r, _ = http.NewRequest("GET", "http://aa.bb.cc.dd.example.com/", nil) + beegoInput.Request = r + if beegoInput.SubDomains() != "aa.bb.cc.dd" { + t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) + } +} diff --git a/context/output.go b/context/output.go index 6298ee77..2141513d 100644 --- a/context/output.go +++ b/context/output.go @@ -188,7 +188,7 @@ func sanitizeValue(v string) string { // Json writes json to response body. // if coding is true, it converts utf-8 to \u0000 type. func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) error { - output.Header("Content-Type", "application/json;charset=UTF-8") + output.Header("Content-Type", "application/json; charset=utf-8") var content []byte var err error if hasIndent { @@ -209,7 +209,7 @@ func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) e // Jsonp writes jsonp to response body. func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error { - output.Header("Content-Type", "application/javascript;charset=UTF-8") + output.Header("Content-Type", "application/javascript; charset=utf-8") var content []byte var err error if hasIndent { @@ -235,7 +235,7 @@ func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error { // Xml writes xml string to response body. func (output *BeegoOutput) Xml(data interface{}, hasIndent bool) error { - output.Header("Content-Type", "application/xml;charset=UTF-8") + output.Header("Content-Type", "application/xml; charset=utf-8") var content []byte var err error if hasIndent { diff --git a/controller.go b/controller.go index 72ba323b..e056f52d 100644 --- a/controller.go +++ b/controller.go @@ -270,16 +270,22 @@ func (c *Controller) Redirect(url string, code int) { // Aborts stops controller handler and show the error data if code is defined in ErrorMap or code string. func (c *Controller) Abort(code string) { status, err := strconv.Atoi(code) - if err == nil { - c.Ctx.Abort(status, code) - } else { - c.Ctx.Abort(200, code) + if err != nil { + status = 200 } + c.CustomAbort(status, code) } // CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. func (c *Controller) CustomAbort(status int, body string) { - c.Ctx.Abort(status, body) + c.Ctx.ResponseWriter.WriteHeader(status) + // first panic from ErrorMaps, is is user defined error functions. + if _, ok := ErrorMaps[body]; ok { + panic(body) + } + // last panic user string + c.Ctx.ResponseWriter.Write([]byte(body)) + panic(USERSTOPRUN) } // StopRun makes panic of USERSTOPRUN error and go to recover function if defined. @@ -289,7 +295,7 @@ func (c *Controller) StopRun() { // UrlFor does another controller handler in this request function. // it goes to this controller method if endpoint is not clear. -func (c *Controller) UrlFor(endpoint string, values ...string) string { +func (c *Controller) UrlFor(endpoint string, values ...interface{}) string { if len(endpoint) <= 0 { return "" } @@ -363,67 +369,144 @@ func (c *Controller) ParseForm(obj interface{}) error { return ParseForm(c.Input(), obj) } -// GetString returns the input value by key string. -func (c *Controller) GetString(key string) string { - return c.Ctx.Input.Query(key) +// GetString returns the input value by key string or the default value while it's present and input is blank +func (c *Controller) GetString(key string, def ...string) string { + var defv string + if len(def) > 0 { + defv = def[0] + } + + if v := c.Ctx.Input.Query(key); v != "" { + return v + } else { + return defv + } } -// GetStrings returns the input string slice by key string. +// GetStrings returns the input string slice by key string or the default value while it's present and input is blank // it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection. -func (c *Controller) GetStrings(key string) []string { +func (c *Controller) GetStrings(key string, def ...[]string) []string { + var defv []string + if len(def) > 0 { + defv = def[0] + } + f := c.Input() if f == nil { - return []string{} + return defv } + vs := f[key] if len(vs) > 0 { return vs + } else { + return defv } - return []string{} } -// GetInt returns input as an int -func (c *Controller) GetInt(key string) (int, error) { - return strconv.Atoi(c.Ctx.Input.Query(key)) +// GetInt returns input as an int or the default value while it's present and input is blank +func (c *Controller) GetInt(key string, def ...int) (int, error) { + var defv int + if len(def) > 0 { + defv = def[0] + } + + if strv := c.Ctx.Input.Query(key); strv != "" { + return strconv.Atoi(strv) + } else { + return defv, nil + } } -// GetInt8 return input as an int8 -func (c *Controller) GetInt8(key string) (int8, error) { - i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 8) - i8 := int8(i64) +// GetInt8 return input as an int8 or the default value while it's present and input is blank +func (c *Controller) GetInt8(key string, def ...int8) (int8, error) { + var defv int8 + if len(def) > 0 { + defv = def[0] + } - return i8, err + if strv := c.Ctx.Input.Query(key); strv != "" { + i64, err := strconv.ParseInt(strv, 10, 8) + i8 := int8(i64) + return i8, err + } else { + return defv, nil + } } -// GetInt16 returns input as an int16 -func (c *Controller) GetInt16(key string) (int16, error) { - i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 16) - i16 := int16(i64) +// GetInt16 returns input as an int16 or the default value while it's present and input is blank +func (c *Controller) GetInt16(key string, def ...int16) (int16, error) { + var defv int16 + if len(def) > 0 { + defv = def[0] + } - return i16, err + if strv := c.Ctx.Input.Query(key); strv != "" { + i64, err := strconv.ParseInt(strv, 10, 16) + i16 := int16(i64) + + return i16, err + } else { + return defv, nil + } } -// GetInt32 returns input as an int32 -func (c *Controller) GetInt32(key string) (int32, error) { - i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32) - i32 := int32(i64) +// GetInt32 returns input as an int32 or the default value while it's present and input is blank +func (c *Controller) GetInt32(key string, def ...int32) (int32, error) { + var defv int32 + if len(def) > 0 { + defv = def[0] + } - return i32, err + if strv := c.Ctx.Input.Query(key); strv != "" { + i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32) + i32 := int32(i64) + return i32, err + } else { + return defv, nil + } } -// GetInt64 returns input value as int64. -func (c *Controller) GetInt64(key string) (int64, error) { - return strconv.ParseInt(c.Ctx.Input.Query(key), 10, 64) +// GetInt64 returns input value as int64 or the default value while it's present and input is blank. +func (c *Controller) GetInt64(key string, def ...int64) (int64, error) { + var defv int64 + if len(def) > 0 { + defv = def[0] + } + + if strv := c.Ctx.Input.Query(key); strv != "" { + return strconv.ParseInt(strv, 10, 64) + } else { + return defv, nil + } } -// GetBool returns input value as bool. -func (c *Controller) GetBool(key string) (bool, error) { - return strconv.ParseBool(c.Ctx.Input.Query(key)) +// GetBool returns input value as bool or the default value while it's present and input is blank. +func (c *Controller) GetBool(key string, def ...bool) (bool, error) { + var defv bool + if len(def) > 0 { + defv = def[0] + } + + if strv := c.Ctx.Input.Query(key); strv != "" { + return strconv.ParseBool(strv) + } else { + return defv, nil + } } -// GetFloat returns input value as float64. -func (c *Controller) GetFloat(key string) (float64, error) { - return strconv.ParseFloat(c.Ctx.Input.Query(key), 64) +// GetFloat returns input value as float64 or the default value while it's present and input is blank. +func (c *Controller) GetFloat(key string, def ...float64) (float64, error) { + var defv float64 + if len(def) > 0 { + defv = def[0] + } + + if strv := c.Ctx.Input.Query(key); strv != "" { + return strconv.ParseFloat(c.Ctx.Input.Query(key), 64) + } else { + return defv, nil + } } // GetFile returns the file data in file upload field named as key. diff --git a/middleware/error.go b/error.go similarity index 51% rename from middleware/error.go rename to error.go index 3b7191e9..71be6916 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,15 +196,68 @@ 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 401 unauthorized error. +func unauthorized(rw http.ResponseWriter, r *http.Request) { + t, _ := template.New("beegoerrortemp").Parse(errtpl) + data := make(map[string]interface{}) + data["Title"] = "Unauthorized" + data["Content"] = template.HTML("
The page you have requested can't be authorized." + + "
Perhaps you are here because:" + + "

") + data["BeegoVersion"] = VERSION + t.Execute(rw, data) +} + +// show 402 Payment Required +func paymentRequired(rw http.ResponseWriter, r *http.Request) { + t, _ := template.New("beegoerrortemp").Parse(errtpl) + data := make(map[string]interface{}) + data["Title"] = "Payment Required" + data["Content"] = template.HTML("
The page you have requested Payment Required." + + "
Perhaps you are here because:" + + "

") + data["BeegoVersion"] = VERSION + t.Execute(rw, data) +} + +// show 403 forbidden error. +func forbidden(rw http.ResponseWriter, r *http.Request) { + t, _ := template.New("beegoerrortemp").Parse(errtpl) + data := make(map[string]interface{}) + data["Title"] = "Forbidden" + data["Content"] = template.HTML("
The page you have requested is forbidden." + + "
Perhaps you are here because:" + + "

") + data["BeegoVersion"] = VERSION + t.Execute(rw, data) } // show 404 notfound error. -func NotFound(rw http.ResponseWriter, r *http.Request) { +func notFound(rw http.ResponseWriter, r *http.Request) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := make(map[string]interface{}) data["Title"] = "Page Not Found" @@ -211,45 +270,66 @@ func NotFound(rw http.ResponseWriter, r *http.Request) { "
You like 404 pages" + "") data["BeegoVersion"] = VERSION - //rw.WriteHeader(http.StatusNotFound) t.Execute(rw, data) } -// show 401 unauthorized error. -func Unauthorized(rw http.ResponseWriter, r *http.Request) { +// show 405 Method Not Allowed +func methodNotAllowed(rw http.ResponseWriter, r *http.Request) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := make(map[string]interface{}) - data["Title"] = "Unauthorized" - data["Content"] = template.HTML("
The page you have requested can't be authorized." + + data["Title"] = "Method Not Allowed" + data["Content"] = template.HTML("
The method you have requested Not Allowed." + "
Perhaps you are here because:" + "

") data["BeegoVersion"] = VERSION - //rw.WriteHeader(http.StatusUnauthorized) t.Execute(rw, data) } -// show 403 forbidden error. -func Forbidden(rw http.ResponseWriter, r *http.Request) { +// show 500 internal server error. +func internalServerError(rw http.ResponseWriter, r *http.Request) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := make(map[string]interface{}) - data["Title"] = "Forbidden" - data["Content"] = template.HTML("
The page you have requested is forbidden." + - "
Perhaps you are here because:" + + data["Title"] = "Internal Server Error" + data["Content"] = template.HTML("
The page you have requested is down right now." + "

") + "
Please try again later and report the error to the website administrator" + + "
") + data["BeegoVersion"] = VERSION + t.Execute(rw, data) +} + +// show 501 Not Implemented. +func notImplemented(rw http.ResponseWriter, r *http.Request) { + t, _ := template.New("beegoerrortemp").Parse(errtpl) + data := make(map[string]interface{}) + data["Title"] = "Not Implemented" + data["Content"] = template.HTML("
The page you have requested is Not Implemented." + + "

") + data["BeegoVersion"] = VERSION + t.Execute(rw, data) +} + +// show 502 Bad Gateway. +func badGateway(rw http.ResponseWriter, r *http.Request) { + t, _ := template.New("beegoerrortemp").Parse(errtpl) + data := make(map[string]interface{}) + data["Title"] = "Bad Gateway" + data["Content"] = template.HTML("
The page you have requested is down right now." + + "

") data["BeegoVersion"] = VERSION - //rw.WriteHeader(http.StatusForbidden) t.Execute(rw, data) } // show 503 service unavailable error. -func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) { +func serviceUnavailable(rw http.ResponseWriter, r *http.Request) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := make(map[string]interface{}) data["Title"] = "Service Unavailable" @@ -260,80 +340,151 @@ func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) { "
Please try again later." + "") data["BeegoVersion"] = VERSION - //rw.WriteHeader(http.StatusServiceUnavailable) t.Execute(rw, data) } -// show 500 internal server error. -func InternalServerError(rw http.ResponseWriter, r *http.Request) { +// show 504 Gateway Timeout. +func gatewayTimeout(rw http.ResponseWriter, r *http.Request) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := make(map[string]interface{}) - data["Title"] = "Internal Server Error" - data["Content"] = template.HTML("
The page you have requested is down right now." + + data["Title"] = "Gateway Timeout" + data["Content"] = template.HTML("
The page you have requested is unavailable." + + "
Perhaps you are here because:" + "

") + "

The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI." + + "
Please try again later." + + "") data["BeegoVersion"] = VERSION - //rw.WriteHeader(http.StatusInternalServerError) t.Execute(rw, data) } -// show 500 internal error with simple text string. -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() { - if _, ok := ErrorMaps["404"]; !ok { - ErrorMaps["404"] = NotFound +func registerDefaultErrorHandler() { + if _, ok := ErrorMaps["401"]; !ok { + Errorhandler("401", unauthorized) } - if _, ok := ErrorMaps["401"]; !ok { - ErrorMaps["401"] = Unauthorized + if _, ok := ErrorMaps["402"]; !ok { + Errorhandler("402", paymentRequired) } if _, ok := ErrorMaps["403"]; !ok { - ErrorMaps["403"] = Forbidden + Errorhandler("403", forbidden) } - if _, ok := ErrorMaps["503"]; !ok { - ErrorMaps["503"] = ServiceUnavailable + if _, ok := ErrorMaps["404"]; !ok { + Errorhandler("404", notFound) + } + + if _, ok := ErrorMaps["405"]; !ok { + Errorhandler("405", methodNotAllowed) } if _, ok := ErrorMaps["500"]; !ok { - ErrorMaps["500"] = InternalServerError + Errorhandler("500", internalServerError) } + if _, ok := ErrorMaps["501"]; !ok { + Errorhandler("501", notImplemented) + } + if _, ok := ErrorMaps["502"]; !ok { + Errorhandler("502", badGateway) + } + + if _, ok := ErrorMaps["503"]; !ok { + Errorhandler("503", serviceUnavailable) + } + + if _, ok := ErrorMaps["504"]; !ok { + Errorhandler("504", gatewayTimeout) + } +} + +// 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 { - isint, err := strconv.Atoi(errcode) - if err != nil { - isint = 500 - } - if isint == 400 { - msg = "404 page not found" - } - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(isint) - fmt.Fprintln(w, msg) - return + ctx.WriteString(errcode) + } +} + +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) + } + } + } + + // finish all runrouter. release resource + execController.Finish() } } diff --git a/httplib/httplib.go b/httplib/httplib.go index 37ba3b33..7ff2f1d2 100644 --- a/httplib/httplib.go +++ b/httplib/httplib.go @@ -253,30 +253,20 @@ func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest { return b } -func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { - if b.resp.StatusCode != 0 { - return b.resp, nil - } - var paramBody string - if len(b.params) > 0 { - var buf bytes.Buffer - for k, v := range b.params { - buf.WriteString(url.QueryEscape(k)) - buf.WriteByte('=') - buf.WriteString(url.QueryEscape(v)) - buf.WriteByte('&') - } - paramBody = buf.String() - paramBody = paramBody[0 : len(paramBody)-1] - } - +func (b *BeegoHttpRequest) buildUrl(paramBody string) { + // build GET url with query string if b.req.Method == "GET" && len(paramBody) > 0 { if strings.Index(b.url, "?") != -1 { b.url += "&" + paramBody } else { b.url = b.url + "?" + paramBody } - } else if b.req.Method == "POST" && b.req.Body == nil { + return + } + + // build POST url and body + if b.req.Method == "POST" && b.req.Body == nil { + // with files if len(b.files) > 0 { pr, pw := io.Pipe() bodyWriter := multipart.NewWriter(pw) @@ -305,12 +295,35 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { }() b.Header("Content-Type", bodyWriter.FormDataContentType()) b.req.Body = ioutil.NopCloser(pr) - } else if len(paramBody) > 0 { + return + } + + // with params + if len(paramBody) > 0 { b.Header("Content-Type", "application/x-www-form-urlencoded") b.Body(paramBody) } } +} +func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { + if b.resp.StatusCode != 0 { + return b.resp, nil + } + var paramBody string + if len(b.params) > 0 { + var buf bytes.Buffer + for k, v := range b.params { + buf.WriteString(url.QueryEscape(k)) + buf.WriteByte('=') + buf.WriteString(url.QueryEscape(v)) + buf.WriteByte('&') + } + paramBody = buf.String() + paramBody = paramBody[0 : len(paramBody)-1] + } + + b.buildUrl(paramBody) url, err := url.Parse(b.url) if err != nil { return nil, err @@ -342,14 +355,12 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { } } - var jar http.CookieJar + var jar http.CookieJar = nil if b.setting.EnableCookie { if defaultCookieJar == nil { createDefaultCookie() } jar = defaultCookieJar - } else { - jar = nil } client := &http.Client{ @@ -402,12 +413,11 @@ func (b *BeegoHttpRequest) Bytes() ([]byte, error) { return nil, nil } defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) + b.body, err = ioutil.ReadAll(resp.Body) if err != nil { return nil, err } - b.body = data - return data, nil + return b.body, nil } // ToFile saves the body data in response to one file. @@ -438,8 +448,7 @@ func (b *BeegoHttpRequest) ToJson(v interface{}) error { if err != nil { return err } - err = json.Unmarshal(data, v) - return err + return json.Unmarshal(data, v) } // ToXml returns the map that marshals from the body bytes as xml in response . @@ -449,8 +458,7 @@ func (b *BeegoHttpRequest) ToXml(v interface{}) error { if err != nil { return err } - err = xml.Unmarshal(data, v) - return err + return xml.Unmarshal(data, v) } // Response executes request client gets response mannually. diff --git a/logs/conn.go b/logs/conn.go index 612634fa..2240eece 100644 --- a/logs/conn.go +++ b/logs/conn.go @@ -43,11 +43,7 @@ func NewConn() LoggerInterface { // init connection writer with json config. // json config only need key "level". func (c *ConnWriter) Init(jsonconfig string) error { - err := json.Unmarshal([]byte(jsonconfig), c) - if err != nil { - return err - } - return nil + return json.Unmarshal([]byte(jsonconfig), c) } // write message in connection. @@ -77,10 +73,9 @@ func (c *ConnWriter) Flush() { // destroy connection writer and close tcp listener. func (c *ConnWriter) Destroy() { - if c.innerWriter == nil { - return + if c.innerWriter != nil { + c.innerWriter.Close() } - c.innerWriter.Close() } func (c *ConnWriter) connect() error { diff --git a/logs/console.go b/logs/console.go index 461291c2..ce7ecd54 100644 --- a/logs/console.go +++ b/logs/console.go @@ -50,9 +50,10 @@ type ConsoleWriter struct { // create ConsoleWriter returning as LoggerInterface. func NewConsole() LoggerInterface { - cw := new(ConsoleWriter) - cw.lg = log.New(os.Stdout, "", log.Ldate|log.Ltime) - cw.Level = LevelDebug + cw := &ConsoleWriter{ + lg: log.New(os.Stdout, "", log.Ldate|log.Ltime), + Level: LevelDebug, + } return cw } @@ -62,11 +63,7 @@ func (c *ConsoleWriter) Init(jsonconfig string) error { if len(jsonconfig) == 0 { return nil } - err := json.Unmarshal([]byte(jsonconfig), c) - if err != nil { - return err - } - return nil + return json.Unmarshal([]byte(jsonconfig), c) } // write message in console. @@ -76,9 +73,10 @@ func (c *ConsoleWriter) WriteMsg(msg string, level int) error { } if goos := runtime.GOOS; goos == "windows" { c.lg.Println(msg) - } else { - c.lg.Println(colors[level](msg)) + return nil } + c.lg.Println(colors[level](msg)) + return nil } diff --git a/logs/file.go b/logs/file.go index 39220cc0..2d3449ce 100644 --- a/logs/file.go +++ b/logs/file.go @@ -15,10 +15,11 @@ package logs import ( + "bytes" "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "log" "os" "path/filepath" @@ -122,11 +123,7 @@ func (w *FileLogWriter) startLogger() error { return err } w.mw.SetFd(fd) - err = w.initFd() - if err != nil { - return err - } - return nil + return w.initFd() } func (w *FileLogWriter) docheck(size int) { @@ -169,18 +166,44 @@ func (w *FileLogWriter) initFd() error { } w.maxsize_cursize = int(finfo.Size()) w.daily_opendate = time.Now().Day() + w.maxlines_curlines = 0 if finfo.Size() > 0 { - content, err := ioutil.ReadFile(w.Filename) + count, err := w.lines() if err != nil { return err } - w.maxlines_curlines = len(strings.Split(string(content), "\n")) - } else { - w.maxlines_curlines = 0 + w.maxlines_curlines = count } return nil } +func (w *FileLogWriter) lines() (int, error) { + fd, err := os.Open(w.Filename) + if err != nil { + return 0, err + } + defer fd.Close() + + buf := make([]byte, 32768) // 32k + count := 0 + lineSep := []byte{'\n'} + + for { + c, err := fd.Read(buf) + if err != nil && err != io.EOF { + return count, err + } + + count += bytes.Count(buf[:c], lineSep) + + if err == io.EOF { + break + } + } + + return count, nil +} + // DoRotate means it need to write file in new file. // new file name like xx.log.2013-01-01.2 func (w *FileLogWriter) DoRotate() error { diff --git a/logs/log.go b/logs/log.go index 6abfb005..32e0187c 100644 --- a/logs/log.go +++ b/logs/log.go @@ -292,9 +292,9 @@ func (bl *BeeLogger) Close() { fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err) } } - } else { - break + continue } + break } for _, l := range bl.outputs { l.Flush() diff --git a/logs/smtp.go b/logs/smtp.go index 19a0f510..95123ebf 100644 --- a/logs/smtp.go +++ b/logs/smtp.go @@ -25,7 +25,8 @@ import ( ) const ( - subjectPhrase = "Diagnostic message from server" +// no usage +// subjectPhrase = "Diagnostic message from server" ) // smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server. @@ -146,9 +147,7 @@ func (s *SmtpWriter) WriteMsg(msg string, level int) error { mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + ">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg) - err := s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) - - return err + return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) } // implementing method. empty. 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/middleware/i18n.go b/middleware/i18n.go index e4dab693..f54b4bb5 100644 --- a/middleware/i18n.go +++ b/middleware/i18n.go @@ -34,7 +34,6 @@ type Translation struct { } func NewLocale(filepath string, defaultlocal string) *Translation { - i18n := make(map[string]map[string]string) file, err := os.Open(filepath) if err != nil { panic("open " + filepath + " err :" + err.Error()) @@ -43,8 +42,9 @@ func NewLocale(filepath string, defaultlocal string) *Translation { if err != nil { panic("read " + filepath + " err :" + err.Error()) } - err = json.Unmarshal(data, &i18n) - if err != nil { + + i18n := make(map[string]map[string]string) + if err = json.Unmarshal(data, &i18n); err != nil { panic("json.Unmarshal " + filepath + " err :" + err.Error()) } return &Translation{ 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/orm/cmd_utils.go b/orm/cmd_utils.go index 8304da6b..ea105624 100644 --- a/orm/cmd_utils.go +++ b/orm/cmd_utils.go @@ -104,7 +104,11 @@ func getColumnAddQuery(al *alias, fi *fieldInfo) string { typ += " " + "NOT NULL" } - return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s", Q, fi.mi.table, Q, Q, fi.column, Q, typ) + return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s", + Q, fi.mi.table, Q, + Q, fi.column, Q, + typ, getColumnDefault(fi), + ) } // create database creation string. @@ -155,6 +159,9 @@ func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex //if fi.initial.String() != "" { // column += " DEFAULT " + fi.initial.String() //} + + // Append attribute DEFAULT + column += getColumnDefault(fi) if fi.unique { column += " " + "UNIQUE" @@ -239,3 +246,44 @@ func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex return } + + +// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands +func getColumnDefault(fi *fieldInfo) string { + var ( + v, t, d string + ) + + // Skip default attribute if field is in relations + if fi.rel || fi.reverse { + return v + } + + t = " DEFAULT '%s' " + + // These defaults will be useful if there no config value orm:"default" and NOT NULL is on + switch fi.fieldType { + case TypeDateField, TypeDateTimeField: + return v; + + case TypeBooleanField, TypeBitField, TypeSmallIntegerField, TypeIntegerField, + TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField, + TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField, + TypeDecimalField: + d = "0" + } + + if fi.colDefault { + if !fi.initial.Exist() { + v = fmt.Sprintf(t, "") + } else { + v = fmt.Sprintf(t, fi.initial.String()) + } + } else { + if !fi.null { + v = fmt.Sprintf(t, d) + } + } + + return v +} diff --git a/orm/models_info_f.go b/orm/models_info_f.go index a79ffab2..84a0c024 100644 --- a/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -116,6 +116,7 @@ type fieldInfo struct { null bool index bool unique bool + colDefault bool initial StrTo size int auto_now bool @@ -280,6 +281,11 @@ checkType: fi.pk = attrs["pk"] fi.unique = attrs["unique"] + // Mark object property if there is attribute "default" in the orm configuration + if _, ok := tags["default"]; ok { + fi.colDefault = true + } + switch fieldType { case RelManyToMany, RelReverseMany, RelReverseOne: fi.null = false diff --git a/orm/orm.go b/orm/orm.go index cdb7f27c..f881433b 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -489,10 +489,6 @@ func (o *orm) Driver() Driver { return driver(o.alias.Name) } -func (o *orm) GetDB() dbQuerier { - panic(ErrNotImplement) -} - // create new orm func NewOrm() Ormer { BootStrap() // execute only once diff --git a/orm/types.go b/orm/types.go index c342e1c2..b46be4fc 100644 --- a/orm/types.go +++ b/orm/types.go @@ -51,7 +51,6 @@ type Ormer interface { Rollback() error Raw(string, ...interface{}) RawSeter Driver() Driver - GetDB() dbQuerier } // insert prepared statement diff --git a/parser.go b/parser.go index bd673044..be91b4cb 100644 --- a/parser.go +++ b/parser.go @@ -57,7 +57,7 @@ func parserPkg(pkgRealpath, pkgpath string) error { rep := strings.NewReplacer("/", "_", ".", "_") commentFilename = COMMENTFL + rep.Replace(pkgpath) + ".go" if !compareFile(pkgRealpath) { - Info(pkgRealpath + " don't has updated") + Info(pkgRealpath + " has not changed, not reloading") return nil } genInfoList = make(map[string][]ControllerComments) diff --git a/plugins/cors/cors.go b/plugins/cors/cors.go index dce750eb..052d3bc6 100644 --- a/plugins/cors/cors.go +++ b/plugins/cors/cors.go @@ -217,6 +217,7 @@ func Allow(opts *Options) beego.FilterFunc { ctx.Output.Header(key, value) } ctx.Output.SetStatus(http.StatusOK) + ctx.WriteString("") return } headers = opts.Header(origin) diff --git a/router.go b/router.go index 1f47c907..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" ) @@ -153,7 +152,7 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM if val := reflectVal.MethodByName(colon[1]); val.IsValid() { methods[strings.ToUpper(m)] = colon[1] } else { - panic(colon[1] + " method doesn't exist in the controller " + t.Name()) + 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) @@ -429,7 +428,7 @@ func (p *ControllerRegistor) insertFilterRouter(pos int, mr *FilterRouter) error // UrlFor does another controller handler in this request function. // it can access any controller method. -func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { +func (p *ControllerRegistor) UrlFor(endpoint string, values ...interface{}) string { paths := strings.Split(endpoint, ".") if len(paths) <= 1 { Warn("urlfor endpoint must like path.controller.method") @@ -444,9 +443,9 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string { key := "" for k, v := range values { if k%2 == 0 { - key = v + key = fmt.Sprint(v) } else { - params[key] = v + params[key] = fmt.Sprint(v) } } } @@ -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,28 +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.WriteHeader(he.StatusCode) - 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) @@ -881,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/session/ledis/ledis_session.go b/session/ledis/ledis_session.go index 3ada47ac..400d7238 100644 --- a/session/ledis/ledis_session.go +++ b/session/ledis/ledis_session.go @@ -2,6 +2,8 @@ package session import ( "net/http" + "strconv" + "strings" "sync" "github.com/astaxie/beego/session" @@ -74,19 +76,29 @@ func (ls *LedisSessionStore) SessionRelease(w http.ResponseWriter) { type LedisProvider struct { maxlifetime int64 savePath string + db int } // init ledis session // savepath like ledis server saveDataPath,pool size // e.g. 127.0.0.1:6379,100,astaxie func (lp *LedisProvider) SessionInit(maxlifetime int64, savePath string) error { + var err error lp.maxlifetime = maxlifetime - lp.savePath = savePath + configs := strings.Split(savepath, ",") + if len(configs) == 1 { + lp.savePath = configs[0] + } else if len(configs) == 2 { + lp.savePath = configs[0] + lp.db, err = strconv.Atoi(configs[1]) + if err != nil { + return err + } + } cfg := new(config.Config) cfg.DataDir = lp.savePath - var err error nowLedis, err := ledis.Open(cfg) - c, err = nowLedis.Select(0) + c, err = nowLedis.Select(lp.db) if err != nil { println(err) return nil diff --git a/session/redis/sess_redis.go b/session/redis/sess_redis.go index 82cdd812..887fb520 100644 --- a/session/redis/sess_redis.go +++ b/session/redis/sess_redis.go @@ -118,12 +118,13 @@ type RedisProvider struct { savePath string poolsize int password string + dbNum int poollist *redis.Pool } // init redis session -// savepath like redis server addr,pool size,password -// e.g. 127.0.0.1:6379,100,astaxie +// savepath like redis server addr,pool size,password,dbnum +// e.g. 127.0.0.1:6379,100,astaxie,0 func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { rp.maxlifetime = maxlifetime configs := strings.Split(savePath, ",") @@ -143,6 +144,16 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { if len(configs) > 2 { rp.password = configs[2] } + if len(configs) > 3 { + dbnum, err := strconv.Atoi(configs[1]) + if err != nil || dbnum < 0 { + rp.dbNum = 0 + } else { + rp.dbNum = dbnum + } + } else { + rp.dbNum = 0 + } rp.poollist = redis.NewPool(func() (redis.Conn, error) { c, err := redis.Dial("tcp", rp.savePath) if err != nil { @@ -154,6 +165,11 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { return nil, err } } + _, err = c.Do("SELECT", rp.dbNum) + if err != nil { + c.Close() + return nil, err + } return c, err }, rp.poolsize) 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) diff --git a/template.go b/template.go index 2ca84f22..64b1939e 100644 --- a/template.go +++ b/template.go @@ -42,6 +42,9 @@ func init() { beegoTplFuncMap["dateformat"] = DateFormat beegoTplFuncMap["date"] = Date beegoTplFuncMap["compare"] = Compare + beegoTplFuncMap["compare_not"] = CompareNot + beegoTplFuncMap["not_nil"] = NotNil + beegoTplFuncMap["not_null"] = NotNil beegoTplFuncMap["substr"] = Substr beegoTplFuncMap["html2str"] = Html2str beegoTplFuncMap["str2html"] = Str2html diff --git a/templatefunc.go b/templatefunc.go index 16067613..28ed15a3 100644 --- a/templatefunc.go +++ b/templatefunc.go @@ -139,6 +139,14 @@ func Compare(a, b interface{}) (equal bool) { return } +func CompareNot(a, b interface{}) (equal bool) { + return ! Compare(a, b) +} + +func NotNil(a interface{}) (is_nil bool) { + return CompareNot(a, nil) +} + func Config(returnType, key string, defaultVal interface{}) (value interface{}, err error) { switch returnType { case "String": @@ -246,7 +254,7 @@ func Htmlunquote(src string) string { // /user/John%20Doe // // more detail http://beego.me/docs/mvc/controller/urlbuilding.md -func UrlFor(endpoint string, values ...string) string { +func UrlFor(endpoint string, values ...interface{}) string { return BeeApp.Handlers.UrlFor(endpoint, values...) } @@ -350,11 +358,32 @@ func ParseForm(form url.Values, obj interface{}) error { } fieldV.Set(reflect.ValueOf(t)) } + case reflect.Slice: + if fieldT.Type == sliceOfInts { + formVals := form[tag] + fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals))) + for i := 0; i < len(formVals); i++ { + val, err := strconv.Atoi(formVals[i]) + if err != nil { + return err + } + fieldV.Index(i).SetInt(int64(val)) + } + } else if fieldT.Type == sliceOfStrings { + formVals := form[tag] + fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals))) + for i := 0; i < len(formVals); i++ { + fieldV.Index(i).SetString(formVals[i]) + } + } } } return nil } +var sliceOfInts = reflect.TypeOf([]int(nil)) +var sliceOfStrings = reflect.TypeOf([]string(nil)) + var unKind = map[reflect.Kind]bool{ reflect.Uintptr: true, reflect.Complex64: true, diff --git a/templatefunc_test.go b/templatefunc_test.go index 3692a821..60af5bf5 100644 --- a/templatefunc_test.go +++ b/templatefunc_test.go @@ -72,7 +72,7 @@ func TestDate(t *testing.T) { } } -func TestCompare(t *testing.T) { +func TestCompareRelated(t *testing.T) { if !Compare("abc", "abc") { t.Error("should be equal") } @@ -82,6 +82,15 @@ func TestCompare(t *testing.T) { if !Compare("1", 1) { t.Error("should be equal") } + if CompareNot("abc", "abc") { + t.Error("should be equal") + } + if !CompareNot("abc", "aBc") { + t.Error("should be not equal") + } + if !NotNil("a string") { + t.Error("should not be nil") + } } func TestHtmlquote(t *testing.T) { diff --git a/toolbox/task.go b/toolbox/task.go index b50c54fb..23e2fb5e 100644 --- a/toolbox/task.go +++ b/toolbox/task.go @@ -84,6 +84,7 @@ type TaskFunc func() error // task interface type Tasker interface { + GetSpec() string GetStatus() string Run() error SetNext(time.Time) @@ -102,6 +103,7 @@ type taskerr struct { type Task struct { Taskname string Spec *Schedule + SpecStr string DoFunc TaskFunc Prev time.Time Next time.Time @@ -116,16 +118,22 @@ func NewTask(tname string, spec string, f TaskFunc) *Task { Taskname: tname, DoFunc: f, ErrLimit: 100, + SpecStr: spec, } task.SetCron(spec) return task } +//get spec string +func (s *Task) GetSpec() string { + return s.SpecStr +} + // get current task status func (tk *Task) GetStatus() string { var str string for _, v := range tk.Errlist { - str += v.t.String() + ":" + v.errinfo + "\n" + str += v.t.String() + ":" + v.errinfo + "
" } return str } diff --git a/tree.go b/tree.go index 25947442..93e87470 100644 --- a/tree.go +++ b/tree.go @@ -422,6 +422,9 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string // "/admin/" -> ["admin"] // "/admin/users" -> ["admin", "users"] func splitPath(key string) []string { + if key == "" { + return []string{} + } elements := strings.Split(key, "/") if elements[0] == "" { elements = elements[1:] diff --git a/tree_test.go b/tree_test.go index 358898e7..fa289716 100644 --- a/tree_test.go +++ b/tree_test.go @@ -149,7 +149,11 @@ func TestAddTree2(t *testing.T) { } func TestSplitPath(t *testing.T) { - a := splitPath("/") + a := splitPath("") + if len(a) != 0 { + t.Fatal("/ should retrun []") + } + a = splitPath("/") if len(a) != 0 { t.Fatal("/ should retrun []") } diff --git a/utils/pagination/controller.go b/utils/pagination/controller.go index 28473f8a..f63b30e9 100644 --- a/utils/pagination/controller.go +++ b/utils/pagination/controller.go @@ -21,6 +21,6 @@ import ( // Instantiates a Paginator and assigns it to context.Input.Data["paginator"]. func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) { paginator = NewPaginator(context.Request, per, nums) - context.Input.Data["paginator"] = paginator + context.Input.Data["paginator"] = &paginator return } diff --git a/utils/pagination/paginator.go b/utils/pagination/paginator.go index f89e878e..e537f1ad 100644 --- a/utils/pagination/paginator.go +++ b/utils/pagination/paginator.go @@ -114,7 +114,7 @@ func (p *Paginator) Pages() []int { // Returns URL for a given page index. func (p *Paginator) PageLink(page int) string { - link, _ := url.ParseRequestURI(p.Request.RequestURI) + link, _ := url.ParseRequestURI(p.Request.URL.String()) values := link.Query() if page == 1 { values.Del("p")