1
0
mirror of https://github.com/astaxie/beego.git synced 2025-07-11 15:21:02 +00:00

57 Commits

Author SHA1 Message Date
ab08aa9c9e MethodByName 2013-07-25 16:08:18 +08:00
7c610ee7c9 fix reflect find methodByName 2013-07-25 16:00:42 +08:00
23deaedd39 add to beego 2013-07-25 15:50:16 +08:00
538d39a704 add some tips 2013-07-25 15:46:47 +08:00
b961abb52b update docs 2013-07-25 15:41:25 +08:00
b32b12b208 add some comments 2013-07-25 15:40:33 +08:00
e88c2be013 update docs & update beego's version 2013-07-25 15:37:38 +08:00
d5ddd0a9dd support user define function
+//Add("/user",&UserController{})
+//Add("/api/list",&RestController{},"*:ListFood")
+//Add("/api/create",&RestController{},"post:CreateFood")
+//Add("/api/update",&RestController{},"put:UpdateFood")
+//Add("/api/delete",&RestController{},"delete:DeleteFood")
+//Add("/api",&RestController{},"get,post:ApiFunc")
+//Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
2013-07-25 15:17:09 +08:00
dff36a18a2 Merge pull request #101 from miraclesu/valid
Valid
2013-07-23 21:44:35 -07:00
fb78d83ec3 Merge branch 'master' of https://github.com/astaxie/beego into valid 2013-07-24 12:37:07 +08:00
d23700b919 update README 2013-07-24 12:36:46 +08:00
92db56c0cb add struct tag support 2013-07-24 12:20:42 +08:00
4c6163baa0 add funcmap 2013-07-24 01:20:24 +08:00
f46388fa63 setcookie set to unique. fix multi setcookie 2013-07-23 21:54:45 +08:00
aba1728bc3 add some util funcs 2013-07-23 14:42:14 +08:00
ddb9ed39a5 add validation README 2013-07-22 17:40:32 +08:00
a242f61b8e Merge pull request #100 from miraclesu/valid
Valid
2013-07-21 19:26:43 -07:00
d19de30d9c add test 2013-07-21 23:46:18 +08:00
6d05163c9f add validation funcs 2013-07-21 01:37:24 +08:00
a41cd17092 add validators 2013-07-19 16:49:28 +08:00
ec7324e972 Merge pull request #98 from Unknwon/master
Fixed bug: error page cannot show correct corresponding status code
2013-07-18 01:20:16 -07:00
7f5dd13422 Fixed bug: error page cannot show correct corresponding status code 2013-07-18 14:42:45 +08:00
7f4ad7ff46 fix #91 2013-07-16 19:05:44 +08:00
60200689f4 fix setcookie time type 2013-07-11 10:57:34 +08:00
af3797e16c Merge pull request #93 from Unknwon/master
Sync documentation of English with Chinese version.
2013-07-10 07:12:07 -07:00
d4743fb10d Sync documentation of English with Chinese version 2013-07-10 22:06:28 +08:00
38b083e117 add docs about how to write api application 2013-07-09 16:43:03 +08:00
fece5adc2a add example for api application 2013-07-09 13:59:47 +08:00
7bfb4126d7 support copy requestbody 2013-07-08 23:12:31 +08:00
2abe584bc5 support restful router 2013-07-08 18:35:10 +08:00
ee9223b1b9 fix #18
func (this *MainController) Get() {
this.GoToFunc("Test")
}

func (this *MainController) Test() {
this.Ctx.WriteString("testtest")
}
2013-07-08 17:35:09 +08:00
d2a16ff8f6 fix #26 add xsrf function 2013-07-08 16:17:08 +08:00
f1e5059682 fix #69 refer to http://www.php.net/manual/zh/function.setcookie.php 2013-07-08 15:13:51 +08:00
11977f4f77 fix #90 2013-07-07 17:58:50 +08:00
461eac46b9 fix #89 2013-07-07 17:45:39 +08:00
75af664511 change r.ParseMultipartForm position 2013-07-04 23:41:35 +08:00
174298b497 fix cache's bug expird is not changed by get method 2013-07-04 13:02:11 +08:00
9b392a0601 update to hotupdate's connet timeout 2013-07-03 16:58:15 +08:00
bf9de3bcf6 add HttpServerTimeOut setting 2013-07-03 15:29:54 +08:00
189df1280c fix #87 2013-07-02 09:45:12 +08:00
8807c327d1 fix log delete 2013-06-29 14:39:02 +08:00
d627ec013e add config to countol if enable hotupdate 2013-06-28 22:09:08 +08:00
d0bbc67b27 Merge pull request #86 from slene/master
fix logrotate
2013-06-28 06:18:51 -07:00
453557948e fix logrotate close fd before rename file, add a MuxWriter for Logger 2013-06-27 22:04:01 +08:00
4033692dcb Merge pull request #85 from Unknwon/master
sync quickstart.
2013-06-26 18:45:40 -07:00
236f28c53c sync quickstart. 2013-06-27 00:24:06 +08:00
b2bfed8937 fix close err 2013-06-26 23:34:32 +08:00
71adbdd7d7 add mutex 2013-06-26 22:16:03 +08:00
573df2e747 add isclose call close many times 2013-06-26 22:13:25 +08:00
aa9cb6d052 delete session's cookie Expires 2013-06-25 23:08:47 +08:00
8358e0ff48 Merge pull request #83 from shxsun/master
In go version 1.0.3 will call build error
2013-06-25 07:08:35 -07:00
1687ec85de fix build problem 2013-06-25 21:40:42 +08:00
d5fc0a4bda Merge pull request #82 from matrixik/master
Change: SetRotateMaxDay => SetRotateMaxDays
2013-06-25 04:08:51 -07:00
3bcff77947 Change: SetRotateMaxDay => SetRotateMaxDays 2013-06-25 12:54:51 +02:00
1521842d7a add static global function & add seocms example 2013-06-25 18:49:08 +08:00
b04813e472 fix #80
add a function to delete default StaticPath
2013-06-25 17:21:19 +08:00
9e41d93184 delete strcut map
I think if user should set field in controller, there's no need to have
thie feature
2013-06-25 16:44:53 +08:00
31 changed files with 2255 additions and 324 deletions

View File

@ -38,4 +38,5 @@ beego is licensed under the Apache Licence, Version 2.0
## Use case ## Use case
- Displaying API documentation: [gowalker](https://github.com/Unknwon/gowalker) - Displaying API documentation: [gowalker](https://github.com/Unknwon/gowalker)
- seocms: [seocms](https://github.com/chinakr/seocms)
- CMS: [toropress](https://github.com/insionng/toropress) - CMS: [toropress](https://github.com/insionng/toropress)

View File

@ -10,9 +10,10 @@ import (
"os" "os"
"path" "path"
"runtime" "runtime"
"time"
) )
const VERSION = "0.7.0" const VERSION = "0.8.0"
var ( var (
BeeApp *App BeeApp *App
@ -38,8 +39,13 @@ var (
SessionSavePath string // session savepath if use mysql/redis/file this set to the connectinfo SessionSavePath string // session savepath if use mysql/redis/file this set to the connectinfo
UseFcgi bool UseFcgi bool
MaxMemory int64 MaxMemory int64
EnableGzip bool // enable gzip EnableGzip bool // enable gzip
DirectoryIndex bool //ebable DirectoryIndex default is false DirectoryIndex bool //ebable DirectoryIndex default is false
EnbaleHotUpdate bool //enable HotUpdate default is false
HttpServerTimeOut int64 //set httpserver timeout
ErrorsShow bool //set weather show errors
XSRFKEY string //set XSRF
CopyRequestBody bool //When in raw application, You want to the reqeustbody
) )
func init() { func init() {
@ -66,6 +72,9 @@ func init() {
EnableGzip = false EnableGzip = false
StaticDir["/static"] = "static" StaticDir["/static"] = "static"
AppConfigPath = path.Join(AppPath, "conf", "app.conf") AppConfigPath = path.Join(AppPath, "conf", "app.conf")
HttpServerTimeOut = 0
ErrorsShow = true
XSRFKEY = "beegoxsrf"
ParseConfig() ParseConfig()
} }
@ -93,24 +102,39 @@ func (app *App) Run() {
} }
err = fcgi.Serve(l, app.Handlers) err = fcgi.Serve(l, app.Handlers)
} else { } else {
server := &http.Server{Handler: app.Handlers} if EnbaleHotUpdate {
laddr, err := net.ResolveTCPAddr("tcp", addr) server := &http.Server{
if nil != err { Handler: app.Handlers,
BeeLogger.Fatal("ResolveTCPAddr:", err) ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,
WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
}
laddr, err := net.ResolveTCPAddr("tcp", addr)
if nil != err {
BeeLogger.Fatal("ResolveTCPAddr:", err)
}
l, err = GetInitListner(laddr)
theStoppable = newStoppable(l)
err = server.Serve(theStoppable)
theStoppable.wg.Wait()
CloseSelf()
} else {
s := &http.Server{
Addr: addr,
Handler: app.Handlers,
ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,
WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
}
err = s.ListenAndServe()
} }
l, err = GetInitListner(laddr)
theStoppable = newStoppable(l)
err = server.Serve(theStoppable)
theStoppable.wg.Wait()
CloseSelf()
} }
if err != nil { if err != nil {
BeeLogger.Fatal("ListenAndServe: ", err) BeeLogger.Fatal("ListenAndServe: ", err)
} }
} }
func (app *App) Router(path string, c ControllerInterface) *App { func (app *App) Router(path string, c ControllerInterface, mappingMethods ...string) *App {
app.Handlers.Add(path, c) app.Handlers.Add(path, c, mappingMethods...)
return app return app
} }
@ -139,6 +163,11 @@ func (app *App) SetStaticPath(url string, path string) *App {
return app return app
} }
func (app *App) DelStaticPath(url string) *App {
delete(StaticDir, url)
return app
}
func (app *App) ErrorLog(ctx *Context) { func (app *App) ErrorLog(ctx *Context) {
BeeLogger.Printf("[ERR] host: '%s', request: '%s %s', proto: '%s', ua: '%s', remote: '%s'\n", ctx.Request.Host, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.Proto, ctx.Request.UserAgent(), ctx.Request.RemoteAddr) BeeLogger.Printf("[ERR] host: '%s', request: '%s %s', proto: '%s', ua: '%s', remote: '%s'\n", ctx.Request.Host, ctx.Request.Method, ctx.Request.URL.Path, ctx.Request.Proto, ctx.Request.UserAgent(), ctx.Request.RemoteAddr)
} }
@ -152,8 +181,14 @@ func RegisterController(path string, c ControllerInterface) *App {
return BeeApp return BeeApp
} }
func Router(path string, c ControllerInterface) *App { func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
BeeApp.Router(path, c) BeeApp.Router(rootpath, c, mappingMethods...)
return BeeApp
}
func RESTRouter(rootpath string, c ControllerInterface) *App {
Router(rootpath, c)
Router(path.Join(rootpath, ":objectId"), c)
return BeeApp return BeeApp
} }
@ -177,6 +212,11 @@ func SetStaticPath(url string, path string) *App {
return BeeApp return BeeApp
} }
func DelStaticPath(url string) *App {
delete(StaticDir, url)
return BeeApp
}
func Filter(filter http.HandlerFunc) *App { func Filter(filter http.HandlerFunc) *App {
BeeApp.Filter(filter) BeeApp.Filter(filter)
return BeeApp return BeeApp

6
cache/cache.go vendored
View File

@ -6,8 +6,10 @@ import (
type Cache interface { type Cache interface {
Get(key string) interface{} Get(key string) interface{}
Put(key string, val interface{}, timeout int) error Put(key string, val interface{}, timeout int64) error
Delete(key string) error Delete(key string) error
Incr(key string) error
Decr(key string) error
IsExist(key string) bool IsExist(key string) bool
ClearAll() error ClearAll() error
StartAndGC(config string) error StartAndGC(config string) error
@ -28,7 +30,7 @@ func Register(name string, adapter Cache) {
adapters[name] = adapter adapters[name] = adapter
} }
// config need to be correct JSON as string: {"interval":360} // config need to be correct JSON as string: {"interval":360}
func NewCache(adapterName, config string) (Cache, error) { func NewCache(adapterName, config string) (Cache, error) {
adapter, ok := adapters[adapterName] adapter, ok := adapters[adapterName]
if !ok { if !ok {

19
cache/cache_test.go vendored
View File

@ -6,7 +6,7 @@ import (
) )
func Test_cache(t *testing.T) { func Test_cache(t *testing.T) {
bm, err := NewCache("memory", `{"interval":60}`) bm, err := NewCache("memory", `{"interval":20}`)
if err != nil { if err != nil {
t.Error("init err") t.Error("init err")
} }
@ -21,7 +21,7 @@ func Test_cache(t *testing.T) {
t.Error("get err") t.Error("get err")
} }
time.Sleep(70 * time.Second) time.Sleep(30 * time.Second)
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("check err") t.Error("check err")
@ -31,6 +31,21 @@ func Test_cache(t *testing.T) {
t.Error("set Error", err) t.Error("set Error", 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") bm.Delete("astaxie")
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("delete err") t.Error("delete err")

10
cache/memcache.go vendored
View File

@ -28,7 +28,7 @@ func (rc *MemcacheCache) Get(key string) interface{} {
return contain return contain
} }
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int) error { func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error {
if rc.c == nil { if rc.c == nil {
rc.c = rc.connectInit() rc.c = rc.connectInit()
} }
@ -51,6 +51,14 @@ func (rc *MemcacheCache) Delete(key string) error {
return err return err
} }
func (rc *MemcacheCache) Incr(key string) error {
return errors.New("not support in memcache")
}
func (rc *MemcacheCache) Decr(key string) error {
return errors.New("not support in memcache")
}
func (rc *MemcacheCache) IsExist(key string) bool { func (rc *MemcacheCache) IsExist(key string) bool {
if rc.c == nil { if rc.c == nil {
rc.c = rc.connectInit() rc.c = rc.connectInit()

100
cache/memory.go vendored
View File

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strconv"
"sync" "sync"
"time" "time"
) )
@ -16,12 +15,7 @@ var (
type MemoryItem struct { type MemoryItem struct {
val interface{} val interface{}
Lastaccess time.Time Lastaccess time.Time
expired int expired int64
}
func (itm *MemoryItem) Access() interface{} {
itm.Lastaccess = time.Now()
return itm.val
} }
type MemoryCache struct { type MemoryCache struct {
@ -44,13 +38,21 @@ func (bc *MemoryCache) Get(name string) interface{} {
if !ok { if !ok {
return nil return nil
} }
return itm.Access() if (time.Now().Unix() - itm.Lastaccess.Unix()) > itm.expired {
go bc.Delete(name)
return nil
}
return itm.val
} }
func (bc *MemoryCache) Put(name string, value interface{}, expired int) error { func (bc *MemoryCache) Put(name string, value interface{}, expired int64) error {
bc.lock.Lock() bc.lock.Lock()
defer bc.lock.Unlock() defer bc.lock.Unlock()
t := MemoryItem{val: value, Lastaccess: time.Now(), expired: expired} t := MemoryItem{
val: value,
Lastaccess: time.Now(),
expired: expired,
}
if _, ok := bc.items[name]; ok { if _, ok := bc.items[name]; ok {
return errors.New("the key is exist") return errors.New("the key is exist")
} else { } else {
@ -73,6 +75,70 @@ func (bc *MemoryCache) Delete(name string) error {
return nil return nil
} }
func (bc *MemoryCache) Incr(key string) error {
bc.lock.RLock()
defer bc.lock.RUnlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
case int:
itm.val = itm.val.(int) + 1
case int64:
itm.val = itm.val.(int64) + 1
case int32:
itm.val = itm.val.(int32) + 1
case uint:
itm.val = itm.val.(uint) + 1
case uint32:
itm.val = itm.val.(uint32) + 1
case uint64:
itm.val = itm.val.(uint64) + 1
default:
return errors.New("item val is not int int64 int32")
}
return nil
}
func (bc *MemoryCache) Decr(key string) error {
bc.lock.RLock()
defer bc.lock.RUnlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
case int:
itm.val = itm.val.(int) - 1
case int64:
itm.val = itm.val.(int64) - 1
case int32:
itm.val = itm.val.(int32) - 1
case uint:
if itm.val.(uint) > 0 {
itm.val = itm.val.(uint) - 1
} else {
return errors.New("item val is less than 0")
}
case uint32:
if itm.val.(uint32) > 0 {
itm.val = itm.val.(uint32) - 1
} else {
return errors.New("item val is less than 0")
}
case uint64:
if itm.val.(uint64) > 0 {
itm.val = itm.val.(uint64) - 1
} else {
return errors.New("item val is less than 0")
}
default:
return errors.New("item val is not int int64 int32")
}
return nil
}
func (bc *MemoryCache) IsExist(name string) bool { func (bc *MemoryCache) IsExist(name string) bool {
bc.lock.RLock() bc.lock.RLock()
defer bc.lock.RUnlock() defer bc.lock.RUnlock()
@ -87,11 +153,11 @@ func (bc *MemoryCache) ClearAll() error {
return nil return nil
} }
// Start activates the file cache; it will // Start activates the file cache; it will
func (bc *MemoryCache) StartAndGC(config string) error { func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int var cf map[string]int
json.Unmarshal([]byte(config), &cf) json.Unmarshal([]byte(config), &cf)
if _, ok := cf["every"]; !ok { if _, ok := cf["interval"]; !ok {
cf = make(map[string]int) cf = make(map[string]int)
cf["interval"] = DefaultEvery cf["interval"] = DefaultEvery
} }
@ -110,7 +176,7 @@ func (bc *MemoryCache) vaccuum() {
return return
} }
for { for {
<-time.After(time.Duration(bc.dur) * time.Second) <-time.After(bc.dur)
if bc.items == nil { if bc.items == nil {
return return
} }
@ -128,12 +194,8 @@ func (bc *MemoryCache) item_expired(name string) bool {
if !ok { if !ok {
return true return true
} }
dur := time.Now().Sub(itm.Lastaccess) sec := time.Now().Unix() - itm.Lastaccess.Unix()
sec, err := strconv.Atoi(fmt.Sprintf("%0.0f", dur.Seconds())) if sec >= itm.expired {
if err != nil {
delete(bc.items, name)
return true
} else if sec >= itm.expired {
delete(bc.items, name) delete(bc.items, name)
return true return true
} }

24
cache/redis.go vendored
View File

@ -31,7 +31,7 @@ func (rc *RedisCache) Get(key string) interface{} {
return v return v
} }
func (rc *RedisCache) Put(key string, val interface{}, timeout int) error { func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
if rc.c == nil { if rc.c == nil {
rc.c = rc.connectInit() rc.c = rc.connectInit()
} }
@ -58,6 +58,28 @@ func (rc *RedisCache) IsExist(key string) bool {
return v return v
} }
func (rc *RedisCache) Incr(key string) error {
if rc.c == nil {
rc.c = rc.connectInit()
}
_, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, 1))
if err != nil {
return err
}
return nil
}
func (rc *RedisCache) Decr(key string) error {
if rc.c == nil {
rc.c = rc.connectInit()
}
_, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, -1))
if err != nil {
return err
}
return nil
}
func (rc *RedisCache) ClearAll() error { func (rc *RedisCache) ClearAll() error {
if rc.c == nil { if rc.c == nil {
rc.c = rc.connectInit() rc.c = rc.connectInit()

View File

@ -133,49 +133,64 @@ func ParseConfig() (err error) {
if v, err := AppConfig.Int("httpport"); err == nil { if v, err := AppConfig.Int("httpport"); err == nil {
HttpPort = v HttpPort = v
} }
if v, err := AppConfig.Int64("maxmemory"); err == nil { if maxmemory, err := AppConfig.Int64("maxmemory"); err == nil {
MaxMemory = v MaxMemory = maxmemory
} }
AppName = AppConfig.String("appname") AppName = AppConfig.String("appname")
if runmode := AppConfig.String("runmode"); runmode != "" { if runmode := AppConfig.String("runmode"); runmode != "" {
RunMode = runmode RunMode = runmode
} }
if ar, err := AppConfig.Bool("autorender"); err == nil { if autorender, err := AppConfig.Bool("autorender"); err == nil {
AutoRender = ar AutoRender = autorender
} }
if ar, err := AppConfig.Bool("autorecover"); err == nil { if autorecover, err := AppConfig.Bool("autorecover"); err == nil {
RecoverPanic = ar RecoverPanic = autorecover
} }
if ar, err := AppConfig.Bool("pprofon"); err == nil { if pprofon, err := AppConfig.Bool("pprofon"); err == nil {
PprofOn = ar PprofOn = pprofon
} }
if views := AppConfig.String("viewspath"); views != "" { if views := AppConfig.String("viewspath"); views != "" {
ViewsPath = views ViewsPath = views
} }
if ar, err := AppConfig.Bool("sessionon"); err == nil { if sessionon, err := AppConfig.Bool("sessionon"); err == nil {
SessionOn = ar SessionOn = sessionon
} }
if ar := AppConfig.String("sessionprovider"); ar != "" { if sessProvider := AppConfig.String("sessionprovider"); sessProvider != "" {
SessionProvider = ar SessionProvider = sessProvider
} }
if ar := AppConfig.String("sessionname"); ar != "" { if sessName := AppConfig.String("sessionname"); sessName != "" {
SessionName = ar SessionName = sessName
} }
if ar := AppConfig.String("sessionsavepath"); ar != "" { if sesssavepath := AppConfig.String("sessionsavepath"); sesssavepath != "" {
SessionSavePath = ar SessionSavePath = sesssavepath
} }
if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err == nil && ar != 0 { if sessMaxLifeTime, err := AppConfig.Int("sessiongcmaxlifetime"); err == nil && sessMaxLifeTime != 0 {
int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64) int64val, _ := strconv.ParseInt(strconv.Itoa(sessMaxLifeTime), 10, 64)
SessionGCMaxLifetime = int64val SessionGCMaxLifetime = int64val
} }
if ar, err := AppConfig.Bool("usefcgi"); err == nil { if usefcgi, err := AppConfig.Bool("usefcgi"); err == nil {
UseFcgi = ar UseFcgi = usefcgi
} }
if ar, err := AppConfig.Bool("enablegzip"); err == nil { if enablegzip, err := AppConfig.Bool("enablegzip"); err == nil {
EnableGzip = ar EnableGzip = enablegzip
} }
if ar, err := AppConfig.Bool("directoryindex"); err == nil { if directoryindex, err := AppConfig.Bool("directoryindex"); err == nil {
DirectoryIndex = ar DirectoryIndex = directoryindex
}
if hotupdate, err := AppConfig.Bool("hotupdate"); err == nil {
EnbaleHotUpdate = hotupdate
}
if timeout, err := AppConfig.Int64("httpservertimeout"); err == nil {
HttpServerTimeOut = timeout
}
if errorsshow, err := AppConfig.Bool("errorsshow"); err == nil {
ErrorsShow = errorsshow
}
if copyrequestbody, err := AppConfig.Bool("copyrequestbody"); err == nil {
CopyRequestBody = copyrequestbody
}
if xsrfkey := AppConfig.String("xsrfkey"); xsrfkey != "" {
XSRFKEY = xsrfkey
} }
} }
return nil return nil

View File

@ -1,16 +1,17 @@
package beego package beego
import ( import (
"bytes"
"fmt" "fmt"
"mime" "mime"
"net/http" "net/http"
"strings" "strings"
"time"
) )
type Context struct { type Context struct {
ResponseWriter http.ResponseWriter ResponseWriter http.ResponseWriter
Request *http.Request Request *http.Request
RequestBody []byte
Params map[string]string Params map[string]string
} }
@ -37,7 +38,7 @@ func (ctx *Context) NotFound(message string) {
ctx.ResponseWriter.Write([]byte(message)) ctx.ResponseWriter.Write([]byte(message))
} }
//Sets the content type by extension, as defined in the mime package. //Sets the content type by extension, as defined in the mime package.
//For example, ctx.ContentType("json") sets the content-type to "application/json" //For example, ctx.ContentType("json") sets the content-type to "application/json"
func (ctx *Context) ContentType(ext string) { func (ctx *Context) ContentType(ext string) {
if !strings.HasPrefix(ext, ".") { if !strings.HasPrefix(ext, ".") {
@ -58,14 +59,61 @@ func (ctx *Context) SetHeader(hdr string, val string, unique bool) {
} }
//Sets a cookie -- duration is the amount of time in seconds. 0 = forever //Sets a cookie -- duration is the amount of time in seconds. 0 = forever
func (ctx *Context) SetCookie(name string, value string, age int64) {
var utctime time.Time //params:
if age == 0 { //string name
// 2^31 - 1 seconds (roughly 2038) //string value
utctime = time.Unix(2147483647, 0) //int64 expire = 0
//string $path
//string $domain
//bool $secure = false
//bool $httponly = false
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
var b bytes.Buffer
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
if len(others) > 0 {
switch others[0].(type) {
case int:
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int))
case int64:
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int64))
case int32:
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int32))
}
} else { } else {
utctime = time.Unix(time.Now().Unix()+age, 0) fmt.Fprintf(&b, "; Max-Age=0")
} }
cookie := fmt.Sprintf("%s=%s; Expires=%s; Path=/", name, value, webTime(utctime)) if len(others) > 1 {
ctx.SetHeader("Set-Cookie", cookie, true) fmt.Fprintf(&b, "; Path=%s", sanitizeValue(others[1].(string)))
}
if len(others) > 2 {
fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(others[2].(string)))
}
if len(others) > 3 {
fmt.Fprintf(&b, "; Secure")
}
if len(others) > 4 {
fmt.Fprintf(&b, "; HttpOnly")
}
ctx.SetHeader("Set-Cookie", b.String(), false)
}
var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
func sanitizeName(n string) string {
return cookieNameSanitizer.Replace(n)
}
var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
func sanitizeValue(v string) string {
return cookieValueSanitizer.Replace(v)
}
func (ctx *Context) GetCookie(key string) string {
keycookie, err := ctx.Request.Cookie(key)
if err != nil {
return ""
}
return keycookie.Value
} }

View File

@ -4,9 +4,13 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"compress/zlib" "compress/zlib"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
"html/template" "html/template"
"io" "io"
@ -18,16 +22,19 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"time"
) )
type Controller struct { type Controller struct {
Ctx *Context Ctx *Context
Data map[interface{}]interface{} Data map[interface{}]interface{}
ChildName string ChildName string
TplNames string TplNames string
Layout string Layout string
TplExt string TplExt string
CruSession session.SessionStore _xsrf_token string
gotofunc string
CruSession session.SessionStore
} }
type ControllerInterface interface { type ControllerInterface interface {
@ -262,15 +269,15 @@ func (c *Controller) GetString(key string) string {
} }
func (c *Controller) GetStrings(key string) []string { func (c *Controller) GetStrings(key string) []string {
r := c.Ctx.Request; r := c.Ctx.Request
if r.Form == nil { if r.Form == nil {
return []string{} return []string{}
} }
vs := r.Form[key] vs := r.Form[key]
if len(vs) > 0 { if len(vs) > 0 {
return vs return vs
} }
return []string{} return []string{}
} }
func (c *Controller) GetInt(key string) (int64, error) { func (c *Controller) GetInt(key string) (int64, error) {
@ -327,3 +334,53 @@ func (c *Controller) DelSession(name interface{}) {
} }
c.CruSession.Delete(name) c.CruSession.Delete(name)
} }
func (c *Controller) IsAjax() bool {
return (c.Ctx.Request.Header.Get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest")
}
func (c *Controller) XsrfToken() string {
if c._xsrf_token == "" {
token := c.Ctx.GetCookie("_xsrf")
if token == "" {
h := hmac.New(sha1.New, []byte(XSRFKEY))
fmt.Fprintf(h, "%s:%d", c.Ctx.Request.RemoteAddr, time.Now().UnixNano())
tok := fmt.Sprintf("%s:%d", h.Sum(nil), time.Now().UnixNano())
token := base64.URLEncoding.EncodeToString([]byte(tok))
c.Ctx.SetCookie("_xsrf", token)
}
c._xsrf_token = token
}
return c._xsrf_token
}
func (c *Controller) CheckXsrfCookie() bool {
token := c.GetString("_xsrf")
if token == "" {
token = c.Ctx.Request.Header.Get("X-Xsrftoken")
}
if token == "" {
token = c.Ctx.Request.Header.Get("X-Csrftoken")
}
if token == "" {
c.Ctx.Abort(403, "'_xsrf' argument missing from POST")
}
if c._xsrf_token != token {
c.Ctx.Abort(403, "XSRF cookie does not match POST argument")
}
return true
}
func (c *Controller) XsrfFormHtml() string {
return "<input type=\"hidden\" name=\"_xsrf\" value=\"" +
c._xsrf_token + "\"/>"
}
func (c *Controller) GoToFunc(funcname string) {
if funcname[0] < 65 || funcname[0] > 90 {
panic("GoToFunc should exported function")
}
c.gotofunc = funcname
}

102
docs/en/API.md Normal file
View File

@ -0,0 +1,102 @@
# Getting start with API application development
Go is very good for developing API applications which I think is the biggest strength compare to other dynamic languages. Beego provides powerful and quick setup tool for developing API applications, which gives you more focus on business logic.
## Quick setup
bee can setup a API application very quick by executing commands under any `$GOPATH/src`.
`bee api beeapi`
## Application directory structure
```
├── conf
│ └── app.conf
├── controllers
│ └── default.go
├── models
│ └── object.go
└── main.go
```
## Source code explanation
- app.conf has following configuration options for your API applications:
- autorender = false // Disable auto-render since API applications don't need.
- copyrequestbody = true // RESTFul applications sends raw body instead of form, so we need to read body specifically.
- main.go is for registering routers of RESTFul.
beego.RESTRouter("/object", &controllers.ObejctController{})
Match rules as follows:
<table>
<tr>
<th>URL</th> <th>HTTP Verb</th> <th>Functionality</th>
</tr>
<tr>
<td>/object</td> <td>POST</td> <td>Creating Objects</td>
</tr>
<tr>
<td>/object/objectId</td> <td>GET</td> <td>Retrieving Objects</td>
</tr>
<tr>
<td>/object/objectId</td> <td>PUT</td> <td>Updating Objects</td>
</tr>
<tr>
<td>/object</td> <td>GET</td> <td>Queries</td>
</tr>
<tr>
<td>/object/objectId</td> <td>DELETE</td> <td>Deleting Objects</td>
</tr>
</table>
- ObejctController implemented corresponding methods:
type ObejctController struct {
beego.Controller
}
func (this *ObejctController) Post(){
}
func (this *ObejctController) Get(){
}
func (this *ObejctController) Put(){
}
func (this *ObejctController) Delete(){
}
- models implemented corresponding object operation for adding, deleting, updating and getting.
## Test
- Add a new object:
curl -X POST -d '{"Score":1337,"PlayerName":"Sean Plott"}' http://127.0.0.1:8080/object
Returns a corresponding objectID:astaxie1373349756660423900
- Query a object:
`curl -X GET http://127.0.0.1:8080/object/astaxie1373349756660423900`
- Query all objects:
`curl -X GET http://127.0.0.1:8080/object`
- Update a object:
`curl -X PUT -d '{"Score":10000}'http://127.0.0.1:8080/object/astaxie1373349756660423900`
- Delete a object:
`curl -X DELETE http://127.0.0.1:8080/object/astaxie1373349756660423900`

View File

@ -762,16 +762,29 @@ Beego has a default BeeLogger object that outputs log into stdout, and you can u
beego.SetLogger(*log.Logger) beego.SetLogger(*log.Logger)
You can output everything that implemented `*log.Logger`, for example, write to file: Now Beego supports new way to record your log with automatically log rotate. Use following code in your main function:
fd,err := os.OpenFile("/var/log/beeapp/beeapp.log", os.O_RDWR|os.O_APPEND, 0644) filew := beego.NewFileWriter("tmp/log.log", true)
err := filew.StartLogger()
if err != nil { if err != nil {
beego.Critical("openfile beeapp.log:", err) beego.Critical("NewFileWriter err", err)
return
} }
lg := log.New(fd, "", log.Ldate|log.Ltime)
beego.SetLogger(lg)
So Beego records your log into file `tmp/log.log`, the second argument indicates whether enable log rotate or not. The rules of rotate as follows:
1. segment log every 1,000,000 lines.
2. segment log every 256 MB file size.
3. segment log daily.
4. save log file up to 7 days as default.
You cannot segment log over 999 times everyday, the segmented file name with format `<defined file name>.<date>.<three digits>`.
You are able to modify rotate rules with following methods, be sure that you call them before `StartLogger()`.
- func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter
- func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter
- func (w *FileLogWriter) SetRotateMaxDays(maxdays int64) *FileLogWriter
- func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter
### Different levels of log ### Different levels of log

106
docs/zh/API.md Normal file
View File

@ -0,0 +1,106 @@
# API应用开发入门
Go是非常适合用来开发API应用的而且我认为也是Go相对于其他动态语言的最大优势应用。beego在开发API应用方面提供了非常强大和快速的工具方便用户快速的建立API应用原型专心业务逻辑就行了。
## 快速建立原型
bee快速开发工具提供了一个API应用建立的工具在gopath/src下的任意目录执行如下命令就可以快速的建立一个API应用
`bee api beeapi`
## 应用的目录结构
应用的目录结构如下所示:
```
├── conf
│ └── app.conf
├── controllers
│ └── default.go
├── models
│ └── object.go
└── main.go
```
## 源码解析
- app.conf里面主要针对API的配置如下
autorender = false //API应用不需要模板渲染所以关闭自动渲染
copyrequestbody = true //RESTFul应用发送信息的时候是raw body而不是普通的form表单所以需要额外的读取body信息
- main.go文件主要针对RESTFul的路由注册
`beego.RESTRouter("/object", &controllers.ObejctController{})`
这个路由可以匹配如下的规则
<table>
<tr>
<th>URL</th> <th>HTTP Verb</th> <th>Functionality</th>
</tr>
<tr>
<td>/object</td> <td>POST</td> <td>Creating Objects</td>
</tr>
<tr>
<td>/object/objectId</td> <td>GET</td> <td>Retrieving Objects</td>
</tr>
<tr>
<td>/object/objectId</td> <td>PUT</td> <td>Updating Objects</td>
</tr>
<tr>
<td>/object</td> <td>GET</td> <td>Queries</td>
</tr>
<tr>
<td>/object/objectId</td> <td>DELETE</td> <td>Deleting Objects</td>
</tr>
</table>
- ObejctController实现了对应的方法
```
type ObejctController struct {
beego.Controller
}
func (this *ObejctController) Post(){
}
func (this *ObejctController) Get(){
}
func (this *ObejctController) Put(){
}
func (this *ObejctController) Delete(){
}
```
- models里面实现了对应操作对象的增删改取等操作
## 测试
- 添加一个对象:
`curl -X POST -d '{"Score":1337,"PlayerName":"Sean Plott"}' http://127.0.0.1:8080/object`
返回一个相应的objectID:astaxie1373349756660423900
- 查询一个对象
`curl -X GET http://127.0.0.1:8080/object/astaxie1373349756660423900`
- 查询全部的对象
`curl -X GET http://127.0.0.1:8080/object`
- 更新一个对象
`curl -X PUT -d '{"Score":10000}'http://127.0.0.1:8080/object/astaxie1373349756660423900`
- 删除一个对象
`curl -X DELETE http://127.0.0.1:8080/object/astaxie1373349756660423900`

View File

@ -4,25 +4,25 @@
**导航** **导航**
- [最小应用](#-1) - [最小应用](#%E6%9C%80%E5%B0%8F%E5%BA%94%E7%94%A8)
- [新建项目](#-2) - [新建项目](#%E6%96%B0%E5%BB%BA%E9%A1%B9%E7%9B%AE)
- [开发模式](#-3) - [开发模式](#%E5%BC%80%E5%8F%91%E6%A8%A1%E5%BC%8F)
- [路由设置](#-4) - [路由设置](#%E8%B7%AF%E7%94%B1%E8%AE%BE%E7%BD%AE)
- [静态文件](#-5) - [静态文件](#%E9%9D%99%E6%80%81%E6%96%87%E4%BB%B6)
- [过滤和中间件](#-6) - [过滤和中间件](#%E8%BF%87%E6%BB%A4%E5%92%8C%E4%B8%AD%E9%97%B4%E4%BB%B6)
- [Controller设计](#-7) - [Controller设计](#%E6%8E%A7%E5%88%B6%E5%99%A8%E8%AE%BE%E8%AE%A1)
- [模板处理](#-8) - [模板处理](#%E6%A8%A1%E6%9D%BF%E5%A4%84%E7%90%86)
- [request处理](#request) - [request处理](#request%E5%A4%84%E7%90%86)
- [跳转和错误](#-15) - [跳转和错误](#%E8%B7%B3%E8%BD%AC%E5%92%8C%E9%94%99%E8%AF%AF)
- [response处理](#response) - [response处理](#response%E5%A4%84%E7%90%86)
- [Sessions](#sessions) - [Sessions](#sessions)
- [Cache设置](#cache) - [Cache设置](#cache%E8%AE%BE%E7%BD%AE)
- [安全的Map](#map) - [安全的Map](#%E5%AE%89%E5%85%A8%E7%9A%84map)
- [日志处理](#-16) - [日志处理](#%E6%97%A5%E5%BF%97%E5%A4%84%E7%90%86)
- [配置管理](#-17) - [配置管理](#%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86)
- [beego参数](#-18) - [beego参数](#%E7%B3%BB%E7%BB%9F%E9%BB%98%E8%AE%A4%E5%8F%82%E6%95%B0)
- [第三方应用集成](#-19) - [第三方应用集成](#%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%94%E7%94%A8%E9%9B%86%E6%88%90)
- [部署编译应用](#-20) - [部署编译应用](#%E9%83%A8%E7%BD%B2%E7%BC%96%E8%AF%91%E5%BA%94%E7%94%A8)
## 最小应用 ## 最小应用
@ -179,6 +179,47 @@
this.Ctx.Params[":path"] this.Ctx.Params[":path"]
this.Ctx.Params[":ext"] this.Ctx.Params[":ext"]
上面列举的是默认的请求方法名(请求的method和函数名一致例如GET请求执行Get函数POST请求执行Post函数),如果用户期望自定义函数名,那么可以使用如下方式:
beego.Router("/",&IndexController{},"*:Index")
使用第三个参数第三个参数就是用来设置对应method到函数名定义如下
- *表示任意的method都执行该函数
- 使用`httpmethod:funcname`格式来展示
- 多个不同的格式使用`;`分割
- 多个method对应同一个funcnamemethod之间通过`,`来分割
以下是一个RESTful的设计如下
- beego.Router("/api/list",&RestController{},"*:ListFood")
- beego.Router("/api/create",&RestController{},"post:CreateFood")
- beego.Router("/api/update",&RestController{},"put:UpdateFood")
- beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
以下是多个http method指向同一个函数
beego.Router("/api",&RestController{},"get,post:ApiFunc")
一下是不同的method对应不同的函数通过`;`进行分割
beego.Router("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
可用的http method
- * :包含一下所有的函数
- get GET请求
- post POST请求
- put PUT请求
- delete DELETE请求
- patch PATCH请求
- options OPTIONS请求
- head HEAD请求
>>>如果同时存在*和对应的http method那么优先执行http method的方法例如同时注册了如下所示的路由
>>> beego.Router("/simple",&SimpleController{},"*:AllFunc;post:PostFunc")
>>>那么执行POST请求的时候执行PostFunc而不执行AllFunc
## 静态文件 ## 静态文件
@ -787,7 +828,7 @@ beego默认有一个初始化的BeeLogger对象输出内容到stdout中你可
- func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter - func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter
- func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter - func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter
- func (w *FileLogWriter) SetRotateMaxDay(maxday int64) *FileLogWriter - func (w *FileLogWriter) SetRotateMaxDays(maxdays int64) *FileLogWriter
- func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter - func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter
但是这些函数调用必须在调用`StartLogger`之前 但是这些函数调用必须在调用`StartLogger`之前

View File

@ -1,52 +1,53 @@
# beego介绍 # beego介绍
beego是一个类似tornado的Go应用框架采用了RESTFul的方式来实现应用框架是一个超轻量级的框架主要有如下的特点 beego是一个类似tornado的Go应用框架采用了RESTFul的方式来实现应用框架是一个超轻量级的框架主要有如下的特点
- 支持MVC的方式用户只需要关注逻辑实现对应method的方法即可 - 支持MVC的方式用户只需要关注逻辑实现对应method的方法即可
- 支持websocket通过自定义Handler实现集成sockjs等方式实现 - 支持websocket通过自定义Handler实现集成sockjs等方式实现
- 支持自定义路由支持各种方式的路由正则、语意均支持类似sinatra - 支持自定义路由支持各种方式的路由正则、语意均支持类似sinatra
- session集成支持memory、file、redis、mysql等存储 - session集成支持memory、file、redis、mysql等存储
- 表单处理自动化解析,用户可以很方便的获取数据 - 表单处理自动化解析,用户可以很方便的获取数据
- 日志分级系统,用户可以很方便的调试和应用日志记录 - 日志分级系统,用户可以很方便的调试和应用日志记录
- 自定义配置文件支持ini格式的文本配置可以方便的在系统中调参数 - 自定义配置文件支持ini格式的文本配置可以方便的在系统中调参数
- 采用了Go内置的模板集成实现了很多Web开发中常用的函数 - 采用了Go内置的模板集成实现了很多Web开发中常用的函数
执行过程如下所示: 执行过程如下所示:
![](images/beego.png) ![](images/beego.png)
# beego简单例子 # beego简单例子
package main package main
import ( import (
"github.com/astaxie/beego" "github.com/astaxie/beego"
) )
type MainController struct { type MainController struct {
beego.Controller beego.Controller
} }
func (this *MainController) Get() { func (this *MainController) Get() {
this.Ctx.WriteString("hello world") this.Ctx.WriteString("hello world")
} }
func main() { func main() {
beego.Router("/", &MainController{}) beego.Router("/", &MainController{})
beego.Run() beego.Run()
} }
# beego 指南 # beego 指南
* [为什么设计beego](Why.md) * [为什么设计beego](Why.md)
* [安装入门](Install.md) * [安装入门](Install.md)
* [快速入门](Quickstart.md) * [快速入门](Quickstart.md)
* [一步一步开发应用](Tutorial.md) * [一步一步开发应用](Tutorial.md)
* [beego案例](Application.md) * [beego案例](Application.md)
* [热升级](HotUpdate.md) * [热升级](HotUpdate.md)
* [API应用开发入门](API.md)
# API接口
# API接口
API对于我们平时开发应用非常有用用于查询一些开发的函数godoc做的非常好了
API对于我们平时开发应用非常有用用于查询一些开发的函数godoc做的非常好了
[Go Walker](http://gowalker.org/github.com/astaxie/beego)
[Go Walker](http://gowalker.org/github.com/astaxie/beego)

View File

@ -189,6 +189,7 @@ func NotFound(rw http.ResponseWriter, r *http.Request) {
"<br>You like 404 pages" + "<br>You like 404 pages" +
"</ul>") "</ul>")
data["BeegoVersion"] = VERSION data["BeegoVersion"] = VERSION
rw.WriteHeader(http.StatusNotFound)
t.Execute(rw, data) t.Execute(rw, data)
} }
@ -204,6 +205,7 @@ func Unauthorized(rw http.ResponseWriter, r *http.Request) {
"<br>Check the address for errors" + "<br>Check the address for errors" +
"</ul>") "</ul>")
data["BeegoVersion"] = VERSION data["BeegoVersion"] = VERSION
rw.WriteHeader(http.StatusUnauthorized)
t.Execute(rw, data) t.Execute(rw, data)
} }
@ -220,6 +222,7 @@ func Forbidden(rw http.ResponseWriter, r *http.Request) {
"<br>You need to log in" + "<br>You need to log in" +
"</ul>") "</ul>")
data["BeegoVersion"] = VERSION data["BeegoVersion"] = VERSION
rw.WriteHeader(http.StatusForbidden)
t.Execute(rw, data) t.Execute(rw, data)
} }
@ -235,6 +238,7 @@ func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
"<br>Please try again later." + "<br>Please try again later." +
"</ul>") "</ul>")
data["BeegoVersion"] = VERSION data["BeegoVersion"] = VERSION
rw.WriteHeader(http.StatusServiceUnavailable)
t.Execute(rw, data) t.Execute(rw, data)
} }
@ -249,6 +253,7 @@ func InternalServerError(rw http.ResponseWriter, r *http.Request) {
"<br>you should report the fault to the website administrator" + "<br>you should report the fault to the website administrator" +
"</ul>") "</ul>")
data["BeegoVersion"] = VERSION data["BeegoVersion"] = VERSION
rw.WriteHeader(http.StatusInternalServerError)
t.Execute(rw, data) t.Execute(rw, data)
} }

View File

@ -0,0 +1,5 @@
appname = beeapi
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true

View File

@ -0,0 +1,56 @@
package controllers
import (
"encoding/json"
"github.com/astaxie/beego"
"github.com/astaxie/beego/example/beeapi/models"
)
type ObejctController struct {
beego.Controller
}
func (this *ObejctController) Post() {
var ob models.Object
json.Unmarshal(this.Ctx.RequestBody, &ob)
objectid := models.AddOne(ob)
this.Data["json"] = "{\"ObjectId\":\"" + objectid + "\"}"
this.ServeJson()
}
func (this *ObejctController) Get() {
objectId := this.Ctx.Params[":objectId"]
if objectId != "" {
ob, err := models.GetOne(objectId)
if err != nil {
this.Data["json"] = err
} else {
this.Data["json"] = ob
}
} else {
obs := models.GetAll()
this.Data["json"] = obs
}
this.ServeJson()
}
func (this *ObejctController) Put() {
objectId := this.Ctx.Params[":objectId"]
var ob models.Object
json.Unmarshal(this.Ctx.RequestBody, &ob)
err := models.Update(objectId, ob.Score)
if err != nil {
this.Data["json"] = err
} else {
this.Data["json"] = "update success!"
}
this.ServeJson()
}
func (this *ObejctController) Delete() {
objectId := this.Ctx.Params[":objectId"]
models.Delete(objectId)
this.Data["json"] = "delete success!"
this.ServeJson()
}

20
example/beeapi/main.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/example/beeapi/controllers"
)
// Objects
// URL HTTP Verb Functionality
// /object POST Creating Objects
// /object/<objectId> GET Retrieving Objects
// /object/<objectId> PUT Updating Objects
// /object GET Queries
// /object/<objectId> DELETE Deleting Objects
func main() {
beego.RESTRouter("/object", &controllers.ObejctController{})
beego.Run()
}

View File

@ -0,0 +1,52 @@
package models
import (
"errors"
"strconv"
"time"
)
var (
Objects map[string]*Object
)
type Object struct {
ObjectId string
Score int64
PlayerName string
}
func init() {
Objects = make(map[string]*Object)
Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}
func AddOne(object Object) (ObjectId string) {
object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
Objects[object.ObjectId] = &object
return object.ObjectId
}
func GetOne(ObjectId string) (object *Object, err error) {
if v, ok := Objects[ObjectId]; ok {
return v, nil
}
return nil, errors.New("ObjectId Not Exist")
}
func GetAll() map[string]*Object {
return Objects
}
func Update(ObjectId string, Score int64) (err error) {
if v, ok := Objects[ObjectId]; ok {
v.Score = Score
return nil
}
return errors.New("ObjectId Not Exist")
}
func Delete(ObjectId string) {
delete(Objects, ObjectId)
}

128
log.go
View File

@ -14,6 +14,7 @@ import (
type FileLogWriter struct { type FileLogWriter struct {
*log.Logger *log.Logger
mw *MuxWriter
// The opened file // The opened file
filename string filename string
@ -26,7 +27,7 @@ type FileLogWriter struct {
// Rotate daily // Rotate daily
daily bool daily bool
maxday int64 maxdays int64
daily_opendate int daily_opendate int
rotate bool rotate bool
@ -34,15 +35,37 @@ type FileLogWriter struct {
startLock sync.Mutex //only one log can writer to the file startLock sync.Mutex //only one log can writer to the file
} }
type MuxWriter struct {
sync.Mutex
fd *os.File
}
func (l *MuxWriter) Write(b []byte) (int, error) {
l.Lock()
defer l.Unlock()
return l.fd.Write(b)
}
func (l *MuxWriter) SetFd(fd *os.File) {
if l.fd != nil {
l.fd.Close()
}
l.fd = fd
}
func NewFileWriter(fname string, rotate bool) *FileLogWriter { func NewFileWriter(fname string, rotate bool) *FileLogWriter {
w := &FileLogWriter{ w := &FileLogWriter{
filename: fname, filename: fname,
maxlines: 1000000, maxlines: 1000000,
maxsize: 1 << 28, //256 MB maxsize: 1 << 28, //256 MB
daily: true, daily: true,
maxday: 7, maxdays: 7,
rotate: rotate, rotate: rotate,
} }
// use MuxWriter instead direct use os.File for lock write when rotate
w.mw = new(MuxWriter)
// set MuxWriter as Logger's io.Writer
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
return w return w
} }
@ -64,16 +87,23 @@ func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
return w return w
} }
// Set rotate daily's log keep for maxday,other will delete // Set rotate daily's log keep for maxdays, other will delete
func (w *FileLogWriter) SetRotateMaxDay(maxday int64) *FileLogWriter { func (w *FileLogWriter) SetRotateMaxDays(maxdays int64) *FileLogWriter {
w.maxday = maxday w.maxdays = maxdays
return w return w
} }
func (w *FileLogWriter) StartLogger() error { func (w *FileLogWriter) StartLogger() error {
if err := w.DoRotate(false); err != nil { fd, err := w.createLogFile()
if err != nil {
return err return err
} }
w.mw.SetFd(fd)
err = w.initFd()
if err != nil {
return err
}
BeeLogger = w
return nil return nil
} }
@ -83,7 +113,7 @@ func (w *FileLogWriter) docheck(size int) {
if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) || if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
(w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) || (w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
(w.daily && time.Now().Day() != w.daily_opendate) { (w.daily && time.Now().Day() != w.daily_opendate) {
if err := w.DoRotate(true); err != nil { if err := w.DoRotate(); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err) fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
return return
} }
@ -101,37 +131,14 @@ func (w *FileLogWriter) Printf(format string, v ...interface{}) {
w.Logger.Printf(format, v...) w.Logger.Printf(format, v...)
} }
func (w *FileLogWriter) DoRotate(rotate bool) error { func (w *FileLogWriter) createLogFile() (*os.File, error) {
if rotate {
_, err := os.Lstat(w.filename)
if err == nil { // file exists
// Find the next available number
num := 1
fname := ""
for ; err == nil && num <= 999; num++ {
fname = w.filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
_, err = os.Lstat(fname)
}
// return error if the last file checked still existed
if err == nil {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
}
// Rename the file to its newfound home
err = os.Rename(w.filename, fname)
if err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
go w.deleteOldLog()
}
}
// Open the log file // Open the log file
fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
if err != nil { return fd, err
return err }
}
w.Logger = log.New(fd, "", log.Ldate|log.Ltime) func (w *FileLogWriter) initFd() error {
fd := w.mw.fd
finfo, err := fd.Stat() finfo, err := fd.Stat()
if err != nil { if err != nil {
return fmt.Errorf("get stat err: %s\n", err) return fmt.Errorf("get stat err: %s\n", err)
@ -144,19 +151,60 @@ func (w *FileLogWriter) DoRotate(rotate bool) error {
fmt.Println(err) fmt.Println(err)
} }
w.maxlines_curlines = len(strings.Split(string(content), "\n")) w.maxlines_curlines = len(strings.Split(string(content), "\n"))
} else { } else {
w.maxlines_curlines = 0 w.maxlines_curlines = 0
} }
BeeLogger = w return nil
}
func (w *FileLogWriter) DoRotate() error {
_, err := os.Lstat(w.filename)
if err == nil { // file exists
// Find the next available number
num := 1
fname := ""
for ; err == nil && num <= 999; num++ {
fname = w.filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
_, err = os.Lstat(fname)
}
// return error if the last file checked still existed
if err == nil {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
}
// block Logger's io.Writer
w.mw.Lock()
defer w.mw.Unlock()
fd := w.mw.fd
fd.Close()
// close fd before rename
// Rename the file to its newfound home
err = os.Rename(w.filename, fname)
if err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
// re-start logger
err = w.StartLogger()
if err != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", err)
}
go w.deleteOldLog()
}
return nil return nil
} }
func (w *FileLogWriter) deleteOldLog() { func (w *FileLogWriter) deleteOldLog() {
dir := path.Dir(w.filename) dir := path.Dir(w.filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.maxday) { if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.maxdays) {
os.Remove(path) if strings.HasPrefix(filepath.Base(path), filepath.Base(w.filename)) {
os.Remove(path)
}
} }
return nil return nil
}) })

View File

@ -27,12 +27,19 @@ var ErrInitStart = errors.New("init from")
// Allows for us to notice when the connection is closed. // Allows for us to notice when the connection is closed.
type conn struct { type conn struct {
net.Conn net.Conn
wg *sync.WaitGroup wg *sync.WaitGroup
isclose bool
lock sync.Mutex
} }
func (c conn) Close() error { func (c conn) Close() error {
c.lock.Lock()
defer c.lock.Unlock()
err := c.Conn.Close() err := c.Conn.Close()
c.wg.Done() if !c.isclose && err == nil {
c.wg.Done()
c.isclose = true
}
return err return err
} }
@ -137,16 +144,15 @@ func GetInitListner(tcpaddr *net.TCPAddr) (l net.Listener, err error) {
countStr := os.Getenv(FDKey) countStr := os.Getenv(FDKey)
if countStr == "" { if countStr == "" {
return net.ListenTCP("tcp", tcpaddr) return net.ListenTCP("tcp", tcpaddr)
} else {
count, err := strconv.Atoi(countStr)
if err != nil {
return nil, err
}
f := os.NewFile(uintptr(count), "listen socket")
l, err = net.FileListener(f)
if err != nil {
return nil, err
}
return l, nil
} }
count, err := strconv.Atoi(countStr)
if err != nil {
return nil, err
}
f := os.NewFile(uintptr(count), "listen socket")
l, err = net.FileListener(f)
if err != nil {
return nil, err
}
return l, nil
} }

221
router.go
View File

@ -1,26 +1,26 @@
package beego package beego
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
"strconv"
"strings" "strings"
) )
var ( var HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head"}
sc *Controller = &Controller{}
)
type controllerInfo struct { type controllerInfo struct {
pattern string pattern string
regex *regexp.Regexp regex *regexp.Regexp
params map[int]string params map[int]string
controllerType reflect.Type controllerType reflect.Type
methods map[string]string
} }
type userHandler struct { type userHandler struct {
@ -41,7 +41,16 @@ func NewControllerRegistor() *ControllerRegistor {
return &ControllerRegistor{routers: make([]*controllerInfo, 0), userHandlers: make(map[string]*userHandler)} return &ControllerRegistor{routers: make([]*controllerInfo, 0), userHandlers: make(map[string]*userHandler)}
} }
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { //methods support like this:
//default methods is the same name as method
//Add("/user",&UserController{})
//Add("/api/list",&RestController{},"*:ListFood")
//Add("/api/create",&RestController{},"post:CreateFood")
//Add("/api/update",&RestController{},"put:UpdateFood")
//Add("/api/delete",&RestController{},"delete:DeleteFood")
//Add("/api",&RestController{},"get,post:ApiFunc")
//Add("/simple",&SimpleController{},"get:GetFunc;post:PostFunc")
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) {
parts := strings.Split(pattern, "/") parts := strings.Split(pattern, "/")
j := 0 j := 0
@ -85,13 +94,36 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
} }
} }
} }
reflectVal := reflect.ValueOf(c)
t := reflect.Indirect(reflectVal).Type()
methods := make(map[string]string)
if len(mappingMethods) > 0 {
semi := strings.Split(mappingMethods[0], ";")
for _, v := range semi {
colon := strings.Split(v, ":")
if len(colon) != 2 {
panic("method mapping fomate is error")
}
comma := strings.Split(colon[0], ",")
for _, m := range comma {
if m == "*" || inSlice(strings.ToLower(m), HTTPMETHOD) {
if val := reflectVal.MethodByName(colon[1]); val.IsValid() {
methods[strings.ToLower(m)] = colon[1]
} else {
panic(colon[1] + " method don't exist in the controller " + t.Name())
}
} else {
panic(v + " is an error method mapping,Don't exist method named " + m)
}
}
}
}
if j == 0 { if j == 0 {
//now create the Route //now create the Route
t := reflect.Indirect(reflect.ValueOf(c)).Type()
route := &controllerInfo{} route := &controllerInfo{}
route.pattern = pattern route.pattern = pattern
route.controllerType = t route.controllerType = t
route.methods = methods
p.fixrouters = append(p.fixrouters, route) p.fixrouters = append(p.fixrouters, route)
} else { // add regexp routers } else { // add regexp routers
//recreate the url pattern, with parameters replaced //recreate the url pattern, with parameters replaced
@ -105,11 +137,12 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
} }
//now create the Route //now create the Route
t := reflect.Indirect(reflect.ValueOf(c)).Type()
route := &controllerInfo{} route := &controllerInfo{}
route.regex = regex route.regex = regex
route.params = params route.params = params
route.pattern = pattern route.pattern = pattern
route.methods = methods
route.controllerType = t route.controllerType = t
p.routers = append(p.routers, route) p.routers = append(p.routers, route)
} }
@ -189,91 +222,12 @@ func (p *ControllerRegistor) FilterPrefixPath(path string, filter http.HandlerFu
}) })
} }
func StructMap(vc reflect.Value, r *http.Request) error {
for k, t := range r.Form {
v := t[0]
names := strings.Split(k, ".")
var value reflect.Value = vc
for i, name := range names {
name = strings.Title(name)
if i == 0 {
if reflect.ValueOf(sc).Elem().FieldByName(name).IsValid() {
Trace("Controller's property should not be changed by mapper.")
break
}
}
if value.Kind() != reflect.Struct {
Trace(fmt.Sprintf("arg error, value kind is %v", value.Kind()))
break
}
if i != len(names)-1 {
value = value.FieldByName(name)
if !value.IsValid() {
Trace(fmt.Sprintf("(%v value is not valid %v)", name, value))
break
}
} else {
tv := value.FieldByName(name)
if !tv.IsValid() {
Trace(fmt.Sprintf("struct %v has no field named %v", value, name))
break
}
if !tv.CanSet() {
Trace("can not set " + k)
break
}
var l interface{}
switch k := tv.Kind(); k {
case reflect.String:
l = v
case reflect.Bool:
l = (v == "true")
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
x, err := strconv.Atoi(v)
if err != nil {
Trace("arg " + v + " as int: " + err.Error())
break
}
l = x
case reflect.Int64:
x, err := strconv.ParseInt(v, 10, 64)
if err != nil {
Trace("arg " + v + " as int: " + err.Error())
break
}
l = x
case reflect.Float32, reflect.Float64:
x, err := strconv.ParseFloat(v, 64)
if err != nil {
Trace("arg " + v + " as float64: " + err.Error())
break
}
l = x
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
x, err := strconv.ParseUint(v, 10, 64)
if err != nil {
Trace("arg " + v + " as int: " + err.Error())
break
}
l = x
case reflect.Struct:
Trace("can not set an struct")
}
tv.Set(reflect.ValueOf(l))
}
}
}
return nil
}
// AutoRoute // AutoRoute
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) { func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
errstr := fmt.Sprint(err) errstr := fmt.Sprint(err)
if handler, ok := ErrorMaps[errstr]; ok { if handler, ok := ErrorMaps[errstr]; ok && ErrorsShow {
handler(rw, r) handler(rw, r)
} else { } else {
if !RecoverPanic { if !RecoverPanic {
@ -341,6 +295,19 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
} }
requestPath := r.URL.Path requestPath := r.URL.Path
var requestbody []byte
if CopyRequestBody {
requestbody, _ = ioutil.ReadAll(r.Body)
r.Body.Close()
bf := bytes.NewBuffer(requestbody)
r.Body = ioutil.NopCloser(bf)
}
r.ParseMultipartForm(MaxMemory) r.ParseMultipartForm(MaxMemory)
//user defined Handler //user defined Handler
@ -429,7 +396,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
params[route.params[i]] = match params[route.params[i]] = match
} }
//reassemble query params and add to RawQuery //reassemble query params and add to RawQuery
r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery r.URL.RawQuery = url.Values(values).Encode()
//r.URL.RawQuery = url.Values(values).Encode() //r.URL.RawQuery = url.Values(values).Encode()
} }
runrouter = route runrouter = route
@ -450,12 +417,10 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
//Invoke the request handler //Invoke the request handler
vc := reflect.New(runrouter.controllerType) vc := reflect.New(runrouter.controllerType)
StructMap(vc.Elem(), r)
//call the controller init function //call the controller init function
init := vc.MethodByName("Init") init := vc.MethodByName("Init")
in := make([]reflect.Value, 2) in := make([]reflect.Value, 2)
ct := &Context{ResponseWriter: w, Request: r, Params: params} ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody}
in[0] = reflect.ValueOf(ct) in[0] = reflect.ValueOf(ct)
in[1] = reflect.ValueOf(runrouter.controllerType.Name()) in[1] = reflect.ValueOf(runrouter.controllerType.Name())
@ -468,36 +433,85 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
//if response has written,yes don't run next //if response has written,yes don't run next
if !w.started { if !w.started {
if r.Method == "GET" { if r.Method == "GET" {
method = vc.MethodByName("Get") if m, ok := runrouter.methods["get"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Get")
}
method.Call(in) method.Call(in)
} else if r.Method == "HEAD" { } else if r.Method == "HEAD" {
method = vc.MethodByName("Head") if m, ok := runrouter.methods["head"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Head")
}
method.Call(in) method.Call(in)
} else if r.Method == "DELETE" || (r.Method == "POST" && r.Form.Get("_method") == "delete") { } else if r.Method == "DELETE" || (r.Method == "POST" && r.Form.Get("_method") == "delete") {
method = vc.MethodByName("Delete") if m, ok := runrouter.methods["delete"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Delete")
}
method.Call(in) method.Call(in)
} else if r.Method == "PUT" || (r.Method == "POST" && r.Form.Get("_method") == "put") { } else if r.Method == "PUT" || (r.Method == "POST" && r.Form.Get("_method") == "put") {
method = vc.MethodByName("Put") if m, ok := runrouter.methods["put"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Put")
}
method.Call(in) method.Call(in)
} else if r.Method == "POST" { } else if r.Method == "POST" {
method = vc.MethodByName("Post") if m, ok := runrouter.methods["post"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Post")
}
method.Call(in) method.Call(in)
} else if r.Method == "PATCH" { } else if r.Method == "PATCH" {
method = vc.MethodByName("Patch") if m, ok := runrouter.methods["patch"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Patch")
}
method.Call(in) method.Call(in)
} else if r.Method == "OPTIONS" { } else if r.Method == "OPTIONS" {
method = vc.MethodByName("Options") if m, ok := runrouter.methods["options"]; ok {
method = vc.MethodByName(m)
} else if m, ok = runrouter.methods["*"]; ok {
method = vc.MethodByName(m)
} else {
method = vc.MethodByName("Options")
}
method.Call(in) method.Call(in)
} }
gotofunc := vc.Elem().FieldByName("gotofunc").String()
if gotofunc != "" {
method = vc.MethodByName(gotofunc)
if method.IsValid() {
method.Call(in)
} else {
panic("gotofunc is exists:" + gotofunc)
}
}
if !w.started { if !w.started {
if AutoRender { if AutoRender {
method = vc.MethodByName("Render") method = vc.MethodByName("Render")
method.Call(in) method.Call(in)
} }
if !w.started { method = vc.MethodByName("Finish")
method = vc.MethodByName("Finish") method.Call(in)
method.Call(in)
}
} }
} }
method = vc.MethodByName("Destructor") method = vc.MethodByName("Destructor")
@ -507,6 +521,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
//if no matches to url, throw a not found exception //if no matches to url, throw a not found exception
if !findrouter { if !findrouter {
if h, ok := ErrorMaps["404"]; ok { if h, ok := ErrorMaps["404"]; ok {
w.status = 404
h(w, r) h(w, r)
} else { } else {
http.NotFound(w, r) http.NotFound(w, r)

View File

@ -66,11 +66,11 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
Path: "/", Path: "/",
HttpOnly: true, HttpOnly: true,
Secure: false} Secure: false}
cookie.Expires = time.Now().Add(time.Duration(manager.maxlifetime) * time.Second) //cookie.Expires = time.Now().Add(time.Duration(manager.maxlifetime) * time.Second)
http.SetCookie(w, &cookie) http.SetCookie(w, &cookie)
r.AddCookie(&cookie) r.AddCookie(&cookie)
} else { } else {
cookie.Expires = time.Now().Add(time.Duration(manager.maxlifetime) * time.Second) //cookie.Expires = time.Now().Add(time.Duration(manager.maxlifetime) * time.Second)
cookie.HttpOnly = true cookie.HttpOnly = true
cookie.Path = "/" cookie.Path = "/"
http.SetCookie(w, cookie) http.SetCookie(w, cookie)

View File

@ -170,3 +170,12 @@ func Htmlunquote(src string) string {
return strings.TrimSpace(text) return strings.TrimSpace(text)
} }
func inSlice(v string, sl []string) bool {
for _, vv := range sl {
if vv == v {
return true
}
}
return false
}

99
validation/README.md Normal file
View File

@ -0,0 +1,99 @@
validation
==============
validation is a form validation for a data validation and error collecting using Go.
## Installation and tests
Install:
go get github.com/astaxie/beego/validation
Test:
go test github.com/astaxie/beego/validation
## Example
Direct Use:
import (
"github.com/astaxie/beego/validation"
"log"
)
type User struct {
Name string
Age int
}
func main() {
u := User{"man", 40}
valid := validation.Validation{}
valid.Required(u.Name, "name")
valid.MaxSize(u.Name, 15, "nameMax")
valid.Range(u.Age, 0, 140, "age")
if valid.HasErrors {
// validation does not pass
// print invalid message
for _, err := range valid.Errors {
log.Println(err.Key, err.Message)
}
}
// or use like this
if v := valid.Max(u.Age, 140); !v.Ok {
log.Println(v.Error.Key, v.Error.Message)
}
}
Struct Tag Use:
import (
"github.com/astaxie/beego/validation"
)
// validation function follow with "valid" tag
// functions divide with ";"
// parameters in parentheses "()" and divide with ","
type user struct {
Id int
Name string `valid:"Required"`
Age int `valid:"Required;Range(1, 140)"`
}
func main() {
valid := Validation{}
u := user{Name: "test", Age: 40}
b, err := valid.Valid(u)
if err != nil {
// handle error
}
if !b {
// validation does not pass
// blabla...
}
}
Struct Tag Functions:
Required
Min(min int)
Max(max int)
Range(min, max int)
MinSize(min int)
MaxSize(max int)
Length(length int)
Alpha
Numeric
AlphaNumeric
Match(regexp string) // does not support yet
NoMatch(regexp string) // does not support yet
AlphaDash
Email
IP
Base64
## LICENSE
BSD License http://creativecommons.org/licenses/BSD/

198
validation/util.go Normal file
View File

@ -0,0 +1,198 @@
package validation
import (
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
const (
VALIDTAG = "valid"
)
var (
// key: function name
// value: the number of parameters
funcs = make(Funcs)
// doesn't belong to validation functions
unFuncs = map[string]bool{
"Clear": true,
"HasErrors": true,
"ErrorMap": true,
"Error": true,
"apply": true,
"Check": true,
"Valid": true,
}
)
func init() {
v := &Validation{}
t := reflect.TypeOf(v)
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
if !unFuncs[m.Name] {
funcs[m.Name] = m.Func
}
}
}
type ValidFunc struct {
Name string
Params []interface{}
}
type Funcs map[string]reflect.Value
func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
if _, ok := f[name]; !ok {
err = fmt.Errorf("%s does not exist", name)
return
}
if len(params) != f[name].Type().NumIn() {
err = fmt.Errorf("The number of params is not adapted")
return
}
in := make([]reflect.Value, len(params))
for k, param := range params {
in[k] = reflect.ValueOf(param)
}
result = f[name].Call(in)
return
}
func isStruct(t reflect.Type) bool {
return t.Kind() == reflect.Struct
}
func isStructPtr(t reflect.Type) bool {
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}
func getValidFuncs(f reflect.StructField) (vfs []ValidFunc, err error) {
tag := f.Tag.Get(VALIDTAG)
if len(tag) == 0 {
return
}
fs := strings.Split(tag, ";")
for _, vfunc := range fs {
var vf ValidFunc
vf, err = parseFunc(vfunc)
if err != nil {
return
}
vfs = append(vfs, vf)
}
return
}
func parseFunc(vfunc string) (v ValidFunc, err error) {
defer func() {
if r := recover(); r != nil {
err = r.(error)
}
}()
vfunc = strings.TrimSpace(vfunc)
start := strings.Index(vfunc, "(")
var num int
// doesn't need parameter valid function
if start == -1 {
if num, err = numIn(vfunc); err != nil {
return
}
if num != 0 {
err = fmt.Errorf("%s require %d parameters", vfunc, num)
return
}
v = ValidFunc{vfunc, []interface{}{vfunc}}
return
}
end := strings.Index(vfunc, ")")
if end == -1 {
err = fmt.Errorf("invalid valid function")
return
}
name := strings.TrimSpace(vfunc[:start])
if num, err = numIn(name); err != nil {
return
}
params := strings.Split(vfunc[start+1:end], ",")
// the num of param must be equal
if num != len(params) {
err = fmt.Errorf("%s require %d parameters", name, num)
return
}
tParams, err := trim(name, params)
if err != nil {
return
}
v = ValidFunc{name, tParams}
return
}
func numIn(name string) (num int, err error) {
fn, ok := funcs[name]
if !ok {
err = fmt.Errorf("doesn't exsits %s valid function", name)
return
}
// sub *Validation obj and key
num = fn.Type().NumIn() - 3
return
}
func trim(name string, s []string) (ts []interface{}, err error) {
ts = make([]interface{}, len(s), len(s)+1)
fn, ok := funcs[name]
if !ok {
err = fmt.Errorf("doesn't exsits %s valid function", name)
return
}
for i := 0; i < len(s); i++ {
var param interface{}
// skip *Validation and obj params
if param, err = magic(fn.Type().In(i+2), strings.TrimSpace(s[i])); err != nil {
return
}
ts[i] = param
}
ts = append(ts, name)
return
}
// modify the parameters's type to adapt the function input parameters' type
func magic(t reflect.Type, s string) (i interface{}, err error) {
switch t.Kind() {
case reflect.Int:
i, err = strconv.Atoi(s)
case reflect.String:
i = s
case reflect.Ptr:
if t.Elem().String() != "regexp.Regexp" {
err = fmt.Errorf("does not support %s", t.Elem().String())
return
}
i, err = regexp.Compile(s)
default:
err = fmt.Errorf("does not support %s", t.Kind().String())
}
return
}
func mergeParam(v *Validation, obj interface{}, params []interface{}) []interface{} {
return append([]interface{}{v, obj}, params...)
}

77
validation/util_test.go Normal file
View File

@ -0,0 +1,77 @@
package validation
import (
"reflect"
"testing"
)
type user struct {
Id int
Tag string `valid:"Maxx(aa)"`
Name string `valid:"Required"`
Age int `valid:"Required;Range(1, 140)"`
}
func TestGetValidFuncs(t *testing.T) {
u := user{Name: "test", Age: 1}
tf := reflect.TypeOf(u)
var vfs []ValidFunc
var err error
f, _ := tf.FieldByName("Id")
if vfs, err = getValidFuncs(f); err != nil {
t.Fatal(err)
}
if len(vfs) != 0 {
t.Fatal("should get none ValidFunc")
}
f, _ = tf.FieldByName("Tag")
if vfs, err = getValidFuncs(f); err.Error() != "doesn't exsits Maxx valid function" {
t.Fatal(err)
}
f, _ = tf.FieldByName("Name")
if vfs, err = getValidFuncs(f); err != nil {
t.Fatal(err)
}
if len(vfs) != 1 {
t.Fatal("should get 1 ValidFunc")
}
if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 {
t.Error("Required funcs should be got")
}
f, _ = tf.FieldByName("Age")
if vfs, err = getValidFuncs(f); err != nil {
t.Fatal(err)
}
if len(vfs) != 2 {
t.Fatal("should get 2 ValidFunc")
}
if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 {
t.Error("Required funcs should be got")
}
if vfs[1].Name != "Range" && len(vfs[1].Params) != 2 {
t.Error("Range funcs should be got")
}
}
func TestCall(t *testing.T) {
u := user{Name: "test", Age: 180}
tf := reflect.TypeOf(u)
var vfs []ValidFunc
var err error
f, _ := tf.FieldByName("Age")
if vfs, err = getValidFuncs(f); err != nil {
t.Fatal(err)
}
valid := &Validation{}
vfs[1].Params = append([]interface{}{valid, u.Age}, vfs[1].Params...)
if _, err = funcs.Call(vfs[1].Name, vfs[1].Params...); err != nil {
t.Fatal(err)
}
if len(valid.Errors) != 1 {
t.Error("age out of range should be has an error")
}
}

207
validation/validation.go Normal file
View File

@ -0,0 +1,207 @@
package validation
import (
"fmt"
"reflect"
"regexp"
)
type ValidationError struct {
Message, Key string
}
// Returns the Message.
func (e *ValidationError) String() string {
if e == nil {
return ""
}
return e.Message
}
// A Validation context manages data validation and error messages.
type Validation struct {
Errors []*ValidationError
}
func (v *Validation) Clear() {
v.Errors = []*ValidationError{}
}
func (v *Validation) HasErrors() bool {
return len(v.Errors) > 0
}
// Return the errors mapped by key.
// If there are multiple validation errors associated with a single key, the
// first one "wins". (Typically the first validation will be the more basic).
func (v *Validation) ErrorMap() map[string]*ValidationError {
m := map[string]*ValidationError{}
for _, e := range v.Errors {
if _, ok := m[e.Key]; !ok {
m[e.Key] = e
}
}
return m
}
// Add an error to the validation context.
func (v *Validation) Error(message string, args ...interface{}) *ValidationResult {
result := (&ValidationResult{
Ok: false,
Error: &ValidationError{},
}).Message(message, args...)
v.Errors = append(v.Errors, result.Error)
return result
}
// A ValidationResult is returned from every validation method.
// It provides an indication of success, and a pointer to the Error (if any).
type ValidationResult struct {
Error *ValidationError
Ok bool
}
func (r *ValidationResult) Key(key string) *ValidationResult {
if r.Error != nil {
r.Error.Key = key
}
return r
}
func (r *ValidationResult) Message(message string, args ...interface{}) *ValidationResult {
if r.Error != nil {
if len(args) == 0 {
r.Error.Message = message
} else {
r.Error.Message = fmt.Sprintf(message, args...)
}
}
return r
}
// Test that the argument is non-nil and non-empty (if string or list)
func (v *Validation) Required(obj interface{}, key string) *ValidationResult {
return v.apply(Required{key}, obj)
}
func (v *Validation) Min(n int, min int, key string) *ValidationResult {
return v.apply(Min{min, key}, n)
}
func (v *Validation) Max(n int, max int, key string) *ValidationResult {
return v.apply(Max{max, key}, n)
}
func (v *Validation) Range(n, min, max int, key string) *ValidationResult {
return v.apply(Range{Min{Min: min}, Max{Max: max}, key}, n)
}
func (v *Validation) MinSize(obj interface{}, min int, key string) *ValidationResult {
return v.apply(MinSize{min, key}, obj)
}
func (v *Validation) MaxSize(obj interface{}, max int, key string) *ValidationResult {
return v.apply(MaxSize{max, key}, obj)
}
func (v *Validation) Length(obj interface{}, n int, key string) *ValidationResult {
return v.apply(Length{n, key}, obj)
}
func (v *Validation) Alpha(obj interface{}, key string) *ValidationResult {
return v.apply(Alpha{key}, obj)
}
func (v *Validation) Numeric(obj interface{}, key string) *ValidationResult {
return v.apply(Numeric{key}, obj)
}
func (v *Validation) AlphaNumeric(obj interface{}, key string) *ValidationResult {
return v.apply(AlphaNumeric{key}, obj)
}
func (v *Validation) Match(str string, regex *regexp.Regexp, key string) *ValidationResult {
return v.apply(Match{regex, key}, str)
}
func (v *Validation) NoMatch(str string, regex *regexp.Regexp, key string) *ValidationResult {
return v.apply(NoMatch{Match{Regexp: regex}, key}, str)
}
func (v *Validation) AlphaDash(str string, key string) *ValidationResult {
return v.apply(AlphaDash{NoMatch{Match: Match{Regexp: alphaDashPattern}}, key}, str)
}
func (v *Validation) Email(str string, key string) *ValidationResult {
return v.apply(Email{Match{Regexp: emailPattern}, key}, str)
}
func (v *Validation) IP(str string, key string) *ValidationResult {
return v.apply(IP{Match{Regexp: ipPattern}, key}, str)
}
func (v *Validation) Base64(str string, key string) *ValidationResult {
return v.apply(Base64{Match{Regexp: base64Pattern}, key}, str)
}
func (v *Validation) apply(chk Validator, obj interface{}) *ValidationResult {
if chk.IsSatisfied(obj) {
return &ValidationResult{Ok: true}
}
// Add the error to the validation context.
err := &ValidationError{
Message: chk.DefaultMessage(),
Key: chk.GetKey(),
}
v.Errors = append(v.Errors, err)
// Also return it in the result.
return &ValidationResult{
Ok: false,
Error: err,
}
}
// Apply a group of validators to a field, in order, and return the
// ValidationResult from the first one that fails, or the last one that
// succeeds.
func (v *Validation) Check(obj interface{}, checks ...Validator) *ValidationResult {
var result *ValidationResult
for _, check := range checks {
result = v.apply(check, obj)
if !result.Ok {
return result
}
}
return result
}
// the obj parameter must be a struct or a struct pointer
func (v *Validation) Valid(obj interface{}) (b bool, err error) {
objT := reflect.TypeOf(obj)
objV := reflect.ValueOf(obj)
switch {
case isStruct(objT):
case isStructPtr(objT):
objT = objT.Elem()
objV = objV.Elem()
default:
err = fmt.Errorf("%v must be a struct or a struct pointer", obj)
return
}
for i := 0; i < objT.NumField(); i++ {
var vfs []ValidFunc
if vfs, err = getValidFuncs(objT.Field(i)); err != nil {
return
}
for _, vf := range vfs {
if _, err = funcs.Call(vf.Name,
mergeParam(v, objV.Field(i).Interface(), vf.Params)...); err != nil {
return
}
}
}
return !v.HasErrors(), nil
}

View File

@ -0,0 +1,246 @@
package validation
import (
"regexp"
"testing"
"time"
)
func TestRequired(t *testing.T) {
valid := Validation{}
if valid.Required(nil, "nil").Ok {
t.Error("nil object should be false")
}
if valid.Required("", "string").Ok {
t.Error("\"'\" string should be false")
}
if !valid.Required("astaxie", "string").Ok {
t.Error("string should be true")
}
if valid.Required(0, "zero").Ok {
t.Error("Integer should not be equal 0")
}
if !valid.Required(1, "int").Ok {
t.Error("Integer except 0 should be true")
}
if !valid.Required(time.Now(), "time").Ok {
t.Error("time should be true")
}
if valid.Required([]string{}, "emptySlice").Ok {
t.Error("empty slice should be false")
}
if !valid.Required([]interface{}{"ok"}, "slice").Ok {
t.Error("slice should be true")
}
}
func TestMin(t *testing.T) {
valid := Validation{}
if valid.Min(-1, 0, "min0").Ok {
t.Error("-1 is less than the minimum value of 0 should be false")
}
if !valid.Min(1, 0, "min0").Ok {
t.Error("1 is greater or equal than the minimum value of 0 should be true")
}
}
func TestMax(t *testing.T) {
valid := Validation{}
if valid.Max(1, 0, "max0").Ok {
t.Error("1 is greater than the minimum value of 0 should be false")
}
if !valid.Max(-1, 0, "max0").Ok {
t.Error("-1 is less or equal than the maximum value of 0 should be true")
}
}
func TestRange(t *testing.T) {
valid := Validation{}
if valid.Range(-1, 0, 1, "range0_1").Ok {
t.Error("-1 is bettween 0 and 1 should be false")
}
if !valid.Range(1, 0, 1, "range0_1").Ok {
t.Error("1 is bettween 0 and 1 should be true")
}
}
func TestMinSize(t *testing.T) {
valid := Validation{}
if valid.MinSize("", 1, "minSize1").Ok {
t.Error("the length of \"\" is less than the minimum value of 1 should be false")
}
if !valid.MinSize("ok", 1, "minSize1").Ok {
t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true")
}
if valid.MinSize([]string{}, 1, "minSize1").Ok {
t.Error("the length of empty slice is less than the minimum value of 1 should be false")
}
if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok {
t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true")
}
}
func TestMaxSize(t *testing.T) {
valid := Validation{}
if valid.MaxSize("ok", 1, "maxSize1").Ok {
t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false")
}
if !valid.MaxSize("", 1, "maxSize1").Ok {
t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true")
}
if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok {
t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false")
}
if !valid.MaxSize([]string{}, 1, "maxSize1").Ok {
t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true")
}
}
func TestLength(t *testing.T) {
valid := Validation{}
if valid.Length("", 1, "length1").Ok {
t.Error("the length of \"\" must equal 1 should be false")
}
if !valid.Length("1", 1, "length1").Ok {
t.Error("the length of \"1\" must equal 1 should be true")
}
if valid.Length([]string{}, 1, "length1").Ok {
t.Error("the length of empty slice must equal 1 should be false")
}
if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok {
t.Error("the length of [\"ok\"] must equal 1 should be true")
}
}
func TestAlpha(t *testing.T) {
valid := Validation{}
if valid.Alpha("a,1-@ $", "alpha").Ok {
t.Error("\"a,1-@ $\" are valid alpha characters should be false")
}
if !valid.Alpha("abCD", "alpha").Ok {
t.Error("\"abCD\" are valid alpha characters should be true")
}
}
func TestNumeric(t *testing.T) {
valid := Validation{}
if valid.Numeric("a,1-@ $", "numeric").Ok {
t.Error("\"a,1-@ $\" are valid numeric characters should be false")
}
if !valid.Numeric("1234", "numeric").Ok {
t.Error("\"1234\" are valid numeric characters should be true")
}
}
func TestAlphaNumeric(t *testing.T) {
valid := Validation{}
if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok {
t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false")
}
if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok {
t.Error("\"1234aB\" are valid alpha or numeric characters should be true")
}
}
func TestMatch(t *testing.T) {
valid := Validation{}
if valid.Match("suchuangji@gmail", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok {
t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false")
}
if !valid.Match("suchuangji@gmail.com", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok {
t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true")
}
}
func TestNoMatch(t *testing.T) {
valid := Validation{}
if valid.NoMatch("123@gmail", regexp.MustCompile("[^\\w\\d]"), "nomatch").Ok {
t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false")
}
if !valid.NoMatch("123gmail", regexp.MustCompile("[^\\w\\d]"), "match").Ok {
t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true")
}
}
func TestAlphaDash(t *testing.T) {
valid := Validation{}
if valid.AlphaDash("a,1-@ $", "alphaDash").Ok {
t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false")
}
if !valid.AlphaDash("1234aB-_", "alphaDash").Ok {
t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true")
}
}
func TestEmail(t *testing.T) {
valid := Validation{}
if valid.Email("not@a email", "email").Ok {
t.Error("\"not@a email\" is a valid email address should be false")
}
if !valid.Email("suchuangji@gmail.com", "email").Ok {
t.Error("\"suchuangji@gmail.com\" is a valid email address should be true")
}
}
func TestIP(t *testing.T) {
valid := Validation{}
if valid.IP("11.255.255.256", "IP").Ok {
t.Error("\"11.255.255.256\" is a valid ip address should be false")
}
if !valid.IP("01.11.11.11", "IP").Ok {
t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true")
}
}
func TestBase64(t *testing.T) {
valid := Validation{}
if valid.Base64("suchuangji@gmail.com", "base64").Ok {
t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false")
}
if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok {
t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true")
}
}
func TestValid(t *testing.T) {
type user struct {
Id int
Name string `valid:"Required"`
Age int `valid:"Required;Range(1, 140)"`
}
valid := Validation{}
u := user{Name: "test", Age: 40}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Error("validation should be passed")
}
uptr := &user{Name: "test", Age: 180}
b, err = valid.Valid(uptr)
if err != nil {
t.Fatal(err)
}
if b {
t.Error("validation should not be passed")
}
}

355
validation/validators.go Normal file
View File

@ -0,0 +1,355 @@
package validation
import (
"fmt"
"reflect"
"regexp"
"time"
)
type Validator interface {
IsSatisfied(interface{}) bool
DefaultMessage() string
GetKey() string
}
type Required struct {
Key string
}
func (r Required) IsSatisfied(obj interface{}) bool {
if obj == nil {
return false
}
if str, ok := obj.(string); ok {
return len(str) > 0
}
if b, ok := obj.(bool); ok {
return b
}
if i, ok := obj.(int); ok {
return i != 0
}
if t, ok := obj.(time.Time); ok {
return !t.IsZero()
}
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Slice {
return v.Len() > 0
}
return true
}
func (r Required) DefaultMessage() string {
return "Required"
}
func (r Required) GetKey() string {
return r.Key
}
type Min struct {
Min int
Key string
}
func (m Min) IsSatisfied(obj interface{}) bool {
num, ok := obj.(int)
if ok {
return num >= m.Min
}
return false
}
func (m Min) DefaultMessage() string {
return fmt.Sprint("Minimum is ", m.Min)
}
func (m Min) GetKey() string {
return m.Key
}
type Max struct {
Max int
Key string
}
func (m Max) IsSatisfied(obj interface{}) bool {
num, ok := obj.(int)
if ok {
return num <= m.Max
}
return false
}
func (m Max) DefaultMessage() string {
return fmt.Sprint("Maximum is ", m.Max)
}
func (m Max) GetKey() string {
return m.Key
}
// Requires an integer to be within Min, Max inclusive.
type Range struct {
Min
Max
Key string
}
func (r Range) IsSatisfied(obj interface{}) bool {
return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj)
}
func (r Range) DefaultMessage() string {
return fmt.Sprint("Range is ", r.Min.Min, " to ", r.Max.Max)
}
func (r Range) GetKey() string {
return r.Key
}
// Requires an array or string to be at least a given length.
type MinSize struct {
Min int
Key string
}
func (m MinSize) IsSatisfied(obj interface{}) bool {
if str, ok := obj.(string); ok {
return len(str) >= m.Min
}
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Slice {
return v.Len() >= m.Min
}
return false
}
func (m MinSize) DefaultMessage() string {
return fmt.Sprint("Minimum size is ", m.Min)
}
func (m MinSize) GetKey() string {
return m.Key
}
// Requires an array or string to be at most a given length.
type MaxSize struct {
Max int
Key string
}
func (m MaxSize) IsSatisfied(obj interface{}) bool {
if str, ok := obj.(string); ok {
return len(str) <= m.Max
}
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Slice {
return v.Len() <= m.Max
}
return false
}
func (m MaxSize) DefaultMessage() string {
return fmt.Sprint("Maximum size is ", m.Max)
}
func (m MaxSize) GetKey() string {
return m.Key
}
// Requires an array or string to be exactly a given length.
type Length struct {
N int
Key string
}
func (l Length) IsSatisfied(obj interface{}) bool {
if str, ok := obj.(string); ok {
return len(str) == l.N
}
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Slice {
return v.Len() == l.N
}
return false
}
func (l Length) DefaultMessage() string {
return fmt.Sprint("Required length is ", l.N)
}
func (l Length) GetKey() string {
return l.Key
}
type Alpha struct {
Key string
}
func (a Alpha) IsSatisfied(obj interface{}) bool {
if str, ok := obj.(string); ok {
for _, v := range str {
if ('Z' < v || v < 'A') && ('z' < v || v < 'a') {
return false
}
}
return true
}
return false
}
func (a Alpha) DefaultMessage() string {
return fmt.Sprint("Must be valid alpha characters")
}
func (a Alpha) GetKey() string {
return a.Key
}
type Numeric struct {
Key string
}
func (n Numeric) IsSatisfied(obj interface{}) bool {
if str, ok := obj.(string); ok {
for _, v := range str {
if '9' < v || v < '0' {
return false
}
}
return true
}
return false
}
func (n Numeric) DefaultMessage() string {
return fmt.Sprint("Must be valid numeric characters")
}
func (n Numeric) GetKey() string {
return n.Key
}
type AlphaNumeric struct {
Key string
}
func (a AlphaNumeric) IsSatisfied(obj interface{}) bool {
if str, ok := obj.(string); ok {
for _, v := range str {
if ('Z' < v || v < 'A') && ('z' < v || v < 'a') && ('9' < v || v < '0') {
return false
}
}
return true
}
return false
}
func (a AlphaNumeric) DefaultMessage() string {
return fmt.Sprint("Must be valid alpha or numeric characters")
}
func (a AlphaNumeric) GetKey() string {
return a.Key
}
// Requires a string to match a given regex.
type Match struct {
Regexp *regexp.Regexp
Key string
}
func (m Match) IsSatisfied(obj interface{}) bool {
str := obj.(string)
return m.Regexp.MatchString(str)
}
func (m Match) DefaultMessage() string {
return fmt.Sprint("Must match ", m.Regexp)
}
func (m Match) GetKey() string {
return m.Key
}
// Requires a string to not match a given regex.
type NoMatch struct {
Match
Key string
}
func (n NoMatch) IsSatisfied(obj interface{}) bool {
return !n.Match.IsSatisfied(obj)
}
func (n NoMatch) DefaultMessage() string {
return fmt.Sprint("Must not match ", n.Regexp)
}
func (n NoMatch) GetKey() string {
return n.Key
}
var alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
type AlphaDash struct {
NoMatch
Key string
}
func (a AlphaDash) DefaultMessage() string {
return fmt.Sprint("Must be valid alpha or numeric or dash(-_) characters")
}
func (a AlphaDash) GetKey() string {
return a.Key
}
var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
type Email struct {
Match
Key string
}
func (e Email) DefaultMessage() string {
return fmt.Sprint("Must be a valid email address")
}
func (e Email) GetKey() string {
return e.Key
}
var ipPattern = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$")
type IP struct {
Match
Key string
}
func (i IP) DefaultMessage() string {
return fmt.Sprint("Must be a valid ip address")
}
func (i IP) GetKey() string {
return i.Key
}
var base64Pattern = regexp.MustCompile("^(?:[A-Za-z0-99+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$")
type Base64 struct {
Match
Key string
}
func (b Base64) DefaultMessage() string {
return fmt.Sprint("Must be valid base64 characters")
}
func (b Base64) GetKey() string {
return b.Key
}