mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 12:11:02 +00:00
Compare commits
184 Commits
Author | SHA1 | Date | |
---|---|---|---|
1aeb3d9051 | |||
778a5a11ac | |||
4801099675 | |||
420cd507b2 | |||
22196d7841 | |||
571f9b4b65 | |||
1f0a65f0a2 | |||
90e7d252a7 | |||
31f7524dae | |||
3a12e238cc | |||
f45b271b96 | |||
589616b303 | |||
65b13eddad | |||
686d2e834e | |||
adbae18e8c | |||
f21cff0166 | |||
3dd9020249 | |||
9a2696d216 | |||
64e0858d44 | |||
d86ab2ed31 | |||
b2f071395b | |||
54b5120a64 | |||
5e2384e95a | |||
adb41eb299 | |||
bd04be4470 | |||
ef59a0ed63 | |||
14be252d2a | |||
5698b5dc92 | |||
0a0fc351e7 | |||
662bea352f | |||
2f18b9103b | |||
2c5ef8ccc8 | |||
1ddb1ce2fe | |||
9ddc2f5474 | |||
e29f4b57a3 | |||
0caadb9b66 | |||
22e3900403 | |||
b39830dff3 | |||
be23c42674 | |||
90344a7b8f | |||
920862884d | |||
48ec7f736e | |||
292d8f2c00 | |||
f6f34306ee | |||
a40c0dd156 | |||
795092bdd2 | |||
524446c857 | |||
f5adec31c6 | |||
6747c55a81 | |||
1f46c1d231 | |||
8bd1be8e29 | |||
226e54e0d8 | |||
3379a2b7ed | |||
19d921d3f5 | |||
4b99e41880 | |||
8ff74e71cb | |||
2a148473e9 | |||
b30ce768f8 | |||
70e63570f5 | |||
36e3160904 | |||
ca3c57fbc6 | |||
387dd6ec0e | |||
26cc040f9a | |||
d81a768802 | |||
f0dcaa7f84 | |||
9da4d1d847 | |||
a144f117a3 | |||
477de9a3f3 | |||
ffbb45e567 | |||
67fbafb380 | |||
6eaa5537f5 | |||
62cc987620 | |||
f0a41f978f | |||
3da28535fe | |||
85f55fcb41 | |||
8c37e76503 | |||
76d69b6e51 | |||
20301bc212 | |||
9119f766d2 | |||
891016a0a2 | |||
d5f07d65bb | |||
9411063574 | |||
23860e6807 | |||
d35c50a8e0 | |||
810f6db8d2 | |||
1f716dda3e | |||
f8c4b3aa4c | |||
51b1095e73 | |||
68cc53e92b | |||
6caa3ecd91 | |||
304a5ccea0 | |||
9806a43783 | |||
a1cb000701 | |||
2b23764ee0 | |||
2301633d42 | |||
2efe7c4c89 | |||
c71ac7431d | |||
360220161b | |||
5dec3d127c | |||
16b01c362a | |||
441f795a1a | |||
4906b600e3 | |||
bb50383aa9 | |||
631b4d36f9 | |||
da38cbfe41 | |||
09193213a0 | |||
0382146c60 | |||
1bf52e8922 | |||
5cd1ed8106 | |||
bd79972d85 | |||
ecab397073 | |||
a043432398 | |||
7f888e3d18 | |||
85c0fcd335 | |||
f769016126 | |||
453d744db9 | |||
31ef4ae507 | |||
ccce566ba7 | |||
e357f6846b | |||
321bcc606a | |||
e549d0fd9c | |||
c59a029ce7 | |||
4ce094a29a | |||
fbb98fbe1f | |||
20efd5236e | |||
330b3b1931 | |||
d9e6250d08 | |||
e3810b599d | |||
e1f9491aed | |||
6aeff53d8c | |||
bf870eb9a2 | |||
f26d360ec9 | |||
f73eaf6393 | |||
e11d150e8b | |||
f2567bc114 | |||
b5a07c6ba8 | |||
01ccc75d6b | |||
b19f9bf88c | |||
6cc3d4470a | |||
61e9dc74c9 | |||
8611862fd7 | |||
d1481ea659 | |||
5930f27da7 | |||
4de91f675d | |||
15e9ba19c0 | |||
f8004b69ad | |||
3dac344ff6 | |||
e7d4452af0 | |||
7e3ad5bcb0 | |||
87650ce8bc | |||
fdce4af9c8 | |||
bcac4bb8e3 | |||
0e17e2a3d2 | |||
a80feb00b8 | |||
cf055c9db2 | |||
3d7354b9d2 | |||
09d3d89c6f | |||
3031bdd176 | |||
4c1cfc1386 | |||
57d522a96a | |||
fd7473466b | |||
007af6224e | |||
cbc7f43e88 | |||
ecf24640fd | |||
51ae45a799 | |||
be544f963e | |||
af346e871b | |||
cb5fd49612 | |||
0f85c82a21 | |||
93b04e8a3b | |||
07937dea9a | |||
5757e6548e | |||
b48f251043 | |||
35e340b937 | |||
befeac5b61 | |||
23bb36d35c | |||
4b52a38183 | |||
30e5634bdb | |||
fa8f6e5a53 | |||
6e987bfdaf | |||
918b9e510f | |||
12e5584c01 | |||
ac3b013de7 | |||
089242eda0 |
27
.travis.yml
27
.travis.yml
@ -1,8 +1,10 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5.1
|
||||
|
||||
- tip
|
||||
- 1.6.0
|
||||
- 1.5.3
|
||||
- 1.4.3
|
||||
services:
|
||||
- redis-server
|
||||
- mysql
|
||||
@ -12,6 +14,11 @@ env:
|
||||
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
|
||||
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||
before_install:
|
||||
- git clone git://github.com/ideawu/ssdb.git
|
||||
- cd ssdb
|
||||
- make
|
||||
- cd ..
|
||||
install:
|
||||
- go get github.com/lib/pq
|
||||
- go get github.com/go-sql-driver/mysql
|
||||
@ -19,12 +26,26 @@ install:
|
||||
- go get github.com/bradfitz/gomemcache/memcache
|
||||
- go get github.com/garyburd/redigo/redis
|
||||
- go get github.com/beego/x2j
|
||||
- go get github.com/couchbase/go-couchbase
|
||||
- go get github.com/beego/goyaml2
|
||||
- go get github.com/belogik/goes
|
||||
- go get github.com/couchbase/go-couchbase
|
||||
- go get github.com/siddontang/ledisdb/config
|
||||
- go get github.com/siddontang/ledisdb/ledis
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get github.com/ssdb/gossdb/ssdb
|
||||
before_script:
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
|
||||
- mkdir -p res/var
|
||||
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
||||
after_script:
|
||||
-killall -w ssdb-server
|
||||
- rm -rf ./res/var/*
|
||||
script:
|
||||
- go vet -x ./...
|
||||
- $HOME/gopath/bin/golint ./...
|
||||
- go test -v ./...
|
||||
notifications:
|
||||
webhooks: https://hooks.pubu.im/services/z7m9bvybl3rgtg9
|
||||
|
@ -53,6 +53,7 @@ Please see [Documentation](http://beego.me/docs) for more.
|
||||
## Community
|
||||
|
||||
* [http://beego.me/community](http://beego.me/community)
|
||||
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
|
||||
|
||||
## LICENSE
|
||||
|
||||
|
4
admin.go
4
admin.go
@ -90,8 +90,8 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
switch command {
|
||||
case "conf":
|
||||
m := make(map[string]interface{})
|
||||
m["AppConfigPath"] = AppConfigPath
|
||||
m["AppConfigProvider"] = AppConfigProvider
|
||||
m["AppConfigPath"] = appConfigPath
|
||||
m["AppConfigProvider"] = appConfigProvider
|
||||
m["BConfig.AppName"] = BConfig.AppName
|
||||
m["BConfig.RunMode"] = BConfig.RunMode
|
||||
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
|
||||
|
20
beego.go
20
beego.go
@ -15,7 +15,6 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@ -24,7 +23,7 @@ import (
|
||||
|
||||
const (
|
||||
// VERSION represent beego web framework version.
|
||||
VERSION = "1.6.0"
|
||||
VERSION = "1.6.1"
|
||||
|
||||
// DEV is for develop
|
||||
DEV = "dev"
|
||||
@ -68,21 +67,6 @@ func Run(params ...string) {
|
||||
}
|
||||
|
||||
func initBeforeHTTPRun() {
|
||||
// if AppConfigPath is setted or conf/app.conf exist
|
||||
err := ParseConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//init log
|
||||
for adaptor, config := range BConfig.Log.Outputs {
|
||||
err = BeeLogger.SetLogger(adaptor, config)
|
||||
if err != nil {
|
||||
fmt.Printf("%s with the config `%s` got err:%s\n", adaptor, config, err)
|
||||
}
|
||||
}
|
||||
|
||||
SetLogFuncCall(BConfig.Log.FileLineNum)
|
||||
|
||||
//init hooks
|
||||
AddAPPStartHook(registerMime)
|
||||
AddAPPStartHook(registerDefaultErrorHandler)
|
||||
@ -101,7 +85,7 @@ func initBeforeHTTPRun() {
|
||||
// TestBeegoInit is for test package init
|
||||
func TestBeegoInit(ap string) {
|
||||
os.Setenv("BEEGO_RUNMODE", "test")
|
||||
AppConfigPath = filepath.Join(ap, "conf", "app.conf")
|
||||
appConfigPath = filepath.Join(ap, "conf", "app.conf")
|
||||
os.Chdir(ap)
|
||||
initBeforeHTTPRun()
|
||||
}
|
||||
|
9
cache/memcache/memcache_test.go
vendored
9
cache/memcache/memcache_test.go
vendored
@ -17,10 +17,11 @@ package memcache
|
||||
import (
|
||||
_ "github.com/bradfitz/gomemcache/memcache"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
)
|
||||
|
||||
func TestMemcacheCache(t *testing.T) {
|
||||
@ -36,7 +37,7 @@ func TestMemcacheCache(t *testing.T) {
|
||||
t.Error("check err")
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
time.Sleep(11 * time.Second)
|
||||
|
||||
if bm.IsExist("astaxie") {
|
||||
t.Error("check err")
|
||||
@ -93,10 +94,10 @@ func TestMemcacheCache(t *testing.T) {
|
||||
if len(vv) != 2 {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[0].(string) != "author" {
|
||||
if vv[0].(string) != "author" && vv[0].(string) != "author1" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
if vv[1].(string) != "author1" {
|
||||
if vv[1].(string) != "author1" && vv[1].(string) != "author" {
|
||||
t.Error("GetMulti ERROR")
|
||||
}
|
||||
|
||||
|
240
cache/ssdb/ssdb.go
vendored
Normal file
240
cache/ssdb/ssdb.go
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ssdb/gossdb/ssdb"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
)
|
||||
|
||||
// Cache SSDB adapter
|
||||
type Cache struct {
|
||||
conn *ssdb.Client
|
||||
conninfo []string
|
||||
}
|
||||
|
||||
//NewSsdbCache create new ssdb adapter.
|
||||
func NewSsdbCache() cache.Cache {
|
||||
return &Cache{}
|
||||
}
|
||||
|
||||
// Get get value from memcache.
|
||||
func (rc *Cache) Get(key string) interface{} {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
value, err := rc.conn.Get(key)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMulti get value from memcache.
|
||||
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||
size := len(keys)
|
||||
var values []interface{}
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
for i := 0; i < size; i++ {
|
||||
values = append(values, err)
|
||||
}
|
||||
return values
|
||||
}
|
||||
}
|
||||
res, err := rc.conn.Do("multi_get", keys)
|
||||
resSize := len(res)
|
||||
if err == nil {
|
||||
for i := 1; i < resSize; i += 2 {
|
||||
values = append(values, string(res[i+1]))
|
||||
}
|
||||
return values
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
values = append(values, err)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// DelMulti get value from memcache.
|
||||
func (rc *Cache) DelMulti(keys []string) error {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.conn.Do("multi_del", keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put put value to memcache. only support string.
|
||||
func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return errors.New("value must string")
|
||||
}
|
||||
var resp []string
|
||||
var err error
|
||||
ttl := int(timeout / time.Second)
|
||||
if ttl < 0 {
|
||||
resp, err = rc.conn.Do("set", key, v)
|
||||
} else {
|
||||
resp, err = rc.conn.Do("setx", key, v, ttl)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp) == 2 && resp[0] == "ok" {
|
||||
return nil
|
||||
}
|
||||
return errors.New("bad response")
|
||||
}
|
||||
|
||||
// Delete delete value in memcache.
|
||||
func (rc *Cache) Delete(key string) error {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.conn.Del(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Incr increase counter.
|
||||
func (rc *Cache) Incr(key string) error {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.conn.Do("incr", key, 1)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decr decrease counter.
|
||||
func (rc *Cache) Decr(key string) error {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := rc.conn.Do("incr", key, -1)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsExist check value exists in memcache.
|
||||
func (rc *Cache) IsExist(key string) bool {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
resp, err := rc.conn.Do("exists", key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if resp[1] == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
// ClearAll clear all cached in memcache.
|
||||
func (rc *Cache) ClearAll() error {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
keyStart, keyEnd, limit := "", "", 50
|
||||
resp, err := rc.Scan(keyStart, keyEnd, limit)
|
||||
for err == nil {
|
||||
size := len(resp)
|
||||
if size == 1 {
|
||||
return nil
|
||||
}
|
||||
keys := []string{}
|
||||
for i := 1; i < size; i += 2 {
|
||||
keys = append(keys, string(resp[i]))
|
||||
}
|
||||
_, e := rc.conn.Do("multi_del", keys)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
keyStart = resp[size-2]
|
||||
resp, err = rc.Scan(keyStart, keyEnd, limit)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Scan key all cached in ssdb.
|
||||
func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// StartAndGC start memcache adapter.
|
||||
// config string is like {"conn":"connection info"}.
|
||||
// if connecting error, return.
|
||||
func (rc *Cache) StartAndGC(config string) error {
|
||||
var cf map[string]string
|
||||
json.Unmarshal([]byte(config), &cf)
|
||||
if _, ok := cf["conn"]; !ok {
|
||||
return errors.New("config has no conn key")
|
||||
}
|
||||
rc.conninfo = strings.Split(cf["conn"], ";")
|
||||
if rc.conn == nil {
|
||||
if err := rc.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// connect to memcache and keep the connection.
|
||||
func (rc *Cache) connectInit() error {
|
||||
conninfoArray := strings.Split(rc.conninfo[0], ":")
|
||||
host := conninfoArray[0]
|
||||
port, e := strconv.Atoi(conninfoArray[1])
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
var err error
|
||||
rc.conn, err = ssdb.Connect(host, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cache.Register("ssdb", NewSsdbCache)
|
||||
}
|
103
cache/ssdb/ssdb_test.go
vendored
Normal file
103
cache/ssdb/ssdb_test.go
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/cache"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSsdbcacheCache(t *testing.T) {
|
||||
ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`)
|
||||
if err != nil {
|
||||
t.Error("init err")
|
||||
}
|
||||
|
||||
// test put and exist
|
||||
if ssdb.IsExist("ssdb") {
|
||||
t.Error("check err")
|
||||
}
|
||||
timeoutDuration := 10 * time.Second
|
||||
//timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent
|
||||
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
if !ssdb.IsExist("ssdb") {
|
||||
t.Error("check err")
|
||||
}
|
||||
|
||||
// Get test done
|
||||
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
|
||||
if v := ssdb.Get("ssdb"); v != "ssdb" {
|
||||
t.Error("get Error")
|
||||
}
|
||||
|
||||
//inc/dec test done
|
||||
if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
if err = ssdb.Incr("ssdb"); err != nil {
|
||||
t.Error("incr Error", err)
|
||||
}
|
||||
|
||||
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
if err = ssdb.Decr("ssdb"); err != nil {
|
||||
t.Error("decr error")
|
||||
}
|
||||
|
||||
// test del
|
||||
if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
|
||||
t.Error("get err")
|
||||
}
|
||||
if err := ssdb.Delete("ssdb"); err == nil {
|
||||
if ssdb.IsExist("ssdb") {
|
||||
t.Error("delete err")
|
||||
}
|
||||
}
|
||||
|
||||
//test string
|
||||
if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
if !ssdb.IsExist("ssdb") {
|
||||
t.Error("check err")
|
||||
}
|
||||
if v := ssdb.Get("ssdb").(string); v != "ssdb" {
|
||||
t.Error("get err")
|
||||
}
|
||||
|
||||
//test GetMulti done
|
||||
if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
if !ssdb.IsExist("ssdb1") {
|
||||
t.Error("check err")
|
||||
}
|
||||
vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
|
||||
if len(vv) != 2 {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
if vv[0].(string) != "ssdb" {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
if vv[1].(string) != "ssdb1" {
|
||||
t.Error("getmulti error")
|
||||
}
|
||||
|
||||
// test clear all done
|
||||
if err = ssdb.ClearAll(); err != nil {
|
||||
t.Error("clear all err")
|
||||
}
|
||||
if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") {
|
||||
t.Error("check err")
|
||||
}
|
||||
}
|
124
config.go
124
config.go
@ -15,7 +15,7 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -103,17 +103,22 @@ var (
|
||||
BConfig *Config
|
||||
// AppConfig is the instance of Config, store the config information from file
|
||||
AppConfig *beegoAppConfig
|
||||
// AppConfigPath is the path to the config files
|
||||
AppConfigPath string
|
||||
// AppConfigProvider is the provider for the config, default is ini
|
||||
AppConfigProvider = "ini"
|
||||
// TemplateCache stores template caching
|
||||
TemplateCache map[string]*template.Template
|
||||
// AppPath is the absolute path to the app
|
||||
AppPath string
|
||||
// GlobalSessions is the instance for the session manager
|
||||
GlobalSessions *session.Manager
|
||||
|
||||
// appConfigPath is the path to the config files
|
||||
appConfigPath string
|
||||
// appConfigProvider is the provider for the config, default is ini
|
||||
appConfigProvider = "ini"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
|
||||
os.Chdir(AppPath)
|
||||
|
||||
BConfig = &Config{
|
||||
AppName: "beego",
|
||||
RunMode: DEV,
|
||||
@ -162,7 +167,7 @@ func init() {
|
||||
SessionName: "beegosessionID",
|
||||
SessionGCMaxLifetime: 3600,
|
||||
SessionProviderConfig: "",
|
||||
SessionCookieLifeTime: 0, //set cookie default is the brower life
|
||||
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||
SessionAutoSetCookie: true,
|
||||
SessionDomain: "",
|
||||
},
|
||||
@ -173,29 +178,29 @@ func init() {
|
||||
Outputs: map[string]string{"console": ""},
|
||||
},
|
||||
}
|
||||
ParseConfig()
|
||||
|
||||
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
||||
if !utils.FileExists(appConfigPath) {
|
||||
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
||||
return
|
||||
}
|
||||
|
||||
if err := parseConfig(appConfigPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ParseConfig parsed default config file.
|
||||
// now only support ini, next will support json.
|
||||
func ParseConfig() (err error) {
|
||||
if AppConfigPath == "" {
|
||||
if utils.FileExists(filepath.Join("conf", "app.conf")) {
|
||||
AppConfigPath = filepath.Join("conf", "app.conf")
|
||||
} else {
|
||||
AppConfig = &beegoAppConfig{config.NewFakeConfig()}
|
||||
return
|
||||
}
|
||||
}
|
||||
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
|
||||
func parseConfig(appConfigPath string) (err error) {
|
||||
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// set the runmode first
|
||||
// set the run mode first
|
||||
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
|
||||
BConfig.RunMode = envRunMode
|
||||
} else if runmode := AppConfig.String("RunMode"); runmode != "" {
|
||||
BConfig.RunMode = runmode
|
||||
} else if runMode := AppConfig.String("RunMode"); runMode != "" {
|
||||
BConfig.RunMode = runMode
|
||||
}
|
||||
|
||||
BConfig.AppName = AppConfig.DefaultString("AppName", BConfig.AppName)
|
||||
@ -241,6 +246,8 @@ func ParseConfig() (err error) {
|
||||
BConfig.WebConfig.Session.SessionCookieLifeTime = AppConfig.DefaultInt("SessionCookieLifeTime", BConfig.WebConfig.Session.SessionCookieLifeTime)
|
||||
BConfig.WebConfig.Session.SessionAutoSetCookie = AppConfig.DefaultBool("SessionAutoSetCookie", BConfig.WebConfig.Session.SessionAutoSetCookie)
|
||||
BConfig.WebConfig.Session.SessionDomain = AppConfig.DefaultString("SessionDomain", BConfig.WebConfig.Session.SessionDomain)
|
||||
BConfig.Log.AccessLogs = AppConfig.DefaultBool("LogAccessLogs", BConfig.Log.AccessLogs)
|
||||
BConfig.Log.FileLineNum = AppConfig.DefaultBool("LogFileLineNum", BConfig.Log.FileLineNum)
|
||||
|
||||
if sd := AppConfig.String("StaticDir"); sd != "" {
|
||||
for k := range BConfig.WebConfig.StaticDir {
|
||||
@ -273,15 +280,58 @@ func ParseConfig() (err error) {
|
||||
BConfig.WebConfig.StaticExtensionsToGzip = fileExts
|
||||
}
|
||||
}
|
||||
|
||||
if lo := AppConfig.String("LogOutputs"); lo != "" {
|
||||
los := strings.Split(lo, ";")
|
||||
for _, v := range los {
|
||||
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
|
||||
BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//init log
|
||||
BeeLogger.Reset()
|
||||
for adaptor, config := range BConfig.Log.Outputs {
|
||||
err = BeeLogger.SetLogger(adaptor, config)
|
||||
if err != nil {
|
||||
fmt.Printf("%s with the config `%s` got err:%s\n", adaptor, config, err)
|
||||
}
|
||||
}
|
||||
SetLogFuncCall(BConfig.Log.FileLineNum)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAppConfig allow developer to apply a config file
|
||||
func LoadAppConfig(adapterName, configPath string) error {
|
||||
absConfigPath, err := filepath.Abs(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !utils.FileExists(absConfigPath) {
|
||||
return fmt.Errorf("the target config file: %s don't exist", configPath)
|
||||
}
|
||||
|
||||
if absConfigPath == appConfigPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
appConfigPath = absConfigPath
|
||||
appConfigProvider = adapterName
|
||||
|
||||
return parseConfig(appConfigPath)
|
||||
}
|
||||
|
||||
type beegoAppConfig struct {
|
||||
innerConfig config.Configer
|
||||
}
|
||||
|
||||
func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, error) {
|
||||
ac, err := config.NewConfig(AppConfigProvider, AppConfigPath)
|
||||
func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
|
||||
ac, err := config.NewConfig(appConfigProvider, appConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -337,46 +387,46 @@ func (b *beegoAppConfig) Float(key string) (float64, error) {
|
||||
return b.innerConfig.Float(key)
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string {
|
||||
func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
|
||||
if v := b.String(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string {
|
||||
func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
|
||||
if v := b.Strings(key); len(v) != 0 {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int {
|
||||
func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
|
||||
if v, err := b.Int(key); err == nil {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 {
|
||||
func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
|
||||
if v, err := b.Int64(key); err == nil {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool {
|
||||
func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
|
||||
if v, err := b.Bool(key); err == nil {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 {
|
||||
func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
|
||||
if v, err := b.Float(key); err == nil {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
|
||||
|
@ -106,3 +106,39 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
|
||||
}
|
||||
return adapter.ParseData(data)
|
||||
}
|
||||
|
||||
// ParseBool returns the boolean value represented by the string.
|
||||
//
|
||||
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
|
||||
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
|
||||
// Any other value returns an error.
|
||||
func ParseBool(val interface{}) (value bool, err error) {
|
||||
if val != nil {
|
||||
switch v := val.(type) {
|
||||
case bool:
|
||||
return v, nil
|
||||
case string:
|
||||
switch v {
|
||||
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
|
||||
return true, nil
|
||||
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
|
||||
return false, nil
|
||||
}
|
||||
case int8, int32, int64:
|
||||
strV := fmt.Sprintf("%s", v)
|
||||
if strV == "1" {
|
||||
return true, nil
|
||||
} else if strV == "0" {
|
||||
return false, nil
|
||||
}
|
||||
case float64:
|
||||
if v == 1 {
|
||||
return true, nil
|
||||
} else if v == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("parsing %q: invalid syntax", val)
|
||||
}
|
||||
return false, fmt.Errorf("parsing <nil>: invalid syntax")
|
||||
}
|
||||
|
@ -46,12 +46,16 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) Strings(key string) []string {
|
||||
return strings.Split(c.getData(key), ";")
|
||||
v := c.getData(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(v, ";")
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||
v := c.Strings(key)
|
||||
if len(v) == 0 {
|
||||
if v == nil {
|
||||
return defaultval
|
||||
}
|
||||
return v
|
||||
@ -82,7 +86,7 @@ func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.getData(key))
|
||||
return ParseBool(c.getData(key))
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -97,9 +96,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||
}
|
||||
if bComment != nil {
|
||||
line = bytes.TrimLeft(line, string(bComment))
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
// Need append to a new line if multi-line comments.
|
||||
if comment.Len() > 0 {
|
||||
comment.WriteByte('\n')
|
||||
}
|
||||
comment.Write(line)
|
||||
comment.WriteByte('\n')
|
||||
continue
|
||||
}
|
||||
|
||||
@ -194,7 +195,7 @@ type IniConfigContainer struct {
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.getdata(key))
|
||||
return ParseBool(c.getdata(key))
|
||||
}
|
||||
|
||||
// DefaultBool returns the boolean value for a given key.
|
||||
@ -268,15 +269,20 @@ func (c *IniConfigContainer) DefaultString(key string, defaultval string) string
|
||||
}
|
||||
|
||||
// Strings returns the []string value for a given key.
|
||||
// Return nil if config value does not exist or is empty.
|
||||
func (c *IniConfigContainer) Strings(key string) []string {
|
||||
return strings.Split(c.String(key), ";")
|
||||
v := c.String(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(v, ";")
|
||||
}
|
||||
|
||||
// DefaultStrings returns the []string value for a given key.
|
||||
// if err != nil return defaltval
|
||||
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||
v := c.Strings(key)
|
||||
if len(v) == 0 {
|
||||
if v == nil {
|
||||
return defaultval
|
||||
}
|
||||
return v
|
||||
@ -299,14 +305,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Get section or key comments. Fixed #1607
|
||||
getCommentStr := func(section, key string) string {
|
||||
comment, ok := "", false
|
||||
if len(key) == 0 {
|
||||
comment, ok = c.sectionComment[section]
|
||||
} else {
|
||||
comment, ok = c.keyComment[section+"."+key]
|
||||
}
|
||||
|
||||
if ok {
|
||||
// Empty comment
|
||||
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
|
||||
return string(bNumComment)
|
||||
}
|
||||
prefix := string(bNumComment)
|
||||
// Add the line head character "#"
|
||||
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
// Save default section at first place
|
||||
if dt, ok := c.data[defaultSection]; ok {
|
||||
for key, val := range dt {
|
||||
if key != " " {
|
||||
// Write key comments.
|
||||
if v, ok := c.keyComment[key]; ok {
|
||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||
if v := getCommentStr(defaultSection, key); len(v) > 0 {
|
||||
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -327,8 +354,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
for section, dt := range c.data {
|
||||
if section != defaultSection {
|
||||
// Write section comments.
|
||||
if v, ok := c.sectionComment[section]; ok {
|
||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||
if v := getCommentStr(section, ""); len(v) > 0 {
|
||||
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -341,8 +368,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
for key, val := range dt {
|
||||
if key != " " {
|
||||
// Write key comments.
|
||||
if v, ok := c.keyComment[key]; ok {
|
||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||
if v := getCommentStr(section, key); len(v) > 0 {
|
||||
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var inicontext = `
|
||||
func TestIni(t *testing.T) {
|
||||
|
||||
var (
|
||||
inicontext = `
|
||||
;comment one
|
||||
#comment two
|
||||
appname = beeapi
|
||||
@ -29,6 +35,13 @@ PI = 3.1415976
|
||||
runmode = "dev"
|
||||
autorender = false
|
||||
copyrequestbody = true
|
||||
session= on
|
||||
cookieon= off
|
||||
newreg = OFF
|
||||
needlogin = ON
|
||||
enableSession = Y
|
||||
enableCookie = N
|
||||
flag = 1
|
||||
[demo]
|
||||
key1="asta"
|
||||
key2 = "xie"
|
||||
@ -36,7 +49,32 @@ CaseInsensitive = true
|
||||
peers = one;two;three
|
||||
`
|
||||
|
||||
func TestIni(t *testing.T) {
|
||||
keyValue = map[string]interface{}{
|
||||
"appname": "beeapi",
|
||||
"httpport": 8080,
|
||||
"mysqlport": int64(3600),
|
||||
"pi": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"session": true,
|
||||
"cookieon": false,
|
||||
"newreg": false,
|
||||
"needlogin": true,
|
||||
"enableSession": true,
|
||||
"enableCookie": false,
|
||||
"flag": true,
|
||||
"demo::key1": "asta",
|
||||
"demo::key2": "xie",
|
||||
"demo::CaseInsensitive": true,
|
||||
"demo::peers": []string{"one", "two", "three"},
|
||||
"null": "",
|
||||
"demo2::key1": "",
|
||||
"error": "",
|
||||
"emptystrings": []string{},
|
||||
}
|
||||
)
|
||||
|
||||
f, err := os.Create("testini.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -52,31 +90,31 @@ func TestIni(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := iniconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := iniconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
for k, v := range keyValue {
|
||||
var err error
|
||||
var value interface{}
|
||||
switch v.(type) {
|
||||
case int:
|
||||
value, err = iniconf.Int(k)
|
||||
case int64:
|
||||
value, err = iniconf.Int64(k)
|
||||
case float64:
|
||||
value, err = iniconf.Float(k)
|
||||
case bool:
|
||||
value, err = iniconf.Bool(k)
|
||||
case []string:
|
||||
value = iniconf.Strings(k)
|
||||
case string:
|
||||
value = iniconf.String(k)
|
||||
default:
|
||||
value, err = iniconf.DIY(k)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("get key %q value fail,err %s", k, err)
|
||||
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||
}
|
||||
|
||||
}
|
||||
if err = iniconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -84,20 +122,63 @@ func TestIni(t *testing.T) {
|
||||
if iniconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if iniconf.String("demo::key1") != "asta" {
|
||||
t.Fatal("get demo.key1 error")
|
||||
}
|
||||
if iniconf.String("demo::key2") != "xie" {
|
||||
t.Fatal("get demo.key2 error")
|
||||
}
|
||||
if v, err := iniconf.Bool("demo::caseinsensitive"); err != nil || v != true {
|
||||
t.Fatal("get demo.caseinsensitive error")
|
||||
}
|
||||
|
||||
if data := iniconf.Strings("demo::peers"); len(data) != 3 {
|
||||
t.Fatal("get strings error", data)
|
||||
} else if data[0] != "one" {
|
||||
t.Fatal("get first params error not equat to one")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIniSave(t *testing.T) {
|
||||
|
||||
const (
|
||||
inicontext = `
|
||||
app = app
|
||||
;comment one
|
||||
#comment two
|
||||
# comment three
|
||||
appname = beeapi
|
||||
httpport = 8080
|
||||
# DB Info
|
||||
# enable db
|
||||
[dbinfo]
|
||||
# db type name
|
||||
# suport mysql,sqlserver
|
||||
name = mysql
|
||||
`
|
||||
|
||||
saveResult = `
|
||||
app=app
|
||||
#comment one
|
||||
#comment two
|
||||
# comment three
|
||||
appname=beeapi
|
||||
httpport=8080
|
||||
|
||||
# DB Info
|
||||
# enable db
|
||||
[dbinfo]
|
||||
# db type name
|
||||
# suport mysql,sqlserver
|
||||
name=mysql
|
||||
`
|
||||
)
|
||||
cfg, err := NewConfigData("ini", []byte(inicontext))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name := "newIniConfig.ini"
|
||||
if err := cfg.SaveConfigFile(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(name)
|
||||
|
||||
if data, err := ioutil.ReadFile(name); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
cfgData := string(data)
|
||||
datas := strings.Split(saveResult, "\n")
|
||||
for _, line := range datas {
|
||||
if strings.Contains(cfgData, line+"\n") == false {
|
||||
t.Fatalf("different after save ini config file. need contains %q", line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@ -70,12 +71,9 @@ type JSONConfigContainer struct {
|
||||
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
|
||||
val := c.getData(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(bool); ok {
|
||||
return v, nil
|
||||
}
|
||||
return false, errors.New("not bool value")
|
||||
return ParseBool(val)
|
||||
}
|
||||
return false, errors.New("not exist key:" + key)
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
@ -175,7 +173,7 @@ func (c *JSONConfigContainer) DefaultString(key string, defaultval string) strin
|
||||
func (c *JSONConfigContainer) Strings(key string) []string {
|
||||
stringVal := c.String(key)
|
||||
if stringVal == "" {
|
||||
return []string{}
|
||||
return nil
|
||||
}
|
||||
return strings.Split(c.String(key), ";")
|
||||
}
|
||||
@ -183,7 +181,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
|
||||
// DefaultStrings returns the []string value for a given key.
|
||||
// if err != nil return defaltval
|
||||
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||
if v := c.Strings(key); len(v) > 0 {
|
||||
if v := c.Strings(key); v != nil {
|
||||
return v
|
||||
}
|
||||
return defaultval
|
||||
|
@ -15,34 +15,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var jsoncontext = `{
|
||||
"appname": "beeapi",
|
||||
"testnames": "foo;bar",
|
||||
"httpport": 8080,
|
||||
"mysqlport": 3600,
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"database": {
|
||||
"host": "host",
|
||||
"port": "port",
|
||||
"database": "database",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"conns":{
|
||||
"maxconnection":12,
|
||||
"autoconnect":true,
|
||||
"connectioninfo":"info"
|
||||
}
|
||||
}
|
||||
}`
|
||||
func TestJsonStartsWithArray(t *testing.T) {
|
||||
|
||||
var jsoncontextwitharray = `[
|
||||
const jsoncontextwitharray = `[
|
||||
{
|
||||
"url": "user",
|
||||
"serviceAPI": "http://www.test.com/user"
|
||||
@ -52,8 +32,6 @@ var jsoncontextwitharray = `[
|
||||
"serviceAPI": "http://www.test.com/employee"
|
||||
}
|
||||
]`
|
||||
|
||||
func TestJsonStartsWithArray(t *testing.T) {
|
||||
f, err := os.Create("testjsonWithArray.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -90,6 +68,64 @@ func TestJsonStartsWithArray(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
|
||||
var (
|
||||
jsoncontext = `{
|
||||
"appname": "beeapi",
|
||||
"testnames": "foo;bar",
|
||||
"httpport": 8080,
|
||||
"mysqlport": 3600,
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"session": "on",
|
||||
"cookieon": "off",
|
||||
"newreg": "OFF",
|
||||
"needlogin": "ON",
|
||||
"enableSession": "Y",
|
||||
"enableCookie": "N",
|
||||
"flag": 1,
|
||||
"database": {
|
||||
"host": "host",
|
||||
"port": "port",
|
||||
"database": "database",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"conns":{
|
||||
"maxconnection":12,
|
||||
"autoconnect":true,
|
||||
"connectioninfo":"info"
|
||||
}
|
||||
}
|
||||
}`
|
||||
keyValue = map[string]interface{}{
|
||||
"appname": "beeapi",
|
||||
"testnames": []string{"foo", "bar"},
|
||||
"httpport": 8080,
|
||||
"mysqlport": int64(3600),
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"session": true,
|
||||
"cookieon": false,
|
||||
"newreg": false,
|
||||
"needlogin": true,
|
||||
"enableSession": true,
|
||||
"enableCookie": false,
|
||||
"flag": true,
|
||||
"database::host": "host",
|
||||
"database::port": "port",
|
||||
"database::database": "database",
|
||||
"database::password": "password",
|
||||
"database::conns::maxconnection": 12,
|
||||
"database::conns::autoconnect": true,
|
||||
"database::conns::connectioninfo": "info",
|
||||
"unknown": "",
|
||||
}
|
||||
)
|
||||
|
||||
f, err := os.Create("testjson.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -105,37 +141,32 @@ func TestJson(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v := jsonconf.Strings("unknown"); len(v) > 0 {
|
||||
t.Fatal("unknown strings, the length should be 0")
|
||||
}
|
||||
if v := jsonconf.Strings("testnames"); len(v) != 2 {
|
||||
t.Fatal("testnames length should be 2")
|
||||
}
|
||||
if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
|
||||
for k, v := range keyValue {
|
||||
var err error
|
||||
var value interface{}
|
||||
switch v.(type) {
|
||||
case int:
|
||||
value, err = jsonconf.Int(k)
|
||||
case int64:
|
||||
value, err = jsonconf.Int64(k)
|
||||
case float64:
|
||||
value, err = jsonconf.Float(k)
|
||||
case bool:
|
||||
value, err = jsonconf.Bool(k)
|
||||
case []string:
|
||||
value = jsonconf.Strings(k)
|
||||
case string:
|
||||
value = jsonconf.String(k)
|
||||
default:
|
||||
value, err = jsonconf.DIY(k)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
|
||||
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||
}
|
||||
|
||||
}
|
||||
if err = jsonconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -143,15 +174,7 @@ func TestJson(t *testing.T) {
|
||||
if jsonconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if jsonconf.String("database::host") != "host" {
|
||||
t.Fatal("get database::host error")
|
||||
}
|
||||
if jsonconf.String("database::conns::connectioninfo") != "info" {
|
||||
t.Fatal("get database::conns::connectioninfo error")
|
||||
}
|
||||
if maxconnection, err := jsonconf.Int("database::conns::maxconnection"); err != nil || maxconnection != 12 {
|
||||
t.Fatal("get database::conns::maxconnection error")
|
||||
}
|
||||
|
||||
if db, err := jsonconf.DIY("database"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m, ok := db.(map[string]interface{}); !ok {
|
||||
|
@ -92,7 +92,10 @@ type ConfigContainer struct {
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *ConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.data[key].(string))
|
||||
if v, ok := c.data[key]; ok {
|
||||
return config.ParseBool(v)
|
||||
}
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
@ -171,14 +174,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||
|
||||
// Strings returns the []string value for a given key.
|
||||
func (c *ConfigContainer) Strings(key string) []string {
|
||||
return strings.Split(c.String(key), ";")
|
||||
v := c.String(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(v, ";")
|
||||
}
|
||||
|
||||
// DefaultStrings returns the []string value for a given key.
|
||||
// if err != nil return defaltval
|
||||
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||
v := c.Strings(key)
|
||||
if len(v) == 0 {
|
||||
if v == nil {
|
||||
return defaultval
|
||||
}
|
||||
return v
|
||||
|
@ -82,4 +82,7 @@ func TestXML(t *testing.T) {
|
||||
if xmlconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if xmlconf.Strings("emptystrings") != nil {
|
||||
t.Fatal("get emtpy strings error")
|
||||
}
|
||||
}
|
||||
|
@ -121,10 +121,10 @@ type ConfigContainer struct {
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *ConfigContainer) Bool(key string) (bool, error) {
|
||||
if v, ok := c.data[key].(bool); ok {
|
||||
return v, nil
|
||||
if v, ok := c.data[key]; ok {
|
||||
return config.ParseBool(v)
|
||||
}
|
||||
return false, errors.New("not bool value")
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
@ -211,14 +211,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
|
||||
|
||||
// Strings returns the []string value for a given key.
|
||||
func (c *ConfigContainer) Strings(key string) []string {
|
||||
return strings.Split(c.String(key), ";")
|
||||
v := c.String(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(v, ";")
|
||||
}
|
||||
|
||||
// DefaultStrings returns the []string value for a given key.
|
||||
// if err != nil return defaltval
|
||||
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||
v := c.Strings(key)
|
||||
if len(v) == 0 {
|
||||
if v == nil {
|
||||
return defaultval
|
||||
}
|
||||
return v
|
||||
|
@ -79,4 +79,8 @@ func TestYaml(t *testing.T) {
|
||||
if yamlconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
|
||||
if yamlconf.Strings("emptystrings") != nil {
|
||||
t.Fatal("get emtpy strings error")
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ package context
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -59,7 +61,10 @@ type Context struct {
|
||||
// Reset init Context, BeegoInput and BeegoOutput
|
||||
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx.Request = r
|
||||
ctx.ResponseWriter = &Response{rw, false, 0}
|
||||
if ctx.ResponseWriter == nil {
|
||||
ctx.ResponseWriter = &Response{}
|
||||
}
|
||||
ctx.ResponseWriter.reset(rw)
|
||||
ctx.Input.Reset(ctx)
|
||||
ctx.Output.Reset(ctx)
|
||||
}
|
||||
@ -176,25 +181,43 @@ type Response struct {
|
||||
Status int
|
||||
}
|
||||
|
||||
func (r *Response) reset(rw http.ResponseWriter) {
|
||||
r.ResponseWriter = rw
|
||||
r.Status = 0
|
||||
r.Started = false
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply,
|
||||
// and sets `started` to true.
|
||||
// started means the response has sent out.
|
||||
func (w *Response) Write(p []byte) (int, error) {
|
||||
w.Started = true
|
||||
return w.ResponseWriter.Write(p)
|
||||
func (r *Response) Write(p []byte) (int, error) {
|
||||
r.Started = true
|
||||
return r.ResponseWriter.Write(p)
|
||||
}
|
||||
|
||||
// Copy writes the data to the connection as part of an HTTP reply,
|
||||
// and sets `started` to true.
|
||||
// started means the response has sent out.
|
||||
func (r *Response) Copy(buf *bytes.Buffer) (int64, error) {
|
||||
r.Started = true
|
||||
return io.Copy(r.ResponseWriter, buf)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code,
|
||||
// and sets `started` to true.
|
||||
func (w *Response) WriteHeader(code int) {
|
||||
w.Status = code
|
||||
w.Started = true
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.Status > 0 {
|
||||
//prevent multiple response.WriteHeader calls
|
||||
return
|
||||
}
|
||||
r.Status = code
|
||||
r.Started = true
|
||||
r.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// Hijack hijacker for http
|
||||
func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj, ok := w.ResponseWriter.(http.Hijacker)
|
||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj, ok := r.ResponseWriter.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("webserver doesn't support hijacking")
|
||||
}
|
||||
@ -202,15 +225,15 @@ func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
}
|
||||
|
||||
// Flush http.Flusher
|
||||
func (w *Response) Flush() {
|
||||
if f, ok := w.ResponseWriter.(http.Flusher); ok {
|
||||
func (r *Response) Flush() {
|
||||
if f, ok := r.ResponseWriter.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// CloseNotify http.CloseNotifier
|
||||
func (w *Response) CloseNotify() <-chan bool {
|
||||
if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok {
|
||||
func (r *Response) CloseNotify() <-chan bool {
|
||||
if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok {
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
return nil
|
||||
|
@ -287,6 +287,13 @@ func (input *BeegoInput) Params() map[string]string {
|
||||
|
||||
// SetParam will set the param with key and value
|
||||
func (input *BeegoInput) SetParam(key, val string) {
|
||||
// check if already exists
|
||||
for i, v := range input.pnames {
|
||||
if v == key && i <= len(input.pvalues) {
|
||||
input.pvalues[i] = val
|
||||
return
|
||||
}
|
||||
}
|
||||
input.pvalues = append(input.pvalues, val)
|
||||
input.pnames = append(input.pnames, key)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -117,3 +118,56 @@ func TestSubDomain(t *testing.T) {
|
||||
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
|
||||
}
|
||||
}
|
||||
|
||||
func TestParams(t *testing.T) {
|
||||
inp := NewInput()
|
||||
|
||||
inp.SetParam("p1", "val1_ver1")
|
||||
inp.SetParam("p2", "val2_ver1")
|
||||
inp.SetParam("p3", "val3_ver1")
|
||||
if l := inp.ParamsLen(); l != 3 {
|
||||
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
|
||||
}
|
||||
|
||||
if val := inp.Param("p1"); val != "val1_ver1" {
|
||||
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver1")
|
||||
}
|
||||
if val := inp.Param("p3"); val != "val3_ver1" {
|
||||
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val3_ver1")
|
||||
}
|
||||
vals := inp.Params()
|
||||
expected := map[string]string{
|
||||
"p1": "val1_ver1",
|
||||
"p2": "val2_ver1",
|
||||
"p3": "val3_ver1",
|
||||
}
|
||||
if !reflect.DeepEqual(vals, expected) {
|
||||
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
|
||||
}
|
||||
|
||||
// overwriting existing params
|
||||
inp.SetParam("p1", "val1_ver2")
|
||||
inp.SetParam("p2", "val2_ver2")
|
||||
expected = map[string]string{
|
||||
"p1": "val1_ver2",
|
||||
"p2": "val2_ver2",
|
||||
"p3": "val3_ver1",
|
||||
}
|
||||
vals = inp.Params()
|
||||
if !reflect.DeepEqual(vals, expected) {
|
||||
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
|
||||
}
|
||||
|
||||
if l := inp.ParamsLen(); l != 3 {
|
||||
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
|
||||
}
|
||||
|
||||
if val := inp.Param("p1"); val != "val1_ver2" {
|
||||
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
|
||||
}
|
||||
|
||||
if val := inp.Param("p2"); val != "val2_ver2" {
|
||||
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
@ -57,7 +56,7 @@ func (output *BeegoOutput) Header(key, val string) {
|
||||
// Body sets response body content.
|
||||
// if EnableGzip, compress content string.
|
||||
// it sends out response body directly.
|
||||
func (output *BeegoOutput) Body(content []byte) {
|
||||
func (output *BeegoOutput) Body(content []byte) error {
|
||||
var encoding string
|
||||
var buf = &bytes.Buffer{}
|
||||
if output.EnableGzip {
|
||||
@ -75,7 +74,8 @@ func (output *BeegoOutput) Body(content []byte) {
|
||||
output.Status = 0
|
||||
}
|
||||
|
||||
io.Copy(output.Context.ResponseWriter, buf)
|
||||
_, err := output.Context.ResponseWriter.Copy(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// Cookie sets cookie value via given key.
|
||||
@ -97,9 +97,10 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
|
||||
maxAge = v
|
||||
}
|
||||
|
||||
if maxAge > 0 {
|
||||
switch {
|
||||
case maxAge > 0:
|
||||
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge)
|
||||
} else {
|
||||
case maxAge < 0:
|
||||
fmt.Fprintf(&b, "; Max-Age=0")
|
||||
}
|
||||
}
|
||||
@ -186,8 +187,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
|
||||
if coding {
|
||||
content = []byte(stringsToJSON(string(content)))
|
||||
}
|
||||
output.Body(content)
|
||||
return nil
|
||||
return output.Body(content)
|
||||
}
|
||||
|
||||
// JSONP writes jsonp to response body.
|
||||
@ -212,8 +212,7 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||
callbackContent.WriteString("(")
|
||||
callbackContent.Write(content)
|
||||
callbackContent.WriteString(");\r\n")
|
||||
output.Body(callbackContent.Bytes())
|
||||
return nil
|
||||
return output.Body(callbackContent.Bytes())
|
||||
}
|
||||
|
||||
// XML writes xml string to response body.
|
||||
@ -230,8 +229,7 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
|
||||
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return err
|
||||
}
|
||||
output.Body(content)
|
||||
return nil
|
||||
return output.Body(content)
|
||||
}
|
||||
|
||||
// Download forces response for download file.
|
||||
|
@ -185,8 +185,7 @@ func (c *Controller) Render() error {
|
||||
return err
|
||||
}
|
||||
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Ctx.Output.Body(rb)
|
||||
return nil
|
||||
return c.Ctx.Output.Body(rb)
|
||||
}
|
||||
|
||||
// RenderString returns the rendered template string. Do not send out response.
|
||||
@ -197,33 +196,9 @@ func (c *Controller) RenderString() (string, error) {
|
||||
|
||||
// RenderBytes returns the bytes of rendered template string. Do not send out response.
|
||||
func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
//if the controller has set layout, then first get the tplname's content set the content to the layout
|
||||
var buf bytes.Buffer
|
||||
if c.Layout != "" {
|
||||
if c.TplName == "" {
|
||||
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
|
||||
}
|
||||
|
||||
if BConfig.RunMode == DEV {
|
||||
buildFiles := []string{c.TplName}
|
||||
if c.LayoutSections != nil {
|
||||
for _, sectionTpl := range c.LayoutSections {
|
||||
if sectionTpl == "" {
|
||||
continue
|
||||
}
|
||||
buildFiles = append(buildFiles, sectionTpl)
|
||||
}
|
||||
}
|
||||
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
|
||||
}
|
||||
if _, ok := BeeTemplates[c.TplName]; !ok {
|
||||
panic("can't find templatefile in the path:" + c.TplName)
|
||||
}
|
||||
err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
buf, err := c.renderTemplate()
|
||||
//if the controller has set layout, then first get the tplName's content set the content to the layout
|
||||
if err == nil && c.Layout != "" {
|
||||
c.Data["LayoutContent"] = template.HTML(buf.String())
|
||||
|
||||
if c.LayoutSections != nil {
|
||||
@ -232,11 +207,9 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
c.Data[sectionName] = ""
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
err = BeeTemplates[sectionTpl].ExecuteTemplate(&buf, sectionTpl, c.Data)
|
||||
err = executeTemplate(&buf, sectionTpl, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
c.Data[sectionName] = template.HTML(buf.String())
|
||||
@ -244,30 +217,32 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
err = BeeTemplates[c.Layout].ExecuteTemplate(&buf, c.Layout, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
executeTemplate(&buf, c.Layout, c.Data)
|
||||
}
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func (c *Controller) renderTemplate() (bytes.Buffer, error) {
|
||||
var buf bytes.Buffer
|
||||
if c.TplName == "" {
|
||||
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
|
||||
}
|
||||
if BConfig.RunMode == DEV {
|
||||
BuildTemplate(BConfig.WebConfig.ViewsPath, c.TplName)
|
||||
buildFiles := []string{c.TplName}
|
||||
if c.Layout != "" {
|
||||
buildFiles = append(buildFiles, c.Layout)
|
||||
if c.LayoutSections != nil {
|
||||
for _, sectionTpl := range c.LayoutSections {
|
||||
if sectionTpl == "" {
|
||||
continue
|
||||
}
|
||||
buildFiles = append(buildFiles, sectionTpl)
|
||||
}
|
||||
}
|
||||
}
|
||||
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
|
||||
}
|
||||
if _, ok := BeeTemplates[c.TplName]; !ok {
|
||||
panic("can't find templatefile in the path:" + c.TplName)
|
||||
}
|
||||
buf.Reset()
|
||||
err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
return buf, executeTemplate(&buf, c.TplName, c.Data)
|
||||
}
|
||||
|
||||
// Redirect sends the redirection response to url with status code.
|
||||
@ -286,7 +261,7 @@ func (c *Controller) Abort(code string) {
|
||||
|
||||
// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body.
|
||||
func (c *Controller) CustomAbort(status int, body string) {
|
||||
c.Ctx.ResponseWriter.WriteHeader(status)
|
||||
c.Ctx.Output.Status = status
|
||||
// first panic from ErrorMaps, is is user defined error functions.
|
||||
if _, ok := ErrorMaps[body]; ok {
|
||||
panic(body)
|
||||
@ -568,6 +543,7 @@ func (c *Controller) SessionRegenerateID() {
|
||||
// DestroySession cleans session data and session cookie.
|
||||
func (c *Controller) DestroySession() {
|
||||
c.Ctx.Input.CruSession.Flush()
|
||||
c.Ctx.Input.CruSession = nil
|
||||
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||
}
|
||||
|
||||
|
1
error.go
1
error.go
@ -424,6 +424,7 @@ func exception(errCode string, ctx *context.Context) {
|
||||
|
||||
func executeError(err *errorInfo, ctx *context.Context, code int) {
|
||||
if err.errorType == errorTypeHandler {
|
||||
ctx.ResponseWriter.WriteHeader(code)
|
||||
err.handler(ctx.ResponseWriter, ctx.Request)
|
||||
return
|
||||
}
|
||||
|
@ -90,16 +90,15 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
||||
addr = ":https"
|
||||
}
|
||||
|
||||
config := &tls.Config{}
|
||||
if srv.TLSConfig != nil {
|
||||
*config = *srv.TLSConfig
|
||||
if srv.TLSConfig == nil {
|
||||
srv.TLSConfig = &tls.Config{}
|
||||
}
|
||||
if config.NextProtos == nil {
|
||||
config.NextProtos = []string{"http/1.1"}
|
||||
if srv.TLSConfig.NextProtos == nil {
|
||||
srv.TLSConfig.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -113,7 +112,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
|
||||
}
|
||||
|
||||
srv.tlsInnerListener = newGraceListener(l, srv)
|
||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, config)
|
||||
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
|
||||
|
||||
if srv.isChild {
|
||||
process, err := os.FindProcess(os.Getppid())
|
||||
|
10
hooks.go
10
hooks.go
@ -68,13 +68,11 @@ func registerSession() error {
|
||||
}
|
||||
|
||||
func registerTemplate() error {
|
||||
if BConfig.WebConfig.AutoRender {
|
||||
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
|
||||
if BConfig.RunMode == DEV {
|
||||
Warn(err)
|
||||
}
|
||||
return err
|
||||
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
|
||||
if BConfig.RunMode == DEV {
|
||||
Warn(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -301,13 +301,12 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
|
||||
// JSONBody adds request raw body encoding by JSON.
|
||||
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
|
||||
if b.req.Body == nil && obj != nil {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(obj); err != nil {
|
||||
byts, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
b.req.Body = ioutil.NopCloser(buf)
|
||||
b.req.ContentLength = int64(buf.Len())
|
||||
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
|
||||
b.req.ContentLength = int64(len(byts))
|
||||
b.req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
return b, nil
|
||||
|
19
logs/conn.go
19
logs/conn.go
@ -17,14 +17,14 @@ package logs
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// connWriter implements LoggerInterface.
|
||||
// it writes messages in keep-live tcp connection.
|
||||
type connWriter struct {
|
||||
lg *log.Logger
|
||||
lg *logWriter
|
||||
innerWriter io.WriteCloser
|
||||
ReconnectOnMsg bool `json:"reconnectOnMsg"`
|
||||
Reconnect bool `json:"reconnect"`
|
||||
@ -42,17 +42,17 @@ func NewConn() Logger {
|
||||
|
||||
// Init init connection writer with json config.
|
||||
// json config only need key "level".
|
||||
func (c *connWriter) Init(jsonconfig string) error {
|
||||
return json.Unmarshal([]byte(jsonconfig), c)
|
||||
func (c *connWriter) Init(jsonConfig string) error {
|
||||
return json.Unmarshal([]byte(jsonConfig), c)
|
||||
}
|
||||
|
||||
// WriteMsg write message in connection.
|
||||
// if connection is down, try to re-connect.
|
||||
func (c *connWriter) WriteMsg(msg string, level int) error {
|
||||
func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if c.neddedConnectOnMsg() {
|
||||
if c.needToConnectOnMsg() {
|
||||
err := c.connect()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -62,7 +62,8 @@ func (c *connWriter) WriteMsg(msg string, level int) error {
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
c.lg.Println(msg)
|
||||
|
||||
c.lg.println(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -94,11 +95,11 @@ func (c *connWriter) connect() error {
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = log.New(conn, "", log.Ldate|log.Ltime)
|
||||
c.lg = newLogWriter(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connWriter) neddedConnectOnMsg() bool {
|
||||
func (c *connWriter) needToConnectOnMsg() bool {
|
||||
if c.Reconnect {
|
||||
c.Reconnect = false
|
||||
return true
|
||||
|
@ -16,9 +16,9 @@ package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
@ -34,51 +34,55 @@ func newBrush(color string) brush {
|
||||
}
|
||||
|
||||
var colors = []brush{
|
||||
newBrush("1;37"), // Emergency white
|
||||
newBrush("1;36"), // Alert cyan
|
||||
newBrush("1;35"), // Critical magenta
|
||||
newBrush("1;31"), // Error red
|
||||
newBrush("1;33"), // Warning yellow
|
||||
newBrush("1;32"), // Notice green
|
||||
newBrush("1;34"), // Informational blue
|
||||
newBrush("1;34"), // Debug blue
|
||||
newBrush("1;37"), // Emergency white
|
||||
newBrush("1;36"), // Alert cyan
|
||||
newBrush("1;35"), // Critical magenta
|
||||
newBrush("1;31"), // Error red
|
||||
newBrush("1;33"), // Warning yellow
|
||||
newBrush("1;32"), // Notice green
|
||||
newBrush("1;34"), // Informational blue
|
||||
newBrush("1;34"), // Debug blue
|
||||
}
|
||||
|
||||
// consoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type consoleWriter struct {
|
||||
lg *log.Logger
|
||||
Level int `json:"level"`
|
||||
lg *logWriter
|
||||
Level int `json:"level"`
|
||||
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
|
||||
}
|
||||
|
||||
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
cw := &consoleWriter{
|
||||
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
|
||||
Level: LevelDebug,
|
||||
lg: newLogWriter(os.Stdout),
|
||||
Level: LevelDebug,
|
||||
Colorful: true,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// Init init console logger.
|
||||
// jsonconfig like '{"level":LevelTrace}'.
|
||||
func (c *consoleWriter) Init(jsonconfig string) error {
|
||||
if len(jsonconfig) == 0 {
|
||||
// jsonConfig like '{"level":LevelTrace}'.
|
||||
func (c *consoleWriter) Init(jsonConfig string) error {
|
||||
if len(jsonConfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(jsonconfig), c)
|
||||
err := json.Unmarshal([]byte(jsonConfig), c)
|
||||
if runtime.GOOS == "windows" {
|
||||
c.Colorful = false
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteMsg write message in console.
|
||||
func (c *consoleWriter) WriteMsg(msg string, level int) error {
|
||||
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if goos := runtime.GOOS; goos == "windows" {
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
if c.Colorful {
|
||||
msg = colors[level](msg)
|
||||
}
|
||||
c.lg.Println(colors[level](msg))
|
||||
|
||||
c.lg.println(when, msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -42,3 +42,10 @@ func TestConsole(t *testing.T) {
|
||||
log2.SetLogger("console", `{"level":3}`)
|
||||
testConsoleCalls(log2)
|
||||
}
|
||||
|
||||
// Test console without color
|
||||
func TestConsoleNoColor(t *testing.T) {
|
||||
log := NewLogger(100)
|
||||
log.SetLogger("console", `{"color":false}`)
|
||||
testConsoleCalls(log)
|
||||
}
|
||||
|
@ -48,16 +48,16 @@ func (el *esLogger) Init(jsonconfig string) error {
|
||||
}
|
||||
|
||||
// WriteMsg will write the msg and level into es
|
||||
func (el *esLogger) WriteMsg(msg string, level int) error {
|
||||
func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > el.Level {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
|
||||
vals := make(map[string]interface{})
|
||||
vals["@timestamp"] = t.Format(time.RFC3339)
|
||||
vals["@timestamp"] = when.Format(time.RFC3339)
|
||||
vals["@msg"] = msg
|
||||
d := goes.Document{
|
||||
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
|
||||
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
|
||||
Type: "logs",
|
||||
Fields: vals,
|
||||
}
|
||||
|
78
logs/file.go
78
logs/file.go
@ -53,9 +53,11 @@ type fileLogWriter struct {
|
||||
Level int `json:"level"`
|
||||
|
||||
Perm os.FileMode `json:"perm"`
|
||||
|
||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||
}
|
||||
|
||||
// NewFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Filename: "",
|
||||
@ -89,6 +91,11 @@ func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||
if len(w.Filename) == 0 {
|
||||
return errors.New("jsonconfig must have filename")
|
||||
}
|
||||
w.suffix = filepath.Ext(w.Filename)
|
||||
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
|
||||
if w.suffix == "" {
|
||||
w.suffix = ".log"
|
||||
}
|
||||
err = w.startLogger()
|
||||
return err
|
||||
}
|
||||
@ -114,59 +121,19 @@ func (w *fileLogWriter) needRotate(size int, day int) bool {
|
||||
}
|
||||
|
||||
// WriteMsg write logger message into file.
|
||||
func (w *fileLogWriter) WriteMsg(msg string, level int) error {
|
||||
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
//2016/01/12 21:34:33
|
||||
now := time.Now()
|
||||
y, mo, d := now.Date()
|
||||
h, mi, s := now.Clock()
|
||||
//len(2006/01/02 15:03:04)==19
|
||||
var buf [20]byte
|
||||
t := 3
|
||||
for y >= 10 {
|
||||
p := y / 10
|
||||
buf[t] = byte('0' + y - p*10)
|
||||
y = p
|
||||
t--
|
||||
}
|
||||
buf[0] = byte('0' + y)
|
||||
buf[4] = '/'
|
||||
if mo > 9 {
|
||||
buf[5] = '1'
|
||||
buf[6] = byte('0' + mo - 9)
|
||||
} else {
|
||||
buf[5] = '0'
|
||||
buf[6] = byte('0' + mo)
|
||||
}
|
||||
buf[7] = '/'
|
||||
t = d / 10
|
||||
buf[8] = byte('0' + t)
|
||||
buf[9] = byte('0' + d - t*10)
|
||||
buf[10] = ' '
|
||||
t = h / 10
|
||||
buf[11] = byte('0' + t)
|
||||
buf[12] = byte('0' + h - t*10)
|
||||
buf[13] = ':'
|
||||
t = mi / 10
|
||||
buf[14] = byte('0' + t)
|
||||
buf[15] = byte('0' + mi - t*10)
|
||||
buf[16] = ':'
|
||||
t = s / 10
|
||||
buf[17] = byte('0' + t)
|
||||
buf[18] = byte('0' + s - t*10)
|
||||
buf[19] = ' '
|
||||
msg = string(buf[0:]) + msg + "\n"
|
||||
|
||||
h, d := formatTimeHeader(when)
|
||||
msg = string(h) + msg + "\n"
|
||||
if w.Rotate {
|
||||
if w.needRotate(len(msg), d) {
|
||||
w.Lock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
if err := w.doRotate(); err != nil {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
@ -235,8 +202,8 @@ func (w *fileLogWriter) lines() (int, error) {
|
||||
}
|
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.2013-01-01.2.log
|
||||
func (w *fileLogWriter) doRotate() error {
|
||||
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
|
||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -245,13 +212,13 @@ func (w *fileLogWriter) doRotate() error {
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fName := ""
|
||||
suffix := filepath.Ext(w.Filename)
|
||||
filenameOnly := strings.TrimSuffix(w.Filename, suffix)
|
||||
if suffix == "" {
|
||||
suffix = ".log"
|
||||
}
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", time.Now().Format("2006-01-02"), num, suffix)
|
||||
if w.MaxLines > 0 || w.MaxSize > 0 {
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
} else {
|
||||
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
@ -289,7 +256,8 @@ func (w *fileLogWriter) deleteOldLog() {
|
||||
}()
|
||||
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
|
||||
if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
}
|
||||
|
92
logs/log.go
92
logs/log.go
@ -40,6 +40,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RFC5424 log message levels.
|
||||
@ -68,7 +69,7 @@ type loggerType func() Logger
|
||||
// Logger defines the behavior of a log provider.
|
||||
type Logger interface {
|
||||
Init(config string) error
|
||||
WriteMsg(msg string, level int) error
|
||||
WriteMsg(when time.Time, msg string, level int) error
|
||||
Destroy()
|
||||
Flush()
|
||||
}
|
||||
@ -97,6 +98,8 @@ type BeeLogger struct {
|
||||
loggerFuncCallDepth int
|
||||
asynchronous bool
|
||||
msgChan chan *logMsg
|
||||
signalChan chan string
|
||||
wg sync.WaitGroup
|
||||
outputs []*nameLogger
|
||||
}
|
||||
|
||||
@ -108,6 +111,7 @@ type nameLogger struct {
|
||||
type logMsg struct {
|
||||
level int
|
||||
msg string
|
||||
when time.Time
|
||||
}
|
||||
|
||||
var logMsgPool *sync.Pool
|
||||
@ -120,6 +124,7 @@ func NewLogger(channelLen int64) *BeeLogger {
|
||||
bl.level = LevelDebug
|
||||
bl.loggerFuncCallDepth = 2
|
||||
bl.msgChan = make(chan *logMsg, channelLen)
|
||||
bl.signalChan = make(chan string, 1)
|
||||
return bl
|
||||
}
|
||||
|
||||
@ -131,6 +136,7 @@ func (bl *BeeLogger) Async() *BeeLogger {
|
||||
return &logMsg{}
|
||||
},
|
||||
}
|
||||
bl.wg.Add(1)
|
||||
go bl.startLogger()
|
||||
return bl
|
||||
}
|
||||
@ -140,17 +146,25 @@ func (bl *BeeLogger) Async() *BeeLogger {
|
||||
func (bl *BeeLogger) SetLogger(adapterName string, config string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if log, ok := adapters[adapterName]; ok {
|
||||
lg := log()
|
||||
err := lg.Init(config)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||
return err
|
||||
|
||||
for _, l := range bl.outputs {
|
||||
if l.name == adapterName {
|
||||
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||
}
|
||||
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||
} else {
|
||||
}
|
||||
|
||||
log, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
}
|
||||
|
||||
lg := log()
|
||||
err := lg.Init(config)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
|
||||
return err
|
||||
}
|
||||
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -173,9 +187,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeToLoggers(msg string, level int) {
|
||||
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(msg, level)
|
||||
err := l.WriteMsg(when, msg, level)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||
}
|
||||
@ -183,6 +197,7 @@ func (bl *BeeLogger) writeToLoggers(msg string, level int) {
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
when := time.Now()
|
||||
if bl.enableFuncCallDepth {
|
||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||
if !ok {
|
||||
@ -196,9 +211,10 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
lm := logMsgPool.Get().(*logMsg)
|
||||
lm.level = logLevel
|
||||
lm.msg = msg
|
||||
lm.when = when
|
||||
bl.msgChan <- lm
|
||||
} else {
|
||||
bl.writeToLoggers(msg, logLevel)
|
||||
bl.writeToLoggers(when, msg, logLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -228,11 +244,26 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
|
||||
// start logger chan reading.
|
||||
// when chan is not empty, write logs.
|
||||
func (bl *BeeLogger) startLogger() {
|
||||
gameOver := false
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msgChan:
|
||||
bl.writeToLoggers(bm.msg, bm.level)
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
case sg := <-bl.signalChan:
|
||||
// Now should only send "flush" or "close" to bl.signalChan
|
||||
bl.flush()
|
||||
if sg == "close" {
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
gameOver = true
|
||||
}
|
||||
bl.wg.Done()
|
||||
}
|
||||
if gameOver {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -341,17 +372,45 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
|
||||
// Flush flush all chan data.
|
||||
func (bl *BeeLogger) Flush() {
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "flush"
|
||||
bl.wg.Wait()
|
||||
bl.wg.Add(1)
|
||||
return
|
||||
}
|
||||
bl.flush()
|
||||
}
|
||||
|
||||
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
|
||||
func (bl *BeeLogger) Close() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "close"
|
||||
bl.wg.Wait()
|
||||
} else {
|
||||
bl.flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
close(bl.msgChan)
|
||||
close(bl.signalChan)
|
||||
}
|
||||
|
||||
// Reset close all outputs, and set bl.outputs to nil
|
||||
func (bl *BeeLogger) Reset() {
|
||||
bl.Flush()
|
||||
for _, l := range bl.outputs {
|
||||
l.Destroy()
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) flush() {
|
||||
for {
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm.msg, bm.level)
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
}
|
||||
@ -359,6 +418,5 @@ func (bl *BeeLogger) Close() {
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
l.Destroy()
|
||||
}
|
||||
}
|
||||
|
80
logs/logger.go
Normal file
80
logs/logger.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logWriter struct {
|
||||
sync.Mutex
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
func newLogWriter(wr io.Writer) *logWriter {
|
||||
return &logWriter{writer: wr}
|
||||
}
|
||||
|
||||
func (lg *logWriter) println(when time.Time, msg string) {
|
||||
lg.Lock()
|
||||
h, _ := formatTimeHeader(when)
|
||||
lg.writer.Write(append(append(h, msg...), '\n'))
|
||||
lg.Unlock()
|
||||
}
|
||||
|
||||
func formatTimeHeader(when time.Time) ([]byte, int) {
|
||||
y, mo, d := when.Date()
|
||||
h, mi, s := when.Clock()
|
||||
//len(2006/01/02 15:03:04)==19
|
||||
var buf [20]byte
|
||||
t := 3
|
||||
for y >= 10 {
|
||||
p := y / 10
|
||||
buf[t] = byte('0' + y - p*10)
|
||||
y = p
|
||||
t--
|
||||
}
|
||||
buf[0] = byte('0' + y)
|
||||
buf[4] = '/'
|
||||
if mo > 9 {
|
||||
buf[5] = '1'
|
||||
buf[6] = byte('0' + mo - 9)
|
||||
} else {
|
||||
buf[5] = '0'
|
||||
buf[6] = byte('0' + mo)
|
||||
}
|
||||
buf[7] = '/'
|
||||
t = d / 10
|
||||
buf[8] = byte('0' + t)
|
||||
buf[9] = byte('0' + d - t*10)
|
||||
buf[10] = ' '
|
||||
t = h / 10
|
||||
buf[11] = byte('0' + t)
|
||||
buf[12] = byte('0' + h - t*10)
|
||||
buf[13] = ':'
|
||||
t = mi / 10
|
||||
buf[14] = byte('0' + t)
|
||||
buf[15] = byte('0' + mi - t*10)
|
||||
buf[16] = ':'
|
||||
t = s / 10
|
||||
buf[17] = byte('0' + t)
|
||||
buf[18] = byte('0' + s - t*10)
|
||||
buf[19] = ' '
|
||||
|
||||
return buf[0:], d
|
||||
}
|
116
logs/multifile.go
Normal file
116
logs/multifile.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A filesLogWriter manages several fileLogWriter
|
||||
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
|
||||
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
|
||||
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
|
||||
// the rotate attribute also acts like fileLogWriter
|
||||
type multiFileLogWriter struct {
|
||||
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
|
||||
fullLogWriter *fileLogWriter
|
||||
Separate []string `json:"separate"`
|
||||
}
|
||||
|
||||
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
|
||||
|
||||
// Init file logger with json config.
|
||||
// jsonConfig like:
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":0,
|
||||
// "maxsize":0,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":0600,
|
||||
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
|
||||
// }
|
||||
|
||||
func (f *multiFileLogWriter) Init(config string) error {
|
||||
writer := newFileWriter().(*fileLogWriter)
|
||||
err := writer.Init(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fullLogWriter = writer
|
||||
f.writers[LevelDebug+1] = writer
|
||||
|
||||
//unmarshal "separate" field to f.Separate
|
||||
json.Unmarshal([]byte(config), f)
|
||||
|
||||
jsonMap := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(config), &jsonMap)
|
||||
|
||||
for i := LevelEmergency; i < LevelDebug+1; i++ {
|
||||
for _, v := range f.Separate {
|
||||
if v == levelNames[i] {
|
||||
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
|
||||
jsonMap["level"] = i
|
||||
bs, _ := json.Marshal(jsonMap)
|
||||
writer = newFileWriter().(*fileLogWriter)
|
||||
writer.Init(string(bs))
|
||||
f.writers[i] = writer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Destroy() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if f.fullLogWriter != nil {
|
||||
f.fullLogWriter.WriteMsg(when, msg, level)
|
||||
}
|
||||
for i := 0; i < len(f.writers)-1; i++ {
|
||||
if f.writers[i] != nil {
|
||||
if level == f.writers[i].Level {
|
||||
f.writers[i].WriteMsg(when, msg, level)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *multiFileLogWriter) Flush() {
|
||||
for i := 0; i < len(f.writers); i++ {
|
||||
if f.writers[i] != nil {
|
||||
f.writers[i].Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFilesWriter() Logger {
|
||||
return &multiFileLogWriter{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("multifile", newFilesWriter)
|
||||
}
|
78
logs/multifile_test.go
Normal file
78
logs/multifile_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFiles_1(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
fns := []string{""}
|
||||
fns = append(fns, levelNames[0:]...)
|
||||
name := "test"
|
||||
suffix := ".log"
|
||||
for _, fn := range fns {
|
||||
|
||||
file := name + suffix
|
||||
if fn != "" {
|
||||
file = name + "." + fn + suffix
|
||||
}
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := bufio.NewReader(f)
|
||||
lineNum := 0
|
||||
lastLine := ""
|
||||
for {
|
||||
line, _, err := b.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(line) > 0 {
|
||||
lastLine = string(line)
|
||||
lineNum++
|
||||
}
|
||||
}
|
||||
var expected = 1
|
||||
if fn == "" {
|
||||
expected = LevelDebug + 1
|
||||
}
|
||||
if lineNum != expected {
|
||||
t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines")
|
||||
}
|
||||
if lineNum == 1 {
|
||||
if !strings.Contains(lastLine, fn) {
|
||||
t.Fatal(file + " " + lastLine + " not contains the log msg " + fn)
|
||||
}
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
|
||||
}
|
@ -126,7 +126,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
|
||||
|
||||
// WriteMsg write message in smtp writer.
|
||||
// it will send an email with subject and only this message.
|
||||
func (s *SMTPWriter) WriteMsg(msg string, level int) error {
|
||||
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > s.Level {
|
||||
return nil
|
||||
}
|
||||
@ -140,7 +140,7 @@ func (s *SMTPWriter) WriteMsg(msg string, level int) error {
|
||||
// and send the email all in one step.
|
||||
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
|
||||
|
||||
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||
}
|
||||
|
@ -388,3 +388,10 @@ func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace {
|
||||
ns.Namespace(n)
|
||||
}
|
||||
}
|
||||
|
||||
// NSHandler add handler
|
||||
func NSHandler(rootpath string, h http.Handler) LinkNamespace {
|
||||
return func(ns *Namespace) {
|
||||
ns.Handler(rootpath, h)
|
||||
}
|
||||
}
|
||||
|
14
orm/db.go
14
orm/db.go
@ -113,7 +113,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
||||
if fi.pk {
|
||||
_, value, _ = getExistPk(mi, ind)
|
||||
} else {
|
||||
field := ind.Field(fi.fieldIndex)
|
||||
field := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.isFielder {
|
||||
f := field.Addr().Interface().(Fielder)
|
||||
value = f.RawValue()
|
||||
@ -517,9 +517,9 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
||||
if num > 0 {
|
||||
if mi.fields.pk.auto {
|
||||
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
|
||||
ind.Field(mi.fields.pk.fieldIndex).SetUint(0)
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0)
|
||||
} else {
|
||||
ind.Field(mi.fields.pk.fieldIndex).SetInt(0)
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
|
||||
}
|
||||
}
|
||||
err := d.deleteRels(q, mi, []interface{}{pkValue}, tz)
|
||||
@ -859,13 +859,13 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
|
||||
mmi = fi.relModelInfo
|
||||
field := last
|
||||
if last.Kind() != reflect.Invalid {
|
||||
field = reflect.Indirect(last.Field(fi.fieldIndex))
|
||||
field = reflect.Indirect(last.FieldByIndex(fi.fieldIndex))
|
||||
if field.IsValid() {
|
||||
d.setColsValues(mmi, &field, mmi.fields.dbcols, trefs[:len(mmi.fields.dbcols)], tz)
|
||||
for _, fi := range mmi.fields.fieldsReverse {
|
||||
if fi.inModel && fi.reverseFieldInfo.mi == lastm {
|
||||
if fi.reverseFieldInfo != nil {
|
||||
f := field.Field(fi.fieldIndex)
|
||||
f := field.FieldByIndex(fi.fieldIndex)
|
||||
if f.Kind() == reflect.Ptr {
|
||||
f.Set(last.Addr())
|
||||
}
|
||||
@ -1014,7 +1014,7 @@ func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string,
|
||||
|
||||
fi := mi.fields.GetByColumn(column)
|
||||
|
||||
field := ind.Field(fi.fieldIndex)
|
||||
field := ind.FieldByIndex(fi.fieldIndex)
|
||||
|
||||
value, err := d.convertValueFromDB(fi, val, tz)
|
||||
if err != nil {
|
||||
@ -1350,7 +1350,7 @@ setValue:
|
||||
fieldType = fi.relModelInfo.fields.pk.fieldType
|
||||
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||
field.Set(mf)
|
||||
f := mf.Elem().Field(fi.relModelInfo.fields.pk.fieldIndex)
|
||||
f := mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||
field = f
|
||||
goto setValue
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ var (
|
||||
"postgres": DRPostgres,
|
||||
"sqlite3": DRSqlite,
|
||||
"tidb": DRTiDB,
|
||||
"oracle": DROracle,
|
||||
}
|
||||
dbBasers = map[DriverType]dbBaser{
|
||||
DRMySQL: newdbBaseMysql(),
|
||||
@ -151,7 +152,7 @@ func detectTZ(al *alias) {
|
||||
al.Engine = "INNODB"
|
||||
}
|
||||
|
||||
case DRSqlite:
|
||||
case DRSqlite, DROracle:
|
||||
al.TZ = time.UTC
|
||||
|
||||
case DRPostgres:
|
||||
|
@ -14,6 +14,41 @@
|
||||
|
||||
package orm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// oracle operators.
|
||||
var oracleOperators = map[string]string{
|
||||
"exact": "= ?",
|
||||
"gt": "> ?",
|
||||
"gte": ">= ?",
|
||||
"lt": "< ?",
|
||||
"lte": "<= ?",
|
||||
"//iendswith": "LIKE ?",
|
||||
}
|
||||
|
||||
// oracle column field types.
|
||||
var oracleTypes = map[string]string{
|
||||
"pk": "NOT NULL PRIMARY KEY",
|
||||
"bool": "bool",
|
||||
"string": "VARCHAR2(%d)",
|
||||
"string-text": "VARCHAR2(%d)",
|
||||
"time.Time-date": "DATE",
|
||||
"time.Time": "TIMESTAMP",
|
||||
"int8": "INTEGER",
|
||||
"int16": "INTEGER",
|
||||
"int32": "INTEGER",
|
||||
"int64": "INTEGER",
|
||||
"uint8": "INTEGER",
|
||||
"uint16": "INTEGER",
|
||||
"uint32": "INTEGER",
|
||||
"uint64": "INTEGER",
|
||||
"float64": "NUMBER",
|
||||
"float64-decimal": "NUMBER(%d, %d)",
|
||||
}
|
||||
|
||||
// oracle dbBaser
|
||||
type dbBaseOracle struct {
|
||||
dbBase
|
||||
@ -27,3 +62,35 @@ func newdbBaseOracle() dbBaser {
|
||||
b.ins = b
|
||||
return b
|
||||
}
|
||||
|
||||
// OperatorSQL get oracle operator.
|
||||
func (d *dbBaseOracle) OperatorSQL(operator string) string {
|
||||
return oracleOperators[operator]
|
||||
}
|
||||
|
||||
// DbTypes get oracle table field types.
|
||||
func (d *dbBaseOracle) DbTypes() map[string]string {
|
||||
return oracleTypes
|
||||
}
|
||||
|
||||
//ShowTablesQuery show all the tables in database
|
||||
func (d *dbBaseOracle) ShowTablesQuery() string {
|
||||
return "SELECT TABLE_NAME FROM USER_TABLES"
|
||||
}
|
||||
|
||||
// Oracle
|
||||
func (d *dbBaseOracle) ShowColumnsQuery(table string) string {
|
||||
return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+
|
||||
"WHERE TABLE_NAME ='%s'", strings.ToUpper(table))
|
||||
}
|
||||
|
||||
// check index is exist
|
||||
func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool {
|
||||
row := db.QueryRow("SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
|
||||
"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+
|
||||
"AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name))
|
||||
|
||||
var cnt int
|
||||
row.Scan(&cnt)
|
||||
return cnt > 0
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func getDbAlias(name string) *alias {
|
||||
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
|
||||
fi := mi.fields.pk
|
||||
|
||||
v := ind.Field(fi.fieldIndex)
|
||||
v := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsPostiveIntegerField > 0 {
|
||||
vu := v.Uint()
|
||||
exist = vu > 0
|
||||
|
@ -102,7 +102,7 @@ func newFields() *fields {
|
||||
// single field info
|
||||
type fieldInfo struct {
|
||||
mi *modelInfo
|
||||
fieldIndex int
|
||||
fieldIndex []int
|
||||
fieldType int
|
||||
dbcol bool
|
||||
inModel bool
|
||||
@ -138,7 +138,7 @@ type fieldInfo struct {
|
||||
}
|
||||
|
||||
// new field info
|
||||
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) {
|
||||
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) {
|
||||
var (
|
||||
tag string
|
||||
tagValue string
|
||||
@ -278,7 +278,7 @@ checkType:
|
||||
fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
|
||||
fi.addrValue = addrField
|
||||
fi.sf = sf
|
||||
fi.fullName = mi.fullName + "." + sf.Name
|
||||
fi.fullName = mi.fullName + mName + "." + sf.Name
|
||||
|
||||
fi.null = attrs["null"]
|
||||
fi.index = attrs["index"]
|
||||
|
@ -36,11 +36,6 @@ type modelInfo struct {
|
||||
|
||||
// new model info
|
||||
func newModelInfo(val reflect.Value) (info *modelInfo) {
|
||||
var (
|
||||
err error
|
||||
fi *fieldInfo
|
||||
sf reflect.StructField
|
||||
)
|
||||
|
||||
info = &modelInfo{}
|
||||
info.fields = newFields()
|
||||
@ -53,13 +48,31 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
|
||||
info.name = typ.Name()
|
||||
info.fullName = getFullName(typ)
|
||||
|
||||
addModelFields(info, ind, "", []int{})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []int) {
|
||||
var (
|
||||
err error
|
||||
fi *fieldInfo
|
||||
sf reflect.StructField
|
||||
)
|
||||
|
||||
for i := 0; i < ind.NumField(); i++ {
|
||||
field := ind.Field(i)
|
||||
sf = ind.Type().Field(i)
|
||||
if sf.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
fi, err = newFieldInfo(info, field, sf)
|
||||
// add anonymous struct fields
|
||||
if sf.Anonymous {
|
||||
addModelFields(info, field, mName+"."+sf.Name, append(index, i))
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err = newFieldInfo(info, field, sf, mName)
|
||||
|
||||
if err != nil {
|
||||
if err == errSkipField {
|
||||
@ -84,7 +97,7 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
fi.fieldIndex = i
|
||||
fi.fieldIndex = append(index, i)
|
||||
fi.mi = info
|
||||
fi.inModel = true
|
||||
}
|
||||
@ -93,8 +106,6 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
|
||||
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// combine related model info to new model info.
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
// As tidb can't use go get, so disable the tidb testing now
|
||||
// _ "github.com/pingcap/tidb"
|
||||
)
|
||||
@ -352,6 +351,30 @@ type GroupPermissions struct {
|
||||
Permission *Permission `orm:"rel(fk)"`
|
||||
}
|
||||
|
||||
type ModelID struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
type ModelBase struct {
|
||||
ModelID
|
||||
|
||||
Created time.Time `orm:"auto_now_add;type(datetime)"`
|
||||
Updated time.Time `orm:"auto_now;type(datetime)"`
|
||||
}
|
||||
|
||||
type InLine struct {
|
||||
// Common Fields
|
||||
ModelBase
|
||||
|
||||
// Other Fields
|
||||
Name string `orm:"unique"`
|
||||
Email string
|
||||
}
|
||||
|
||||
func NewInLine() *InLine {
|
||||
return new(InLine)
|
||||
}
|
||||
|
||||
var DBARGS = struct {
|
||||
Driver string
|
||||
Source string
|
||||
|
@ -140,7 +140,7 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i
|
||||
return (err == nil), id, err
|
||||
}
|
||||
|
||||
return false, ind.Field(mi.fields.pk.fieldIndex).Int(), err
|
||||
return false, ind.FieldByIndex(mi.fields.pk.fieldIndex).Int(), err
|
||||
}
|
||||
|
||||
// insert model data to database
|
||||
@ -160,9 +160,9 @@ func (o *orm) Insert(md interface{}) (int64, error) {
|
||||
func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) {
|
||||
if mi.fields.pk.auto {
|
||||
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
|
||||
ind.Field(mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
} else {
|
||||
ind.Field(mi.fields.pk.fieldIndex).SetInt(id)
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,7 +290,7 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int
|
||||
qs.orders = []string{order}
|
||||
}
|
||||
|
||||
find := ind.Field(fi.fieldIndex)
|
||||
find := ind.FieldByIndex(fi.fieldIndex)
|
||||
|
||||
var nums int64
|
||||
var err error
|
||||
|
@ -51,9 +51,9 @@ func (o *insertSet) Insert(md interface{}) (int64, error) {
|
||||
if id > 0 {
|
||||
if o.mi.fields.pk.auto {
|
||||
if o.mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
|
||||
ind.Field(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
} else {
|
||||
ind.Field(o.mi.fields.pk.fieldIndex).SetInt(id)
|
||||
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
|
||||
for _, col := range columns {
|
||||
if fi := sMi.fields.GetByColumn(col); fi != nil {
|
||||
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
|
||||
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value)
|
||||
o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -480,7 +480,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
||||
for _, col := range columns {
|
||||
if fi := sMi.fields.GetByColumn(col); fi != nil {
|
||||
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
|
||||
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value)
|
||||
o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -187,6 +187,7 @@ func TestSyncDb(t *testing.T) {
|
||||
RegisterModel(new(Group))
|
||||
RegisterModel(new(Permission))
|
||||
RegisterModel(new(GroupPermissions))
|
||||
RegisterModel(new(InLine))
|
||||
|
||||
err := RunSyncdb("default", true, Debug)
|
||||
throwFail(t, err)
|
||||
@ -206,6 +207,7 @@ func TestRegisterModels(t *testing.T) {
|
||||
RegisterModel(new(Group))
|
||||
RegisterModel(new(Permission))
|
||||
RegisterModel(new(GroupPermissions))
|
||||
RegisterModel(new(InLine))
|
||||
|
||||
BootStrap()
|
||||
|
||||
@ -1928,3 +1930,25 @@ func TestReadOrCreate(t *testing.T) {
|
||||
|
||||
dORM.Delete(u)
|
||||
}
|
||||
|
||||
func TestInLine(t *testing.T) {
|
||||
name := "inline"
|
||||
email := "hello@go.com"
|
||||
inline := NewInLine()
|
||||
inline.Name = name
|
||||
inline.Email = email
|
||||
|
||||
id, err := dORM.Insert(inline)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(id, 1))
|
||||
|
||||
il := NewInLine()
|
||||
il.ID = 1
|
||||
err = dORM.Read(il)
|
||||
throwFail(t, err)
|
||||
|
||||
throwFail(t, AssertIs(il.Name, name))
|
||||
throwFail(t, AssertIs(il.Email, email))
|
||||
throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate))
|
||||
throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime))
|
||||
}
|
||||
|
10
orm/types.go
10
orm/types.go
@ -148,6 +148,10 @@ type QuerySeter interface {
|
||||
// add OFFSET value
|
||||
// same as Limit function's args[0]
|
||||
Offset(offset interface{}) QuerySeter
|
||||
// add GROUP BY expression
|
||||
// for example:
|
||||
// qs.GroupBy("id")
|
||||
GroupBy(exprs ...string) QuerySeter
|
||||
// add ORDER expression.
|
||||
// "column" means ASC, "-column" means DESC.
|
||||
// for example:
|
||||
@ -162,6 +166,12 @@ type QuerySeter interface {
|
||||
// qs.RelatedSel("profile").One(&user)
|
||||
// user.Profile.Age = 32
|
||||
RelatedSel(params ...interface{}) QuerySeter
|
||||
// Set Distinct
|
||||
// for example:
|
||||
// o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
|
||||
// Distinct().
|
||||
// All(&permissions)
|
||||
Distinct() QuerySeter
|
||||
// return QuerySeter execution result number
|
||||
// for example:
|
||||
// num, err = qs.Filter("profile__age__gt", 28).Count()
|
||||
|
@ -58,7 +58,7 @@ func parserPkg(pkgRealpath, pkgpath string) error {
|
||||
rep := strings.NewReplacer("/", "_", ".", "_")
|
||||
commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go"
|
||||
if !compareFile(pkgRealpath) {
|
||||
Info(pkgRealpath + " has not changed, not reloading")
|
||||
Info(pkgRealpath + " no changed")
|
||||
return nil
|
||||
}
|
||||
genInfoList = make(map[string][]ControllerComments)
|
||||
@ -130,7 +130,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
|
||||
}
|
||||
|
||||
func genRouterCode() {
|
||||
os.Mkdir("routers", 0755)
|
||||
os.Mkdir(path.Join(AppPath, "routers"), 0755)
|
||||
Info("generate router from comments")
|
||||
var (
|
||||
globalinfo string
|
||||
@ -172,7 +172,7 @@ func genRouterCode() {
|
||||
}
|
||||
}
|
||||
if globalinfo != "" {
|
||||
f, err := os.Create(path.Join("routers", commentFilename))
|
||||
f, err := os.Create(path.Join(AppPath, "routers", commentFilename))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -182,7 +182,7 @@ func genRouterCode() {
|
||||
}
|
||||
|
||||
func compareFile(pkgRealpath string) bool {
|
||||
if !utils.FileExists(path.Join("routers", commentFilename)) {
|
||||
if !utils.FileExists(path.Join(AppPath, "routers", commentFilename)) {
|
||||
return true
|
||||
}
|
||||
if utils.FileExists(lastupdateFilename) {
|
||||
|
@ -36,6 +36,7 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -215,6 +216,7 @@ func Allow(opts *Options) beego.FilterFunc {
|
||||
for key, value := range headers {
|
||||
ctx.Output.Header(key, value)
|
||||
}
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
headers = opts.Header(origin)
|
||||
|
30
router.go
30
router.go
@ -62,12 +62,12 @@ var (
|
||||
}
|
||||
// these beego.Controller's methods shouldn't reflect to AutoRouter
|
||||
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
|
||||
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJson", "ServeJsonp",
|
||||
"ServeXml", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
|
||||
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
|
||||
"ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
|
||||
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
|
||||
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
|
||||
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
|
||||
"GetControllerAndAction"}
|
||||
"GetControllerAndAction", "ServeFormatted"}
|
||||
|
||||
urlPlaceholder = "{{placeholder}}"
|
||||
// DefaultAccessLogFilter will skip the accesslog if return true
|
||||
@ -321,7 +321,8 @@ func (p *ControllerRegister) Any(pattern string, f FilterFunc) {
|
||||
// ctx.Output.Body("hello world")
|
||||
// })
|
||||
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
|
||||
if _, ok := HTTPMETHOD[strings.ToUpper(method)]; method != "*" && !ok {
|
||||
method = strings.ToUpper(method)
|
||||
if _, ok := HTTPMETHOD[method]; method != "*" && !ok {
|
||||
panic("not support http method: " + method)
|
||||
}
|
||||
route := &controllerInfo{}
|
||||
@ -334,7 +335,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
|
||||
methods[val] = val
|
||||
}
|
||||
} else {
|
||||
methods[strings.ToUpper(method)] = strings.ToUpper(method)
|
||||
methods[method] = method
|
||||
}
|
||||
route.methods = methods
|
||||
for k := range methods {
|
||||
@ -606,6 +607,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
)
|
||||
context := p.pool.Get().(*beecontext.Context)
|
||||
context.Reset(rw, r)
|
||||
|
||||
defer p.pool.Put(context)
|
||||
defer p.recoverPanic(context)
|
||||
|
||||
@ -639,6 +641,13 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
goto Admin
|
||||
}
|
||||
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
|
||||
context.Input.CopyBody(BConfig.MaxMemory)
|
||||
}
|
||||
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
|
||||
}
|
||||
|
||||
// session init
|
||||
if BConfig.WebConfig.Session.SessionOn {
|
||||
var err error
|
||||
@ -649,17 +658,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
context.Input.CruSession.SessionRelease(rw)
|
||||
if context.Input.CruSession != nil {
|
||||
context.Input.CruSession.SessionRelease(rw)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if r.Method != "GET" && r.Method != "HEAD" {
|
||||
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
|
||||
context.Input.CopyBody(BConfig.MaxMemory)
|
||||
}
|
||||
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
|
||||
}
|
||||
|
||||
if p.execFilter(context, BeforeRouter, urlPath) {
|
||||
goto Admin
|
||||
}
|
||||
|
@ -65,6 +65,11 @@ func (tc *TestController) GetManyRouter() {
|
||||
tc.Ctx.WriteString(tc.Ctx.Input.Query(":id") + tc.Ctx.Input.Query(":page"))
|
||||
}
|
||||
|
||||
func (tc *TestController) GetEmptyBody() {
|
||||
var res []byte
|
||||
tc.Ctx.Output.Body(res)
|
||||
}
|
||||
|
||||
type ResStatus struct {
|
||||
Code int
|
||||
Msg string
|
||||
@ -239,6 +244,21 @@ func TestManyRoute(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test for issue #1669
|
||||
func TestEmptyResponse(t *testing.T) {
|
||||
|
||||
r, _ := http.NewRequest("GET", "/beego-empty.html", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler := NewControllerRegister()
|
||||
handler.Add("/beego-empty.html", &TestController{}, "get:GetEmptyBody")
|
||||
handler.ServeHTTP(w, r)
|
||||
|
||||
if body := w.Body.String(); body != "" {
|
||||
t.Error("want empty body")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotFound(t *testing.T) {
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
@ -98,18 +98,13 @@ func (rs *SessionStore) SessionID() string {
|
||||
|
||||
// SessionRelease save session values to redis
|
||||
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
|
||||
|
||||
b, err := session.EncodeGob(rs.values)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c := rs.p.Get()
|
||||
defer c.Close()
|
||||
// Update session value if exists or error.
|
||||
if existed, err := redis.Bool(c.Do("EXISTS", rs.sid)); existed || err != nil {
|
||||
c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
|
||||
}
|
||||
c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
|
||||
}
|
||||
|
||||
// Provider redis session provider
|
||||
|
@ -1,64 +0,0 @@
|
||||
// Copyright 2016 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSessionRelease(t *testing.T) {
|
||||
|
||||
provider := Provider{}
|
||||
if err := provider.SessionInit(3, "127.0.0.1:6379"); err != nil {
|
||||
t.Fatal("init session err,", err)
|
||||
}
|
||||
|
||||
sessionID := "beegosessionid_00001"
|
||||
|
||||
session, err := provider.SessionRegenerate("", sessionID)
|
||||
if err != nil {
|
||||
t.Fatal("new session error,", err)
|
||||
}
|
||||
|
||||
// set item.
|
||||
session.Set("k1", "v1")
|
||||
// update.
|
||||
session.SessionRelease(nil)
|
||||
|
||||
session, err = provider.SessionRead(sessionID)
|
||||
if err != nil {
|
||||
t.Fatal("read session error,", err)
|
||||
}
|
||||
if v1 := session.Get("k1"); v1 == nil {
|
||||
t.Fatal("want v1 got nil")
|
||||
} else if v, _ := v1.(string); v != "v1" {
|
||||
t.Fatalf("want v1 got %s", v)
|
||||
}
|
||||
|
||||
// delete
|
||||
provider.SessionDestroy(sessionID)
|
||||
session.Set("k2", "v2")
|
||||
|
||||
session.SessionRelease(nil)
|
||||
|
||||
session, err = provider.SessionRead(sessionID)
|
||||
if err != nil {
|
||||
t.Fatal("read session error,", err)
|
||||
}
|
||||
if session.Get("k1") != nil || session.Get("k2") != nil {
|
||||
t.Fatalf("want emtpy session value,got %s,%s", session.Get("k1"), session.Get("k2"))
|
||||
}
|
||||
|
||||
}
|
@ -102,7 +102,7 @@ func (pder *MemProvider) SessionRead(sid string) (Store, error) {
|
||||
pder.lock.RUnlock()
|
||||
pder.lock.Lock()
|
||||
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
|
||||
element := pder.list.PushBack(newsess)
|
||||
element := pder.list.PushFront(newsess)
|
||||
pder.sessions[sid] = element
|
||||
pder.lock.Unlock()
|
||||
return newsess, nil
|
||||
@ -134,7 +134,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
|
||||
pder.lock.RUnlock()
|
||||
pder.lock.Lock()
|
||||
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
|
||||
element := pder.list.PushBack(newsess)
|
||||
element := pder.list.PushFront(newsess)
|
||||
pder.sessions[sid] = element
|
||||
pder.lock.Unlock()
|
||||
return newsess, nil
|
||||
|
@ -201,7 +201,9 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil || cookie.Value == "" {
|
||||
return
|
||||
}
|
||||
manager.provider.SessionDestroy(cookie.Value)
|
||||
|
||||
sid, _ := url.QueryUnescape(cookie.Value)
|
||||
manager.provider.SessionDestroy(sid)
|
||||
if manager.config.EnableSetCookie {
|
||||
expiration := time.Now()
|
||||
cookie = &http.Cookie{Name: manager.config.CookieName,
|
||||
|
@ -93,12 +93,14 @@ type serveContentHolder struct {
|
||||
|
||||
var (
|
||||
staticFileMap = make(map[string]*serveContentHolder)
|
||||
mapLock sync.Mutex
|
||||
mapLock sync.RWMutex
|
||||
)
|
||||
|
||||
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) {
|
||||
mapKey := acceptEncoding + ":" + filePath
|
||||
mapLock.RLock()
|
||||
mapFile, _ := staticFileMap[mapKey]
|
||||
mapLock.RUnlock()
|
||||
if isOk(mapFile, fi) {
|
||||
return mapFile.encoding != "", mapFile.encoding, mapFile, nil
|
||||
}
|
||||
|
@ -7,10 +7,12 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const licenseFile = "./LICENSE"
|
||||
var currentWorkDir, _ = os.Getwd()
|
||||
var licenseFile = filepath.Join(currentWorkDir, "LICENSE")
|
||||
|
||||
func testOpenFile(encoding string, content []byte, t *testing.T) {
|
||||
fi, _ := os.Stat(licenseFile)
|
||||
|
139
template.go
139
template.go
@ -18,23 +18,41 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
beegoTplFuncMap = make(template.FuncMap)
|
||||
// BeeTemplates caching map and supported template file extensions.
|
||||
BeeTemplates = make(map[string]*template.Template)
|
||||
// BeeTemplateExt stores the template extension which will build
|
||||
BeeTemplateExt = []string{"tpl", "html"}
|
||||
// beeTemplates caching map and supported template file extensions.
|
||||
beeTemplates = make(map[string]*template.Template)
|
||||
templatesLock sync.RWMutex
|
||||
// beeTemplateExt stores the template extension which will build
|
||||
beeTemplateExt = []string{"tpl", "html"}
|
||||
)
|
||||
|
||||
func executeTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
if BConfig.RunMode == DEV {
|
||||
templatesLock.RLock()
|
||||
defer templatesLock.RUnlock()
|
||||
}
|
||||
if t, ok := beeTemplates[name]; ok {
|
||||
err := t.ExecuteTemplate(wr, name, data)
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
panic("can't find templatefile in the path:" + name)
|
||||
}
|
||||
|
||||
func init() {
|
||||
beegoTplFuncMap["dateformat"] = DateFormat
|
||||
beegoTplFuncMap["date"] = Date
|
||||
@ -53,7 +71,6 @@ func init() {
|
||||
beegoTplFuncMap["config"] = GetConfig
|
||||
beegoTplFuncMap["map_get"] = MapGet
|
||||
|
||||
// go1.2 added template funcs
|
||||
// Comparisons
|
||||
beegoTplFuncMap["eq"] = eq // ==
|
||||
beegoTplFuncMap["ge"] = ge // >=
|
||||
@ -62,21 +79,25 @@ func init() {
|
||||
beegoTplFuncMap["lt"] = lt // <
|
||||
beegoTplFuncMap["ne"] = ne // !=
|
||||
|
||||
beegoTplFuncMap["urlfor"] = URLFor // !=
|
||||
beegoTplFuncMap["urlfor"] = URLFor // build a URL to match a Controller and it's method
|
||||
}
|
||||
|
||||
// AddFuncMap let user to register a func in the template.
|
||||
func AddFuncMap(key string, funname interface{}) error {
|
||||
beegoTplFuncMap[key] = funname
|
||||
func AddFuncMap(key string, fn interface{}) error {
|
||||
beegoTplFuncMap[key] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
type templatefile struct {
|
||||
type templateFile struct {
|
||||
root string
|
||||
files map[string][]string
|
||||
}
|
||||
|
||||
func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error {
|
||||
// visit will make the paths into two part,the first is subDir (without tf.root),the second is full path(without tf.root).
|
||||
// if tf.root="views" and
|
||||
// paths is "views/errors/404.html",the subDir will be "errors",the file will be "errors/404.html"
|
||||
// paths is "views/admin/errors/404.html",the subDir will be "admin/errors",the file will be "admin/errors/404.html"
|
||||
func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error {
|
||||
if f == nil {
|
||||
return err
|
||||
}
|
||||
@ -88,24 +109,16 @@ func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error {
|
||||
}
|
||||
|
||||
replace := strings.NewReplacer("\\", "/")
|
||||
a := []byte(paths)
|
||||
a = a[len([]byte(tf.root)):]
|
||||
file := strings.TrimLeft(replace.Replace(string(a)), "/")
|
||||
subdir := filepath.Dir(file)
|
||||
if _, ok := tf.files[subdir]; ok {
|
||||
tf.files[subdir] = append(tf.files[subdir], file)
|
||||
} else {
|
||||
m := make([]string, 1)
|
||||
m[0] = file
|
||||
tf.files[subdir] = m
|
||||
}
|
||||
file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/")
|
||||
subDir := filepath.Dir(file)
|
||||
|
||||
tf.files[subDir] = append(tf.files[subDir], file)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasTemplateExt return this path contains supported template extension of beego or not.
|
||||
func HasTemplateExt(paths string) bool {
|
||||
for _, v := range BeeTemplateExt {
|
||||
for _, v := range beeTemplateExt {
|
||||
if strings.HasSuffix(paths, "."+v) {
|
||||
return true
|
||||
}
|
||||
@ -115,12 +128,12 @@ func HasTemplateExt(paths string) bool {
|
||||
|
||||
// AddTemplateExt add new extension for template.
|
||||
func AddTemplateExt(ext string) {
|
||||
for _, v := range BeeTemplateExt {
|
||||
for _, v := range beeTemplateExt {
|
||||
if v == ext {
|
||||
return
|
||||
}
|
||||
}
|
||||
BeeTemplateExt = append(BeeTemplateExt, ext)
|
||||
beeTemplateExt = append(beeTemplateExt, ext)
|
||||
}
|
||||
|
||||
// BuildTemplate will build all template files in a directory.
|
||||
@ -132,7 +145,7 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
}
|
||||
return errors.New("dir open err")
|
||||
}
|
||||
self := &templatefile{
|
||||
self := &templateFile{
|
||||
root: dir,
|
||||
files: make(map[string][]string),
|
||||
}
|
||||
@ -146,12 +159,14 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
for _, v := range self.files {
|
||||
for _, file := range v {
|
||||
if len(files) == 0 || utils.InSlice(file, files) {
|
||||
templatesLock.Lock()
|
||||
t, err := getTemplate(self.root, file, v...)
|
||||
if err != nil {
|
||||
Trace("parse template err:", file, err)
|
||||
} else {
|
||||
BeeTemplates[file] = t
|
||||
beeTemplates[file] = t
|
||||
}
|
||||
templatesLock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,16 +174,16 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
}
|
||||
|
||||
func getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) {
|
||||
var fileabspath string
|
||||
var fileAbsPath string
|
||||
if filepath.HasPrefix(file, "../") {
|
||||
fileabspath = filepath.Join(root, filepath.Dir(parent), file)
|
||||
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
|
||||
} else {
|
||||
fileabspath = filepath.Join(root, file)
|
||||
fileAbsPath = filepath.Join(root, file)
|
||||
}
|
||||
if e := utils.FileExists(fileabspath); !e {
|
||||
if e := utils.FileExists(fileAbsPath); !e {
|
||||
panic("can't find template file:" + file)
|
||||
}
|
||||
data, err := ioutil.ReadFile(fileabspath)
|
||||
data, err := ioutil.ReadFile(fileAbsPath)
|
||||
if err != nil {
|
||||
return nil, [][]string{}, err
|
||||
}
|
||||
@ -177,11 +192,11 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
|
||||
return nil, [][]string{}, err
|
||||
}
|
||||
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"")
|
||||
allsub := reg.FindAllStringSubmatch(string(data), -1)
|
||||
for _, m := range allsub {
|
||||
allSub := reg.FindAllStringSubmatch(string(data), -1)
|
||||
for _, m := range allSub {
|
||||
if len(m) == 2 {
|
||||
tlook := t.Lookup(m[1])
|
||||
if tlook != nil {
|
||||
tl := t.Lookup(m[1])
|
||||
if tl != nil {
|
||||
continue
|
||||
}
|
||||
if !HasTemplateExt(m[1]) {
|
||||
@ -193,17 +208,17 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
|
||||
}
|
||||
}
|
||||
}
|
||||
return t, allsub, nil
|
||||
return t, allSub, nil
|
||||
}
|
||||
|
||||
func getTemplate(root, file string, others ...string) (t *template.Template, err error) {
|
||||
t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap)
|
||||
var submods [][]string
|
||||
t, submods, err = getTplDeep(root, file, "", t)
|
||||
var subMods [][]string
|
||||
t, subMods, err = getTplDeep(root, file, "", t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err = _getTemplate(t, root, submods, others...)
|
||||
t, err = _getTemplate(t, root, subMods, others...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -211,44 +226,44 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err
|
||||
return
|
||||
}
|
||||
|
||||
func _getTemplate(t0 *template.Template, root string, submods [][]string, others ...string) (t *template.Template, err error) {
|
||||
func _getTemplate(t0 *template.Template, root string, subMods [][]string, others ...string) (t *template.Template, err error) {
|
||||
t = t0
|
||||
for _, m := range submods {
|
||||
for _, m := range subMods {
|
||||
if len(m) == 2 {
|
||||
templ := t.Lookup(m[1])
|
||||
if templ != nil {
|
||||
tpl := t.Lookup(m[1])
|
||||
if tpl != nil {
|
||||
continue
|
||||
}
|
||||
//first check filename
|
||||
for _, otherfile := range others {
|
||||
if otherfile == m[1] {
|
||||
var submods1 [][]string
|
||||
t, submods1, err = getTplDeep(root, otherfile, "", t)
|
||||
for _, otherFile := range others {
|
||||
if otherFile == m[1] {
|
||||
var subMods1 [][]string
|
||||
t, subMods1, err = getTplDeep(root, otherFile, "", t)
|
||||
if err != nil {
|
||||
Trace("template parse file err:", err)
|
||||
} else if submods1 != nil && len(submods1) > 0 {
|
||||
t, err = _getTemplate(t, root, submods1, others...)
|
||||
} else if subMods1 != nil && len(subMods1) > 0 {
|
||||
t, err = _getTemplate(t, root, subMods1, others...)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
//second check define
|
||||
for _, otherfile := range others {
|
||||
fileabspath := filepath.Join(root, otherfile)
|
||||
data, err := ioutil.ReadFile(fileabspath)
|
||||
for _, otherFile := range others {
|
||||
fileAbsPath := filepath.Join(root, otherFile)
|
||||
data, err := ioutil.ReadFile(fileAbsPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"")
|
||||
allsub := reg.FindAllStringSubmatch(string(data), -1)
|
||||
for _, sub := range allsub {
|
||||
allSub := reg.FindAllStringSubmatch(string(data), -1)
|
||||
for _, sub := range allSub {
|
||||
if len(sub) == 2 && sub[1] == m[1] {
|
||||
var submods1 [][]string
|
||||
t, submods1, err = getTplDeep(root, otherfile, "", t)
|
||||
var subMods1 [][]string
|
||||
t, subMods1, err = getTplDeep(root, otherFile, "", t)
|
||||
if err != nil {
|
||||
Trace("template parse file err:", err)
|
||||
} else if submods1 != nil && len(submods1) > 0 {
|
||||
t, err = _getTemplate(t, root, submods1, others...)
|
||||
} else if subMods1 != nil && len(subMods1) > 0 {
|
||||
t, err = _getTemplate(t, root, subMods1, others...)
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -272,7 +287,9 @@ func SetStaticPath(url string, path string) *App {
|
||||
if !strings.HasPrefix(url, "/") {
|
||||
url = "/" + url
|
||||
}
|
||||
url = strings.TrimRight(url, "/")
|
||||
if url != "/" {
|
||||
url = strings.TrimRight(url, "/")
|
||||
}
|
||||
BConfig.WebConfig.StaticDir[url] = path
|
||||
return BeeApp
|
||||
}
|
||||
@ -282,7 +299,9 @@ func DelStaticPath(url string) *App {
|
||||
if !strings.HasPrefix(url, "/") {
|
||||
url = "/" + url
|
||||
}
|
||||
url = strings.TrimRight(url, "/")
|
||||
if url != "/" {
|
||||
url = strings.TrimRight(url, "/")
|
||||
}
|
||||
delete(BConfig.WebConfig.StaticDir, url)
|
||||
return BeeApp
|
||||
}
|
||||
|
@ -70,10 +70,10 @@ func TestTemplate(t *testing.T) {
|
||||
if err := BuildTemplate(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(BeeTemplates) != 3 {
|
||||
t.Fatalf("should be 3 but got %v", len(BeeTemplates))
|
||||
if len(beeTemplates) != 3 {
|
||||
t.Fatalf("should be 3 but got %v", len(beeTemplates))
|
||||
}
|
||||
if err := BeeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", nil); err != nil {
|
||||
if err := beeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range files {
|
||||
@ -126,7 +126,7 @@ func TestRelativeTemplate(t *testing.T) {
|
||||
if err := BuildTemplate(dir, files[1]); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := BeeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil {
|
||||
if err := beeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, name := range files {
|
||||
|
45
tree.go
45
tree.go
@ -141,7 +141,7 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st
|
||||
regexpStr = "([^.]+).(.+)"
|
||||
params = params[1:]
|
||||
} else {
|
||||
for range params {
|
||||
for _ = range params {
|
||||
regexpStr = "([^/]+)/" + regexpStr
|
||||
}
|
||||
}
|
||||
@ -254,7 +254,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
|
||||
regexpStr = "/([^.]+).(.+)"
|
||||
params = params[1:]
|
||||
} else {
|
||||
for range params {
|
||||
for _ = range params {
|
||||
regexpStr = "/([^/]+)" + regexpStr
|
||||
}
|
||||
}
|
||||
@ -265,15 +265,14 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
|
||||
}
|
||||
t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
|
||||
} else {
|
||||
var ok bool
|
||||
var subTree *Tree
|
||||
for _, subTree = range t.fixrouters {
|
||||
if t.prefix == seg {
|
||||
ok = true
|
||||
for _, sub := range t.fixrouters {
|
||||
if sub.prefix == seg {
|
||||
subTree = sub
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
if subTree == nil {
|
||||
subTree = NewTree()
|
||||
subTree.prefix = seg
|
||||
t.fixrouters = append(t.fixrouters, subTree)
|
||||
@ -390,7 +389,7 @@ type leafInfo struct {
|
||||
func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok bool) {
|
||||
//fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps)
|
||||
if leaf.regexps == nil {
|
||||
if len(wildcardValues) == 0 { // static path
|
||||
if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path
|
||||
return true
|
||||
}
|
||||
// match *
|
||||
@ -420,7 +419,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
|
||||
if len(strs) == 2 {
|
||||
ctx.Input.SetParam(":ext", strs[1])
|
||||
}
|
||||
ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
|
||||
if index > (len(wildcardValues) - 1) {
|
||||
ctx.Input.SetParam(":path", "")
|
||||
} else {
|
||||
ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
|
||||
}
|
||||
return true
|
||||
}
|
||||
// match :id
|
||||
@ -438,7 +441,9 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
|
||||
}
|
||||
matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
|
||||
for i, match := range matches[1:] {
|
||||
ctx.Input.SetParam(leaf.wildcards[i], match)
|
||||
if i < len(leaf.wildcards) {
|
||||
ctx.Input.SetParam(leaf.wildcards[i], match)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -448,17 +453,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
|
||||
// "/admin/" -> ["admin"]
|
||||
// "/admin/users" -> ["admin", "users"]
|
||||
func splitPath(key string) []string {
|
||||
key = strings.Trim(key, "/ ")
|
||||
if key == "" {
|
||||
return []string{}
|
||||
}
|
||||
elements := strings.Split(key, "/")
|
||||
if elements[0] == "" {
|
||||
elements = elements[1:]
|
||||
}
|
||||
if elements[len(elements)-1] == "" {
|
||||
elements = elements[:len(elements)-1]
|
||||
}
|
||||
return elements
|
||||
return strings.Split(key, "/")
|
||||
}
|
||||
|
||||
// "admin" -> false, nil, ""
|
||||
@ -542,13 +541,19 @@ func splitSegment(key string) (bool, []string, string) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v == ':' {
|
||||
// Escape Sequence '\'
|
||||
if i > 0 && key[i-1] == '\\' {
|
||||
out = append(out, v)
|
||||
} else if v == ':' {
|
||||
param = make([]rune, 0)
|
||||
start = true
|
||||
} else if v == '(' {
|
||||
startexp = true
|
||||
start = false
|
||||
params = append(params, ":"+string(param))
|
||||
if len(param) > 0 {
|
||||
params = append(params, ":"+string(param))
|
||||
param = make([]rune, 0)
|
||||
}
|
||||
paramsNum++
|
||||
expt = make([]rune, 0)
|
||||
expt = append(expt, '(')
|
||||
|
98
tree_test.go
98
tree_test.go
@ -15,6 +15,7 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
@ -57,6 +58,9 @@ func init() {
|
||||
"/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg",
|
||||
map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(a)", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(b)", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(c)", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}})
|
||||
@ -93,6 +97,21 @@ func TestTreeRouters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStaticPath(t *testing.T) {
|
||||
tr := NewTree()
|
||||
tr.AddRouter("/topic/:id", "wildcard")
|
||||
tr.AddRouter("/topic", "static")
|
||||
ctx := context.NewContext()
|
||||
obj := tr.Match("/topic", ctx)
|
||||
if obj == nil || obj.(string) != "static" {
|
||||
t.Fatal("/topic is a static route")
|
||||
}
|
||||
obj = tr.Match("/topic/1", ctx)
|
||||
if obj == nil || obj.(string) != "wildcard" {
|
||||
t.Fatal("/topic/1 is a wildcard route")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddTree(t *testing.T) {
|
||||
tr := NewTree()
|
||||
tr.AddRouter("/shop/:id/account", "astaxie")
|
||||
@ -217,6 +236,18 @@ func TestAddTree4(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test for issue #1595
|
||||
func TestAddTree5(t *testing.T) {
|
||||
tr := NewTree()
|
||||
tr.AddRouter("/v1/shop/:id", "shopdetail")
|
||||
tr.AddRouter("/v1/shop/", "shophome")
|
||||
ctx := context.NewContext()
|
||||
obj := tr.Match("/v1/shop/", ctx)
|
||||
if obj == nil || obj.(string) != "shophome" {
|
||||
t.Fatal("url /v1/shop/ need match router /v1/shop/ ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitPath(t *testing.T) {
|
||||
a := splitPath("")
|
||||
if len(a) != 0 {
|
||||
@ -245,48 +276,31 @@ func TestSplitPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSplitSegment(t *testing.T) {
|
||||
b, w, r := splitSegment("admin")
|
||||
if b || len(w) != 0 || r != "" {
|
||||
t.Fatal("admin should return false, nil, ''")
|
||||
|
||||
items := map[string]struct {
|
||||
isReg bool
|
||||
params []string
|
||||
regStr string
|
||||
}{
|
||||
"admin": {false, nil, ""},
|
||||
"*": {true, []string{":splat"}, ""},
|
||||
"*.*": {true, []string{".", ":path", ":ext"}, ""},
|
||||
":id": {true, []string{":id"}, ""},
|
||||
"?:id": {true, []string{":", ":id"}, ""},
|
||||
":id:int": {true, []string{":id"}, "([0-9]+)"},
|
||||
":name:string": {true, []string{":name"}, `([\w]+)`},
|
||||
":id([0-9]+)": {true, []string{":id"}, `([0-9]+)`},
|
||||
":id([0-9]+)_:name": {true, []string{":id", ":name"}, `([0-9]+)_(.+)`},
|
||||
":id(.+)_cms.html": {true, []string{":id"}, `(.+)_cms.html`},
|
||||
"cms_:id(.+)_:page(.+).html": {true, []string{":id", ":page"}, `cms_(.+)_(.+).html`},
|
||||
`:app(a|b|c)`: {true, []string{":app"}, `(a|b|c)`},
|
||||
`:app\((a|b|c)\)`: {true, []string{":app"}, `(.+)\((a|b|c)\)`},
|
||||
}
|
||||
b, w, r = splitSegment("*")
|
||||
if !b || len(w) != 1 || w[0] != ":splat" || r != "" {
|
||||
t.Fatal("* should return true, [:splat], ''")
|
||||
}
|
||||
b, w, r = splitSegment("*.*")
|
||||
if !b || len(w) != 3 || w[1] != ":path" || w[2] != ":ext" || w[0] != "." || r != "" {
|
||||
t.Fatal("admin should return true,[. :path :ext], ''")
|
||||
}
|
||||
b, w, r = splitSegment(":id")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != "" {
|
||||
t.Fatal(":id should return true, [:id], ''")
|
||||
}
|
||||
b, w, r = splitSegment("?:id")
|
||||
if !b || len(w) != 2 || w[0] != ":" || w[1] != ":id" || r != "" {
|
||||
t.Fatal("?:id should return true, [: :id], ''")
|
||||
}
|
||||
b, w, r = splitSegment(":id:int")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != "([0-9]+)" {
|
||||
t.Fatal(":id:int should return true, [:id], '([0-9]+)'")
|
||||
}
|
||||
b, w, r = splitSegment(":name:string")
|
||||
if !b || len(w) != 1 || w[0] != ":name" || r != `([\w]+)` {
|
||||
t.Fatal(`:name:string should return true, [:name], '([\w]+)'`)
|
||||
}
|
||||
b, w, r = splitSegment(":id([0-9]+)")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != `([0-9]+)` {
|
||||
t.Fatal(`:id([0-9]+) should return true, [:id], '([0-9]+)'`)
|
||||
}
|
||||
b, w, r = splitSegment(":id([0-9]+)_:name")
|
||||
if !b || len(w) != 2 || w[0] != ":id" || w[1] != ":name" || r != `([0-9]+)_(.+)` {
|
||||
t.Fatal(`:id([0-9]+)_:name should return true, [:id :name], '([0-9]+)_(.+)'`)
|
||||
}
|
||||
b, w, r = splitSegment(":id(.+)_cms.html")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != `(.+)_cms.html` {
|
||||
t.Fatal(":id_cms.html should return true, [:id], '(.+)_cms.html'")
|
||||
}
|
||||
b, w, r = splitSegment("cms_:id(.+)_:page(.+).html")
|
||||
if !b || len(w) != 2 || w[0] != ":id" || w[1] != ":page" || r != `cms_(.+)_(.+).html` {
|
||||
t.Fatal(":id_cms.html should return true, [:id :page], cms_(.+)_(.+).html")
|
||||
|
||||
for pattern, v := range items {
|
||||
b, w, r := splitSegment(pattern)
|
||||
if b != v.isReg || r != v.regStr || strings.Join(w, ",") != strings.Join(v.params, ",") {
|
||||
t.Fatalf("%s should return %t,%s,%q, got %t,%s,%q", pattern, v.isReg, v.params, v.regStr, b, w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha {
|
||||
beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler)
|
||||
|
||||
// add to template func map
|
||||
beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML())
|
||||
beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML)
|
||||
|
||||
return cpt
|
||||
}
|
||||
|
@ -31,10 +31,13 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLineLength = 76
|
||||
|
||||
upperhex = "0123456789ABCDEF"
|
||||
)
|
||||
|
||||
// Email is the type used for email messages
|
||||
@ -74,9 +77,6 @@ func NewEMail(config string) *Email {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if e.From == "" {
|
||||
e.From = e.Username
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@ -228,14 +228,21 @@ func (e *Email) Send() error {
|
||||
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
|
||||
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
||||
// Check to make sure there is at least one recipient and one "From" address
|
||||
if e.From == "" || len(to) == 0 {
|
||||
return errors.New("Must specify at least one From address and one To address")
|
||||
if len(to) == 0 {
|
||||
return errors.New("Must specify at least one To address")
|
||||
}
|
||||
from, err := mail.ParseAddress(e.From)
|
||||
|
||||
from, err := mail.ParseAddress(e.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.From = from.String()
|
||||
|
||||
if len(e.From) == 0 {
|
||||
e.From = e.Username
|
||||
}
|
||||
// use mail's RFC 2047 to encode any string
|
||||
e.Subject = qEncode("utf-8", e.Subject)
|
||||
|
||||
raw, err := e.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -342,3 +349,73 @@ func base64Wrap(w io.Writer, b []byte) {
|
||||
w.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode returns the encoded-word form of s. If s is ASCII without special
|
||||
// characters, it is returned unchanged. The provided charset is the IANA
|
||||
// charset name of s. It is case insensitive.
|
||||
// RFC 2047 encoded-word
|
||||
func qEncode(charset, s string) string {
|
||||
if !needsEncoding(s) {
|
||||
return s
|
||||
}
|
||||
return encodeWord(charset, s)
|
||||
}
|
||||
|
||||
func needsEncoding(s string) bool {
|
||||
for _, b := range s {
|
||||
if (b < ' ' || b > '~') && b != '\t' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeWord encodes a string into an encoded-word.
|
||||
func encodeWord(charset, s string) string {
|
||||
buf := getBuffer()
|
||||
|
||||
buf.WriteString("=?")
|
||||
buf.WriteString(charset)
|
||||
buf.WriteByte('?')
|
||||
buf.WriteByte('q')
|
||||
buf.WriteByte('?')
|
||||
|
||||
enc := make([]byte, 3)
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case b == ' ':
|
||||
buf.WriteByte('_')
|
||||
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
|
||||
buf.WriteByte(b)
|
||||
default:
|
||||
enc[0] = '='
|
||||
enc[1] = upperhex[b>>4]
|
||||
enc[2] = upperhex[b&0x0f]
|
||||
buf.Write(enc)
|
||||
}
|
||||
}
|
||||
buf.WriteString("?=")
|
||||
|
||||
es := buf.String()
|
||||
putBuffer(buf)
|
||||
return es
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
if buf.Len() > 1024 {
|
||||
return
|
||||
}
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
}
|
||||
|
@ -588,7 +588,7 @@ func (b Base64) GetLimitValue() interface{} {
|
||||
}
|
||||
|
||||
// just for chinese mobile phone number
|
||||
var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][0679]|[4][579]))\\d{8}$")
|
||||
var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\\d{8}$")
|
||||
|
||||
// Mobile check struct
|
||||
type Mobile struct {
|
||||
|
Reference in New Issue
Block a user