1
0
mirror of https://github.com/astaxie/beego.git synced 2025-07-11 22:41:01 +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
- Displaying API documentation: [gowalker](https://github.com/Unknwon/gowalker)
- seocms: [seocms](https://github.com/chinakr/seocms)
- CMS: [toropress](https://github.com/insionng/toropress)

View File

@ -10,9 +10,10 @@ import (
"os"
"path"
"runtime"
"time"
)
const VERSION = "0.7.0"
const VERSION = "0.8.0"
var (
BeeApp *App
@ -38,8 +39,13 @@ var (
SessionSavePath string // session savepath if use mysql/redis/file this set to the connectinfo
UseFcgi bool
MaxMemory int64
EnableGzip bool // enable gzip
DirectoryIndex bool //ebable DirectoryIndex default is false
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,24 +102,39 @@ func (app *App) Run() {
}
err = fcgi.Serve(l, app.Handlers)
} else {
server := &http.Server{Handler: app.Handlers}
laddr, err := net.ResolveTCPAddr("tcp", addr)
if nil != err {
BeeLogger.Fatal("ResolveTCPAddr:", err)
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)
}
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 {
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

6
cache/cache.go vendored
View File

@ -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
@ -28,7 +30,7 @@ func Register(name string, adapter Cache) {
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) {
adapter, ok := adapters[adapterName]
if !ok {

19
cache/cache_test.go vendored
View File

@ -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
View File

@ -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()

100
cache/memory.go vendored
View File

@ -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()
@ -87,11 +153,11 @@ func (bc *MemoryCache) ClearAll() error {
return nil
}
// Start activates the file cache; it will
// Start activates the file cache; it will
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
View File

@ -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()

View File

@ -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

View File

@ -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
}
@ -37,7 +38,7 @@ func (ctx *Context) NotFound(message string) {
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"
func (ctx *Context) ContentType(ext string) {
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
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
}

View File

@ -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,16 +22,19 @@ import (
"path"
"strconv"
"strings"
"time"
)
type Controller struct {
Ctx *Context
Data map[interface{}]interface{}
ChildName string
TplNames string
Layout string
TplExt string
CruSession session.SessionStore
Ctx *Context
Data map[interface{}]interface{}
ChildName string
TplNames string
Layout string
TplExt string
_xsrf_token string
gotofunc string
CruSession session.SessionStore
}
type ControllerInterface interface {
@ -262,15 +269,15 @@ func (c *Controller) GetString(key string) string {
}
func (c *Controller) GetStrings(key string) []string {
r := c.Ctx.Request;
if r.Form == nil {
r := c.Ctx.Request
if r.Form == nil {
return []string{}
}
vs := r.Form[key]
if len(vs) > 0 {
return vs
}
return []string{}
vs := r.Form[key]
if len(vs) > 0 {
return vs
}
return []string{}
}
func (c *Controller) GetInt(key string) (int64, error) {
@ -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
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)
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
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)
- [新建项目](#-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对应同一个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) 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`之前

View File

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

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 {
*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,37 +131,14 @@ func (w *FileLogWriter) Printf(format string, v ...interface{}) {
w.Logger.Printf(format, v...)
}
func (w *FileLogWriter) DoRotate(rotate bool) 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()
}
}
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)
if err != nil {
return err
}
w.Logger = log.New(fd, "", log.Ldate|log.Ltime)
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)
@ -144,19 +151,60 @@ func (w *FileLogWriter) DoRotate(rotate bool) error {
fmt.Println(err)
}
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
} else {
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
}
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) {
os.Remove(path)
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
})

View File

@ -27,12 +27,19 @@ var ErrInitStart = errors.New("init from")
// Allows for us to notice when the connection is closed.
type conn struct {
net.Conn
wg *sync.WaitGroup
wg *sync.WaitGroup
isclose bool
lock sync.Mutex
}
func (c conn) Close() error {
c.lock.Lock()
defer c.lock.Unlock()
err := c.Conn.Close()
c.wg.Done()
if !c.isclose && err == nil {
c.wg.Done()
c.isclose = true
}
return err
}
@ -137,16 +144,15 @@ 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
}
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
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,36 +433,85 @@ 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" {
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)
} 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)
} 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)
} 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)
} 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)
} 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)
} 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)
}
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("Finish")
method.Call(in)
}
}
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 !findrouter {
if h, ok := ErrorMaps["404"]; ok {
w.status = 404
h(w, r)
} else {
http.NotFound(w, r)

View File

@ -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)

View File

@ -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
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
}