diff --git a/README.md b/README.md index 8d010eb9..776e5c2d 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,5 @@ More info [beego.me](http://beego.me) beego is licensed under the Apache Licence, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html). - -## Use case - -- Displaying API documentation: [gowalker](https://github.com/Unknwon/gowalker) -- seocms: [seocms](https://github.com/chinakr/seocms) -- CMS: [toropress](https://github.com/insionng/toropress) +[![Clone in Koding](http://learn.koding.com/btn/clone_d.png)][koding] +[koding]: https://koding.com/Teamwork?import=https://github.com/astaxie/beego/archive/master.zip&c=git1 \ No newline at end of file diff --git a/app.go b/app.go index da18718e..f252367b 100644 --- a/app.go +++ b/app.go @@ -118,6 +118,14 @@ func (app *App) AutoRouter(c ControllerInterface) *App { return app } +// AutoRouterWithPrefix adds beego-defined controller handler with prefix. +// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page, +// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function. +func (app *App) AutoRouterWithPrefix(prefix string, c ControllerInterface) *App { + app.Handlers.AddAutoPrefix(prefix, c) + return app +} + // UrlFor creates a url with another registered controller handler with params. // The endpoint is formed as path.controller.name to defined the controller method which will run. // The values need key-pair data to assign into controller method. diff --git a/beego.go b/beego.go index 64e224da..640fb5ab 100644 --- a/beego.go +++ b/beego.go @@ -4,6 +4,7 @@ import ( "net/http" "path" "path/filepath" + "strconv" "strings" "github.com/astaxie/beego/middleware" @@ -13,6 +14,76 @@ import ( // beego web framework version. const VERSION = "1.0.1" +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([]groupRouter, 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.AutoRouterWithPrefix(prefix, v.controller) + } else if v.mappingMethods != "" { + BeeApp.Router(prefix+v.pattern, v.controller, v.mappingMethods) + } else { + BeeApp.Router(prefix+v.pattern, v.controller) + } + + } + return BeeApp +} + // Router adds a patterned controller handler to BeeApp. // it's an alias method of App.Router. func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App { @@ -36,6 +107,13 @@ func AutoRouter(c ControllerInterface) *App { return BeeApp } +// AutoPrefix adds controller handler to BeeApp with prefix. +// it's same to App.AutoRouterWithPrefix. +func AutoPrefix(prefix string, c ControllerInterface) *App { + BeeApp.AutoRouterWithPrefix(prefix, c) + return BeeApp +} + // ErrorHandler registers http.HandlerFunc to each http err code string. // usage: // beego.ErrorHandler("404",NotFound) @@ -87,6 +165,12 @@ func InsertFilter(pattern string, pos int, filter FilterFunc) *App { return BeeApp } +// The hookfunc will run in beego.Run() +// such as sessionInit, middlerware start, buildtemplate, admin start +func AddAPPStartHook(hf hookfunc) { + hooks = append(hooks, hf) +} + // Run beego application. // it's alias of App.Run. func Run() { @@ -99,18 +183,32 @@ func Run() { } } - //init mime - initMime() + // do hooks function + for _, hk := range hooks { + err := hk() + if err != nil { + panic(err) + } + } if SessionOn { - GlobalSessions, _ = session.NewManager(SessionProvider, - SessionName, - SessionGCMaxLifetime, - SessionSavePath, - HttpTLS, - SessionHashFunc, - SessionHashKey, - SessionCookieLifeTime) + var err error + sessionConfig := AppConfig.String("sessionConfig") + if sessionConfig == "" { + sessionConfig = `{"cookieName":"` + SessionName + `",` + + `"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` + + `"providerConfig":"` + SessionSavePath + `",` + + `"secure":` + strconv.FormatBool(HttpTLS) + `,` + + `"sessionIDHashFunc":"` + SessionHashFunc + `",` + + `"sessionIDHashKey":"` + SessionHashKey + `",` + + `"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` + + `"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}` + } + GlobalSessions, err = session.NewManager(SessionProvider, + sessionConfig) + if err != nil { + panic(err) + } go GlobalSessions.GC() } @@ -123,7 +221,7 @@ func Run() { middleware.VERSION = VERSION middleware.AppName = AppName - middleware.RegisterErrorHander() + middleware.RegisterErrorHandler() if EnableAdmin { go BeeAdminApp.Run() @@ -131,3 +229,9 @@ func Run() { BeeApp.Run() } + +func init() { + hooks = make([]hookfunc, 0) + //init mime + AddAPPStartHook(initMime) +} diff --git a/cache/README.md b/cache/README.md index d4c80fad..c0bfcc59 100644 --- a/cache/README.md +++ b/cache/README.md @@ -43,7 +43,7 @@ interval means the gc time. The cache will check at each time interval, whether ## Memcache adapter -memory adapter use the vitess's [Memcache](http://code.google.com/p/vitess/go/memcache) client. +Memcache adapter use the vitess's [Memcache](http://code.google.com/p/vitess/go/memcache) client. Configure like this: diff --git a/cache/cache_test.go b/cache/cache_test.go index cb0fc76c..bc484e0f 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -5,7 +5,7 @@ import ( "time" ) -func Test_cache(t *testing.T) { +func TestCache(t *testing.T) { bm, err := NewCache("memory", `{"interval":20}`) if err != nil { t.Error("init err") @@ -51,3 +51,51 @@ func Test_cache(t *testing.T) { t.Error("delete err") } } + +func TestFileCache(t *testing.T) { + bm, err := NewCache("file", `{"CachePath":"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`) + if err != nil { + t.Error("init err") + } + if err = bm.Put("astaxie", 1, 10); err != nil { + t.Error("set Error", err) + } + if !bm.IsExist("astaxie") { + t.Error("check err") + } + + if v := bm.Get("astaxie"); v.(int) != 1 { + t.Error("get err") + } + + if err = bm.Incr("astaxie"); err != nil { + t.Error("Incr Error", err) + } + + if v := bm.Get("astaxie"); v.(int) != 2 { + t.Error("get err") + } + + if err = bm.Decr("astaxie"); err != nil { + t.Error("Incr Error", err) + } + + if v := bm.Get("astaxie"); v.(int) != 1 { + t.Error("get err") + } + bm.Delete("astaxie") + if bm.IsExist("astaxie") { + t.Error("delete err") + } + //test string + if err = bm.Put("astaxie", "author", 10); err != nil { + t.Error("set Error", err) + } + if !bm.IsExist("astaxie") { + t.Error("check err") + } + + if v := bm.Get("astaxie"); v.(string) != "author" { + t.Error("get err") + } +} diff --git a/cache/file.go b/cache/file.go index 3807fa7c..410da3a0 100644 --- a/cache/file.go +++ b/cache/file.go @@ -61,6 +61,7 @@ func (this *FileCache) StartAndGC(config string) error { var cfg map[string]string json.Unmarshal([]byte(config), &cfg) //fmt.Println(cfg) + //fmt.Println(config) if _, ok := cfg["CachePath"]; !ok { cfg["CachePath"] = FileCachePath } @@ -135,7 +136,7 @@ func (this *FileCache) Get(key string) interface{} { return "" } var to FileCacheItem - Gob_decode([]byte(filedata), &to) + Gob_decode(filedata, &to) if to.Expired < time.Now().Unix() { return "" } @@ -177,7 +178,7 @@ func (this *FileCache) Delete(key string) error { func (this *FileCache) Incr(key string) error { data := this.Get(key) var incr int - fmt.Println(reflect.TypeOf(data).Name()) + //fmt.Println(reflect.TypeOf(data).Name()) if reflect.TypeOf(data).Name() != "int" { incr = 0 } else { @@ -210,8 +211,7 @@ func (this *FileCache) IsExist(key string) bool { // Clean cached files. // not implemented. func (this *FileCache) ClearAll() error { - //this.CachePath .递归删除 - + //this.CachePath return nil } @@ -271,7 +271,7 @@ func Gob_encode(data interface{}) ([]byte, error) { } // Gob decodes file cache item. -func Gob_decode(data []byte, to interface{}) error { +func Gob_decode(data []byte, to *FileCacheItem) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) return dec.Decode(&to) diff --git a/cache/memcache.go b/cache/memcache.go index 15d3649c..365c5de7 100644 --- a/cache/memcache.go +++ b/cache/memcache.go @@ -21,7 +21,11 @@ func NewMemCache() *MemcacheCache { // get value from memcache. func (rc *MemcacheCache) Get(key string) interface{} { if rc.c == nil { - rc.c = rc.connectInit() + var err error + rc.c, err = rc.connectInit() + if err != nil { + return err + } } v, err := rc.c.Get(key) if err != nil { @@ -39,7 +43,11 @@ func (rc *MemcacheCache) Get(key string) interface{} { // put value to memcache. only support string. func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error { if rc.c == nil { - rc.c = rc.connectInit() + var err error + rc.c, err = rc.connectInit() + if err != nil { + return err + } } v, ok := val.(string) if !ok { @@ -55,7 +63,11 @@ func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error { // delete value in memcache. func (rc *MemcacheCache) Delete(key string) error { if rc.c == nil { - rc.c = rc.connectInit() + var err error + rc.c, err = rc.connectInit() + if err != nil { + return err + } } _, err := rc.c.Delete(key) return err @@ -76,7 +88,11 @@ func (rc *MemcacheCache) Decr(key string) error { // check value exists in memcache. func (rc *MemcacheCache) IsExist(key string) bool { if rc.c == nil { - rc.c = rc.connectInit() + var err error + rc.c, err = rc.connectInit() + if err != nil { + return false + } } v, err := rc.c.Get(key) if err != nil { @@ -93,7 +109,11 @@ func (rc *MemcacheCache) IsExist(key string) bool { // clear all cached in memcache. func (rc *MemcacheCache) ClearAll() error { if rc.c == nil { - rc.c = rc.connectInit() + var err error + rc.c, err = rc.connectInit() + if err != nil { + return err + } } err := rc.c.FlushAll() return err @@ -109,20 +129,21 @@ func (rc *MemcacheCache) StartAndGC(config string) error { return errors.New("config has no conn key") } rc.conninfo = cf["conn"] - rc.c = rc.connectInit() - if rc.c == nil { + var err error + rc.c, err = rc.connectInit() + if err != nil { return errors.New("dial tcp conn error") } return nil } // connect to memcache and keep the connection. -func (rc *MemcacheCache) connectInit() *memcache.Connection { +func (rc *MemcacheCache) connectInit() (*memcache.Connection, error) { c, err := memcache.Connect(rc.conninfo) if err != nil { - return nil + return nil, err } - return c + return c, nil } func init() { diff --git a/cache/redis.go b/cache/redis.go index b923a6df..ba1d4d49 100644 --- a/cache/redis.go +++ b/cache/redis.go @@ -3,6 +3,7 @@ package cache import ( "encoding/json" "errors" + "time" "github.com/beego/redigo/redis" ) @@ -14,7 +15,7 @@ var ( // Redis cache adapter. type RedisCache struct { - c redis.Conn + p *redis.Pool // redis connection pool conninfo string key string } @@ -24,107 +25,62 @@ func NewRedisCache() *RedisCache { return &RedisCache{key: DefaultKey} } +// actually do the redis cmds +func (rc *RedisCache) do(commandName string, args ...interface{}) (reply interface{}, err error) { + c := rc.p.Get() + defer c.Close() + + return c.Do(commandName, args...) +} + // Get cache from redis. func (rc *RedisCache) Get(key string) interface{} { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return nil - } - } - v, err := rc.c.Do("HGET", rc.key, key) + v, err := rc.do("HGET", rc.key, key) if err != nil { return nil } + return v } // put cache to redis. // timeout is ignored. func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return err - } - } - _, err := rc.c.Do("HSET", rc.key, key, val) + _, err := rc.do("HSET", rc.key, key, val) return err } // delete cache in redis. func (rc *RedisCache) Delete(key string) error { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return err - } - } - _, err := rc.c.Do("HDEL", rc.key, key) + _, err := rc.do("HDEL", rc.key, key) return err } // check cache exist in redis. func (rc *RedisCache) IsExist(key string) bool { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return false - } - } - v, err := redis.Bool(rc.c.Do("HEXISTS", rc.key, key)) + v, err := redis.Bool(rc.do("HEXISTS", rc.key, key)) if err != nil { return false } + return v } // increase counter in redis. func (rc *RedisCache) Incr(key string) error { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return err - } - } - _, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, 1)) - if err != nil { - return err - } - return nil + _, err := redis.Bool(rc.do("HINCRBY", rc.key, key, 1)) + return err } // decrease counter in redis. func (rc *RedisCache) Decr(key string) error { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return err - } - } - _, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, -1)) - if err != nil { - return err - } - return nil + _, err := redis.Bool(rc.do("HINCRBY", rc.key, key, -1)) + return err } // clean all cache in redis. delete this redis collection. func (rc *RedisCache) ClearAll() error { - if rc.c == nil { - var err error - rc.c, err = rc.connectInit() - if err != nil { - return err - } - } - _, err := rc.c.Do("DEL", rc.key) + _, err := rc.do("DEL", rc.key) return err } @@ -135,32 +91,42 @@ func (rc *RedisCache) ClearAll() error { func (rc *RedisCache) StartAndGC(config string) error { var cf map[string]string json.Unmarshal([]byte(config), &cf) + if _, ok := cf["key"]; !ok { cf["key"] = DefaultKey } + if _, ok := cf["conn"]; !ok { return errors.New("config has no conn key") } + rc.key = cf["key"] rc.conninfo = cf["conn"] - var err error - rc.c, err = rc.connectInit() - if err != nil { + rc.connectInit() + + c := rc.p.Get() + defer c.Close() + if err := c.Err(); err != nil { return err } - if rc.c == nil { - return errors.New("dial tcp conn error") - } + return nil } // connect to redis. -func (rc *RedisCache) connectInit() (redis.Conn, error) { - c, err := redis.Dial("tcp", rc.conninfo) - if err != nil { - return nil, err +func (rc *RedisCache) connectInit() { + // initialize a new pool + rc.p = &redis.Pool{ + MaxIdle: 3, + IdleTimeout: 180 * time.Second, + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", rc.conninfo) + if err != nil { + return nil, err + } + return c, nil + }, } - return c, nil } func init() { diff --git a/config.go b/config.go index 9baf9aa5..7c21d696 100644 --- a/config.go +++ b/config.go @@ -40,6 +40,7 @@ var ( SessionHashFunc string // session hash generation func. SessionHashKey string // session hash salt string. SessionCookieLifeTime int // the life time of session id in cookie. + SessionAutoSetCookie bool // auto setcookie UseFcgi bool MaxMemory int64 EnableGzip bool // flag of enable gzip @@ -96,6 +97,7 @@ func init() { SessionHashFunc = "sha1" SessionHashKey = "beegoserversessionkey" SessionCookieLifeTime = 0 //set cookie default is the brower life + SessionAutoSetCookie = true UseFcgi = false @@ -139,6 +141,7 @@ func init() { func ParseConfig() (err error) { AppConfig, err = config.NewConfig("ini", AppConfigPath) if err != nil { + AppConfig = config.NewFakeConfig() return err } else { HttpAddr = AppConfig.String("HttpAddr") diff --git a/config/config.go b/config/config.go index 5fb0dd81..5e4c2e9c 100644 --- a/config/config.go +++ b/config/config.go @@ -6,8 +6,9 @@ import ( // ConfigContainer defines how to get and set value from configuration raw data. type ConfigContainer interface { - Set(key, val string) error // support section::key type in given key when using ini type. - String(key string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + Set(key, val string) error // support section::key type in given key when using ini type. + String(key string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. + Strings(key string) []string //get string slice Int(key string) (int, error) Int64(key string) (int64, error) Bool(key string) (bool, error) diff --git a/config/fake.go b/config/fake.go new file mode 100644 index 00000000..26a9f430 --- /dev/null +++ b/config/fake.go @@ -0,0 +1,62 @@ +package config + +import ( + "errors" + "strconv" + "strings" +) + +type fakeConfigContainer struct { + data map[string]string +} + +func (c *fakeConfigContainer) getData(key string) string { + key = strings.ToLower(key) + return c.data[key] +} + +func (c *fakeConfigContainer) Set(key, val string) error { + key = strings.ToLower(key) + c.data[key] = val + return nil +} + +func (c *fakeConfigContainer) String(key string) string { + return c.getData(key) +} + +func (c *fakeConfigContainer) Strings(key string) []string { + return strings.Split(c.getData(key), ";") +} + +func (c *fakeConfigContainer) Int(key string) (int, error) { + return strconv.Atoi(c.getData(key)) +} + +func (c *fakeConfigContainer) Int64(key string) (int64, error) { + return strconv.ParseInt(c.getData(key), 10, 64) +} + +func (c *fakeConfigContainer) Bool(key string) (bool, error) { + return strconv.ParseBool(c.getData(key)) +} + +func (c *fakeConfigContainer) Float(key string) (float64, error) { + return strconv.ParseFloat(c.getData(key), 64) +} + +func (c *fakeConfigContainer) DIY(key string) (interface{}, error) { + key = strings.ToLower(key) + if v, ok := c.data[key]; ok { + return v, nil + } + return nil, errors.New("key not find") +} + +var _ ConfigContainer = new(fakeConfigContainer) + +func NewFakeConfig() ConfigContainer { + return &fakeConfigContainer{ + data: make(map[string]string), + } +} diff --git a/config/ini.go b/config/ini.go index 22c23f40..75e6486c 100644 --- a/config/ini.go +++ b/config/ini.go @@ -146,6 +146,11 @@ func (c *IniConfigContainer) String(key string) string { return c.getdata(key) } +// Strings returns the []string value for a given key. +func (c *IniConfigContainer) Strings(key string) []string { + return strings.Split(c.String(key), ";") +} + // WriteValue writes a new value for key. // if write to one section, the key need be "section::key". // if the section is not existed, it panics. diff --git a/config/ini_test.go b/config/ini_test.go index cf87e77c..08a69e50 100644 --- a/config/ini_test.go +++ b/config/ini_test.go @@ -19,6 +19,7 @@ copyrequestbody = true key1="asta" key2 = "xie" CaseInsensitive = true +peers = one;two;three ` func TestIni(t *testing.T) { @@ -78,4 +79,11 @@ func TestIni(t *testing.T) { if v, err := iniconf.Bool("demo::caseinsensitive"); err != nil || v != true { t.Fatal("get demo.caseinsensitive error") } + + if data := iniconf.Strings("demo::peers"); len(data) != 3 { + t.Fatal("get strings error", data) + } else if data[0] != "one" { + t.Fatal("get first params error not equat to one") + } + } diff --git a/config/json.go b/config/json.go index 883e0674..24874e8a 100644 --- a/config/json.go +++ b/config/json.go @@ -116,6 +116,11 @@ func (c *JsonConfigContainer) String(key string) string { return "" } +// Strings returns the []string value for a given key. +func (c *JsonConfigContainer) Strings(key string) []string { + return strings.Split(c.String(key), ";") +} + // WriteValue writes a new value for key. func (c *JsonConfigContainer) Set(key, val string) error { c.Lock() diff --git a/config/xml.go b/config/xml.go index 35f19336..7943d8fe 100644 --- a/config/xml.go +++ b/config/xml.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "strconv" + "strings" "sync" "github.com/beego/x2j" @@ -72,6 +73,11 @@ func (c *XMLConfigContainer) String(key string) string { return "" } +// Strings returns the []string value for a given key. +func (c *XMLConfigContainer) Strings(key string) []string { + return strings.Split(c.String(key), ";") +} + // WriteValue writes a new value for key. func (c *XMLConfigContainer) Set(key, val string) error { c.Lock() diff --git a/config/yaml.go b/config/yaml.go index 394cb3b2..bd10b846 100644 --- a/config/yaml.go +++ b/config/yaml.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "log" "os" + "strings" "sync" "github.com/beego/goyaml2" @@ -117,6 +118,11 @@ func (c *YAMLConfigContainer) String(key string) string { return "" } +// Strings returns the []string value for a given key. +func (c *YAMLConfigContainer) Strings(key string) []string { + return strings.Split(c.String(key), ";") +} + // WriteValue writes a new value for key. func (c *YAMLConfigContainer) Set(key, val string) error { c.Lock() diff --git a/controller.go b/controller.go index 1f148f06..034e4cb3 100644 --- a/controller.go +++ b/controller.go @@ -3,7 +3,6 @@ package beego import ( "bytes" "crypto/hmac" - "crypto/rand" "crypto/sha1" "encoding/base64" "errors" @@ -22,6 +21,7 @@ import ( "github.com/astaxie/beego/context" "github.com/astaxie/beego/session" + "github.com/astaxie/beego/utils" ) var ( @@ -140,7 +140,7 @@ func (c *Controller) RenderString() (string, error) { return string(b), e } -// RenderBytes returns the bytes of renderd tempate string. Do not send out response. +// RenderBytes returns the bytes of rendered template string. Do not send out response. func (c *Controller) RenderBytes() ([]byte, error) { //if the controller has set layout, then first get the tplname's content set the content to the layout if c.Layout != "" { @@ -165,7 +165,7 @@ func (c *Controller) RenderBytes() ([]byte, error) { if c.LayoutSections != nil { for sectionName, sectionTpl := range c.LayoutSections { - if (sectionTpl == "") { + if sectionTpl == "" { c.Data[sectionName] = "" continue } @@ -391,6 +391,7 @@ func (c *Controller) DelSession(name interface{}) { // SessionRegenerateID regenerates session id for this session. // the session data have no changes. func (c *Controller) SessionRegenerateID() { + c.CruSession.SessionRelease(c.Ctx.ResponseWriter) c.CruSession = GlobalSessions.SessionRegenerateId(c.Ctx.ResponseWriter, c.Ctx.Request) c.Ctx.Input.CruSession = c.CruSession } @@ -454,7 +455,7 @@ func (c *Controller) XsrfToken() string { } else { expire = int64(XSRFExpire) } - token = getRandomString(15) + token = string(utils.RandomCreateBytes(15)) c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire) } c._xsrf_token = token @@ -491,14 +492,3 @@ func (c *Controller) XsrfFormHtml() string { func (c *Controller) GetControllerAndAction() (controllerName, actionName string) { return c.controllerName, c.actionName } - -// getRandomString returns random string. -func getRandomString(n int) string { - const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - var bytes = make([]byte, n) - rand.Read(bytes) - for i, b := range bytes { - bytes[i] = alphanum[b%byte(len(alphanum))] - } - return string(bytes) -} diff --git a/example/chat/controllers/ws.go b/example/chat/controllers/ws.go index 9334336f..9b3f5b10 100644 --- a/example/chat/controllers/ws.go +++ b/example/chat/controllers/ws.go @@ -1,12 +1,13 @@ package controllers import ( - "github.com/astaxie/beego" - "github.com/garyburd/go-websocket/websocket" "io/ioutil" "math/rand" "net/http" "time" + + "github.com/astaxie/beego" + "github.com/gorilla/websocket" ) const ( diff --git a/filter.go b/filter.go index 7e0245a6..98868865 100644 --- a/filter.go +++ b/filter.go @@ -28,6 +28,12 @@ func (mr *FilterRouter) ValidRouter(router string) (bool, map[string]string) { if router == mr.pattern { return true, nil } + //pattern /admin router /admin/ match + //pattern /admin/ router /admin don't match, because url will 301 in router + if n := len(router); n > 1 && router[n-1] == '/' && router[:n-2] == mr.pattern { + return true, nil + } + if mr.hasregex { if !mr.regex.MatchString(router) { return false, nil @@ -46,7 +52,7 @@ func (mr *FilterRouter) ValidRouter(router string) (bool, map[string]string) { return false, nil } -func buildFilter(pattern string, filter FilterFunc) *FilterRouter { +func buildFilter(pattern string, filter FilterFunc) (*FilterRouter, error) { mr := new(FilterRouter) mr.params = make(map[int]string) mr.filterFunc = filter @@ -54,7 +60,7 @@ func buildFilter(pattern string, filter FilterFunc) *FilterRouter { j := 0 for i, part := range parts { if strings.HasPrefix(part, ":") { - expr := "(.+)" + expr := "(.*)" //a user may choose to override the default expression // similar to expressjs: ‘/user/:id([0-9]+)’ if index := strings.Index(part, "("); index != -1 { @@ -77,7 +83,7 @@ func buildFilter(pattern string, filter FilterFunc) *FilterRouter { j++ } if strings.HasPrefix(part, "*") { - expr := "(.+)" + expr := "(.*)" if part == "*.*" { mr.params[j] = ":path" parts[i] = "([^.]+).([^.]+)" @@ -137,12 +143,11 @@ func buildFilter(pattern string, filter FilterFunc) *FilterRouter { pattern = strings.Join(parts, "/") regex, regexErr := regexp.Compile(pattern) if regexErr != nil { - //TODO add error handling here to avoid panic - panic(regexErr) + return nil, regexErr } mr.regex = regex mr.hasregex = true } mr.pattern = pattern - return mr + return mr, nil } diff --git a/fiter_test.go b/fiter_test.go index 4e9dae6a..7fe9a641 100644 --- a/fiter_test.go +++ b/fiter_test.go @@ -23,3 +23,32 @@ func TestFilter(t *testing.T) { t.Errorf("user define func can't run") } } + +var FilterAdminUser = func(ctx *context.Context) { + ctx.Output.Body([]byte("i am admin")) +} + +// Filter pattern /admin/:all +// all url like /admin/ /admin/xie will all get filter + +func TestPatternTwo(t *testing.T) { + r, _ := http.NewRequest("GET", "/admin/", nil) + w := httptest.NewRecorder() + handler := NewControllerRegistor() + handler.AddFilter("/admin/:all", "AfterStatic", FilterAdminUser) + handler.ServeHTTP(w, r) + if w.Body.String() != "i am admin" { + t.Errorf("filter /admin/ can't run") + } +} + +func TestPatternThree(t *testing.T) { + r, _ := http.NewRequest("GET", "/admin/astaxie", nil) + w := httptest.NewRecorder() + handler := NewControllerRegistor() + handler.AddFilter("/admin/:all", "AfterStatic", FilterAdminUser) + handler.ServeHTTP(w, r) + if w.Body.String() != "i am admin" { + t.Errorf("filter /admin/astaxie can't run") + } +} diff --git a/logs/conn.go b/logs/conn.go index a5bc75c5..eed9ae2f 100644 --- a/logs/conn.go +++ b/logs/conn.go @@ -7,6 +7,8 @@ import ( "net" ) +// ConnWriter implements LoggerInterface. +// it writes messages in keep-live tcp connection. type ConnWriter struct { lg *log.Logger innerWriter io.WriteCloser @@ -17,12 +19,15 @@ type ConnWriter struct { Level int `json:"level"` } +// create new ConnWrite returning as LoggerInterface. func NewConn() LoggerInterface { conn := new(ConnWriter) conn.Level = LevelTrace return conn } +// 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 { @@ -31,6 +36,8 @@ func (c *ConnWriter) Init(jsonconfig string) error { return nil } +// write message in connection. +// if connection is down, try to re-connect. func (c *ConnWriter) WriteMsg(msg string, level int) error { if level < c.Level { return nil @@ -49,10 +56,12 @@ func (c *ConnWriter) WriteMsg(msg string, level int) error { return nil } +// implementing method. empty. func (c *ConnWriter) Flush() { } +// destroy connection writer and close tcp listener. func (c *ConnWriter) Destroy() { if c.innerWriter == nil { return diff --git a/logs/console.go b/logs/console.go index 0c7fc1e9..c5fa2380 100644 --- a/logs/console.go +++ b/logs/console.go @@ -6,11 +6,13 @@ import ( "os" ) +// ConsoleWriter implements LoggerInterface and writes messages to terminal. type ConsoleWriter struct { lg *log.Logger Level int `json:"level"` } +// create ConsoleWriter returning as LoggerInterface. func NewConsole() LoggerInterface { cw := new(ConsoleWriter) cw.lg = log.New(os.Stdout, "", log.Ldate|log.Ltime) @@ -18,6 +20,8 @@ func NewConsole() LoggerInterface { return cw } +// init console logger. +// jsonconfig like '{"level":LevelTrace}'. func (c *ConsoleWriter) Init(jsonconfig string) error { err := json.Unmarshal([]byte(jsonconfig), c) if err != nil { @@ -26,6 +30,7 @@ func (c *ConsoleWriter) Init(jsonconfig string) error { return nil } +// write message in console. func (c *ConsoleWriter) WriteMsg(msg string, level int) error { if level < c.Level { return nil @@ -34,10 +39,12 @@ func (c *ConsoleWriter) WriteMsg(msg string, level int) error { return nil } +// implementing method. empty. func (c *ConsoleWriter) Destroy() { } +// implementing method. empty. func (c *ConsoleWriter) Flush() { } diff --git a/logs/file.go b/logs/file.go index e19c6c7f..d0512e26 100644 --- a/logs/file.go +++ b/logs/file.go @@ -13,6 +13,8 @@ import ( "time" ) +// FileLogWriter implements LoggerInterface. +// It writes messages by lines limit, file size limit, or time frequency. type FileLogWriter struct { *log.Logger mw *MuxWriter @@ -38,17 +40,20 @@ type FileLogWriter struct { Level int `json:"level"` } +// an *os.File writer with locker. type MuxWriter struct { sync.Mutex fd *os.File } +// write to os.File. func (l *MuxWriter) Write(b []byte) (int, error) { l.Lock() defer l.Unlock() return l.fd.Write(b) } +// set os.File in writer. func (l *MuxWriter) SetFd(fd *os.File) { if l.fd != nil { l.fd.Close() @@ -56,6 +61,7 @@ func (l *MuxWriter) SetFd(fd *os.File) { l.fd = fd } +// create a FileLogWriter returning as LoggerInterface. func NewFileWriter() LoggerInterface { w := &FileLogWriter{ Filename: "", @@ -73,15 +79,16 @@ func NewFileWriter() LoggerInterface { return w } -// jsonconfig like this -//{ +// Init file logger with json config. +// jsonconfig like: +// { // "filename":"logs/beego.log", // "maxlines":10000, // "maxsize":1<<30, // "daily":true, // "maxdays":15, // "rotate":true -//} +// } func (w *FileLogWriter) Init(jsonconfig string) error { err := json.Unmarshal([]byte(jsonconfig), w) if err != nil { @@ -94,6 +101,7 @@ func (w *FileLogWriter) Init(jsonconfig string) error { return err } +// start file logger. create log file and set to locker-inside file writer. func (w *FileLogWriter) StartLogger() error { fd, err := w.createLogFile() if err != nil { @@ -122,6 +130,7 @@ func (w *FileLogWriter) docheck(size int) { w.maxsize_cursize += size } +// write logger message into file. func (w *FileLogWriter) WriteMsg(msg string, level int) error { if level < w.Level { return nil @@ -158,6 +167,8 @@ func (w *FileLogWriter) initFd() error { return 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 { _, err := os.Lstat(w.Filename) if err == nil { // file exists @@ -211,10 +222,14 @@ func (w *FileLogWriter) deleteOldLog() { }) } +// destroy file logger, close file writer. func (w *FileLogWriter) Destroy() { w.mw.fd.Close() } +// flush file logger. +// there are no buffering messages in file logger in memory. +// flush file means sync file from disk. func (w *FileLogWriter) Flush() { w.mw.fd.Sync() } diff --git a/logs/log.go b/logs/log.go index a9254aaa..b65414cb 100644 --- a/logs/log.go +++ b/logs/log.go @@ -6,6 +6,7 @@ import ( ) const ( + // log message levels LevelTrace = iota LevelDebug LevelInfo @@ -16,6 +17,7 @@ const ( type loggerType func() LoggerInterface +// LoggerInterface defines the behavior of a log provider. type LoggerInterface interface { Init(config string) error WriteMsg(msg string, level int) error @@ -38,6 +40,8 @@ func Register(name string, log loggerType) { adapters[name] = log } +// BeeLogger is default logger in beego application. +// it can contain several providers and log message into all providers. type BeeLogger struct { lock sync.Mutex level int @@ -50,7 +54,9 @@ type logMsg struct { msg string } -// config need to be correct JSON as string: {"interval":360} +// NewLogger returns a new BeeLogger. +// channellen means the number of messages in chan. +// if the buffering chan is full, logger adapters write to file or other way. func NewLogger(channellen int64) *BeeLogger { bl := new(BeeLogger) bl.msg = make(chan *logMsg, channellen) @@ -60,6 +66,8 @@ func NewLogger(channellen int64) *BeeLogger { return bl } +// SetLogger provides a given logger adapter into BeeLogger with config string. +// config need to be correct JSON as string: {"interval":360}. func (bl *BeeLogger) SetLogger(adaptername string, config string) error { bl.lock.Lock() defer bl.lock.Unlock() @@ -73,6 +81,7 @@ func (bl *BeeLogger) SetLogger(adaptername string, config string) error { } } +// remove a logger adapter in BeeLogger. func (bl *BeeLogger) DelLogger(adaptername string) error { bl.lock.Lock() defer bl.lock.Unlock() @@ -96,10 +105,14 @@ func (bl *BeeLogger) writerMsg(loglevel int, msg string) error { return nil } +// set log message level. +// if message level (such as LevelTrace) is less than logger level (such as LevelWarn), ignore message. func (bl *BeeLogger) SetLevel(l int) { bl.level = l } +// start logger chan reading. +// when chan is full, write logs. func (bl *BeeLogger) StartLogger() { for { select { @@ -111,43 +124,50 @@ func (bl *BeeLogger) StartLogger() { } } +// log trace level message. func (bl *BeeLogger) Trace(format string, v ...interface{}) { msg := fmt.Sprintf("[T] "+format, v...) bl.writerMsg(LevelTrace, msg) } +// log debug level message. func (bl *BeeLogger) Debug(format string, v ...interface{}) { msg := fmt.Sprintf("[D] "+format, v...) bl.writerMsg(LevelDebug, msg) } +// log info level message. func (bl *BeeLogger) Info(format string, v ...interface{}) { msg := fmt.Sprintf("[I] "+format, v...) bl.writerMsg(LevelInfo, msg) } +// log warn level message. func (bl *BeeLogger) Warn(format string, v ...interface{}) { msg := fmt.Sprintf("[W] "+format, v...) bl.writerMsg(LevelWarn, msg) } +// log error level message. func (bl *BeeLogger) Error(format string, v ...interface{}) { msg := fmt.Sprintf("[E] "+format, v...) bl.writerMsg(LevelError, msg) } +// log critical level message. func (bl *BeeLogger) Critical(format string, v ...interface{}) { msg := fmt.Sprintf("[C] "+format, v...) bl.writerMsg(LevelCritical, msg) } -//flush all chan data +// flush all chan data. func (bl *BeeLogger) Flush() { for _, l := range bl.outputs { l.Flush() } } +// close logger, flush all chan data and destroy all adapters in BeeLogger. func (bl *BeeLogger) Close() { for { if len(bl.msg) > 0 { diff --git a/logs/smtp.go b/logs/smtp.go index 228977bb..19296887 100644 --- a/logs/smtp.go +++ b/logs/smtp.go @@ -12,7 +12,7 @@ const ( subjectPhrase = "Diagnostic message from server" ) -// smtpWriter is used to send emails via given SMTP-server. +// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server. type SmtpWriter struct { Username string `json:"Username"` Password string `json:"password"` @@ -22,10 +22,21 @@ type SmtpWriter struct { Level int `json:"level"` } +// create smtp writer. func NewSmtpWriter() LoggerInterface { return &SmtpWriter{Level: LevelTrace} } +// init smtp writer with json config. +// config like: +// { +// "Username":"example@gmail.com", +// "password:"password", +// "host":"smtp.gmail.com:465", +// "subject":"email title", +// "sendTos":["email1","email2"], +// "level":LevelError +// } func (s *SmtpWriter) Init(jsonconfig string) error { err := json.Unmarshal([]byte(jsonconfig), s) if err != nil { @@ -34,6 +45,8 @@ func (s *SmtpWriter) Init(jsonconfig string) error { return nil } +// write message in smtp writer. +// it will send an email with subject and only this message. func (s *SmtpWriter) WriteMsg(msg string, level int) error { if level < s.Level { return nil @@ -65,9 +78,12 @@ func (s *SmtpWriter) WriteMsg(msg string, level int) error { return err } +// implementing method. empty. func (s *SmtpWriter) Flush() { return } + +// implementing method. empty. func (s *SmtpWriter) Destroy() { return } diff --git a/memzipfile.go b/memzipfile.go index 43a82a30..8d3edd9c 100644 --- a/memzipfile.go +++ b/memzipfile.go @@ -5,16 +5,17 @@ import ( "compress/flate" "compress/gzip" "errors" - //"fmt" "io" "io/ioutil" "net/http" "os" "strings" + "sync" "time" ) var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo) +var lock sync.RWMutex // OpenMemZipFile returns MemFile object with a compressed static file. // it's used for serve static file if gzip enable. @@ -32,12 +33,12 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) { modtime := osfileinfo.ModTime() fileSize := osfileinfo.Size() - + lock.RLock() cfi, ok := gmfim[zip+":"+path] + lock.RUnlock() if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize { - //fmt.Printf("read %s file %s from cache\n", zip, path) + } else { - //fmt.Printf("NOT read %s file %s from cache\n", zip, path) var content []byte if zip == "gzip" { //将文件内容压缩到zipbuf中 @@ -81,8 +82,9 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) { } cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize} + lock.Lock() + defer lock.Unlock() gmfim[zip+":"+path] = cfi - //fmt.Printf("%s file %s to %d, cache it\n", zip, path, len(content)) } return &MemFile{fi: cfi, offset: 0}, nil } diff --git a/middleware/error.go b/middleware/error.go index 7f854141..5c12b533 100644 --- a/middleware/error.go +++ b/middleware/error.go @@ -61,6 +61,7 @@ var tpl = ` ` +// render default application error page with error and stack string. func ShowErr(err interface{}, rw http.ResponseWriter, r *http.Request, Stack string) { t, _ := template.New("beegoerrortemp").Parse(tpl) data := make(map[string]string) @@ -71,6 +72,7 @@ func ShowErr(err interface{}, rw http.ResponseWriter, r *http.Request, Stack str data["Stack"] = Stack data["BeegoVersion"] = VERSION data["GoVersion"] = runtime.Version() + rw.WriteHeader(500) t.Execute(rw, data) } @@ -174,18 +176,19 @@ var errtpl = ` ` +// map of http handlers for each error string. var ErrorMaps map[string]http.HandlerFunc func init() { ErrorMaps = make(map[string]http.HandlerFunc) } -//404 +// show 404 notfound error. func NotFound(rw http.ResponseWriter, r *http.Request) { t, _ := template.New("beegoerrortemp").Parse(errtpl) data := make(map[string]interface{}) data["Title"] = "Page Not Found" - data["Content"] = template.HTML("
The Page You have requested flown the coop." + + data["Content"] = template.HTML("
The page you have requested has flown the coop." + "
Perhaps you are here because:" + "