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

110 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
309f2e199b add tag to 0.7.0 2013-06-25 11:42:18 +08:00
26baf0f283 update docs for logs & DirectoryIndex 2013-06-25 11:36:25 +08:00
23ff422826 if exist file then read lines err 2013-06-25 11:19:03 +08:00
d99ae3e3a2 add logrotate's setting for rotate 2013-06-25 08:13:06 +08:00
4b09175d27 fix error info tempalte's title 2013-06-24 23:59:06 +08:00
512ddf8a70 fix small bug for not return 2013-06-24 23:56:53 +08:00
73d45b150c log support file & logrotate 2013-06-24 23:24:33 +08:00
c5a23d5cde fix #81 2013-06-24 23:24:33 +08:00
3578bb3287 fix bug substr 2013-06-24 23:24:33 +08:00
40c7b47c24 Merge pull request #76 from matrixik/patch-1
Update: README
2013-06-22 07:49:03 -07:00
f56e7737ce Update: README 2013-06-22 13:17:50 +02:00
86ddfe5375 Merge pull request #75 from matrixik/master
Small addition to supervisord en docs
2013-06-21 07:38:40 -07:00
1c4d0d72ca Update: supervisord en docs 2013-06-21 15:47:58 +02:00
20e101a42d Merge pull request #74 from matrixik/master
Clean and fix docs markdown
2013-06-21 01:59:53 -07:00
10417dd6d5 Update: clean and fix README markdown 2013-06-21 10:40:58 +02:00
541d7f71b9 Update: clean and fix docs markdown 2013-06-21 10:35:46 +02:00
fdb94aeba0 Fix: install en docs links 2013-06-21 08:59:23 +02:00
67511deea7 Merge pull request #73 from matrixik/master
Some docs updates
2013-06-20 07:28:19 -07:00
43f588b5c1 Fix: session README 2013-06-20 15:25:39 +03:00
cc6c84b317 Update: session README 2013-06-20 14:22:06 +02:00
b68a37a115 Some updates to cache README 2013-06-20 13:58:35 +02:00
b886ed59a5 Update cache docs 2013-06-20 14:37:26 +03:00
3475685233 add Server info 2013-06-20 15:49:43 +08:00
0abe3e2157 add build status 2013-06-19 22:27:27 +08:00
25cf08159a Merge pull request #72 from Unknwon/master
documentation for hot update
2013-06-18 03:08:37 -07:00
28d0738cd1 doc_en:hot update 2013-06-18 18:02:51 +08:00
6a260ca76d Merge pull request #71 from yecrane/master
form获取相同名字的对象数组值
2013-06-17 01:03:04 -07:00
27896c5700 Update controller.go
form获取相同名字的对象数组值
2013-06-17 16:12:35 +09:00
384fa8fda9 Merge pull request #70 from luw2007/luw2007
fix jsonp, use Buffer
2013-06-13 18:50:26 -07:00
lw
5d531d3da6 fix jsonp, use Buffer 2013-06-14 09:39:56 +08:00
ea756b68c4 Merge pull request #68 from luw2007/luw2007
feture: add jsonp
2013-06-13 02:43:33 -07:00
lw
06e002cf2c feture: add jsonp 2013-06-13 17:17:54 +08:00
73f0f4f31f Merge pull request #59 from lunny/master
Add a basic map for url params to controller properties.
2013-06-12 08:48:43 -07:00
b2abb4eb80 fix #66 2013-06-11 23:31:40 +08:00
951881b96d update typo 2013-06-07 23:36:01 +08:00
832f160cc8 update 2013-06-07 23:33:45 +08:00
2bbd21ab07 update docs for hotupdate 2013-06-07 22:45:58 +08:00
455367769e support hot-update 2013-06-07 17:48:14 +08:00
f20ad0916f bug fix 2013-06-04 19:45:48 +08:00
be9290364d Merge pull request #62 from coderhaoxin/master
add .gitignore file
2013-06-01 00:39:52 -07:00
4577bfb980 add git ignore file 2013-06-01 13:31:14 +08:00
65041aae71 remove nonuse parseForm and change the StructMap's params 2013-05-31 14:07:06 +08:00
8b7cba037e add url params -> controller's member feature 2013-05-28 16:43:23 +08:00
2dee30183d add structmap function for controller 2013-05-28 13:58:42 +08:00
4fc49b5803 Merge pull request #58 from yecrane/master
修改redis重启后不会自动连接
2013-05-22 07:28:34 -07:00
67695fac9e Update sess_redis.go
修改redis重启后不会自动连接
2013-05-22 22:26:26 +08:00
22510bc272 Merge pull request #56 from yecrane/master
修改redis连接池,设置key,获取key的方式
2013-05-14 18:52:33 -07:00
a2dd859e55 Update sess_redis.go 2013-05-14 23:55:50 +08:00
4d06fec52e Merge pull request #55 from Unknwon/master
sync qucikstart.md to ZH version.
2013-05-13 22:23:32 -07:00
0e916a00f9 sync. quick start.md with ZH version. 2013-05-14 01:08:20 -04:00
ea36f80470 fix typo 2013-05-14 11:35:07 +08:00
1745dd2261 Merge pull request #54 from slene/master
fix session file write bug
2013-05-13 18:31:05 -07:00
6d3486e2a6 fix session file write bug 2013-05-14 07:33:00 +08:00
47 changed files with 3468 additions and 654 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

View File

@ -1,32 +1,42 @@
## beego
beego is a Go Framework which is inspired from tornado and sinatra.
[![Build Status](https://drone.io/github.com/astaxie/beego/status.png)](https://drone.io/github.com/astaxie/beego/latest)
beego is a Go Framework which is inspired from tornado and sinatra.
It is a simply & powerful web framework.
Have this features:
## Features
* RESTFul support
* MVC architecture
* Session support memory/file/redis/mysql
* Cache support memory/redis/memcache
* Session support (store in memory, file, Redis or MySQL)
* Cache support (store in memory, Redis or Memcache)
* Global Config
* Intelligent routing
* thread safe map
* Friendly error display
* Thread-safe map
* Friendly displaying of errors
* Useful template functions
[English Documents](https://github.com/astaxie/beego/tree/master/docs/en)
## Documentation
[English](https://github.com/astaxie/beego/tree/master/docs/en)
[API](http://gowalker.org/github.com/astaxie/beego)
[中文文档](https://github.com/astaxie/beego/tree/master/docs/zh)
## LICENSE
beego is licensed under the Apache Licence, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0.html).
## Use Case
- API documentation [gowalker](https://github.com/Unknwon/gowalker)
- CMS [toropress](https://github.com/insionng/toropress)
## 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.6.0"
const VERSION = "0.8.0"
var (
BeeApp *App
@ -29,16 +30,22 @@ var (
ViewsPath string
RunMode string //"dev" or "prod"
AppConfig *Config
//related to session
//related to session
GlobalSessions *session.Manager //GlobalSessions
SessionOn bool // wheather auto start session,default is false
SessionProvider string // default session provider memory mysql redis
SessionProvider string // default session provider memory mysql redis
SessionName string // sessionName cookie's name
SessionGCMaxLifetime int64 // session's gc maxlifetime
SessionSavePath string // session savepath if use mysql/redis/file this set to the connectinfo
UseFcgi bool
MaxMemory int64
EnableGzip bool // enable gzip
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() {
@ -65,6 +72,9 @@ func init() {
EnableGzip = false
StaticDir["/static"] = "static"
AppConfigPath = path.Join(AppPath, "conf", "app.conf")
HttpServerTimeOut = 0
ErrorsShow = true
XSRFKEY = "beegoxsrf"
ParseConfig()
}
@ -81,23 +91,50 @@ func NewApp() *App {
func (app *App) Run() {
addr := fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
var err error
var (
err error
l net.Listener
)
if UseFcgi {
l, e := net.Listen("tcp", addr)
if e != nil {
BeeLogger.Fatal("Listen: ", e)
l, err = net.Listen("tcp", addr)
if err != nil {
BeeLogger.Fatal("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
} else {
err = http.ListenAndServe(addr, 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)
}
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()
}
}
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
}
@ -126,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)
}
@ -139,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
}
@ -164,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

49
cache/README.md vendored
View File

@ -1,52 +1,59 @@
## cache
cache is a golang cache manager. It can use cache for many adapters. The repo is inspired by `database/sql` .
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
##How to install
## How to install?
go get github.com/astaxie/beego/cache
##how many adapter support
Now this cache support memory/redis/memcache
## how to use it
first you must import it
## What adapters are supported?
As of now this cache support memory, Memcache and Redis.
## How to use it?
First you must import it
import (
"github.com/astaxie/beego/cache"
)
then init an Cache(memory adapter)
Then init a Cache (example with memory adapter)
bm, err := NewCache("memory", `{"interval":60}`)
use it like this:
Use it like this:
bm.Put("astaxie", 1, 10)
bm.Get("astaxie")
bm.IsExist("astaxie")
bm.Delete("astaxie")
## memory adapter
memory adapter config like this:
## Memory adapter
Configure memory adapter like this:
{"interval":60}
interval means the gc time. The cache will every interval time to check wheather have item expired.
interval means the gc time. The cache will check at each time interval, whether item has expired.
## memcache adapter
memory adapter use the vitess's [memcache](code.google.com/p/vitess/go/memcache) client.
the config like this:
## Memcache adapter
memory adapter use the vitess's [Memcache](http://code.google.com/p/vitess/go/memcache) client.
Configure like this:
{"conn":"127.0.0.1:11211"}
## redis adapter
redis adapter use the [redigo](github.com/garyburd/redigo/redis) client.
## Redis adapter
the config like this:
Redis adapter use the [redigo](http://github.com/garyburd/redigo/redis) client.
{"conn":":6039"}
Configure like this:
{"conn":":6039"}

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 is json {"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,46 +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 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 {
@ -212,10 +219,30 @@ func (c *Controller) ServeJson() {
return
}
c.Ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json")
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8")
c.Ctx.ResponseWriter.Write(content)
}
func (c *Controller) ServeJsonp() {
content, err := json.MarshalIndent(c.Data["jsonp"], "", " ")
if err != nil {
http.Error(c.Ctx.ResponseWriter, err.Error(), http.StatusInternalServerError)
return
}
callback := c.Ctx.Request.Form.Get("callback")
if callback == "" {
http.Error(c.Ctx.ResponseWriter, `"callback" parameter required`, http.StatusInternalServerError)
return
}
callback_content := bytes.NewBufferString(callback)
callback_content.WriteString("(")
callback_content.Write(content)
callback_content.WriteString(");\r\n")
c.Ctx.SetHeader("Content-Length", strconv.Itoa(callback_content.Len()), true)
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json;charset=UTF-8")
c.Ctx.ResponseWriter.Write(callback_content.Bytes())
}
func (c *Controller) ServeXml() {
content, err := xml.Marshal(c.Data["xml"])
if err != nil {
@ -223,7 +250,7 @@ func (c *Controller) ServeXml() {
return
}
c.Ctx.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/xml")
c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/xml;charset=UTF-8")
c.Ctx.ResponseWriter.Write(content)
}
@ -241,6 +268,18 @@ func (c *Controller) GetString(key string) string {
return c.Input().Get(key)
}
func (c *Controller) GetStrings(key string) []string {
r := c.Ctx.Request
if r.Form == nil {
return []string{}
}
vs := r.Form[key]
if len(vs) > 0 {
return vs
}
return []string{}
}
func (c *Controller) GetInt(key string) (int64, error) {
return strconv.ParseInt(c.Input().Get(key), 10, 64)
}
@ -295,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`

46
docs/en/HotUpdate.md Normal file
View File

@ -0,0 +1,46 @@
## What is hot update?
If you have used nginx, you may know that nginx supports hot update, which means you can update your nginx without stopping and restarting it. It serves old connections with old version, and accepts new connections with new version. Notice that hot compiling is different from hot update, where hot compiling is monitoring your source files and recompile them when the content changes, it requires stop and restart your applications, `bee start` is a tool for hot compiling.
## Is hot update necessary?
Some people says that hot update is not as useful as its cool name. In my opinion, this is absolutely necessary because zero-down server is our goal for our services. Even though sometimes some errors or hardware problems may occur, but it belongs to design of high availability, don't mix them up. Service update is a known issue, so we need to fix this problem.
## How Beego support hot update?
The basic principle of hot update: main process fork a process, and child process execute corresponding programs. So what happens? We know that after forked a process, main process will have all handles, data and stack, etc, but all handles are saved in `CloseOnExec`, so all copied handles will be closed when you execute it unless you clarify this, and we need child process to reuse the handle of `net.Listener`. Once a process calls exec functions, it is "dead", system replaces it with new code. The only thing it left is the process ID, which is the same number but it is a new program after executed.
Therefore, the first thing we need to do is that let child process fork main process and through `os.StartProcess` to append files that contains handle that is going to be inherited.
The second step is that we hope child process can start listening from same handle, so we can use `net.FileListener` to achieve this goal. Here we also need FD of this file, so we need to add a environment variable to set this FD before we start child process.
The final step is that we want to serve old connections with old version of application, and serve new connections with new version. So how can we know if there is any old connections? To do this, we have to record all connections, then we are able to know. Another problem is that how to let new application to accept connection? Because two versions of applications are listening to same port, they get connection request randomly, so we just close old accept function, and it will get error in `l.Accept`.
Above are three problems that we need to solve, you can see my code logic for specific implementation.
## Show time
1. Write code in your Get method:
func (this *MainController) Get() {
a, _ := this.GetInt("sleep")
time.Sleep(time.Duration(a) * time.Second)
this.Ctx.WriteString("ospid:" + strconv.Itoa(os.Getpid()))
}
2. Open two terminals:
One execute: ` ps -ef|grep <application name>`
Another one execute `curl "http://127.0.0.1:8080/?sleep=20"`
3. Hot update
`kill -HUP <PID>`
4. Open a terminal to request connection: `curl "http://127.0.0.1:8080/?sleep=0"`
As you will see, the first request will wait for 20 seconds, but it's served by old process; after hot update, the first request will print old process ID, but the second request will print new process ID.

View File

@ -1,25 +1,26 @@
#Installation
# Installation
Beego is a simple web framework, but it uses many third-party packages, so you have to install all dependency packages also.
- Before anything you do, you have to check that you installed Go in your computer, see more detail about Go installation in my book: [Chapter 1](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/01.1.md)
- Use `go get ` to install Beego:
- Use `go get ` to install Beego:
go get github.com/astaxie/beego
- Install bee tools for fast-develop Beego applications:
go get github.com/astaxie/bee
- Install bee tools for fast-develop Beego applications:
Good job, you're ready to Beego with powerful bee tools!
go get github.com/astaxie/bee
Good job, you're ready to Beego with powerful bee tools!
![](images/bee.png)
Beego has following dependency packages:
- Session module: [github.com/astaxie/beego/session](github.com/astaxie/beego/session)
- To support redis engine: [github.com/garyburd/redigo/redis](github.com/garyburd/redigo/redis)
- To support mysql engine: [github.com/go-sql-driver/mysql](github.com/go-sql-driver/mysql)
- To support markdown as template function: [github.com/russross/blackfriday](github.com/russross/blackfriday)
- Session module: [github.com/astaxie/beego/session](https://github.com/astaxie/beego/session)
- To support redis engine: [github.com/garyburd/redigo/redis](https://github.com/garyburd/redigo/redis)
- To support mysql engine: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
- To support markdown as template function: [github.com/russross/blackfriday](https://github.com/russross/blackfriday)
- [Introduction](README.md)
- [Quick start](Quickstart.md)
- [Quick start](Quickstart.md)

View File

@ -1,4 +1,5 @@
# Quick start
Hey, you say you've never heard about Beego and don't know how to use it? Don't worry, after you read this section, you will know a lot about Beego. Before you start reading, make sure you installed Beego in your computer, if not, check this tutorial: [Installation](Install.md)
**Navigation**
@ -23,11 +24,13 @@ Hey, you say you've never heard about Beego and don't know how to use it? Don't
- [Integrated third-party applications](#integrated-third-party-applications)
- [Deployment](#deployment)
## Hello world
This is an example of "Hello world" in Beego:
package main
import (
"github.com/astaxie/beego"
)
@ -54,18 +57,25 @@ Open address [http://127.0.0.1:8080](http://127.0.0.1:8080) in your browser and
What happened in behind above example?
1. We import package `github.com/astaxie/beego`. As we know that Go initialize packages and runs init() function in every package(more detail [here](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/02.3.md#main-function-and-init-function)), so Beego initializes the BeeApp application at this time.
1. We import package `github.com/astaxie/beego`. As we know that Go initialize packages and runs init() function in every package ([more details](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/02.3.md#main-function-and-init-function)), so Beego initializes the BeeApp application at this time.
2. Define controller. We define a struct called `MainController` with a anonymous field `beego.Controller`, so the `MainController` has all methods that `beego.Controller` has.
3. Define RESTful methods. Once we use anonymous combination, `MainController` has already had `Get`, `Post`, `Delete`, `Put` and other methods, these methods will be called when user sends corresponding request, like `Post` method for requests that are using POST method. Therefore, after we overloaded `Get` method in `MainController`, all GET requests will use `Get` method in `MainController` instead of in `beego.Controller`.
4. Define main function. All applications in Go use main function as entry point as C does.
5. Register routers, it tells Beego which controller is responsibility for specific requests. Here we register `/` for `MainController`, so all requests in `/` will be handed to `MainController`. Be aware that the first argument is the path and the second one is pointer of controller that you want to register.
6. Run application in port 8080 as default, press `Ctrl+c` to exit.
## New project
Get into your $GOPATH, then use following command to setup Beego project:
bee create hello
It generates folders and files for your project, directory structure as follows:
.
@ -82,7 +92,9 @@ It generates folders and files for your project, directory structure as follows:
└── views
└── index.tpl
## Development mode
Beego uses development mode as default, you can use following code to change mode in your application:
beego.RunMode = "pro"
@ -95,7 +107,7 @@ No differences between two ways.
In development mode, you have following effects:
- If you don't have directory `views`, it prints following error prompt:
- If you don't have directory `views`, it prints following error prompt:
2013/04/13 19:36:17 [W] [stat views: no such file or directory]
@ -104,7 +116,9 @@ In development mode, you have following effects:
![](images/dev.png)
## Router
The main function of router is to connect request URL and handler. Beego wrapped `Controller`, so it connects request URL and `ControllerInterface`. The `ControllerInterface` has following methods:
type ControllerInterface interface {
@ -132,39 +146,41 @@ Users can use following ways to register route rules:
For more convenient configure route rules, Beego references the idea from sinatra, so it supports more kinds of route rules as follows:
- beego.Router("/api/:id([0-9]+)", &controllers.RController{})
- beego.Router("/api/:id([0-9]+)", &controllers.RController{})
Customized regular expression match // match /api/123 :id= 123
Customized regular expression match // match /api/123 :id= 123
- beego.Router("/news/:all", &controllers.RController{})
- beego.Router("/news/:all", &controllers.RController{})
Match rest of all // match /news/path/to/123.html :all= path/to/123.html
- beego.Router("/user/:username([\w]+)", &controllers.RController{})
- beego.Router("/user/:username([\w]+)", &controllers.RController{})
Regular expression // match /user/astaxie :username = astaxie
- beego.Router("/download/`*`.`*`", &controllers.RController{})
- beego.Router("/download/`*`.`*`", &controllers.RController{})
Wildcard character // match /download/file/api.xml :path= file/api :ext=xml
- beego.Router("/download/ceshi/`*`", &controllers.RController{})
- beego.Router("/download/ceshi/`*`", &controllers.RController{})
wildcard character match rest of all // match /download/ceshi/file/api.json :splat=file/api.json
- beego.Router("/:id:int", &controllers.RController{})
- beego.Router("/:id:int", &controllers.RController{})
Match type int // match :id is int type, Beego uses regular expression ([0-9]+) automatically
- beego.Router("/:hi:string", &controllers.RController{})
- beego.Router("/:hi:string", &controllers.RController{})
Match type string // match :hi is string type, Beego uses regular expression ([\w]+) automatically
##Static files
## Static files
Go provides `http.ServeFile` for static files, Beego wrapped this function and use following way to register static file folder:
beego.SetStaticPath("/static","public")
- The first argument is the path of your URL.
- The second argument is the directory in your application path.
@ -176,37 +192,41 @@ Beego supports multiple static file directories as follows:
After you setting static directory, when users visit `/images/login/login.png`Beego accesses `images/login/login.png` in related to your application directory. One more example, if users visit `/static/img/logo.png`, Beego accesses file `public/img/logo.png`.
##Filter and middleware
## Filter and middleware
Beego supports customized filter and middleware, such as security verification, force redirect, etc.
Here is an example of verify user name of all requests, check if it's admin.
var FilterUser = func(w http.ResponseWriter, r *http.Request) {
if r.URL.User == nil || r.URL.User.Username() != "admin" {
http.Error(w, "", http.StatusUnauthorized)
}
if r.URL.User == nil || r.URL.User.Username() != "admin" {
http.Error(w, "", http.StatusUnauthorized)
}
}
beego.Filter(FilterUser)
beego.Filter(FilterUser)
You can also filter by arguments:
beego.Router("/:id([0-9]+)", &admin.EditController{})
beego.FilterParam("id", func(rw http.ResponseWriter, r *http.Request) {
dosomething()
dosomething()
})
Filter by prefix is also available:
beego.FilterPrefixPath("/admin", func(rw http.ResponseWriter, r *http.Request) {
dosomething()
dosomething()
})
##Controller
## Controller
Use `beego.controller` as anonymous in your controller struct to implement the interface in Beego:
type xxxController struct {
beego.Controller
beego.Controller
}
`beego.Controller` implemented`beego.ControllerInterface`, `beego.ControllerInterface` defined following methods:
@ -214,15 +234,15 @@ Use `beego.controller` as anonymous in your controller struct to implement the i
- Init(ct `*`Context, cn string)
Initialize context, controller's name, template's name, and container of template arguments
- Prepare()
This is for expend usages, it executes before all the following methods. Users can overload this method for verification for example.
- Get()
This method executes when client sends request as GET method, 403 as default status code. Users overload this method for customized handle process of GET method.
- Post()
This method executes when client sends request as POST method, 403 as default status code. Users overload this method for customized handle process of POST method.
@ -258,7 +278,7 @@ Use `beego.controller` as anonymous in your controller struct to implement the i
Overload all methods for all customized logic processes, let's see an example:
type AddController struct {
beego.Controller
beego.Controller
}
func (this *AddController) Prepare() {
@ -266,54 +286,64 @@ Overload all methods for all customized logic processes, let's see an example:
}
func (this *AddController) Get() {
this.Data["content"] ="value"
this.Layout = "admin/layout.html"
this.TplNames = "admin/add.tpl"
this.Data["content"] = "value"
this.Layout = "admin/layout.html"
this.TplNames = "admin/add.tpl"
}
func (this *AddController) Post() {
pkgname := this.GetString("pkgname")
content := this.GetString("content")
pk := models.GetCruPkg(pkgname)
if pk.Id == 0 {
var pp models.PkgEntity
pp.Pid = 0
pp.Pathname = pkgname
pp.Intro = pkgname
models.InsertPkg(pp)
pk = models.GetCruPkg(pkgname)
}
var at models.Article
at.Pkgid = pk.Id
at.Content = content
models.InsertArticle(at)
this.Ctx.Redirect(302, "/admin/index")
}
pkgname := this.GetString("pkgname")
content := this.GetString("content")
pk := models.GetCruPkg(pkgname)
if pk.Id == 0 {
var pp models.PkgEntity
pp.Pid = 0
pp.Pathname = pkgname
pp.Intro = pkgname
models.InsertPkg(pp)
pk = models.GetCruPkg(pkgname)
}
var at models.Article
at.Pkgid = pk.Id
at.Content = content
models.InsertArticle(at)
this.Ctx.Redirect(302, "/admin/index")
}
## Template
### Template directory
##Template
###Template directory
Beego uses `views` as the default directory for template files, parses and caches them as needed(cache is not enable in develop mode), but you can **change**(because only one directory can be used for template files) its directory using following code:
beego.ViewsPath = "/myviewpath"
###Auto-render
### Auto-render
You don't need to call render function manually, Beego calls it automatically after corresponding methods executed. If your application is somehow doesn't need templates, you can disable this feature either in code of `main.go` or configuration file.
To disable auto-render in configuration file:
autorender = false
To disable auto-render in `main.go`(before you call `beego.Run()` to run the application):
beego.AutoRender = false
###Template data
### Template data
You can use `this.Data` in controller methods to access the data in templates. Suppose you want to get content of `{{.Content}}`, you can use following code to do this:
this.Data["Context"] = "value"
###Template name
Beego uses built-in template engine of Go, so there is no different in syntax. As for how to write template file, please visit [Template tutorial](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/07.4.md)。
### Template name
Beego uses built-in template engine of Go, so there is no different in syntax. As for how to write template file, please visit [Template tutorial](https://github.com/Unknwon/build-web-application-with-golang_EN/blob/master/eBook/07.4.md).
Beego parses template files in `viewpath` and render it after you set the name of the template file in controller methods. For example, Beego finds the file `add.tpl` in directory `admin` in following code:
@ -329,16 +359,18 @@ If you enabled auto-render and you don't tell Beego which template file you are
Which is `<corresponding controller name>/<request method name>.<template extension>`. For example, your controller name is `AddController` and the request method is POST, and the default file extension is `tpl`, so Beego will try to find file `/<viewpath>/AddController/POST.tpl`.
###Layout design
### Layout design
Beego supports layout design, which means if you are working on an administration application, and some part of its user interface is exactly same all the time, then you can make this part as a layout.
this.Layout = "admin/layout.html"
this.TplNames = "admin/add.tpl"
You have to set following variable in order to make Beego possible to insert your dynamic content:
{{.LayoutContent}}
Beego parses template file and assign content to `LayoutContent`, and render them together.
Right now, Beego caches all template files, so you can use following way to implement another kind of layout:
@ -347,12 +379,14 @@ Right now, Beego caches all template files, so you can use following way to impl
Handle logic
{{template "footer.html"}}
###Template function
### Template function
Beego supports customized template functions that are registered before you call `beego.Run()`.
func hello(in string)(out string){
out = in + "world"
return
out = in + "world"
return
}
beego.AddFuncMap("hi",hello)
@ -363,35 +397,45 @@ Then you can use this function in your template files:
There are some built-in template functions:
* markdown
* markdown
This function converts markdown content to HTML format, use {{markdown .Content}} in template files.
* dateformat
* dateformat
This function converts time to formatted string, use {{dateformat .Time "2006-01-02T15:04:05Z07:00"}} in template files.
* date
* date
This function implements date function like in PHP, use formatted string to get corresponding time, use {{date .T "Y-m-d H:i:s"}} in template files.
* compare
* compare
This functions compares two objects, returns true if they are same, false otherwise, use {{compare .A .B}} in template files.
* substr
* substr
This function cuts out string from another string by index, it supports UTF-8 characters, use {{substr .Str 0 30}} in template files.
* html2str
* html2str
This function escapes HTML to raw string, use {{html2str .Htmlinfo}} in template files.
* str2html
* str2html
This function outputs string in HTML format without escaping, use {{str2html .Strhtml}} in template files.
* htmlquote
* htmlquote
This functions implements basic HTML escape, use {{htmlquote .quote}} in template files.
* htmlunquote
* htmlunquote
This functions implements basic invert-escape of HTML, use {{htmlunquote .unquote}} in template files.
##Handle request
## Handle request
We always need to get data from users, including methods like GET, POST, etc. Beego parses these data automatically, and you can access them by following code:
- GetString(key string) string
@ -413,18 +457,20 @@ If you need other types that are not included above, like you need int64 instead
func (this *MainController) Post() {
id := this.Input().Get("id")
intid, err := strconv.Atoi(id)
}
}
To use `this.Ctx.Request` for more information about request, and object properties and method please read [Request](http://golang.org/pkg/net/http/#Request)
###File upload
### File upload
It's very easy to upload file through Beego, but don't forget to add `enctype="multipart/form-data"` in your form, otherwise the browser will not upload anything.
Files will be saved in memory, if the size is greater than cache memory, the rest part will be saved as temporary file. The default cache memory is 64 MB, and you can using following ways to change this size.
Files will be saved in memory, if the size is greater than cache memory, the rest part will be saved as temporary file. The default cache memory is 64 MB, and you can use following ways to change this size.
In code:
beego.MaxMemory = 1<<22
beego.MaxMemory = 1<<22
In configuration file:
@ -435,63 +481,122 @@ Beego provides two convenient functions to upload files:
- GetFile(key string) (multipart.File, `*`multipart.FileHeader, error)
This function is mainly used to read file name element `the_file` in form and returns corresponding information. You can use this information either filter or save files.
- SaveToFile(fromfile, tofile string) error
This function a wrapper of GetFile and gives ability to save file.
This is an example to save file that is uploaded:
func (this *MainController) Post() {
this.SaveToFile("the_file","/var/www/uploads/uploaded_file.txt"")
}
###Output Json and XML
### Output Json and XML
Beego considered API function design at the beginning, and we often use Json or XML format data as output. Therefore, it's no reason that Beego doesn't support it:
Set `content-type` to `application/json` for output raw Json format data:
func (this *AddController) Get() {
mystruct := { ... }
this.Data["json"] = &mystruct
this.ServeJson()
}
mystruct := { ... }
this.Data["json"] = &mystruct
this.ServeJson()
}
Set `content-type` to `application/xml` for output raw XML format data:
func (this *AddController) Get() {
mystruct := { ... }
this.Data["xml"]=&mystruct
this.ServeXml()
}
##Redirect and error
mystruct := { ... }
this.Data["xml"]=&mystruct
this.ServeXml()
}
## Redirect and error
You can use following to redirect:
func (this *AddController) Get() {
this.Redirect("/", 302)
}
this.Redirect("/", 302)
}
@todo Error processing need to be improved.
You can also throw an exception in your controller as follows:
func (this *MainController) Get() {
this.Abort("401")
v := this.GetSession("asta")
if v == nil {
this.SetSession("asta", int(1))
this.Data["Email"] = 0
} else {
this.SetSession("asta", v.(int)+1)
this.Data["Email"] = v.(int)
}
this.TplNames = "index.tpl"
}
Then Beego will not execute rest code of the function body when you call `this.Abort("401")`, and gives following default page view to users:
![](images/401.png)
Beego supports following error code: 404, 401, 403, 500 and 503, you can customize your error handle, for example, use following code to replace 404 error handle process:
func page_not_found(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").ParseFiles(beego.ViewsPath + "/404.html")
data := make(map[string]interface{})
data["content"] = "page not found"
t.Execute(rw, data)
}
func main() {
beego.Errorhandler("404", page_not_found)
beego.Router("/", &controllers.MainController{})
beego.Run()
}
You may be able to use your own `404.html` for your 404 error.
Beego also gives you ability to modify error message that shows on the error page, the following example shows how to set more meaningful error message when database has problems:
func dbError(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").ParseFiles(beego.ViewsPath + "/dberror.html")
data := make(map[string]interface{})
data["content"] = "database is now down"
t.Execute(rw, data)
}
func main() {
beego.Errorhandler("dbError", dbError)
beego.Router("/", &controllers.MainController{})
beego.Run()
}
After you registered this customized error, you can use `this.Abort("dbError")` for any database error in your applications.
## Handle response
##Handle response
There are some situations that you may have in response:
1. Output template
I've already talked about template above, Beego outputs template after corresponding method executed.
2. Redirect
You can use this.Redirect("/", 302) to redirect page.
3. Output string
Sometimes we just need to print string on the screen:
this.Ctx.WriteString("ok")
## Sessions
Beego has a built-in session module and supports four engines, including memory, file, MySQL and redis. You can implement your own engine based on the interface.
It's easy to use session in Beego, use following code in your main() function:
@ -543,21 +648,21 @@ There are some arguments you can use in session module:
- SessionOn
Whether enable session or not, default is false, corresponding arguments in configuration file: sessionon.
- SessionProvider
Setting session engine, default is memory, other options are file, MySQL and redis, corresponding arguments in configuration file: sessionprovider.
- SessionName
Setting name of cookies, it saves in users' browser with name beegosessionID, corresponding arguments in configuration file: sessionname.
- SessionGCMaxLifetime
Setting session expired time, default is 3600 seconds, corresponding arguments in configuration: sessiongcmaxlifetime
- SessionSavePath
Setting save path or link address of corresponding file, MySQL and redis engines, default is empty, corresponding arguments in configuration file: sessionsavepath
When the SessionProvider is file, SessionSavePath saves file path:
@ -569,13 +674,15 @@ When the SessionProvider is mysql, SessionSavePath is link address, it uses driv
beego.SessionProvider = "mysql"
beego.SessionSavePath = "username:password@protocol(address)/dbname?param=value"
When the SessionProvider is redis, SessionSavePath is link address of redis, it uses driver [redigo](https://github.com/garyburd/redigo):
beego.SessionProvider = "redis"
beego.SessionSavePath = "127.0.0.1:6379"
beego.SessionSavePath = "127.0.0.1:6379"
## Cache
Beego has a built-in cache module, it's like memcache, which caches data in memory. Here is an example of using cache module in Beego:
var (
@ -587,7 +694,7 @@ Beego has a built-in cache module, it's like memcache, which caches data in memo
urllist.Every = 0 // Not expired
urllist.Start()
}
func (this *ShortController) Post() {
var result ShortResult
longurl := this.Input().Get("longurl")
@ -610,8 +717,8 @@ Beego has a built-in cache module, it's like memcache, which caches data in memo
}
this.Data["json"] = result
this.ServeJson()
}
}
To use cache, you need to initialize a `beego.NewBeeCache` object and set expired time, and enable expired check. Then you can use following methods to achieve other operations:
- Get(name string) interface{}
@ -619,7 +726,9 @@ To use cache, you need to initialize a `beego.NewBeeCache` object and set expire
- Delete(name string) (ok bool, err error)
- IsExist(name string) bool
##Safe map
## Safe map
We know that map is not thread safe in Go, if you don't know it, this article may be helpful for you: [atomic_maps](http://golang.org/doc/faq#atomic_maps). However, we need a kind of thread safe map in practice, especially when we are using goroutines. Therefore, Beego provides a simple built-in thread safe map implementation.
bm := NewBeeMap()
@ -629,16 +738,16 @@ We know that map is not thread safe in Go, if you don't know it, this article ma
if !bm.Check("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.Check("astaxie") {
t.Error("delete err")
}
This map has following interfaces:
- Get(k interface{}) interface{}
@ -646,22 +755,38 @@ This map has following interfaces:
- Check(k interface{}) bool
- Delete(k interface{})
##Log
## Log
Beego has a default BeeLogger object that outputs log into stdout, and you can use your own logger as well:
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)
###Different levels of log
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
* Trace(v ...interface{})
* Debug(v ...interface{})
@ -673,43 +798,55 @@ You can output everything that implemented `*log.Logger`, for example, write to
You can use following code to set log level:
beego.SetLevel(beego.LevelError)
Your project may have a lot of log outputs, but you don't want to output everything after your application is running on the internet, for example, you want to ignore Trace, Debug and Info level log outputs, you can use following setting:
beego.SetLevel(beego.LevelWarning)
Then Beego will not output log that has lower level of LevelWarning. Here is the list of all log levels, order from lower to higher:
LevelTraceLevelDebugLevelInfoLevelWarning LevelErrorLevelCritical
LevelTrace, LevelDebug, LevelInfo, LevelWarning, LevelError, LevelCritical
You can use different log level to output different error messages, it's based on how critical the error you think it is:
### Examples of log messages
- Trace
* "Entered parse function validation block"
* "Validation: entered second 'if'"
* "Dictionary 'Dict' is empty. Using default value"
- Debug
* "Web page requested: http://somesite.com Params='...'"
* "Response generated. Response size: 10000. Sending."
* "New file received. Type:PNG Size:20000"
- Info
* "Web server restarted"
* "Hourly statistics: Requested pages: 12345 Errors: 123 ..."
* "Service paused. Waiting for 'resume' call"
- Warn
* "Cache corrupted for file='test.file'. Reading from back-end"
* "Database 192.168.0.7/DB not responding. Using backup 192.168.0.8/DB"
* "No response from statistics server. Statistics not sent"
- Error
* "Internal error. Cannot process request #12345 Error:...."
* "Cannot perform login: credentials DB not responding"
- Critical
* "Critical panic received: .... Shutting down"
* "Fatal error: ... App is shutting down to prevent data corruption or loss"
### Example
func internalCalculationFunc(x, y int) (result int, err error) {
@ -731,26 +868,26 @@ You can use different log level to output different error messages, it's based o
}
retVal := z-3
beego.Debug("Returning ", retVal)
return retVal, nil
}
func processInput(input inputData) {
defer func() {
if r := recover(); r != nil {
beego.Error("Unexpected error occurred: ", r)
beego.Error("Unexpected error occurred: ", r)
outputs <- outputData{result : 0, error : true}
}
}
}()
beego.Info("Received input signal. x:",input.x," y:", input.y)
beego.Info("Received input signal. x:",input.x," y:", input.y)
res, err := internalCalculationFunc(input.x, input.y)
res, err := internalCalculationFunc(input.x, input.y)
if err != nil {
beego.Warn("Error in calculation:", err.Error())
}
beego.Info("Returning result: ",res," error: ",err)
outputs <- outputData{result : res, error : err != nil}
beego.Info("Returning result: ",res," error: ",err)
outputs <- outputData{result : res, error : err != nil}
}
func main() {
@ -776,7 +913,9 @@ You can use different log level to output different error messages, it's based o
}
}
##Configuration
## Configuration
Beego supports to parse .ini file in path `conf/app.conf`, and you have following options:
appname = beepkg
@ -786,7 +925,7 @@ Beego supports to parse .ini file in path `conf/app.conf`, and you have followin
autorender = false
autorecover = false
viewspath = "myview"
If you set value in configuration file, Beego uses it to replace default value.
You can also have other values for your application, for example, database connection information:
@ -795,7 +934,7 @@ You can also have other values for your application, for example, database conne
mysqlpass = "rootpass"
mysqlurls = "127.0.0.1"
mysqldb = "beego"
Then use following code to load your settings:
beego.AppConfig.String("mysqluser")
@ -811,33 +950,35 @@ AppConfig supports following methods:
- Float(key string) (float64, error)
- String(key string) string
##Beego arguments
## Beego arguments
Beego has many configurable arguments, let me introduce to you all of them, so you can use them for more usage in your application:
* BeeApp
Entry point of Beego, it initialized in init() function when you import Beego package.
* AppConfig
It stores values from file `conf/app.conf` and initialized in init() function.
* HttpAddr
Application listening address, default is empty for listening all IP.
* HttpPort
Application listening port, default is 8080.
* AppName
Application name, default is "beego".
* RunMode
* RunMode
Application mode, default is "dev" develop mode and gives friendly error messages.
* AutoRender
This value indicates whether auto-render or not, default is true, you should set to false for API usage applications.
@ -854,46 +995,52 @@ Beego has many configurable arguments, let me introduce to you all of them, so y
/debug/pprof/cmdline
/debug/pprof/profile
/debug/pprof/symbol
For more information about pprof, please read [pprof](http://golang.org/pkg/net/http/pprof/)
For more information about pprof, please read [pprof](http://golang.org/pkg/net/http/pprof/)
* ViewsPath
Template path, default is "views".
* SessionOn
This value indicate whether enable session or not, default is false.
* SessionProvider
Session engine, default is memory.
* SessionName
Name for cookie that save in client browser, default is "beegosessionID".
* SessionGCMaxLifetime
Session expired time, default is 3600 seconds.
* SessionSavePath
Save path of session, default is empty.
* UseFcgi
This value indicates whether enable fastcgi or not, default is false.
* MaxMemory
Maximum memory size for file upload, default is `1 << 26`(64M).
##Integrated third-party applications
* EnableGzip
This value indicate whether enable gzip or not, default is false.
## Integrated third-party applications
Beego supports to integrate third-party application, you can customized `http.Handler` as follows:
beego.RouterHandler("/chat/:info(.*)", sockjshandler)
sockjshandler implemented interface `http.Handler`.
Beego has an example for supporting chat of sockjs, here is the code:
@ -912,7 +1059,7 @@ Beego has an example for supporting chat of sockjs, here is the code:
func chatHandler(s sockjs.Session) {
users.Add(s)
defer users.Remove(s)
for {
m := s.Receive()
if m == nil {
@ -943,7 +1090,9 @@ Beego has an example for supporting chat of sockjs, here is the code:
The above example implemented a simple chat room for sockjs, and you can use `http.Handler` for more extensions.
##Deployment
## Deployment
Go compiles program to binary file, you only need to copy this binary to your server and run it. Because Beego uses MVC model, so you may have folders for static files, configuration files and template files, so you have to copy those files as well. Here is a real example for deployment.
$ mkdir /opt/app/beepkg
@ -951,7 +1100,7 @@ Go compiles program to binary file, you only need to copy this binary to your se
$ cp -fr views /opt/app/beepkg
$ cp -fr static /opt/app/beepkg
$ cp -fr conf /opt/app/beepkg
Here is the directory structure pf `/opt/app/beepkg`.
.
@ -963,12 +1112,12 @@ Here is the directory structure pf `/opt/app/beepkg`.
└── js
└── views
└── index.tpl
├── beepkg
├── beepkg
Now you can run your application in server, here are two good ways to manage your applications, and I recommend the first one.
- Supervisord
More information: [Supervisord](Supervisord.md)
- nohup
@ -976,4 +1125,4 @@ Now you can run your application in server, here are two good ways to manage you
nohup ./beepkg &
- [Introduction](README.md)
- [Step by step](Tutorial.md)
- [Step by step](Tutorial.md)

View File

@ -1,4 +1,5 @@
#Beego
# Beego
Beego is a lightweight, open source, non-blocking and scalable web framework for the Go programming language. It's like tornado in Python. This web framework has already been using for building web server and tools in SNDA's CDN system. Documentation and downloads available at [http://astaxie.github.com/beego](http://astaxie.github.com/beego)
It has following main features:
@ -19,34 +20,41 @@ The working principles of Beego as follows:
Beego is licensed under the Apache Licence, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0.html).
#Simple example
# Simple example
The following example prints string "Hello world" to your browser, it shows how easy to build a web application with Beego.
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego"
)
type MainController struct {
beego.Controller
beego.Controller
}
func (this *MainController) Get() {
this.Ctx.WriteString("hello world")
this.Ctx.WriteString("hello world")
}
func main() {
beego.Router("/", &MainController{})
beego.Run()
beego.Router("/", &MainController{})
beego.Run()
}
#Handbook
# Handbook
- [Purposes](Why.md)
- [Installation](Install.md)
- [Quick start](Quickstart.md)
- [Step by step](Tutorial.md)
- [Real world usage](Application.md)
- [Hot update](HotUpdate.md)
#Documentation
[godoc](http://godoc.org/github.com/astaxie/beego)
# Documentation
[Go Walker](http://gowalker.org/github.com/astaxie/beego)

View File

@ -1,22 +1,24 @@
##supervisord
## Supervisord
[Supervisord](http://supervisord.org/) will make sure your web app is always up.
1. Installation
wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg
sh setuptools-0.6c11-py2.7.egg
sh setuptools-0.6c11-py2.7.egg
easy_install supervisor
echo_supervisord_conf >/etc/supervisord.conf
mkdir /etc/supervisord.conf.d
2. Configure /etc/supervisord.conf
2. Configure `/etc/supervisord.conf`
[include]
files = /etc/supervisord.conf.d/*.conf
3. Add new application
cd /etc/supervisord.conf.d
@ -31,4 +33,4 @@
startsecs = 5
user = root
redirect_stderr = true
stdout_logfile = /var/log/supervisord/beepkg.log
stdout_logfile = /var/log/supervisord/beepkg.log

View File

@ -1,13 +1,19 @@
# 一步一步跟我写博客
## 创建项目
## 数据库结构设计
## 控制器设计
## 模板设计
## 用户登陆退出
## 数据库操作
## 数据库操作

View File

@ -1,4 +1,5 @@
# Design purposes and ideas
People may ask me why I want to build a new web framework rather than use other good ones. I know there are many excellent web frameworks on the internet and almost all of them are open source, and I have my reasons to do this.
Remember when I was writing the book about how to build web applications with Go, I just wanted to tell people what were my valuable experiences with Go in web development, especially I have been working with PHP and Python for almost ten years. At first, I didn't realize that a small web framework can give great help to web developers when they are learning to build web applications in a new programming language, and it also helps people more by studying its source code. Finally, I decided to write a open source web framework called Beego as supporting materiel for my book.
@ -9,11 +10,11 @@ I used to use CI in PHP and tornado in Python, there are both lightweight, so th
2. Learn more about languages by studying their source code, it's not hard to read and understand them because they are both lightweight frameworks.
3. It's quite easy to make secondary development of these frameworks for specific purposes.
Those reasons are my original intention of implementing Beego, and used two chapters in my book to introduce and design this lightweight web framework in GO.
Those reasons are my original intention of implementing Beego, and used two chapters in my book to introduce and design this lightweight web framework in Go.
Then I started to design logic execution of Beego. Because Go and Python have somewhat similar, I referenced some ideas from tornado to design Beego. As you can see, there is no different between Beego and tornado in RESTful processing; they both use GET, POST or some other methods to implement RESTful. I took some ideas from [https://github.com/drone/routes](https://github.com/drone/routes) at the beginning of designing routes. It uses regular expression in route rules processing, which is an excellent idea that to make up for the default Mux router function in Go. However, I have to design my own interface in order to implement RESTful and use inherited ideas in Python.
The controller is the most important part of whole MVC model, and Beego uses the interface and ideas I said above for the controller. Although I haven't decided to have to design the model part, everyone is welcome to implement data management by referencing Beedb, my another open source project. I simply adopt Go built-in template engine for the view part, but add more commonly used functions as template functions. This is how a simple web framework looks like, but I'll keep working on form processing, session handling, log recording, configuration, automated operation, etc, to build a simple but complete web framework.
- [Introduction](README.md)
- [Installation](Install.md)
- [Installation](Install.md)

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`

45
docs/zh/HotUpdate.md Normal file
View File

@ -0,0 +1,45 @@
## 热升级是什么?
热升级是什么呢了解nginx的同学都知道nginx是支持热升级的可以用老进程服务先前链接的链接使用新进程服务新的链接即在不停止服务的情况下完成系统的升级与运行参数修改。那么热升级和热编译是不同的概念热编译是通过监控文件的变化重新编译然后重启进程例如bee start就是这样的工具
## 热升级有必要吗?
很多人认为HTTP的应用有必要支持热升级吗那么我可以很负责的说非常有必要不中断服务始终是我们所追求的目标虽然很多人说可能服务器会坏掉等等这个是属于高可用的设计范畴不要搞混了这个是可预知的问题所以我们需要避免这样的升级带来的用户不可用。你还在为以前升级搞到凌晨升级而烦恼嘛那么现在就赶紧拥抱热升级吧。
## beego如何支持热升级
热升级的原理基本上就是主进程fork一个进程然后子进程exec相应的程序。那么这个过程中发生了什么呢我们知道进程fork之后会把主进程的所有句柄、数据和堆栈继承过来、但是里面所有的句柄存在一个叫做CloseOnExec也就是执行exec的时候copy的所有的句柄都被关闭了除非特别申明而我们期望的是子进程能够复用主进程的net.Listener的句柄。一个进程一旦调用exec类函数它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。
那么我们要做的第一步就是让子进程继承主进程的这个句柄我们可以通过os.StartProcess的参数来附加Files把需要继承的句柄写在里面。
第二步就是我们希望子进程能够从这个句柄启动监听还好Go里面支持net.FileListener直接从句柄来监听但是我们需要子进程知道这个FD所以在启动子进程的时候我们设置了一个环境变量设置这个FD。
第三步就是我们期望老的链接继续服务完而新的链接采用新的进程这里面有两个细节第一就是老的链接继续服务那么我们怎么知道有老链接存在所以我们必须每次接收一个链接记录一下这样我们就知道还存在没有服务完的链接第二就是怎么让老进程停止接收数据让新进程接收数据呢大家都监听在同一个端口理论上是随机来接收的所以这里我们只要关闭老的链接的接收就行这样就会使得在l.Accept的时候报错。
上面是我们需要解决的三个方面的问题,具体的实现大家可以看我实现的代码逻辑。
## 如何演示热升级
1. 编写代码在beego应用的控制器中Get方法实现大概如下
func (this *MainController) Get() {
a, _ := this.GetInt("sleep")
time.Sleep(time.Duration(a) * time.Second)
this.Ctx.WriteString("ospid:" + strconv.Itoa(os.Getpid()))
}
2. 打开两个终端
一个终端输入:` ps -ef|grep 应用名`
一个终端输入请求:`curl "http://127.0.0.1:8080/?sleep=20"`
3. 热升级
`kill -HUP 进程ID`
4. 打开一个终端输入请求:`curl "http://127.0.0.1:8080/?sleep=0"`
我们可以看到这样的结果第一个请求等待20s但是处理他的是老的进程热升级之后第一个请求还在执行最后会输出老的进程ID而第二次请求输出的是新的进程ID

View File

@ -1,4 +1,5 @@
# 安装入门
beego虽然是一个简单的框架但是其中用到了很多第三方的包所以在你安装beego的过程中Go会自动安装其他关联的包。
- 当然第一步你需要安装Go如何安装Go请参考我的书[第一章](https://github.com/astaxie/build-web-application-with-golang/blob/master/ebook/01.1.md)
@ -6,10 +7,10 @@ beego虽然是一个简单的框架但是其中用到了很多第三方的包
- 安装beego
go get github.com/astaxie/beego
- 安装bee工具,这个工具可以用来快速的建立beego的应用
go get github.com/astaxie/bee
go get github.com/astaxie/bee
这样就完成了beego的安装你就可以开始开发了,可以通过bee工具来创建beego项目
@ -19,6 +20,7 @@ beego虽然是一个简单的框架但是其中用到了很多第三方的包
> - session模块github.com/astaxie/beego/session
> - session模块中支持redis引擎github.com/garyburd/redigo/redis
> - session模块中支持mysql引擎github.com/go-sql-driver/mysql
@ -27,4 +29,4 @@ beego虽然是一个简单的框架但是其中用到了很多第三方的包
- [beego介绍](README.md)
- [快速入门](Quickstart.md)
- [快速入门](Quickstart.md)

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +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)
# API接口
API对于我们平时开发应用非常有用用于查询一些开发的函数godoc做的非常好了
[godoc](http://godoc.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

@ -3,20 +3,20 @@
1. setuptools安装
wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg
sh setuptools-0.6c11-py2.7.egg
easy_install supervisor
echo_supervisord_conf >/etc/supervisord.conf
mkdir /etc/supervisord.conf.d
2. 修改配置/etc/supervisord.conf
[include]
files = /etc/supervisord.conf.d/*.conf
3. 新建管理的应用
cd /etc/supervisord.conf.d
@ -31,4 +31,4 @@
startsecs = 5
user = root
redirect_stderr = true
stdout_logfile = /var/log/supervisord/beepkg.log
stdout_logfile = /var/log/supervisord/beepkg.log

View File

@ -1,13 +1,19 @@
# 一步一步跟我写博客
## 创建项目
## 数据库结构设计
## 控制器设计
## 模板设计
## 用户登陆退出
## 数据库操作
## 数据库操作

View File

@ -17,4 +17,4 @@
整个的MVC逻辑中C是最重要的部分这一块采用了我上面说的接口方式M模块目前我还没想好怎么做但是大家可以参考我的另一个开源项目beedb来实现数据的管理V这一块目前采用了Go语言自带的模板引擎但是实现了很多方便的模板函数。这样一个简易的框架就完成了然后我就不断的完善周边的功能包括表单处理、session处理、日志处理、配置处理、自动化运行等功能。
- [beego介绍](README.md)
- [安装入门](Install.md)
- [安装入门](Install.md)

View File

@ -74,7 +74,7 @@ var errtpl = `
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Page Not Found</title>
<title>{{.Title}}</title>
<style type="text/css">
* {
margin:0;
@ -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)
}

230
log.go
View File

@ -1,13 +1,214 @@
package beego
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
)
//--------------------
// LOG LEVEL
//--------------------
type FileLogWriter struct {
*log.Logger
mw *MuxWriter
// The opened file
filename string
maxlines int
maxlines_curlines int
// Rotate at size
maxsize int
maxsize_cursize int
// Rotate daily
daily bool
maxdays int64
daily_opendate int
rotate bool
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,
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
}
// Set rotate at linecount (chainable). Must be called before call StartLogger
func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter {
w.maxlines = maxlines
return w
}
// Set rotate at size (chainable). Must be called before call StartLogger
func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter {
w.maxsize = maxsize
return w
}
// Set rotate daily (chainable). Must be called before call StartLogger
func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
w.daily = daily
return w
}
// 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 {
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
}
func (w *FileLogWriter) docheck(size int) {
w.startLock.Lock()
defer w.startLock.Unlock()
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(); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
return
}
}
w.maxlines_curlines++
w.maxsize_cursize += size
}
func (w *FileLogWriter) Printf(format string, v ...interface{}) {
// Perform the write
str := fmt.Sprintf(format, v...)
n := 24 + len(str) // 24 stand for the length "2013/06/23 21:00:22 [T] "
w.docheck(n)
w.Logger.Printf(format, v...)
}
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
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.maxdays) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.filename)) {
os.Remove(path)
}
}
return nil
})
}
// Log levels to control the logging output.
const (
@ -34,8 +235,29 @@ func SetLevel(l int) {
level = l
}
type IBeeLogger interface {
Fatal(v ...interface{})
Fatalf(format string, v ...interface{})
Fatalln(v ...interface{})
Flags() int
Output(calldepth int, s string) error
Panic(v ...interface{})
Panicf(format string, v ...interface{})
Panicln(v ...interface{})
Prefix() string
Print(v ...interface{})
Printf(format string, v ...interface{})
Println(v ...interface{})
SetFlags(flag int)
SetPrefix(prefix string)
}
// logger references the used application logger.
var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
var BeeLogger IBeeLogger
func init() {
BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime)
}
// SetLogger sets a new logger.
func SetLogger(l *log.Logger) {

158
reload.go Normal file
View File

@ -0,0 +1,158 @@
// Zero-downtime restarts in Go.
package beego
import (
"errors"
"fmt"
"log"
"net"
"os"
"os/exec"
"os/signal"
"reflect"
"strconv"
"sync"
"syscall"
)
const (
FDKey = "BEEGO_HOT_FD"
)
// Export an error equivalent to net.errClosing for use with Accept during
// a graceful exit.
var ErrClosing = errors.New("use of closed network connection")
var ErrInitStart = errors.New("init from")
// Allows for us to notice when the connection is closed.
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
}
type stoppableListener struct {
net.Listener
count int64
stopped bool
wg sync.WaitGroup
}
var theStoppable *stoppableListener
func newStoppable(l net.Listener) (sl *stoppableListener) {
sl = &stoppableListener{Listener: l}
// this goroutine monitors the channel. Can't do this in
// Accept (below) because once it enters sl.Listener.Accept()
// it blocks. We unblock it by closing the fd it is trying to
// accept(2) on.
go func() {
WaitSignal(l)
sl.stopped = true
sl.Listener.Close()
}()
return
}
func (sl *stoppableListener) Accept() (c net.Conn, err error) {
c, err = sl.Listener.Accept()
if err != nil {
return
}
sl.wg.Add(1)
// Wrap the returned connection, so that we can observe when
// it is closed.
c = conn{Conn: c, wg: &sl.wg}
return
}
func WaitSignal(l net.Listener) error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM, syscall.SIGHUP)
for {
sig := <-ch
log.Println(sig.String())
switch sig {
case syscall.SIGTERM:
return nil
case syscall.SIGHUP:
err := Restart(l)
if nil != err {
return err
}
return nil
}
}
return nil // It'll never get here.
}
func CloseSelf() error {
ppid := os.Getpid()
if ppid == 1 { // init provided sockets, for example systemd
return nil
}
p, err := os.FindProcess(ppid)
if err != nil {
return err
}
return p.Kill()
}
// Re-exec this image without dropping the listener passed to this function.
func Restart(l net.Listener) error {
argv0, err := exec.LookPath(os.Args[0])
if nil != err {
return err
}
wd, err := os.Getwd()
if nil != err {
return err
}
v := reflect.ValueOf(l).Elem().FieldByName("fd").Elem()
fd := uintptr(v.FieldByName("sysfd").Int())
allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr},
os.NewFile(fd, string(v.FieldByName("sysfile").String())))
p, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{
Dir: wd,
Env: append(os.Environ(), fmt.Sprintf("%s=%d", FDKey, fd)),
Files: allFiles,
})
if nil != err {
return err
}
log.Printf("spawned child %d\n", p.Pid)
return nil
}
func GetInitListner(tcpaddr *net.TCPAddr) (l net.Listener, err error) {
countStr := os.Getenv(FDKey)
if countStr == "" {
return net.ListenTCP("tcp", tcpaddr)
}
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
}

161
router.go
View File

@ -1,20 +1,26 @@
package beego
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"reflect"
"regexp"
"runtime"
"strings"
)
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 {
@ -35,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
@ -44,7 +59,7 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
if strings.HasPrefix(part, ":") {
expr := "(.+)"
//a user may choose to override the defult expression
// similar to expressjs: /user/:id([0-9]+)
// similar to expressjs: /user/:id([0-9]+)
if index := strings.Index(part, "("); index != -1 {
expr = part[index:]
part = part[:index]
@ -79,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
@ -99,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)
}
@ -118,7 +157,7 @@ func (p *ControllerRegistor) AddHandler(pattern string, c http.Handler) {
if strings.HasPrefix(part, ":") {
expr := "([^/]+)"
//a user may choose to override the defult expression
// similar to expressjs: /user/:id([0-9]+)
// similar to expressjs: /user/:id([0-9]+)
if index := strings.Index(part, "("); index != -1 {
expr = part[index:]
part = part[:index]
@ -188,7 +227,7 @@ 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 {
@ -214,8 +253,10 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
}
}()
w := &responseWriter{writer: rw}
w.Header().Set("Server", "beegoServer")
var runrouter *controllerInfo
var findrouter bool
@ -231,6 +272,22 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
if strings.HasPrefix(r.URL.Path, prefix) {
file := staticDir + r.URL.Path[len(prefix):]
finfo, err := os.Stat(file)
if err != nil {
return
}
//if the request is dir and DirectoryIndex is false then
if finfo.IsDir() && !DirectoryIndex {
if h, ok := ErrorMaps["403"]; ok {
h(w, r)
return
} else {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(403)
fmt.Fprintln(w, "403 Forbidden")
return
}
}
http.ServeFile(w, r, file)
w.started = true
return
@ -238,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
@ -326,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
@ -350,7 +420,8 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
//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())
init.Call(in)
@ -362,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")
@ -401,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

@ -1,60 +1,60 @@
sessionmanager
session
==============
sessionmanager is a golang session manager. It can use session for many providers.Just like the `database/sql` and `database/sql/driver`.
session is a Go session manager. It can use many session providers. Just like the `database/sql` and `database/sql/driver`.
##How to install
## How to install?
go get github.com/astaxie/beego/session
##how many providers support
Now this sessionmanager support memory/file/redis/mysql
## What providers are supported?
As of now this session manager support memory, file, Redis and MySQL.
## How to use it?
##How do we use it?
first you must import it
First you must import it
import (
"github.com/astaxie/beego/session"
)
then in you web app init the globalsession manager
Then in you web app init the global session manager
var globalSessions *session.Manager
use memory as providers:
* Use **memory** as provider:
func init() {
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600,"")
go globalSessions.GC()
}
func init() {
globalSessions, _ = session.NewManager("memory", "gosessionid", 3600,"")
go globalSessions.GC()
}
use mysql as providers,the last param is the DNS, learn more from [mysql](https://github.com/Go-SQL-Driver/MySQL#dsn-data-source-name):
* Use **file** as provider, the last param is the path where you want file to be stored:
func init() {
globalSessions, _ = session.NewManager("mysql", "gosessionid", 3600,"username:password@protocol(address)/dbname?param=value")
go globalSessions.GC()
}
func init() {
globalSessions, _ = session.NewManager("file", "gosessionid", 3600, "./tmp")
go globalSessions.GC()
}
use file as providers,the last param is the path where to store the file:
* Use **Redis** as provider, the last param is the Redis conn address:
func init() {
globalSessions, _ = session.NewManager("file", "gosessionid", 3600,"./tmp")
go globalSessions.GC()
}
func init() {
globalSessions, _ = session.NewManager("redis", "gosessionid", 3600, "127.0.0.1:6379")
go globalSessions.GC()
}
* Use **MySQL** as provider, the last param is the DSN, learn more from [mysql](https://github.com/Go-SQL-Driver/MySQL#dsn-data-source-name):
use redis as providers,the last param is the redis's conn address:
func init() {
globalSessions, _ = session.NewManager(
"mysql", "gosessionid", 3600, "username:password@protocol(address)/dbname?param=value")
go globalSessions.GC()
}
func init() {
globalSessions, _ = session.NewManager("redis", "gosessionid", 3600,"127.0.0.1:6379")
go globalSessions.GC()
}
at last in the handlerfunc you can use it like this
Finally in the handlerfunc you can use it like this
func login(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
@ -70,19 +70,21 @@ at last in the handlerfunc you can use it like this
fmt.Println("password:", r.Form["password"])
}
}
##How to write own provider
When we develop a web app, maybe you want to write a provider because you must meet the requirements.
## How to write own provider?
Write a provider is so easy. You only define two struct type(Session and Provider),which satisfy the interface definition.Maybe The memory provider is a good example for you.
When you develop a web app, maybe you want to write own provider because you must meet the requirements.
Writing a provider is easy. You only need to define two struct types
(Session and Provider), which satisfy the interface definition.
Maybe you will find the **memory** provider as good example.
type SessionStore interface {
Set(key, value interface{}) error //set session value
Get(key interface{}) interface{} //get session value
Delete(key interface{}) error //delete session value
SessionID() string //back current sessionID
Set(key, value interface{}) error // set session value
Get(key interface{}) interface{} // get session value
Delete(key interface{}) error // delete session value
SessionID() string // return current sessionID
SessionRelease() // release the resource
}
@ -93,6 +95,7 @@ Write a provider is so easy. You only define two struct type(Session and Provide
SessionGC()
}
##LICENSE
BSD License http://creativecommons.org/licenses/BSD/
## LICENSE
BSD License http://creativecommons.org/licenses/BSD/

View File

@ -32,7 +32,6 @@ func (fs *FileSessionStore) Set(key, value interface{}) error {
func (fs *FileSessionStore) Get(key interface{}) interface{} {
fs.lock.RLock()
defer fs.lock.RUnlock()
fs.updatecontent()
if v, ok := fs.values[key]; ok {
return v
} else {
@ -58,13 +57,13 @@ func (fs *FileSessionStore) SessionRelease() {
}
func (fs *FileSessionStore) updatecontent() {
if len(fs.values) > 0 {
b, err := encodeGob(fs.values)
if err != nil {
return
}
fs.f.Write(b)
b, err := encodeGob(fs.values)
if err != nil {
return
}
fs.f.Truncate(0)
fs.f.Seek(0, 0)
fs.f.Write(b)
}
type FileProvider struct {
@ -107,7 +106,7 @@ func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) {
}
}
f.Close()
f, err = os.Create(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
f, err = os.OpenFile(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid), os.O_WRONLY|os.O_CREATE, 0777)
ss := &FileSessionStore{f: f, sid: sid, values: kv}
return ss, nil
}

View File

@ -6,18 +6,24 @@ import (
var redispder = &RedisProvider{}
var MAX_POOL_SIZE = 20
var redisPool chan redis.Conn
type RedisSessionStore struct {
c redis.Conn
sid string
}
func (rs *RedisSessionStore) Set(key, value interface{}) error {
//_, err := rs.c.Do("HSET", rs.sid, key, value)
_, err := rs.c.Do("HSET", rs.sid, key, value)
return err
}
func (rs *RedisSessionStore) Get(key interface{}) interface{} {
v, err := rs.c.Do("GET", rs.sid, key)
//v, err := rs.c.Do("GET", rs.sid, key)
v, err := redis.String(rs.c.Do("HGET", rs.sid, key))
if err != nil {
return nil
}
@ -25,6 +31,7 @@ func (rs *RedisSessionStore) Get(key interface{}) interface{} {
}
func (rs *RedisSessionStore) Delete(key interface{}) error {
//_, err := rs.c.Do("HDEL", rs.sid, key)
_, err := rs.c.Do("HDEL", rs.sid, key)
return err
}
@ -43,11 +50,37 @@ type RedisProvider struct {
}
func (rp *RedisProvider) connectInit() redis.Conn {
c, err := redis.Dial("tcp", rp.savePath)
/*c, err := redis.Dial("tcp", rp.savePath)
if err != nil {
return nil
}
return c
return c*/
//if redisPool == nil {
redisPool = make(chan redis.Conn, MAX_POOL_SIZE)
//}
if len(redisPool) == 0 {
go func() {
for i := 0; i < MAX_POOL_SIZE/2; i++ {
c, err := redis.Dial("tcp", rp.savePath)
if err != nil {
panic(err)
}
putRedis(c)
}
}()
}
return <-redisPool
}
func putRedis(conn redis.Conn) {
if redisPool == nil {
redisPool = make(chan redis.Conn, MAX_POOL_SIZE)
}
if len(redisPool) >= MAX_POOL_SIZE {
conn.Close()
return
}
redisPool <- conn
}
func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
@ -58,8 +91,10 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) {
c := rp.connectInit()
if str, err := redis.String(c.Do("GET", sid)); err != nil || str == "" {
c.Do("SET", sid, sid, rp.maxlifetime)
//if str, err := redis.String(c.Do("GET", sid)); err != nil || str == "" {
if str, err := redis.String(c.Do("HGET", sid, sid)); err != nil || str == "" {
//c.Do("SET", sid, sid, rp.maxlifetime)
c.Do("HSET", sid, sid, rp.maxlifetime)
}
rs := &RedisSessionStore{c: c, sid: sid}
return rs, nil

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

@ -32,7 +32,7 @@ func Substr(s string, start, length int) string {
}
var end int
if (start + length) > (len(bt) - 1) {
end = len(bt) - 1
end = len(bt)
} else {
end = start + length
}
@ -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
}