mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 15:21:02 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
ab08aa9c9e | |||
7c610ee7c9 | |||
23deaedd39 | |||
538d39a704 | |||
b961abb52b | |||
b32b12b208 | |||
e88c2be013 | |||
d5ddd0a9dd | |||
dff36a18a2 | |||
fb78d83ec3 | |||
d23700b919 | |||
92db56c0cb | |||
4c6163baa0 | |||
f46388fa63 | |||
aba1728bc3 | |||
ddb9ed39a5 | |||
a242f61b8e | |||
d19de30d9c | |||
6d05163c9f | |||
a41cd17092 | |||
ec7324e972 | |||
7f5dd13422 | |||
7f4ad7ff46 | |||
60200689f4 | |||
af3797e16c | |||
d4743fb10d | |||
38b083e117 | |||
fece5adc2a | |||
7bfb4126d7 | |||
2abe584bc5 | |||
ee9223b1b9 | |||
d2a16ff8f6 | |||
f1e5059682 | |||
11977f4f77 | |||
461eac46b9 | |||
75af664511 | |||
174298b497 | |||
9b392a0601 | |||
bf9de3bcf6 | |||
189df1280c | |||
8807c327d1 | |||
d627ec013e | |||
d0bbc67b27 | |||
453557948e | |||
4033692dcb | |||
236f28c53c | |||
b2bfed8937 | |||
71adbdd7d7 | |||
573df2e747 | |||
aa9cb6d052 | |||
8358e0ff48 | |||
1687ec85de | |||
d5fc0a4bda | |||
3bcff77947 | |||
1521842d7a | |||
b04813e472 | |||
9e41d93184 |
@ -38,4 +38,5 @@ beego is licensed under the Apache Licence, Version 2.0
|
||||
## 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)
|
||||
|
52
beego.go
52
beego.go
@ -10,9 +10,10 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
const VERSION = "0.7.0"
|
||||
const VERSION = "0.8.0"
|
||||
|
||||
var (
|
||||
BeeApp *App
|
||||
@ -40,6 +41,11 @@ var (
|
||||
MaxMemory int64
|
||||
EnableGzip bool // enable gzip
|
||||
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() {
|
||||
@ -66,6 +72,9 @@ func init() {
|
||||
EnableGzip = false
|
||||
StaticDir["/static"] = "static"
|
||||
AppConfigPath = path.Join(AppPath, "conf", "app.conf")
|
||||
HttpServerTimeOut = 0
|
||||
ErrorsShow = true
|
||||
XSRFKEY = "beegoxsrf"
|
||||
ParseConfig()
|
||||
}
|
||||
|
||||
@ -93,7 +102,12 @@ func (app *App) Run() {
|
||||
}
|
||||
err = fcgi.Serve(l, app.Handlers)
|
||||
} else {
|
||||
server := &http.Server{Handler: app.Handlers}
|
||||
if EnbaleHotUpdate {
|
||||
server := &http.Server{
|
||||
Handler: app.Handlers,
|
||||
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)
|
||||
@ -103,14 +117,24 @@ func (app *App) Run() {
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
BeeLogger.Fatal("ListenAndServe: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) Router(path string, c ControllerInterface) *App {
|
||||
app.Handlers.Add(path, c)
|
||||
func (app *App) Router(path string, c ControllerInterface, mappingMethods ...string) *App {
|
||||
app.Handlers.Add(path, c, mappingMethods...)
|
||||
return app
|
||||
}
|
||||
|
||||
@ -139,6 +163,11 @@ func (app *App) SetStaticPath(url string, path string) *App {
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *App) DelStaticPath(url string) *App {
|
||||
delete(StaticDir, url)
|
||||
return app
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -152,8 +181,14 @@ func RegisterController(path string, c ControllerInterface) *App {
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func Router(path string, c ControllerInterface) *App {
|
||||
BeeApp.Router(path, c)
|
||||
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
|
||||
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
|
||||
}
|
||||
|
||||
@ -177,6 +212,11 @@ func SetStaticPath(url string, path string) *App {
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func DelStaticPath(url string) *App {
|
||||
delete(StaticDir, url)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func Filter(filter http.HandlerFunc) *App {
|
||||
BeeApp.Filter(filter)
|
||||
return BeeApp
|
||||
|
4
cache/cache.go
vendored
4
cache/cache.go
vendored
@ -6,8 +6,10 @@ import (
|
||||
|
||||
type Cache 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
|
||||
Incr(key string) error
|
||||
Decr(key string) error
|
||||
IsExist(key string) bool
|
||||
ClearAll() error
|
||||
StartAndGC(config string) error
|
||||
|
19
cache/cache_test.go
vendored
19
cache/cache_test.go
vendored
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_cache(t *testing.T) {
|
||||
bm, err := NewCache("memory", `{"interval":60}`)
|
||||
bm, err := NewCache("memory", `{"interval":20}`)
|
||||
if err != nil {
|
||||
t.Error("init err")
|
||||
}
|
||||
@ -21,7 +21,7 @@ func Test_cache(t *testing.T) {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
time.Sleep(70 * time.Second)
|
||||
time.Sleep(30 * time.Second)
|
||||
|
||||
if bm.IsExist("astaxie") {
|
||||
t.Error("check err")
|
||||
@ -31,6 +31,21 @@ func Test_cache(t *testing.T) {
|
||||
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")
|
||||
if bm.IsExist("astaxie") {
|
||||
t.Error("delete err")
|
||||
|
10
cache/memcache.go
vendored
10
cache/memcache.go
vendored
@ -28,7 +28,7 @@ func (rc *MemcacheCache) Get(key string) interface{} {
|
||||
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 {
|
||||
rc.c = rc.connectInit()
|
||||
}
|
||||
@ -51,6 +51,14 @@ func (rc *MemcacheCache) Delete(key string) error {
|
||||
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 {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
|
98
cache/memory.go
vendored
98
cache/memory.go
vendored
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -16,12 +15,7 @@ var (
|
||||
type MemoryItem struct {
|
||||
val interface{}
|
||||
Lastaccess time.Time
|
||||
expired int
|
||||
}
|
||||
|
||||
func (itm *MemoryItem) Access() interface{} {
|
||||
itm.Lastaccess = time.Now()
|
||||
return itm.val
|
||||
expired int64
|
||||
}
|
||||
|
||||
type MemoryCache struct {
|
||||
@ -44,13 +38,21 @@ func (bc *MemoryCache) Get(name string) interface{} {
|
||||
if !ok {
|
||||
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()
|
||||
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 {
|
||||
return errors.New("the key is exist")
|
||||
} else {
|
||||
@ -73,6 +75,70 @@ func (bc *MemoryCache) Delete(name string) error {
|
||||
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 {
|
||||
bc.lock.RLock()
|
||||
defer bc.lock.RUnlock()
|
||||
@ -91,7 +157,7 @@ func (bc *MemoryCache) ClearAll() error {
|
||||
func (bc *MemoryCache) StartAndGC(config string) error {
|
||||
var cf map[string]int
|
||||
json.Unmarshal([]byte(config), &cf)
|
||||
if _, ok := cf["every"]; !ok {
|
||||
if _, ok := cf["interval"]; !ok {
|
||||
cf = make(map[string]int)
|
||||
cf["interval"] = DefaultEvery
|
||||
}
|
||||
@ -110,7 +176,7 @@ func (bc *MemoryCache) vaccuum() {
|
||||
return
|
||||
}
|
||||
for {
|
||||
<-time.After(time.Duration(bc.dur) * time.Second)
|
||||
<-time.After(bc.dur)
|
||||
if bc.items == nil {
|
||||
return
|
||||
}
|
||||
@ -128,12 +194,8 @@ func (bc *MemoryCache) item_expired(name string) bool {
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
dur := time.Now().Sub(itm.Lastaccess)
|
||||
sec, err := strconv.Atoi(fmt.Sprintf("%0.0f", dur.Seconds()))
|
||||
if err != nil {
|
||||
delete(bc.items, name)
|
||||
return true
|
||||
} else if sec >= itm.expired {
|
||||
sec := time.Now().Unix() - itm.Lastaccess.Unix()
|
||||
if sec >= itm.expired {
|
||||
delete(bc.items, name)
|
||||
return true
|
||||
}
|
||||
|
24
cache/redis.go
vendored
24
cache/redis.go
vendored
@ -31,7 +31,7 @@ func (rc *RedisCache) Get(key string) interface{} {
|
||||
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 {
|
||||
rc.c = rc.connectInit()
|
||||
}
|
||||
@ -58,6 +58,28 @@ func (rc *RedisCache) IsExist(key string) bool {
|
||||
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 {
|
||||
if rc.c == nil {
|
||||
rc.c = rc.connectInit()
|
||||
|
63
config.go
63
config.go
@ -133,49 +133,64 @@ func ParseConfig() (err error) {
|
||||
if v, err := AppConfig.Int("httpport"); err == nil {
|
||||
HttpPort = v
|
||||
}
|
||||
if v, err := AppConfig.Int64("maxmemory"); err == nil {
|
||||
MaxMemory = v
|
||||
if maxmemory, err := AppConfig.Int64("maxmemory"); err == nil {
|
||||
MaxMemory = maxmemory
|
||||
}
|
||||
AppName = AppConfig.String("appname")
|
||||
if runmode := AppConfig.String("runmode"); runmode != "" {
|
||||
RunMode = runmode
|
||||
}
|
||||
if ar, err := AppConfig.Bool("autorender"); err == nil {
|
||||
AutoRender = ar
|
||||
if autorender, err := AppConfig.Bool("autorender"); err == nil {
|
||||
AutoRender = autorender
|
||||
}
|
||||
if ar, err := AppConfig.Bool("autorecover"); err == nil {
|
||||
RecoverPanic = ar
|
||||
if autorecover, err := AppConfig.Bool("autorecover"); err == nil {
|
||||
RecoverPanic = autorecover
|
||||
}
|
||||
if ar, err := AppConfig.Bool("pprofon"); err == nil {
|
||||
PprofOn = ar
|
||||
if pprofon, err := AppConfig.Bool("pprofon"); err == nil {
|
||||
PprofOn = pprofon
|
||||
}
|
||||
if views := AppConfig.String("viewspath"); views != "" {
|
||||
ViewsPath = views
|
||||
}
|
||||
if ar, err := AppConfig.Bool("sessionon"); err == nil {
|
||||
SessionOn = ar
|
||||
if sessionon, err := AppConfig.Bool("sessionon"); err == nil {
|
||||
SessionOn = sessionon
|
||||
}
|
||||
if ar := AppConfig.String("sessionprovider"); ar != "" {
|
||||
SessionProvider = ar
|
||||
if sessProvider := AppConfig.String("sessionprovider"); sessProvider != "" {
|
||||
SessionProvider = sessProvider
|
||||
}
|
||||
if ar := AppConfig.String("sessionname"); ar != "" {
|
||||
SessionName = ar
|
||||
if sessName := AppConfig.String("sessionname"); sessName != "" {
|
||||
SessionName = sessName
|
||||
}
|
||||
if ar := AppConfig.String("sessionsavepath"); ar != "" {
|
||||
SessionSavePath = ar
|
||||
if sesssavepath := AppConfig.String("sessionsavepath"); sesssavepath != "" {
|
||||
SessionSavePath = sesssavepath
|
||||
}
|
||||
if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err == nil && ar != 0 {
|
||||
int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64)
|
||||
if sessMaxLifeTime, err := AppConfig.Int("sessiongcmaxlifetime"); err == nil && sessMaxLifeTime != 0 {
|
||||
int64val, _ := strconv.ParseInt(strconv.Itoa(sessMaxLifeTime), 10, 64)
|
||||
SessionGCMaxLifetime = int64val
|
||||
}
|
||||
if ar, err := AppConfig.Bool("usefcgi"); err == nil {
|
||||
UseFcgi = ar
|
||||
if usefcgi, err := AppConfig.Bool("usefcgi"); err == nil {
|
||||
UseFcgi = usefcgi
|
||||
}
|
||||
if ar, err := AppConfig.Bool("enablegzip"); err == nil {
|
||||
EnableGzip = ar
|
||||
if enablegzip, err := AppConfig.Bool("enablegzip"); err == nil {
|
||||
EnableGzip = enablegzip
|
||||
}
|
||||
if ar, err := AppConfig.Bool("directoryindex"); err == nil {
|
||||
DirectoryIndex = ar
|
||||
if directoryindex, err := AppConfig.Bool("directoryindex"); err == nil {
|
||||
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
|
||||
|
66
context.go
66
context.go
@ -1,16 +1,17 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
ResponseWriter http.ResponseWriter
|
||||
Request *http.Request
|
||||
RequestBody []byte
|
||||
Params map[string]string
|
||||
}
|
||||
|
||||
@ -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
|
||||
func (ctx *Context) SetCookie(name string, value string, age int64) {
|
||||
var utctime time.Time
|
||||
if age == 0 {
|
||||
// 2^31 - 1 seconds (roughly 2038)
|
||||
utctime = time.Unix(2147483647, 0)
|
||||
|
||||
//params:
|
||||
//string name
|
||||
//string value
|
||||
//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 {
|
||||
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))
|
||||
ctx.SetHeader("Set-Cookie", cookie, true)
|
||||
if len(others) > 1 {
|
||||
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
|
||||
}
|
||||
|
@ -4,9 +4,13 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/session"
|
||||
"html/template"
|
||||
"io"
|
||||
@ -18,6 +22,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
@ -27,6 +32,8 @@ type Controller struct {
|
||||
TplNames string
|
||||
Layout string
|
||||
TplExt string
|
||||
_xsrf_token string
|
||||
gotofunc string
|
||||
CruSession session.SessionStore
|
||||
}
|
||||
|
||||
@ -262,7 +269,7 @@ func (c *Controller) GetString(key string) string {
|
||||
}
|
||||
|
||||
func (c *Controller) GetStrings(key string) []string {
|
||||
r := c.Ctx.Request;
|
||||
r := c.Ctx.Request
|
||||
if r.Form == nil {
|
||||
return []string{}
|
||||
}
|
||||
@ -327,3 +334,53 @@ func (c *Controller) DelSession(name interface{}) {
|
||||
}
|
||||
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
102
docs/en/API.md
Normal 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`
|
@ -762,16 +762,29 @@ Beego has a default BeeLogger object that outputs log into stdout, and you can u
|
||||
|
||||
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 {
|
||||
beego.Critical("openfile beeapp.log:", err)
|
||||
return
|
||||
beego.Critical("NewFileWriter err", err)
|
||||
}
|
||||
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
|
||||
|
||||
|
106
docs/zh/API.md
Normal file
106
docs/zh/API.md
Normal 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`
|
@ -4,25 +4,25 @@
|
||||
|
||||
**导航**
|
||||
|
||||
- [最小应用](#-1)
|
||||
- [新建项目](#-2)
|
||||
- [开发模式](#-3)
|
||||
- [路由设置](#-4)
|
||||
- [静态文件](#-5)
|
||||
- [过滤和中间件](#-6)
|
||||
- [Controller设计](#-7)
|
||||
- [模板处理](#-8)
|
||||
- [request处理](#request)
|
||||
- [跳转和错误](#-15)
|
||||
- [response处理](#response)
|
||||
- [最小应用](#%E6%9C%80%E5%B0%8F%E5%BA%94%E7%94%A8)
|
||||
- [新建项目](#%E6%96%B0%E5%BB%BA%E9%A1%B9%E7%9B%AE)
|
||||
- [开发模式](#%E5%BC%80%E5%8F%91%E6%A8%A1%E5%BC%8F)
|
||||
- [路由设置](#%E8%B7%AF%E7%94%B1%E8%AE%BE%E7%BD%AE)
|
||||
- [静态文件](#%E9%9D%99%E6%80%81%E6%96%87%E4%BB%B6)
|
||||
- [过滤和中间件](#%E8%BF%87%E6%BB%A4%E5%92%8C%E4%B8%AD%E9%97%B4%E4%BB%B6)
|
||||
- [Controller设计](#%E6%8E%A7%E5%88%B6%E5%99%A8%E8%AE%BE%E8%AE%A1)
|
||||
- [模板处理](#%E6%A8%A1%E6%9D%BF%E5%A4%84%E7%90%86)
|
||||
- [request处理](#request%E5%A4%84%E7%90%86)
|
||||
- [跳转和错误](#%E8%B7%B3%E8%BD%AC%E5%92%8C%E9%94%99%E8%AF%AF)
|
||||
- [response处理](#response%E5%A4%84%E7%90%86)
|
||||
- [Sessions](#sessions)
|
||||
- [Cache设置](#cache)
|
||||
- [安全的Map](#map)
|
||||
- [日志处理](#-16)
|
||||
- [配置管理](#-17)
|
||||
- [beego参数](#-18)
|
||||
- [第三方应用集成](#-19)
|
||||
- [部署编译应用](#-20)
|
||||
- [Cache设置](#cache%E8%AE%BE%E7%BD%AE)
|
||||
- [安全的Map](#%E5%AE%89%E5%85%A8%E7%9A%84map)
|
||||
- [日志处理](#%E6%97%A5%E5%BF%97%E5%A4%84%E7%90%86)
|
||||
- [配置管理](#%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86)
|
||||
- [beego参数](#%E7%B3%BB%E7%BB%9F%E9%BB%98%E8%AE%A4%E5%8F%82%E6%95%B0)
|
||||
- [第三方应用集成](#%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%94%E7%94%A8%E9%9B%86%E6%88%90)
|
||||
- [部署编译应用](#%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[":ext"]
|
||||
|
||||
上面列举的是默认的请求方法名(请求的method和函数名一致,例如GET请求执行Get函数,POST请求执行Post函数),如果用户期望自定义函数名,那么可以使用如下方式:
|
||||
|
||||
beego.Router("/",&IndexController{},"*:Index")
|
||||
|
||||
使用第三个参数,第三个参数就是用来设置对应method到函数名,定义如下
|
||||
|
||||
- *表示任意的method都执行该函数
|
||||
- 使用`httpmethod:funcname`格式来展示
|
||||
- 多个不同的格式使用`;`分割
|
||||
- 多个method对应同一个funcname,method之间通过`,`来分割
|
||||
|
||||
以下是一个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) 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
|
||||
|
||||
但是这些函数调用必须在调用`StartLogger`之前。
|
||||
|
@ -43,6 +43,7 @@ beego是一个类似tornado的Go应用框架,采用了RESTFul的方式来实
|
||||
* [一步一步开发应用](Tutorial.md)
|
||||
* [beego案例](Application.md)
|
||||
* [热升级](HotUpdate.md)
|
||||
* [API应用开发入门](API.md)
|
||||
|
||||
|
||||
# API接口
|
||||
|
@ -189,6 +189,7 @@ func NotFound(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>You like 404 pages" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -204,6 +205,7 @@ func Unauthorized(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>Check the address for errors" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -220,6 +222,7 @@ func Forbidden(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>You need to log in" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
rw.WriteHeader(http.StatusForbidden)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -235,6 +238,7 @@ func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
|
||||
"<br>Please try again later." +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
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" +
|
||||
"</ul>")
|
||||
data["BeegoVersion"] = VERSION
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
|
5
example/beeapi/conf/app.conf
Normal file
5
example/beeapi/conf/app.conf
Normal file
@ -0,0 +1,5 @@
|
||||
appname = beeapi
|
||||
httpport = 8080
|
||||
runmode = dev
|
||||
autorender = false
|
||||
copyrequestbody = true
|
56
example/beeapi/controllers/default.go
Normal file
56
example/beeapi/controllers/default.go
Normal 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
20
example/beeapi/main.go
Normal 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()
|
||||
}
|
52
example/beeapi/models/object.go
Normal file
52
example/beeapi/models/object.go
Normal 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)
|
||||
}
|
116
log.go
116
log.go
@ -14,6 +14,7 @@ import (
|
||||
|
||||
type FileLogWriter struct {
|
||||
*log.Logger
|
||||
mw *MuxWriter
|
||||
// The opened file
|
||||
filename string
|
||||
|
||||
@ -26,7 +27,7 @@ type FileLogWriter struct {
|
||||
|
||||
// Rotate daily
|
||||
daily bool
|
||||
maxday int64
|
||||
maxdays int64
|
||||
daily_opendate int
|
||||
|
||||
rotate bool
|
||||
@ -34,15 +35,37 @@ type FileLogWriter struct {
|
||||
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 {
|
||||
w := &FileLogWriter{
|
||||
filename: fname,
|
||||
maxlines: 1000000,
|
||||
maxsize: 1 << 28, //256 MB
|
||||
daily: true,
|
||||
maxday: 7,
|
||||
maxdays: 7,
|
||||
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
|
||||
}
|
||||
|
||||
@ -64,16 +87,23 @@ func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
|
||||
return w
|
||||
}
|
||||
|
||||
// Set rotate daily's log keep for maxday,other will delete
|
||||
func (w *FileLogWriter) SetRotateMaxDay(maxday int64) *FileLogWriter {
|
||||
w.maxday = maxday
|
||||
// Set rotate daily's log keep for maxdays, other will delete
|
||||
func (w *FileLogWriter) SetRotateMaxDays(maxdays int64) *FileLogWriter {
|
||||
w.maxdays = maxdays
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) StartLogger() error {
|
||||
if err := w.DoRotate(false); err != nil {
|
||||
fd, err := w.createLogFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.mw.SetFd(fd)
|
||||
err = w.initFd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
BeeLogger = w
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -83,7 +113,7 @@ func (w *FileLogWriter) docheck(size int) {
|
||||
if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
|
||||
(w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
|
||||
(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)
|
||||
return
|
||||
}
|
||||
@ -101,8 +131,33 @@ func (w *FileLogWriter) Printf(format string, v ...interface{}) {
|
||||
w.Logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) DoRotate(rotate bool) error {
|
||||
if rotate {
|
||||
func (w *FileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||
return fd, err
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) initFd() error {
|
||||
fd := w.mw.fd
|
||||
finfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxsize_cursize = int(finfo.Size())
|
||||
w.daily_opendate = time.Now().Day()
|
||||
if finfo.Size() > 0 {
|
||||
content, err := ioutil.ReadFile(w.filename)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
|
||||
} else {
|
||||
w.maxlines_curlines = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) DoRotate() error {
|
||||
_, err := os.Lstat(w.filename)
|
||||
if err == nil { // file exists
|
||||
// Find the next available number
|
||||
@ -117,47 +172,40 @@ func (w *FileLogWriter) DoRotate(rotate bool) error {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Logger = log.New(fd, "", log.Ldate|log.Ltime)
|
||||
finfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxsize_cursize = int(finfo.Size())
|
||||
w.daily_opendate = time.Now().Day()
|
||||
if finfo.Size() > 0 {
|
||||
content, err := ioutil.ReadFile(w.filename)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
|
||||
|
||||
} else {
|
||||
w.maxlines_curlines = 0
|
||||
}
|
||||
BeeLogger = w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *FileLogWriter) deleteOldLog() {
|
||||
dir := path.Dir(w.filename)
|
||||
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) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.filename)) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
10
reload.go
10
reload.go
@ -28,11 +28,18 @@ var ErrInitStart = errors.New("init from")
|
||||
type conn struct {
|
||||
net.Conn
|
||||
wg *sync.WaitGroup
|
||||
isclose bool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (c conn) Close() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
err := c.Conn.Close()
|
||||
if !c.isclose && err == nil {
|
||||
c.wg.Done()
|
||||
c.isclose = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@ -137,7 +144,7 @@ func GetInitListner(tcpaddr *net.TCPAddr) (l net.Listener, err error) {
|
||||
countStr := os.Getenv(FDKey)
|
||||
if countStr == "" {
|
||||
return net.ListenTCP("tcp", tcpaddr)
|
||||
} else {
|
||||
}
|
||||
count, err := strconv.Atoi(countStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -149,4 +156,3 @@ func GetInitListner(tcpaddr *net.TCPAddr) (l net.Listener, err error) {
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
}
|
||||
|
203
router.go
203
router.go
@ -1,26 +1,26 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
sc *Controller = &Controller{}
|
||||
)
|
||||
var HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head"}
|
||||
|
||||
type controllerInfo struct {
|
||||
pattern string
|
||||
regex *regexp.Regexp
|
||||
params map[int]string
|
||||
controllerType reflect.Type
|
||||
methods map[string]string
|
||||
}
|
||||
|
||||
type userHandler struct {
|
||||
@ -41,7 +41,16 @@ func NewControllerRegistor() *ControllerRegistor {
|
||||
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, "/")
|
||||
|
||||
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 {
|
||||
//now create the Route
|
||||
t := reflect.Indirect(reflect.ValueOf(c)).Type()
|
||||
route := &controllerInfo{}
|
||||
route.pattern = pattern
|
||||
route.controllerType = t
|
||||
|
||||
route.methods = methods
|
||||
p.fixrouters = append(p.fixrouters, route)
|
||||
} else { // add regexp routers
|
||||
//recreate the url pattern, with parameters replaced
|
||||
@ -105,11 +137,12 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
|
||||
}
|
||||
|
||||
//now create the Route
|
||||
t := reflect.Indirect(reflect.ValueOf(c)).Type()
|
||||
|
||||
route := &controllerInfo{}
|
||||
route.regex = regex
|
||||
route.params = params
|
||||
route.pattern = pattern
|
||||
route.methods = methods
|
||||
route.controllerType = t
|
||||
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
|
||||
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
errstr := fmt.Sprint(err)
|
||||
if handler, ok := ErrorMaps[errstr]; ok {
|
||||
if handler, ok := ErrorMaps[errstr]; ok && ErrorsShow {
|
||||
handler(rw, r)
|
||||
} else {
|
||||
if !RecoverPanic {
|
||||
@ -341,6 +295,19 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
//user defined Handler
|
||||
@ -429,7 +396,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
params[route.params[i]] = match
|
||||
}
|
||||
//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()
|
||||
}
|
||||
runrouter = route
|
||||
@ -450,12 +417,10 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
//Invoke the request handler
|
||||
vc := reflect.New(runrouter.controllerType)
|
||||
|
||||
StructMap(vc.Elem(), r)
|
||||
|
||||
//call the controller init function
|
||||
init := vc.MethodByName("Init")
|
||||
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[1] = reflect.ValueOf(runrouter.controllerType.Name())
|
||||
@ -468,38 +433,87 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
//if response has written,yes don't run next
|
||||
if !w.started {
|
||||
if r.Method == "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)
|
||||
} else if r.Method == "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)
|
||||
} else if r.Method == "DELETE" || (r.Method == "POST" && r.Form.Get("_method") == "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)
|
||||
} else if r.Method == "PUT" || (r.Method == "POST" && r.Form.Get("_method") == "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)
|
||||
} else if r.Method == "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)
|
||||
} else if r.Method == "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)
|
||||
} else if r.Method == "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)
|
||||
}
|
||||
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 AutoRender {
|
||||
method = vc.MethodByName("Render")
|
||||
method.Call(in)
|
||||
}
|
||||
if !w.started {
|
||||
method = vc.MethodByName("Finish")
|
||||
method.Call(in)
|
||||
}
|
||||
}
|
||||
}
|
||||
method = vc.MethodByName("Destructor")
|
||||
method.Call(in)
|
||||
}
|
||||
@ -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 !findrouter {
|
||||
if h, ok := ErrorMaps["404"]; ok {
|
||||
w.status = 404
|
||||
h(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
|
@ -66,11 +66,11 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
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)
|
||||
r.AddCookie(&cookie)
|
||||
} 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.Path = "/"
|
||||
http.SetCookie(w, cookie)
|
||||
|
9
utils.go
9
utils.go
@ -170,3 +170,12 @@ func Htmlunquote(src string) string {
|
||||
|
||||
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
99
validation/README.md
Normal 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
198
validation/util.go
Normal 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
77
validation/util_test.go
Normal 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
207
validation/validation.go
Normal 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
|
||||
}
|
246
validation/validation_test.go
Normal file
246
validation/validation_test.go
Normal 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
355
validation/validators.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user