mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 07:41:02 +00:00
Compare commits
320 Commits
Author | SHA1 | Date | |
---|---|---|---|
3f67c62dd8 | |||
bb860d2752 | |||
f280dab880 | |||
7283aead42 | |||
0ad4038d9f | |||
227678c2ef | |||
c611da2e3c | |||
00e986cd3b | |||
bed907653e | |||
7253ff2f8c | |||
715c2c4312 | |||
9224cd3ef7 | |||
efbde1ee77 | |||
8b525b1aa5 | |||
86b3162aff | |||
3c016a0b2d | |||
ffd748bf75 | |||
1c54ff27c4 | |||
7760d24761 | |||
3672f96a9d | |||
68311b286e | |||
bed1d9bd27 | |||
44a57e86dd | |||
3362f83662 | |||
7813cb5783 | |||
ef0d3d80dc | |||
47b238728d | |||
1b4f30e11e | |||
61c9387edd | |||
957db1362f | |||
0e786fa4af | |||
01c3812520 | |||
ce6f19871c | |||
74778bb9d4 | |||
7b542e612f | |||
3ca68f9e30 | |||
1a52a34544 | |||
86528c7b3c | |||
ce6d673933 | |||
c2aeab78aa | |||
0cad8207ec | |||
6f0a985755 | |||
15c048d6ca | |||
d5c339530a | |||
b89bfe76d0 | |||
125030800b | |||
e4fe54f6cd | |||
d85293b7c0 | |||
5d1e60b468 | |||
2b867f8152 | |||
909afa9352 | |||
d08e3d1b11 | |||
5485e1334f | |||
4b3ed53158 | |||
cacf6cde19 | |||
4e10100575 | |||
f471ee9025 | |||
182a21172f | |||
02330c6085 | |||
e951c555e4 | |||
6a4ebc67ac | |||
33ef34b95d | |||
bf17558d06 | |||
e0e888ab8f | |||
3583ad8cc0 | |||
e2316c4b9e | |||
6d1b203bca | |||
50c5df32b1 | |||
530c32017c | |||
ec521ad166 | |||
4b8ecced83 | |||
d11823548b | |||
ee26279311 | |||
8099a81b7a | |||
0943ef9e74 | |||
a2e63a3820 | |||
a08e937cf0 | |||
9ab5f6d808 | |||
fee06a23bd | |||
e0393b721c | |||
84b6bef7d0 | |||
8917fe44a9 | |||
21c7821692 | |||
f9f92b4f61 | |||
c893b3472c | |||
479dfdbd40 | |||
1f68e5a705 | |||
415b9cf310 | |||
ed474b517d | |||
9572fdcf9a | |||
cb3f240f44 | |||
844a3b0ffd | |||
448be6e58c | |||
2bd743fcff | |||
0d3a806c23 | |||
2c1cea08dd | |||
3f016840db | |||
d528fafd43 | |||
b7e3402995 | |||
b807362c39 | |||
d9b05e6b3f | |||
e9f967102c | |||
2e0bcf611c | |||
2ebf3cd450 | |||
1fe2226c11 | |||
d4d7621942 | |||
761b6c129c | |||
8f0749ddee | |||
7b051e7ad1 | |||
24b8870637 | |||
cef91db28e | |||
2165fb6e67 | |||
05e929ed8c | |||
f32392e956 | |||
e21b425957 | |||
e77a591a6c | |||
4890dd708c | |||
6234b50111 | |||
2c12383263 | |||
e50f4f5631 | |||
d679a4b865 | |||
153d76e200 | |||
0e4b9563ae | |||
27be1e7ca3 | |||
a4d4b8de77 | |||
dae2a36eb5 | |||
7ceff43db6 | |||
85a8c5c4f4 | |||
b28581a463 | |||
fa8d94fa69 | |||
3c8ed9adfc | |||
8210fd12d1 | |||
77ff15ee33 | |||
4d8e1f93ff | |||
e607be6fa6 | |||
73f499948a | |||
a7452388d9 | |||
2a2b433e19 | |||
272271f588 | |||
fa7416452e | |||
245664010c | |||
9e17f518b8 | |||
830985b90b | |||
a271d67ba4 | |||
710c7c79d4 | |||
5402c753fb | |||
520a417cca | |||
56dc9bf622 | |||
b36afadbdc | |||
e89f562396 | |||
5aa085bf41 | |||
70f3f6b8b1 | |||
86e18bf6f9 | |||
14159eaa7e | |||
df27c96102 | |||
5ac254bf61 | |||
423f2dad35 | |||
203ab3eba8 | |||
0c32255d14 | |||
9ce6dc4cdf | |||
903e21bef2 | |||
3a3f70027c | |||
4cd2408248 | |||
cb0c006cfa | |||
ce4fc7bfcd | |||
81c6c898cf | |||
002dcaab23 | |||
abaa1bbcac | |||
314a447d57 | |||
9679f5e22a | |||
5185816942 | |||
22617aeb13 | |||
9c400778d3 | |||
f6ad2cf848 | |||
7906b18d89 | |||
99f1e6c8b5 | |||
e95bef1331 | |||
657744efb1 | |||
6da765c465 | |||
1cac38b664 | |||
ba3a1f8457 | |||
d7f41ccd0c | |||
881d010c01 | |||
553078d956 | |||
528f273e58 | |||
53d680a493 | |||
ed0e6419f0 | |||
a99c0d4025 | |||
301dcfb626 | |||
6362dc397a | |||
813f47fd41 | |||
933ac0f369 | |||
885d45db05 | |||
d49c7f96cb | |||
ebdf4412b3 | |||
8ec6dd93cf | |||
fe4fa6a095 | |||
ccc84dd8eb | |||
734bbb3337 | |||
caa404cec1 | |||
52fbab329d | |||
8ce9f69b4d | |||
96a5d09ef0 | |||
814b673e3c | |||
99436a75b1 | |||
8e82ed319b | |||
eae2147735 | |||
e281b6e82a | |||
adaa4ab929 | |||
7e65338c87 | |||
5bd7d8c43f | |||
561e7115f3 | |||
220cf91180 | |||
cbd6f31b66 | |||
221306fff4 | |||
f05bb2ecd3 | |||
699de2ae75 | |||
fb77464d69 | |||
1794c52d65 | |||
3ca44071e6 | |||
e0a36fb61e | |||
85a9b05495 | |||
70108131e6 | |||
45f2390128 | |||
826f81f479 | |||
4dbbae61e0 | |||
e59271662c | |||
fa4a231cd4 | |||
850dc59b6e | |||
6d0fe8c4f4 | |||
2db8c753fd | |||
56860d1fea | |||
94bde3a777 | |||
3300db832b | |||
52a0b657b7 | |||
f02ff0420d | |||
3e2ffa545f | |||
06299fa47b | |||
d8bed89c44 | |||
0814eefa62 | |||
8344a60552 | |||
0fb4a8af24 | |||
a6c1377f91 | |||
cdfd830f65 | |||
98dfecfd8a | |||
03840f3fe8 | |||
2e6a23743b | |||
2362ca00b5 | |||
b7d1afbf86 | |||
eaf38bb096 | |||
3be6688cd1 | |||
1eab11ca90 | |||
c4276d31c5 | |||
8f70df6c7b | |||
1786b16e61 | |||
9f18813c2b | |||
88c5dfa6ea | |||
0a86926522 | |||
b78de2b440 | |||
6c0979c314 | |||
5858607f49 | |||
1a401af23b | |||
b2098266a3 | |||
3ac90df5fa | |||
9e9671d8cd | |||
d3b54c46e3 | |||
7bad3d1c67 | |||
4db78f243e | |||
ba7a809de8 | |||
630f77bca3 | |||
f2ed27cc8f | |||
959b9a5a58 | |||
142f4c9f42 | |||
33ae75b251 | |||
84ae930c64 | |||
99c0b1e338 | |||
4caf044be2 | |||
a3d4218d9d | |||
9f21928a90 | |||
0b401481ef | |||
57eace07a7 | |||
35e34261ab | |||
5a9bff2000 | |||
48147f50d8 | |||
0859ec570c | |||
443d71397c | |||
ec35bd0c28 | |||
2b1316c738 | |||
94599013fc | |||
565c4a4d59 | |||
34615ee8fc | |||
c51bc86d3f | |||
8660a54fac | |||
1b04571c0b | |||
9c7d95b071 | |||
c8bbfb75f0 | |||
549a39c478 | |||
e4066d820d | |||
cc2b5f5b62 | |||
c92c3fc8b5 | |||
9aa2e5b575 | |||
dcfcb2789e | |||
d90195061f | |||
474bdd2e6b | |||
1642cbd420 | |||
b2a06c5fa0 | |||
cfef97175e | |||
8b0957cf2e | |||
88816348b9 | |||
66423f6935 | |||
9872041f12 | |||
83fe43f331 | |||
40b41cc121 | |||
86c7f1db9e | |||
9ee9f81861 | |||
10ddb06782 | |||
36f69a04a9 | |||
1222c87be3 | |||
484ca3a643 | |||
cd31c816cc |
17
.github/ISSUE_TEMPLATE
vendored
Normal file
17
.github/ISSUE_TEMPLATE
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
Please answer these questions before submitting your issue. Thanks!
|
||||
|
||||
1. What version of Go and beego are you using (`bee version`)?
|
||||
|
||||
|
||||
2. What operating system and processor architecture are you using (`go env`)?
|
||||
|
||||
|
||||
3. What did you do?
|
||||
If possible, provide a recipe for reproducing the error.
|
||||
A complete runnable program is good.
|
||||
|
||||
|
||||
4. What did you expect to see?
|
||||
|
||||
|
||||
5. What did you see instead?
|
14
.travis.yml
14
.travis.yml
@ -1,8 +1,7 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- tip
|
||||
- 1.6.0
|
||||
- 1.6
|
||||
- 1.5.3
|
||||
- 1.4.3
|
||||
services:
|
||||
@ -31,21 +30,20 @@ install:
|
||||
- go get github.com/belogik/goes
|
||||
- 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:
|
||||
- psql --version
|
||||
- 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"
|
||||
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi"
|
||||
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; 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
|
||||
addons:
|
||||
postgresql: "9.4"
|
||||
|
@ -30,7 +30,7 @@ func main(){
|
||||
```
|
||||
######Congratulations!
|
||||
You just built your first beego app.
|
||||
Open your browser and visit `http://localhost:8000`.
|
||||
Open your browser and visit `http://localhost:8080`.
|
||||
Please see [Documentation](http://beego.me/docs) for more.
|
||||
|
||||
## Features
|
||||
|
81
admin.go
81
admin.go
@ -23,7 +23,10 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/astaxie/beego/grace"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/toolbox"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
@ -90,57 +93,9 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
switch command {
|
||||
case "conf":
|
||||
m := make(map[string]interface{})
|
||||
list("BConfig", BConfig, m)
|
||||
m["AppConfigPath"] = appConfigPath
|
||||
m["AppConfigProvider"] = appConfigProvider
|
||||
m["BConfig.AppName"] = BConfig.AppName
|
||||
m["BConfig.RunMode"] = BConfig.RunMode
|
||||
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
|
||||
m["BConfig.ServerName"] = BConfig.ServerName
|
||||
m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
|
||||
m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
|
||||
m["BConfig.EnableGzip"] = BConfig.EnableGzip
|
||||
m["BConfig.MaxMemory"] = BConfig.MaxMemory
|
||||
m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
|
||||
m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
|
||||
m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
|
||||
m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
|
||||
m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
|
||||
m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
|
||||
m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
|
||||
m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
|
||||
m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
|
||||
m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
|
||||
m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
|
||||
m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
|
||||
m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
|
||||
m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
|
||||
m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
|
||||
m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
|
||||
m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
|
||||
m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
|
||||
m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
|
||||
m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
|
||||
m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
|
||||
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
|
||||
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
|
||||
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
|
||||
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
|
||||
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
|
||||
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
|
||||
m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
|
||||
m["BConfig.WebConfig.XSRFKEY"] = BConfig.WebConfig.XSRFKey
|
||||
m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
|
||||
m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
|
||||
m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
|
||||
m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
|
||||
m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
|
||||
m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
|
||||
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
||||
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
||||
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
||||
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
||||
tmpl = template.Must(tmpl.Parse(configTpl))
|
||||
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
|
||||
@ -196,7 +151,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
BeforeExec: "Before Exec",
|
||||
AfterExec: "After Exec",
|
||||
FinishRouter: "Finish Router"} {
|
||||
if bf, ok := BeeApp.Handlers.filters[k]; ok {
|
||||
if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
|
||||
filterType = fr
|
||||
filterTypes = append(filterTypes, filterType)
|
||||
resultList := new([][]string)
|
||||
@ -223,6 +178,28 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func list(root string, p interface{}, m map[string]interface{}) {
|
||||
pt := reflect.TypeOf(p)
|
||||
pv := reflect.ValueOf(p)
|
||||
if pt.Kind() == reflect.Ptr {
|
||||
pt = pt.Elem()
|
||||
pv = pv.Elem()
|
||||
}
|
||||
for i := 0; i < pv.NumField(); i++ {
|
||||
var key string
|
||||
if root == "" {
|
||||
key = pt.Field(i).Name
|
||||
} else {
|
||||
key = root + "." + pt.Field(i).Name
|
||||
}
|
||||
if pv.Field(i).Kind() == reflect.Struct {
|
||||
list(key, pv.Field(i).Interface(), m)
|
||||
} else {
|
||||
m[key] = pv.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTree(resultList *[][]string, t *Tree) {
|
||||
for _, tr := range t.fixrouters {
|
||||
printTree(resultList, tr)
|
||||
@ -410,7 +387,7 @@ func (admin *adminApp) Run() {
|
||||
for p, f := range admin.routers {
|
||||
http.Handle(p, f)
|
||||
}
|
||||
BeeLogger.Info("Admin server Running on %s", addr)
|
||||
logs.Info("Admin server Running on %s", addr)
|
||||
|
||||
var err error
|
||||
if BConfig.Listen.Graceful {
|
||||
@ -419,6 +396,6 @@ func (admin *adminApp) Run() {
|
||||
err = http.ListenAndServe(addr, nil)
|
||||
}
|
||||
if err != nil {
|
||||
BeeLogger.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||
logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||
}
|
||||
}
|
||||
|
72
admin_test.go
Normal file
72
admin_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestList_01(t *testing.T) {
|
||||
m := make(map[string]interface{})
|
||||
list("BConfig", BConfig, m)
|
||||
t.Log(m)
|
||||
om := oldMap()
|
||||
for k, v := range om {
|
||||
if fmt.Sprint(m[k]) != fmt.Sprint(v) {
|
||||
t.Log(k, "old-key", v, "new-key", m[k])
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func oldMap() map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
m["BConfig.AppName"] = BConfig.AppName
|
||||
m["BConfig.RunMode"] = BConfig.RunMode
|
||||
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
|
||||
m["BConfig.ServerName"] = BConfig.ServerName
|
||||
m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
|
||||
m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
|
||||
m["BConfig.EnableGzip"] = BConfig.EnableGzip
|
||||
m["BConfig.MaxMemory"] = BConfig.MaxMemory
|
||||
m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
|
||||
m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
|
||||
m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
|
||||
m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
|
||||
m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
|
||||
m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
|
||||
m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
|
||||
m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
|
||||
m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
|
||||
m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
|
||||
m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
|
||||
m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
|
||||
m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
|
||||
m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
|
||||
m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
|
||||
m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
|
||||
m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
|
||||
m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
|
||||
m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
|
||||
m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
|
||||
m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
|
||||
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
|
||||
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
|
||||
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
|
||||
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
|
||||
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
|
||||
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
|
||||
m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
|
||||
m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
|
||||
m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
|
||||
m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
|
||||
m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
|
||||
m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
|
||||
m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
|
||||
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
||||
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
||||
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
||||
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||
return m
|
||||
}
|
30
app.go
30
app.go
@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/grace"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
@ -68,9 +69,9 @@ func (app *App) Run() {
|
||||
if BConfig.Listen.EnableFcgi {
|
||||
if BConfig.Listen.EnableStdIo {
|
||||
if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O
|
||||
BeeLogger.Info("Use FCGI via standard I/O")
|
||||
logs.Info("Use FCGI via standard I/O")
|
||||
} else {
|
||||
BeeLogger.Critical("Cannot use FCGI via standard I/O", err)
|
||||
logs.Critical("Cannot use FCGI via standard I/O", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -84,10 +85,10 @@ func (app *App) Run() {
|
||||
l, err = net.Listen("tcp", addr)
|
||||
}
|
||||
if err != nil {
|
||||
BeeLogger.Critical("Listen: ", err)
|
||||
logs.Critical("Listen: ", err)
|
||||
}
|
||||
if err = fcgi.Serve(l, app.Handlers); err != nil {
|
||||
BeeLogger.Critical("fcgi.Serve: ", err)
|
||||
logs.Critical("fcgi.Serve: ", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -95,6 +96,7 @@ func (app *App) Run() {
|
||||
app.Server.Handler = app.Handlers
|
||||
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
||||
|
||||
// run graceful mode
|
||||
if BConfig.Listen.Graceful {
|
||||
@ -111,7 +113,7 @@ func (app *App) Run() {
|
||||
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||
BeeLogger.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
@ -126,7 +128,7 @@ func (app *App) Run() {
|
||||
server.Network = "tcp4"
|
||||
}
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
BeeLogger.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||
logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
@ -137,16 +139,18 @@ func (app *App) Run() {
|
||||
}
|
||||
|
||||
// run normal mode
|
||||
app.Server.Addr = addr
|
||||
if BConfig.Listen.EnableHTTPS {
|
||||
go func() {
|
||||
time.Sleep(20 * time.Microsecond)
|
||||
if BConfig.Listen.HTTPSPort != 0 {
|
||||
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||
} else if BConfig.Listen.EnableHTTP {
|
||||
BeeLogger.Info("Start https server error, confict with http.Please reset https port")
|
||||
return
|
||||
}
|
||||
BeeLogger.Info("https server Running on %s", app.Server.Addr)
|
||||
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||
BeeLogger.Critical("ListenAndServeTLS: ", err)
|
||||
logs.Critical("ListenAndServeTLS: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
@ -155,24 +159,24 @@ func (app *App) Run() {
|
||||
if BConfig.Listen.EnableHTTP {
|
||||
go func() {
|
||||
app.Server.Addr = addr
|
||||
BeeLogger.Info("http server Running on %s", app.Server.Addr)
|
||||
logs.Info("http server Running on http://%s", app.Server.Addr)
|
||||
if BConfig.Listen.ListenTCP4 {
|
||||
ln, err := net.Listen("tcp4", app.Server.Addr)
|
||||
if err != nil {
|
||||
BeeLogger.Critical("ListenAndServe: ", err)
|
||||
logs.Critical("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
return
|
||||
}
|
||||
if err = app.Server.Serve(ln); err != nil {
|
||||
BeeLogger.Critical("ListenAndServe: ", err)
|
||||
logs.Critical("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := app.Server.ListenAndServe(); err != nil {
|
||||
BeeLogger.Critical("ListenAndServe: ", err)
|
||||
logs.Critical("ListenAndServe: ", err)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
endRunning <- true
|
||||
}
|
||||
|
10
beego.go
10
beego.go
@ -23,7 +23,7 @@ import (
|
||||
|
||||
const (
|
||||
// VERSION represent beego web framework version.
|
||||
VERSION = "1.6.1"
|
||||
VERSION = "1.7.0"
|
||||
|
||||
// DEV is for develop
|
||||
DEV = "dev"
|
||||
@ -51,6 +51,7 @@ func AddAPPStartHook(hf hookfunc) {
|
||||
// beego.Run(":8089")
|
||||
// beego.Run("127.0.0.1:8089")
|
||||
func Run(params ...string) {
|
||||
|
||||
initBeforeHTTPRun()
|
||||
|
||||
if len(params) > 0 && params[0] != "" {
|
||||
@ -71,9 +72,9 @@ func initBeforeHTTPRun() {
|
||||
AddAPPStartHook(registerMime)
|
||||
AddAPPStartHook(registerDefaultErrorHandler)
|
||||
AddAPPStartHook(registerSession)
|
||||
AddAPPStartHook(registerDocs)
|
||||
AddAPPStartHook(registerTemplate)
|
||||
AddAPPStartHook(registerAdmin)
|
||||
AddAPPStartHook(registerGzip)
|
||||
|
||||
for _, hk := range hooks {
|
||||
if err := hk(); err != nil {
|
||||
@ -84,8 +85,11 @@ func initBeforeHTTPRun() {
|
||||
|
||||
// TestBeegoInit is for test package init
|
||||
func TestBeegoInit(ap string) {
|
||||
os.Setenv("BEEGO_RUNMODE", "test")
|
||||
appConfigPath = filepath.Join(ap, "conf", "app.conf")
|
||||
os.Chdir(ap)
|
||||
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
BConfig.RunMode = "test"
|
||||
initBeforeHTTPRun()
|
||||
}
|
||||
|
3
cache/redis/redis_test.go
vendored
3
cache/redis/redis_test.go
vendored
@ -18,9 +18,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
func TestRedisCache(t *testing.T) {
|
||||
|
3
cache/ssdb/ssdb_test.go
vendored
3
cache/ssdb/ssdb_test.go
vendored
@ -1,10 +1,11 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/cache"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/cache"
|
||||
)
|
||||
|
||||
func TestSsdbcacheCache(t *testing.T) {
|
||||
|
191
config.go
191
config.go
@ -18,9 +18,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/config"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/session"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
@ -81,14 +83,17 @@ type WebConfig struct {
|
||||
|
||||
// SessionConfig holds session related config
|
||||
type SessionConfig struct {
|
||||
SessionOn bool
|
||||
SessionProvider string
|
||||
SessionName string
|
||||
SessionGCMaxLifetime int64
|
||||
SessionProviderConfig string
|
||||
SessionCookieLifeTime int
|
||||
SessionAutoSetCookie bool
|
||||
SessionDomain string
|
||||
SessionOn bool
|
||||
SessionProvider string
|
||||
SessionName string
|
||||
SessionGCMaxLifetime int64
|
||||
SessionProviderConfig string
|
||||
SessionCookieLifeTime int
|
||||
SessionAutoSetCookie bool
|
||||
SessionDomain string
|
||||
EnableSidInHttpHeader bool // enable store/get the sessionId into/from http headers
|
||||
SessionNameInHttpHeader string
|
||||
EnableSidInUrlQuery bool // enable get the sessionId from Url Query params
|
||||
}
|
||||
|
||||
// LogConfig holds Log related config
|
||||
@ -115,11 +120,30 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
BConfig = newBConfig()
|
||||
var err error
|
||||
if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
workPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
appConfigPath = filepath.Join(workPath, "conf", "app.conf")
|
||||
if !utils.FileExists(appConfigPath) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
os.Chdir(AppPath)
|
||||
|
||||
BConfig = &Config{
|
||||
func newBConfig() *Config {
|
||||
return &Config{
|
||||
AppName: "beego",
|
||||
RunMode: DEV,
|
||||
RouterCaseSensitive: true,
|
||||
@ -162,14 +186,17 @@ func init() {
|
||||
XSRFKey: "beegoxsrf",
|
||||
XSRFExpire: 0,
|
||||
Session: SessionConfig{
|
||||
SessionOn: false,
|
||||
SessionProvider: "memory",
|
||||
SessionName: "beegosessionID",
|
||||
SessionGCMaxLifetime: 3600,
|
||||
SessionProviderConfig: "",
|
||||
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||
SessionAutoSetCookie: true,
|
||||
SessionDomain: "",
|
||||
SessionOn: false,
|
||||
SessionProvider: "memory",
|
||||
SessionName: "beegosessionID",
|
||||
SessionGCMaxLifetime: 3600,
|
||||
SessionProviderConfig: "",
|
||||
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||
SessionAutoSetCookie: true,
|
||||
SessionDomain: "",
|
||||
EnableSidInHttpHeader: false, // enable store/get the sessionId into/from http headers
|
||||
SessionNameInHttpHeader: "Beegosessionid",
|
||||
EnableSidInUrlQuery: false, // enable get the sessionId from Url Query params
|
||||
},
|
||||
},
|
||||
Log: LogConfig{
|
||||
@ -178,16 +205,6 @@ func init() {
|
||||
Outputs: map[string]string{"console": ""},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// now only support ini, next will support json.
|
||||
@ -196,74 +213,34 @@ func parseConfig(appConfigPath string) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return assignConfig(AppConfig)
|
||||
}
|
||||
|
||||
func assignConfig(ac config.Configer) error {
|
||||
// set the run mode first
|
||||
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
|
||||
BConfig.RunMode = envRunMode
|
||||
} else if runMode := AppConfig.String("RunMode"); runMode != "" {
|
||||
} else if runMode := ac.String("RunMode"); runMode != "" {
|
||||
BConfig.RunMode = runMode
|
||||
}
|
||||
|
||||
BConfig.AppName = AppConfig.DefaultString("AppName", BConfig.AppName)
|
||||
BConfig.RecoverPanic = AppConfig.DefaultBool("RecoverPanic", BConfig.RecoverPanic)
|
||||
BConfig.RouterCaseSensitive = AppConfig.DefaultBool("RouterCaseSensitive", BConfig.RouterCaseSensitive)
|
||||
BConfig.ServerName = AppConfig.DefaultString("ServerName", BConfig.ServerName)
|
||||
BConfig.EnableGzip = AppConfig.DefaultBool("EnableGzip", BConfig.EnableGzip)
|
||||
BConfig.EnableErrorsShow = AppConfig.DefaultBool("EnableErrorsShow", BConfig.EnableErrorsShow)
|
||||
BConfig.CopyRequestBody = AppConfig.DefaultBool("CopyRequestBody", BConfig.CopyRequestBody)
|
||||
BConfig.MaxMemory = AppConfig.DefaultInt64("MaxMemory", BConfig.MaxMemory)
|
||||
BConfig.Listen.Graceful = AppConfig.DefaultBool("Graceful", BConfig.Listen.Graceful)
|
||||
BConfig.Listen.HTTPAddr = AppConfig.String("HTTPAddr")
|
||||
BConfig.Listen.HTTPPort = AppConfig.DefaultInt("HTTPPort", BConfig.Listen.HTTPPort)
|
||||
BConfig.Listen.ListenTCP4 = AppConfig.DefaultBool("ListenTCP4", BConfig.Listen.ListenTCP4)
|
||||
BConfig.Listen.EnableHTTP = AppConfig.DefaultBool("EnableHTTP", BConfig.Listen.EnableHTTP)
|
||||
BConfig.Listen.EnableHTTPS = AppConfig.DefaultBool("EnableHTTPS", BConfig.Listen.EnableHTTPS)
|
||||
BConfig.Listen.HTTPSAddr = AppConfig.DefaultString("HTTPSAddr", BConfig.Listen.HTTPSAddr)
|
||||
BConfig.Listen.HTTPSPort = AppConfig.DefaultInt("HTTPSPort", BConfig.Listen.HTTPSPort)
|
||||
BConfig.Listen.HTTPSCertFile = AppConfig.DefaultString("HTTPSCertFile", BConfig.Listen.HTTPSCertFile)
|
||||
BConfig.Listen.HTTPSKeyFile = AppConfig.DefaultString("HTTPSKeyFile", BConfig.Listen.HTTPSKeyFile)
|
||||
BConfig.Listen.EnableAdmin = AppConfig.DefaultBool("EnableAdmin", BConfig.Listen.EnableAdmin)
|
||||
BConfig.Listen.AdminAddr = AppConfig.DefaultString("AdminAddr", BConfig.Listen.AdminAddr)
|
||||
BConfig.Listen.AdminPort = AppConfig.DefaultInt("AdminPort", BConfig.Listen.AdminPort)
|
||||
BConfig.Listen.EnableFcgi = AppConfig.DefaultBool("EnableFcgi", BConfig.Listen.EnableFcgi)
|
||||
BConfig.Listen.EnableStdIo = AppConfig.DefaultBool("EnableStdIo", BConfig.Listen.EnableStdIo)
|
||||
BConfig.Listen.ServerTimeOut = AppConfig.DefaultInt64("ServerTimeOut", BConfig.Listen.ServerTimeOut)
|
||||
BConfig.WebConfig.AutoRender = AppConfig.DefaultBool("AutoRender", BConfig.WebConfig.AutoRender)
|
||||
BConfig.WebConfig.ViewsPath = AppConfig.DefaultString("ViewsPath", BConfig.WebConfig.ViewsPath)
|
||||
BConfig.WebConfig.DirectoryIndex = AppConfig.DefaultBool("DirectoryIndex", BConfig.WebConfig.DirectoryIndex)
|
||||
BConfig.WebConfig.FlashName = AppConfig.DefaultString("FlashName", BConfig.WebConfig.FlashName)
|
||||
BConfig.WebConfig.FlashSeparator = AppConfig.DefaultString("FlashSeparator", BConfig.WebConfig.FlashSeparator)
|
||||
BConfig.WebConfig.EnableDocs = AppConfig.DefaultBool("EnableDocs", BConfig.WebConfig.EnableDocs)
|
||||
BConfig.WebConfig.XSRFKey = AppConfig.DefaultString("XSRFKEY", BConfig.WebConfig.XSRFKey)
|
||||
BConfig.WebConfig.EnableXSRF = AppConfig.DefaultBool("EnableXSRF", BConfig.WebConfig.EnableXSRF)
|
||||
BConfig.WebConfig.XSRFExpire = AppConfig.DefaultInt("XSRFExpire", BConfig.WebConfig.XSRFExpire)
|
||||
BConfig.WebConfig.TemplateLeft = AppConfig.DefaultString("TemplateLeft", BConfig.WebConfig.TemplateLeft)
|
||||
BConfig.WebConfig.TemplateRight = AppConfig.DefaultString("TemplateRight", BConfig.WebConfig.TemplateRight)
|
||||
BConfig.WebConfig.Session.SessionOn = AppConfig.DefaultBool("SessionOn", BConfig.WebConfig.Session.SessionOn)
|
||||
BConfig.WebConfig.Session.SessionProvider = AppConfig.DefaultString("SessionProvider", BConfig.WebConfig.Session.SessionProvider)
|
||||
BConfig.WebConfig.Session.SessionName = AppConfig.DefaultString("SessionName", BConfig.WebConfig.Session.SessionName)
|
||||
BConfig.WebConfig.Session.SessionProviderConfig = AppConfig.DefaultString("SessionProviderConfig", BConfig.WebConfig.Session.SessionProviderConfig)
|
||||
BConfig.WebConfig.Session.SessionGCMaxLifetime = AppConfig.DefaultInt64("SessionGCMaxLifetime", BConfig.WebConfig.Session.SessionGCMaxLifetime)
|
||||
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)
|
||||
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
|
||||
assignSingleConfig(i, ac)
|
||||
}
|
||||
|
||||
if sd := AppConfig.String("StaticDir"); sd != "" {
|
||||
for k := range BConfig.WebConfig.StaticDir {
|
||||
delete(BConfig.WebConfig.StaticDir, k)
|
||||
}
|
||||
if sd := ac.String("StaticDir"); sd != "" {
|
||||
BConfig.WebConfig.StaticDir = map[string]string{}
|
||||
sds := strings.Fields(sd)
|
||||
for _, v := range sds {
|
||||
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
|
||||
BConfig.WebConfig.StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1]
|
||||
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1]
|
||||
} else {
|
||||
BConfig.WebConfig.StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0]
|
||||
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
|
||||
if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" {
|
||||
extensions := strings.Split(sgz, ",")
|
||||
fileExts := []string{}
|
||||
for _, ext := range extensions {
|
||||
@ -281,7 +258,7 @@ func parseConfig(appConfigPath string) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if lo := AppConfig.String("LogOutputs"); lo != "" {
|
||||
if lo := ac.String("LogOutputs"); lo != "" {
|
||||
los := strings.Split(lo, ";")
|
||||
for _, v := range los {
|
||||
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
|
||||
@ -293,18 +270,50 @@ func parseConfig(appConfigPath string) (err error) {
|
||||
}
|
||||
|
||||
//init log
|
||||
BeeLogger.Reset()
|
||||
logs.Reset()
|
||||
for adaptor, config := range BConfig.Log.Outputs {
|
||||
err = BeeLogger.SetLogger(adaptor, config)
|
||||
err := logs.SetLogger(adaptor, config)
|
||||
if err != nil {
|
||||
fmt.Printf("%s with the config `%s` got err:%s\n", adaptor, config, err)
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error()))
|
||||
}
|
||||
}
|
||||
SetLogFuncCall(BConfig.Log.FileLineNum)
|
||||
logs.SetLogFuncCall(BConfig.Log.FileLineNum)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assignSingleConfig(p interface{}, ac config.Configer) {
|
||||
pt := reflect.TypeOf(p)
|
||||
if pt.Kind() != reflect.Ptr {
|
||||
return
|
||||
}
|
||||
pt = pt.Elem()
|
||||
if pt.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
pv := reflect.ValueOf(p).Elem()
|
||||
|
||||
for i := 0; i < pt.NumField(); i++ {
|
||||
pf := pv.Field(i)
|
||||
if !pf.CanSet() {
|
||||
continue
|
||||
}
|
||||
name := pt.Field(i).Name
|
||||
switch pf.Kind() {
|
||||
case reflect.String:
|
||||
pf.SetString(ac.DefaultString(name, pf.String()))
|
||||
case reflect.Int, reflect.Int64:
|
||||
pf.SetInt(int64(ac.DefaultInt64(name, pf.Int())))
|
||||
case reflect.Bool:
|
||||
pf.SetBool(ac.DefaultBool(name, pf.Bool()))
|
||||
case reflect.Struct:
|
||||
default:
|
||||
//do nothing here
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// LoadAppConfig allow developer to apply a config file
|
||||
func LoadAppConfig(adapterName, configPath string) error {
|
||||
absConfigPath, err := filepath.Abs(configPath)
|
||||
@ -316,10 +325,6 @@ func LoadAppConfig(adapterName, configPath string) error {
|
||||
return fmt.Errorf("the target config file: %s don't exist", configPath)
|
||||
}
|
||||
|
||||
if absConfigPath == appConfigPath {
|
||||
return nil
|
||||
}
|
||||
|
||||
appConfigPath = absConfigPath
|
||||
appConfigProvider = adapterName
|
||||
|
||||
@ -353,7 +358,7 @@ func (b *beegoAppConfig) String(key string) string {
|
||||
}
|
||||
|
||||
func (b *beegoAppConfig) Strings(key string) []string {
|
||||
if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); v[0] != "" {
|
||||
if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 {
|
||||
return v
|
||||
}
|
||||
return b.innerConfig.Strings(key)
|
||||
|
@ -12,11 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package config is used to parse config
|
||||
// Package config is used to parse config.
|
||||
// Usage:
|
||||
// import(
|
||||
// "github.com/astaxie/beego/config"
|
||||
// )
|
||||
// import "github.com/astaxie/beego/config"
|
||||
//Examples.
|
||||
//
|
||||
// cnf, err := config.NewConfig("ini", "config.conf")
|
||||
//
|
||||
@ -38,12 +37,12 @@
|
||||
// cnf.DIY(key string) (interface{}, error)
|
||||
// cnf.GetSection(section string) (map[string]string, error)
|
||||
// cnf.SaveConfigFile(filename string) error
|
||||
//
|
||||
// more docs http://beego.me/docs/module/config.md
|
||||
//More docs http://beego.me/docs/module/config.md
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Configer defines how to get and set value from configuration raw data.
|
||||
@ -107,6 +106,69 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
|
||||
return adapter.ParseData(data)
|
||||
}
|
||||
|
||||
// ExpandValueEnvForMap convert all string value with environment variable.
|
||||
func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} {
|
||||
for k, v := range m {
|
||||
switch value := v.(type) {
|
||||
case string:
|
||||
m[k] = ExpandValueEnv(value)
|
||||
case map[string]interface{}:
|
||||
m[k] = ExpandValueEnvForMap(value)
|
||||
case map[string]string:
|
||||
for k2, v2 := range value {
|
||||
value[k2] = ExpandValueEnv(v2)
|
||||
}
|
||||
m[k] = value
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ExpandValueEnv returns value of convert with environment variable.
|
||||
//
|
||||
// Return environment variable if value start with "${" and end with "}".
|
||||
// Return default value if environment variable is empty or not exist.
|
||||
//
|
||||
// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue".
|
||||
// Examples:
|
||||
// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable.
|
||||
// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/".
|
||||
// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie".
|
||||
func ExpandValueEnv(value string) (realValue string) {
|
||||
realValue = value
|
||||
|
||||
vLen := len(value)
|
||||
// 3 = ${}
|
||||
if vLen < 3 {
|
||||
return
|
||||
}
|
||||
// Need start with "${" and end with "}", then return.
|
||||
if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
|
||||
return
|
||||
}
|
||||
|
||||
key := ""
|
||||
defalutV := ""
|
||||
// value start with "${"
|
||||
for i := 2; i < vLen; i++ {
|
||||
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
||||
key = value[2:i]
|
||||
defalutV = value[i+2 : vLen-1] // other string is default value.
|
||||
break
|
||||
} else if value[i] == '}' {
|
||||
key = value[2:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
realValue = os.Getenv(key)
|
||||
if realValue == "" {
|
||||
realValue = defalutV
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
55
config/config_test.go
Normal file
55
config/config_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpandValueEnv(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
item string
|
||||
want string
|
||||
}{
|
||||
{"", ""},
|
||||
{"$", "$"},
|
||||
{"{", "{"},
|
||||
{"{}", "{}"},
|
||||
{"${}", ""},
|
||||
{"${|}", ""},
|
||||
{"${}", ""},
|
||||
{"${{}}", ""},
|
||||
{"${{||}}", "}"},
|
||||
{"${pwd||}", ""},
|
||||
{"${pwd||}", ""},
|
||||
{"${pwd||}", ""},
|
||||
{"${pwd||}}", "}"},
|
||||
{"${pwd||{{||}}}", "{{||}}"},
|
||||
{"${GOPATH}", os.Getenv("GOPATH")},
|
||||
{"${GOPATH||}", os.Getenv("GOPATH")},
|
||||
{"${GOPATH||root}", os.Getenv("GOPATH")},
|
||||
{"${GOPATH_NOT||root}", "root"},
|
||||
{"${GOPATH_NOT||||root}", "||root"},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
if got := ExpandValueEnv(c.item); got != c.want {
|
||||
t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -38,7 +38,7 @@ func (c *fakeConfigContainer) String(key string) string {
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
|
||||
v := c.getData(key)
|
||||
v := c.String(key)
|
||||
if v == "" {
|
||||
return defaultval
|
||||
}
|
||||
@ -46,7 +46,7 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) Strings(key string) []string {
|
||||
v := c.getData(key)
|
||||
v := c.String(key)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -82,11 +82,14 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
//It might be a good idea to throw a error on all unknonw errors?
|
||||
if _, ok := err.(*os.PathError); ok {
|
||||
return nil, err
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if bytes.Equal(line, bEmpty) {
|
||||
continue
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
|
||||
var bComment []byte
|
||||
switch {
|
||||
case bytes.HasPrefix(line, bNumComment):
|
||||
@ -162,7 +165,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||
val = bytes.Trim(val, `"`)
|
||||
}
|
||||
|
||||
cfg.data[section][key] = string(val)
|
||||
cfg.data[section][key] = ExpandValueEnv(string(val))
|
||||
if comment.Len() > 0 {
|
||||
cfg.keyComment[section+"."+key] = comment.String()
|
||||
comment.Reset()
|
||||
@ -296,7 +299,9 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
|
||||
return nil, errors.New("not exist setction")
|
||||
}
|
||||
|
||||
// SaveConfigFile save the config into file
|
||||
// SaveConfigFile save the config into file.
|
||||
//
|
||||
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
|
||||
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
// Write configuration file by filename.
|
||||
f, err := os.Create(filename)
|
||||
|
@ -42,11 +42,14 @@ needlogin = ON
|
||||
enableSession = Y
|
||||
enableCookie = N
|
||||
flag = 1
|
||||
path1 = ${GOPATH}
|
||||
path2 = ${GOPATH||/home/go}
|
||||
[demo]
|
||||
key1="asta"
|
||||
key2 = "xie"
|
||||
CaseInsensitive = true
|
||||
peers = one;two;three
|
||||
password = ${GOPATH}
|
||||
`
|
||||
|
||||
keyValue = map[string]interface{}{
|
||||
@ -64,10 +67,13 @@ peers = one;two;three
|
||||
"enableSession": true,
|
||||
"enableCookie": false,
|
||||
"flag": true,
|
||||
"path1": os.Getenv("GOPATH"),
|
||||
"path2": os.Getenv("GOPATH"),
|
||||
"demo::key1": "asta",
|
||||
"demo::key2": "xie",
|
||||
"demo::CaseInsensitive": true,
|
||||
"demo::peers": []string{"one", "two", "three"},
|
||||
"demo::password": os.Getenv("GOPATH"),
|
||||
"null": "",
|
||||
"demo2::key1": "",
|
||||
"error": "",
|
||||
|
@ -57,6 +57,9 @@ func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
|
||||
}
|
||||
x.data["rootArray"] = wrappingArray
|
||||
}
|
||||
|
||||
x.data = ExpandValueEnvForMap(x.data)
|
||||
|
||||
return x, nil
|
||||
}
|
||||
|
||||
|
@ -86,16 +86,19 @@ func TestJson(t *testing.T) {
|
||||
"enableSession": "Y",
|
||||
"enableCookie": "N",
|
||||
"flag": 1,
|
||||
"path1": "${GOPATH}",
|
||||
"path2": "${GOPATH||/home/go}",
|
||||
"database": {
|
||||
"host": "host",
|
||||
"port": "port",
|
||||
"database": "database",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"password": "${GOPATH}",
|
||||
"conns":{
|
||||
"maxconnection":12,
|
||||
"autoconnect":true,
|
||||
"connectioninfo":"info"
|
||||
"connectioninfo":"info",
|
||||
"root": "${GOPATH}"
|
||||
}
|
||||
}
|
||||
}`
|
||||
@ -115,13 +118,16 @@ func TestJson(t *testing.T) {
|
||||
"enableSession": true,
|
||||
"enableCookie": false,
|
||||
"flag": true,
|
||||
"path1": os.Getenv("GOPATH"),
|
||||
"path2": os.Getenv("GOPATH"),
|
||||
"database::host": "host",
|
||||
"database::port": "port",
|
||||
"database::database": "database",
|
||||
"database::password": "password",
|
||||
"database::password": os.Getenv("GOPATH"),
|
||||
"database::conns::maxconnection": 12,
|
||||
"database::conns::autoconnect": true,
|
||||
"database::conns::connectioninfo": "info",
|
||||
"database::conns::root": os.Getenv("GOPATH"),
|
||||
"unknown": "",
|
||||
}
|
||||
)
|
||||
|
@ -12,21 +12,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package xml for config provider
|
||||
// Package xml for config provider.
|
||||
//
|
||||
// depend on github.com/beego/x2j
|
||||
// depend on github.com/beego/x2j.
|
||||
//
|
||||
// go install github.com/beego/x2j
|
||||
// go install github.com/beego/x2j.
|
||||
//
|
||||
// Usage:
|
||||
// import(
|
||||
// _ "github.com/astaxie/beego/config/xml"
|
||||
// "github.com/astaxie/beego/config"
|
||||
// )
|
||||
// import(
|
||||
// _ "github.com/astaxie/beego/config/xml"
|
||||
// "github.com/astaxie/beego/config"
|
||||
// )
|
||||
//
|
||||
// cnf, err := config.NewConfig("xml", "config.xml")
|
||||
//
|
||||
// more docs http://beego.me/docs/module/config.md
|
||||
//More docs http://beego.me/docs/module/config.md
|
||||
package xml
|
||||
|
||||
import (
|
||||
@ -69,7 +69,7 @@ func (xc *Config) Parse(filename string) (config.Configer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x.data = d["config"].(map[string]interface{})
|
||||
x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
|
||||
return x, nil
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ 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]; ok {
|
||||
if v := c.data[key]; v != nil {
|
||||
return config.ParseBool(v)
|
||||
}
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
|
@ -15,14 +15,18 @@
|
||||
package xml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/config"
|
||||
)
|
||||
|
||||
//xml parse should incluce in <config></config> tags
|
||||
var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
func TestXML(t *testing.T) {
|
||||
|
||||
var (
|
||||
//xml parse should incluce in <config></config> tags
|
||||
xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config>
|
||||
<appname>beeapi</appname>
|
||||
<httpport>8080</httpport>
|
||||
@ -31,10 +35,25 @@ var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<runmode>dev</runmode>
|
||||
<autorender>false</autorender>
|
||||
<copyrequestbody>true</copyrequestbody>
|
||||
<path1>${GOPATH}</path1>
|
||||
<path2>${GOPATH||/home/go}</path2>
|
||||
</config>
|
||||
`
|
||||
keyValue = map[string]interface{}{
|
||||
"appname": "beeapi",
|
||||
"httpport": 8080,
|
||||
"mysqlport": int64(3600),
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"path1": os.Getenv("GOPATH"),
|
||||
"path2": os.Getenv("GOPATH"),
|
||||
"error": "",
|
||||
"emptystrings": []string{},
|
||||
}
|
||||
)
|
||||
|
||||
func TestXML(t *testing.T) {
|
||||
f, err := os.Create("testxml.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -50,39 +69,42 @@ func TestXML(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if xmlconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if xmlconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := xmlconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
|
||||
for k, v := range keyValue {
|
||||
|
||||
var (
|
||||
value interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
switch v.(type) {
|
||||
case int:
|
||||
value, err = xmlconf.Int(k)
|
||||
case int64:
|
||||
value, err = xmlconf.Int64(k)
|
||||
case float64:
|
||||
value, err = xmlconf.Float(k)
|
||||
case bool:
|
||||
value, err = xmlconf.Bool(k)
|
||||
case []string:
|
||||
value = xmlconf.Strings(k)
|
||||
case string:
|
||||
value = xmlconf.String(k)
|
||||
default:
|
||||
value, err = xmlconf.DIY(k)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
|
||||
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||
t.Errorf("get key %q value, want %v got %v .", k, v, value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err = xmlconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if xmlconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if xmlconf.Strings("emptystrings") != nil {
|
||||
t.Fatal("get emtpy strings error")
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,14 @@
|
||||
// go install github.com/beego/goyaml2
|
||||
//
|
||||
// Usage:
|
||||
// import(
|
||||
// import(
|
||||
// _ "github.com/astaxie/beego/config/yaml"
|
||||
// "github.com/astaxie/beego/config"
|
||||
// )
|
||||
// "github.com/astaxie/beego/config"
|
||||
// )
|
||||
//
|
||||
// cnf, err := config.NewConfig("yaml", "config.yaml")
|
||||
//
|
||||
// more docs http://beego.me/docs/module/config.md
|
||||
//More docs http://beego.me/docs/module/config.md
|
||||
package yaml
|
||||
|
||||
import (
|
||||
@ -110,6 +110,7 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
|
||||
log.Println("Not a Map? >> ", string(buf), data)
|
||||
cnf = nil
|
||||
}
|
||||
cnf = config.ExpandValueEnvForMap(cnf)
|
||||
return
|
||||
}
|
||||
|
||||
@ -121,10 +122,11 @@ 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]; ok {
|
||||
return config.ParseBool(v)
|
||||
v, err := c.getData(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
return config.ParseBool(v)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
@ -139,8 +141,12 @@ func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||
|
||||
// Int returns the integer value for a given key.
|
||||
func (c *ConfigContainer) Int(key string) (int, error) {
|
||||
if v, ok := c.data[key].(int64); ok {
|
||||
return int(v), nil
|
||||
if v, err := c.getData(key); err != nil {
|
||||
return 0, err
|
||||
} else if vv, ok := v.(int); ok {
|
||||
return vv, nil
|
||||
} else if vv, ok := v.(int64); ok {
|
||||
return int(vv), nil
|
||||
}
|
||||
return 0, errors.New("not int value")
|
||||
}
|
||||
@ -157,8 +163,10 @@ func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||
|
||||
// Int64 returns the int64 value for a given key.
|
||||
func (c *ConfigContainer) Int64(key string) (int64, error) {
|
||||
if v, ok := c.data[key].(int64); ok {
|
||||
return v, nil
|
||||
if v, err := c.getData(key); err != nil {
|
||||
return 0, err
|
||||
} else if vv, ok := v.(int64); ok {
|
||||
return vv, nil
|
||||
}
|
||||
return 0, errors.New("not bool value")
|
||||
}
|
||||
@ -175,8 +183,14 @@ func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||
|
||||
// Float returns the float value for a given key.
|
||||
func (c *ConfigContainer) Float(key string) (float64, error) {
|
||||
if v, ok := c.data[key].(float64); ok {
|
||||
return v, nil
|
||||
if v, err := c.getData(key); err != nil {
|
||||
return 0.0, err
|
||||
} else if vv, ok := v.(float64); ok {
|
||||
return vv, nil
|
||||
} else if vv, ok := v.(int); ok {
|
||||
return float64(vv), nil
|
||||
} else if vv, ok := v.(int64); ok {
|
||||
return float64(vv), nil
|
||||
}
|
||||
return 0.0, errors.New("not float64 value")
|
||||
}
|
||||
@ -193,8 +207,10 @@ func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||
|
||||
// String returns the string value for a given key.
|
||||
func (c *ConfigContainer) String(key string) string {
|
||||
if v, ok := c.data[key].(string); ok {
|
||||
return v
|
||||
if v, err := c.getData(key); err == nil {
|
||||
if vv, ok := v.(string); ok {
|
||||
return vv
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -230,8 +246,8 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri
|
||||
|
||||
// GetSection returns map for the given section
|
||||
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||
v, ok := c.data[section]
|
||||
if ok {
|
||||
|
||||
if v, ok := c.data[section]; ok {
|
||||
return v.(map[string]string), nil
|
||||
}
|
||||
return nil, errors.New("not exist setction")
|
||||
@ -259,10 +275,19 @@ func (c *ConfigContainer) Set(key, val string) error {
|
||||
|
||||
// DIY returns the raw value by a given key.
|
||||
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||
return c.getData(key)
|
||||
}
|
||||
|
||||
func (c *ConfigContainer) getData(key string) (interface{}, error) {
|
||||
|
||||
if len(key) == 0 {
|
||||
return nil, errors.New("key is empty")
|
||||
}
|
||||
|
||||
if v, ok := c.data[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return nil, errors.New("not exist key")
|
||||
return nil, fmt.Errorf("not exist key %q", key)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -15,13 +15,17 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/config"
|
||||
)
|
||||
|
||||
var yamlcontext = `
|
||||
func TestYaml(t *testing.T) {
|
||||
|
||||
var (
|
||||
yamlcontext = `
|
||||
"appname": beeapi
|
||||
"httpport": 8080
|
||||
"mysqlport": 3600
|
||||
@ -29,9 +33,27 @@ var yamlcontext = `
|
||||
"runmode": dev
|
||||
"autorender": false
|
||||
"copyrequestbody": true
|
||||
"PATH": GOPATH
|
||||
"path1": ${GOPATH}
|
||||
"path2": ${GOPATH||/home/go}
|
||||
"empty": ""
|
||||
`
|
||||
|
||||
func TestYaml(t *testing.T) {
|
||||
keyValue = map[string]interface{}{
|
||||
"appname": "beeapi",
|
||||
"httpport": 8080,
|
||||
"mysqlport": int64(3600),
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"PATH": "GOPATH",
|
||||
"path1": os.Getenv("GOPATH"),
|
||||
"path2": os.Getenv("GOPATH"),
|
||||
"error": "",
|
||||
"emptystrings": []string{},
|
||||
}
|
||||
)
|
||||
f, err := os.Create("testyaml.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -47,32 +69,42 @@ func TestYaml(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if yamlconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
}
|
||||
if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if yamlconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := yamlconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
|
||||
for k, v := range keyValue {
|
||||
|
||||
var (
|
||||
value interface{}
|
||||
err error
|
||||
)
|
||||
|
||||
switch v.(type) {
|
||||
case int:
|
||||
value, err = yamlconf.Int(k)
|
||||
case int64:
|
||||
value, err = yamlconf.Int64(k)
|
||||
case float64:
|
||||
value, err = yamlconf.Float(k)
|
||||
case bool:
|
||||
value, err = yamlconf.Bool(k)
|
||||
case []string:
|
||||
value = yamlconf.Strings(k)
|
||||
case string:
|
||||
value = yamlconf.String(k)
|
||||
default:
|
||||
value, err = yamlconf.DIY(k)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
|
||||
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||
t.Errorf("get key %q value, want %v got %v .", k, v, value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err = yamlconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -80,7 +112,4 @@ func TestYaml(t *testing.T) {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
|
||||
if yamlconf.Strings("emptystrings") != nil {
|
||||
t.Fatal("get emtpy strings error")
|
||||
}
|
||||
}
|
||||
|
109
config_test.go
109
config_test.go
@ -15,7 +15,11 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/config"
|
||||
)
|
||||
|
||||
func TestDefaults(t *testing.T) {
|
||||
@ -27,3 +31,108 @@ func TestDefaults(t *testing.T) {
|
||||
t.Errorf("FlashName was not set to default.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignConfig_01(t *testing.T) {
|
||||
_BConfig := &Config{}
|
||||
_BConfig.AppName = "beego_test"
|
||||
jcf := &config.JSONConfig{}
|
||||
ac, _ := jcf.ParseData([]byte(`{"AppName":"beego_json"}`))
|
||||
assignSingleConfig(_BConfig, ac)
|
||||
if _BConfig.AppName != "beego_json" {
|
||||
t.Log(_BConfig)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignConfig_02(t *testing.T) {
|
||||
_BConfig := &Config{}
|
||||
bs, _ := json.Marshal(newBConfig())
|
||||
|
||||
jsonMap := map[string]interface{}{}
|
||||
json.Unmarshal(bs, &jsonMap)
|
||||
|
||||
configMap := map[string]interface{}{}
|
||||
for k, v := range jsonMap {
|
||||
if reflect.TypeOf(v).Kind() == reflect.Map {
|
||||
for k1, v1 := range v.(map[string]interface{}) {
|
||||
if reflect.TypeOf(v1).Kind() == reflect.Map {
|
||||
for k2, v2 := range v1.(map[string]interface{}) {
|
||||
configMap[k2] = v2
|
||||
}
|
||||
} else {
|
||||
configMap[k1] = v1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
configMap[k] = v
|
||||
}
|
||||
}
|
||||
configMap["MaxMemory"] = 1024
|
||||
configMap["Graceful"] = true
|
||||
configMap["XSRFExpire"] = 32
|
||||
configMap["SessionProviderConfig"] = "file"
|
||||
configMap["FileLineNum"] = true
|
||||
|
||||
jcf := &config.JSONConfig{}
|
||||
bs, _ = json.Marshal(configMap)
|
||||
ac, _ := jcf.ParseData([]byte(bs))
|
||||
|
||||
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
|
||||
assignSingleConfig(i, ac)
|
||||
}
|
||||
|
||||
if _BConfig.MaxMemory != 1024 {
|
||||
t.Log(_BConfig.MaxMemory)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !_BConfig.Listen.Graceful {
|
||||
t.Log(_BConfig.Listen.Graceful)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if _BConfig.WebConfig.XSRFExpire != 32 {
|
||||
t.Log(_BConfig.WebConfig.XSRFExpire)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if _BConfig.WebConfig.Session.SessionProviderConfig != "file" {
|
||||
t.Log(_BConfig.WebConfig.Session.SessionProviderConfig)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !_BConfig.Log.FileLineNum {
|
||||
t.Log(_BConfig.Log.FileLineNum)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAssignConfig_03(t *testing.T) {
|
||||
jcf := &config.JSONConfig{}
|
||||
ac, _ := jcf.ParseData([]byte(`{"AppName":"beego"}`))
|
||||
ac.Set("AppName", "test_app")
|
||||
ac.Set("RunMode", "online")
|
||||
ac.Set("StaticDir", "download:down download2:down2")
|
||||
ac.Set("StaticExtensionsToGzip", ".css,.js,.html,.jpg,.png")
|
||||
assignConfig(ac)
|
||||
|
||||
t.Logf("%#v", BConfig)
|
||||
|
||||
if BConfig.AppName != "test_app" {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if BConfig.RunMode != "online" {
|
||||
t.FailNow()
|
||||
}
|
||||
if BConfig.WebConfig.StaticDir["/download"] != "down" {
|
||||
t.FailNow()
|
||||
}
|
||||
if BConfig.WebConfig.StaticDir["/download2"] != "down2" {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(BConfig.WebConfig.StaticExtensionsToGzip) != 5 {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,33 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
//Default size==20B same as nginx
|
||||
defaultGzipMinLength = 20
|
||||
//Content will only be compressed if content length is either unknown or greater than gzipMinLength.
|
||||
gzipMinLength = defaultGzipMinLength
|
||||
//The compression level used for deflate compression. (0-9).
|
||||
gzipCompressLevel int
|
||||
//List of HTTP methods to compress. If not set, only GET requests are compressed.
|
||||
includedMethods map[string]bool
|
||||
getMethodOnly bool
|
||||
)
|
||||
|
||||
func InitGzip(minLength, compressLevel int, methods []string) {
|
||||
if minLength >= 0 {
|
||||
gzipMinLength = minLength
|
||||
}
|
||||
gzipCompressLevel = compressLevel
|
||||
if gzipCompressLevel < flate.NoCompression || gzipCompressLevel > flate.BestCompression {
|
||||
gzipCompressLevel = flate.BestSpeed
|
||||
}
|
||||
getMethodOnly = (len(methods) == 0) || (len(methods) == 1 && strings.ToUpper(methods[0]) == "GET")
|
||||
includedMethods = make(map[string]bool, len(methods))
|
||||
for _, v := range methods {
|
||||
includedMethods[strings.ToUpper(v)] = true
|
||||
}
|
||||
}
|
||||
|
||||
type resetWriter interface {
|
||||
io.Writer
|
||||
Reset(w io.Writer)
|
||||
@ -41,20 +68,20 @@ func (n nopResetWriter) Reset(w io.Writer) {
|
||||
}
|
||||
|
||||
type acceptEncoder struct {
|
||||
name string
|
||||
levelEncode func(int) resetWriter
|
||||
bestSpeedPool *sync.Pool
|
||||
bestCompressionPool *sync.Pool
|
||||
name string
|
||||
levelEncode func(int) resetWriter
|
||||
customCompressLevelPool *sync.Pool
|
||||
bestCompressionPool *sync.Pool
|
||||
}
|
||||
|
||||
func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter {
|
||||
if ac.bestSpeedPool == nil || ac.bestCompressionPool == nil {
|
||||
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
|
||||
return nopResetWriter{wr}
|
||||
}
|
||||
var rwr resetWriter
|
||||
switch level {
|
||||
case flate.BestSpeed:
|
||||
rwr = ac.bestSpeedPool.Get().(resetWriter)
|
||||
rwr = ac.customCompressLevelPool.Get().(resetWriter)
|
||||
case flate.BestCompression:
|
||||
rwr = ac.bestCompressionPool.Get().(resetWriter)
|
||||
default:
|
||||
@ -65,13 +92,18 @@ func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter {
|
||||
}
|
||||
|
||||
func (ac acceptEncoder) put(wr resetWriter, level int) {
|
||||
if ac.bestSpeedPool == nil || ac.bestCompressionPool == nil {
|
||||
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
|
||||
return
|
||||
}
|
||||
wr.Reset(nil)
|
||||
|
||||
//notice
|
||||
//compressionLevel==BestCompression DOES NOT MATTER
|
||||
//sync.Pool will not memory leak
|
||||
|
||||
switch level {
|
||||
case flate.BestSpeed:
|
||||
ac.bestSpeedPool.Put(wr)
|
||||
case gzipCompressLevel:
|
||||
ac.customCompressLevelPool.Put(wr)
|
||||
case flate.BestCompression:
|
||||
ac.bestCompressionPool.Put(wr)
|
||||
}
|
||||
@ -79,28 +111,22 @@ func (ac acceptEncoder) put(wr resetWriter, level int) {
|
||||
|
||||
var (
|
||||
noneCompressEncoder = acceptEncoder{"", nil, nil, nil}
|
||||
gzipCompressEncoder = acceptEncoder{"gzip",
|
||||
func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr },
|
||||
&sync.Pool{
|
||||
New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestSpeed); return wr },
|
||||
},
|
||||
&sync.Pool{
|
||||
New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr },
|
||||
},
|
||||
gzipCompressEncoder = acceptEncoder{
|
||||
name: "gzip",
|
||||
levelEncode: func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr },
|
||||
customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, gzipCompressLevel); return wr }},
|
||||
bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }},
|
||||
}
|
||||
|
||||
//according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed
|
||||
//deflate
|
||||
//The "zlib" format defined in RFC 1950 [31] in combination with
|
||||
//the "deflate" compression mechanism described in RFC 1951 [29].
|
||||
deflateCompressEncoder = acceptEncoder{"deflate",
|
||||
func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr },
|
||||
&sync.Pool{
|
||||
New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestSpeed); return wr },
|
||||
},
|
||||
&sync.Pool{
|
||||
New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr },
|
||||
},
|
||||
deflateCompressEncoder = acceptEncoder{
|
||||
name: "deflate",
|
||||
levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr },
|
||||
customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, gzipCompressLevel); return wr }},
|
||||
bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr }},
|
||||
}
|
||||
)
|
||||
|
||||
@ -120,7 +146,11 @@ func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string,
|
||||
|
||||
// WriteBody reads writes content to writer by the specific encoding(gzip/deflate)
|
||||
func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) {
|
||||
return writeLevel(encoding, writer, bytes.NewReader(content), flate.BestSpeed)
|
||||
if encoding == "" || len(content) < gzipMinLength {
|
||||
_, err := writer.Write(content)
|
||||
return false, "", err
|
||||
}
|
||||
return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel)
|
||||
}
|
||||
|
||||
// writeLevel reads from reader,writes to writer by specific encoding and compress level
|
||||
@ -156,7 +186,10 @@ func ParseEncoding(r *http.Request) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return parseEncoding(r)
|
||||
if (getMethodOnly && r.Method == "GET") || includedMethods[r.Method] {
|
||||
return parseEncoding(r)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type q struct {
|
||||
@ -176,9 +209,13 @@ func parseEncoding(r *http.Request) string {
|
||||
continue
|
||||
}
|
||||
vs := strings.Split(v, ";")
|
||||
var cf acceptEncoder
|
||||
var ok bool
|
||||
if cf, ok = encoderMap[vs[0]]; !ok {
|
||||
continue
|
||||
}
|
||||
if len(vs) == 1 {
|
||||
lastQ = q{vs[0], 1}
|
||||
break
|
||||
return cf.name
|
||||
}
|
||||
if len(vs) == 2 {
|
||||
f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64)
|
||||
@ -186,12 +223,9 @@ func parseEncoding(r *http.Request) string {
|
||||
continue
|
||||
}
|
||||
if f > lastQ.value {
|
||||
lastQ = q{vs[0], f}
|
||||
lastQ = q{cf.name, f}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cf, ok := encoderMap[lastQ.name]; ok {
|
||||
return cf.name
|
||||
}
|
||||
return ""
|
||||
return lastQ.name
|
||||
}
|
||||
|
@ -41,4 +41,19 @@ func Test_ExtractEncoding(t *testing.T) {
|
||||
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"*"}}}) != "gzip" {
|
||||
t.Fail()
|
||||
}
|
||||
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x,gzip,deflate"}}}) != "gzip" {
|
||||
t.Fail()
|
||||
}
|
||||
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip,x,deflate"}}}) != "gzip" {
|
||||
t.Fail()
|
||||
}
|
||||
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x,deflate"}}}) != "deflate" {
|
||||
t.Fail()
|
||||
}
|
||||
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x"}}}) != "" {
|
||||
t.Fail()
|
||||
}
|
||||
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x;q=0.8"}}}) != "gzip" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,11 @@ package context
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -67,18 +65,18 @@ func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx.ResponseWriter.reset(rw)
|
||||
ctx.Input.Reset(ctx)
|
||||
ctx.Output.Reset(ctx)
|
||||
ctx._xsrfToken = ""
|
||||
}
|
||||
|
||||
// Redirect does redirection to localurl with http header status code.
|
||||
// It sends http response header directly.
|
||||
func (ctx *Context) Redirect(status int, localurl string) {
|
||||
ctx.Output.Header("Location", localurl)
|
||||
ctx.ResponseWriter.WriteHeader(status)
|
||||
http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status)
|
||||
}
|
||||
|
||||
// Abort stops this request.
|
||||
// if beego.ErrorMaps exists, panic body.
|
||||
func (ctx *Context) Abort(status int, body string) {
|
||||
ctx.Output.SetStatus(status)
|
||||
panic(body)
|
||||
}
|
||||
|
||||
@ -195,14 +193,6 @@ func (r *Response) Write(p []byte) (int, error) {
|
||||
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 (r *Response) WriteHeader(code int) {
|
||||
|
47
context/context_test.go
Normal file
47
context/context_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
// 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 context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXsrfReset_01(t *testing.T) {
|
||||
r := &http.Request{}
|
||||
c := NewContext()
|
||||
c.Request = r
|
||||
c.ResponseWriter = &Response{}
|
||||
c.ResponseWriter.reset(httptest.NewRecorder())
|
||||
c.Output.Reset(c)
|
||||
c.Input.Reset(c)
|
||||
c.XSRFToken("key", 16)
|
||||
if c._xsrfToken == "" {
|
||||
t.FailNow()
|
||||
}
|
||||
token := c._xsrfToken
|
||||
c.Reset(&Response{ResponseWriter: httptest.NewRecorder()}, r)
|
||||
if c._xsrfToken != "" {
|
||||
t.FailNow()
|
||||
}
|
||||
c.XSRFToken("key", 16)
|
||||
if c._xsrfToken == "" {
|
||||
t.FailNow()
|
||||
}
|
||||
if token == c._xsrfToken {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
@ -89,6 +89,9 @@ func (input *BeegoInput) Site() string {
|
||||
|
||||
// Scheme returns request scheme as "http" or "https".
|
||||
func (input *BeegoInput) Scheme() string {
|
||||
if scheme := input.Header("X-Forwarded-Proto"); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
if input.Context.Request.URL.Scheme != "" {
|
||||
return input.Context.Request.URL.Scheme
|
||||
}
|
||||
@ -298,6 +301,14 @@ func (input *BeegoInput) SetParam(key, val string) {
|
||||
input.pnames = append(input.pnames, key)
|
||||
}
|
||||
|
||||
// ResetParams clears any of the input's Params
|
||||
// This function is used to clear parameters so they may be reset between filter
|
||||
// passes.
|
||||
func (input *BeegoInput) ResetParams() {
|
||||
input.pnames = input.pnames[:0]
|
||||
input.pvalues = input.pvalues[:0]
|
||||
}
|
||||
|
||||
// Query returns input data item string by a given string.
|
||||
func (input *BeegoInput) Query(key string) string {
|
||||
if val := input.Param(key); val != "" {
|
||||
@ -326,13 +337,16 @@ func (input *BeegoInput) Cookie(key string) string {
|
||||
}
|
||||
|
||||
// Session returns current session item value by a given key.
|
||||
// if non-existed, return empty string.
|
||||
// if non-existed, return nil.
|
||||
func (input *BeegoInput) Session(key interface{}) interface{} {
|
||||
return input.CruSession.Get(key)
|
||||
}
|
||||
|
||||
// CopyBody returns the raw request body data as bytes.
|
||||
func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
|
||||
if input.Context.Request.Body == nil {
|
||||
return []byte{}
|
||||
}
|
||||
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
|
||||
requestbody, _ := ioutil.ReadAll(safe)
|
||||
input.Context.Request.Body.Close()
|
||||
@ -576,12 +590,15 @@ func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.
|
||||
result := reflect.New(typ).Elem()
|
||||
fieldValues := make(map[string]reflect.Value)
|
||||
for reqKey, val := range *params {
|
||||
if !strings.HasPrefix(reqKey, key+".") {
|
||||
var fieldName string
|
||||
if strings.HasPrefix(reqKey, key+".") {
|
||||
fieldName = reqKey[len(key)+1:]
|
||||
} else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' {
|
||||
fieldName = reqKey[len(key)+1 : len(reqKey)-1]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldName := reqKey[len(key)+1:]
|
||||
|
||||
if _, ok := fieldValues[fieldName]; !ok {
|
||||
// Time to bind this field. Get it and make sure we can set it.
|
||||
fieldValue := result.FieldByName(fieldName)
|
||||
|
@ -75,6 +75,24 @@ func TestParse(t *testing.T) {
|
||||
fmt.Println(user)
|
||||
}
|
||||
|
||||
func TestParse2(t *testing.T) {
|
||||
r, _ := http.NewRequest("GET", "/?user[0][Username]=Raph&user[1].Username=Leo&user[0].Password=123456&user[1][Password]=654321", nil)
|
||||
beegoInput := NewInput()
|
||||
beegoInput.Context = NewContext()
|
||||
beegoInput.Context.Reset(httptest.NewRecorder(), r)
|
||||
beegoInput.ParseFormOrMulitForm(1 << 20)
|
||||
type User struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
var users []User
|
||||
err := beegoInput.Bind(&users, "user")
|
||||
fmt.Println(users)
|
||||
if err != nil || users[0].Username != "Raph" || users[0].Password != "123456" || users[1].Username != "Leo" || users[1].Password != "654321" {
|
||||
t.Fatal("users info wrong")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubDomain(t *testing.T) {
|
||||
r, _ := http.NewRequest("GET", "http://www.example.com/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil)
|
||||
beegoInput := NewInput()
|
||||
@ -100,7 +118,7 @@ func TestSubDomain(t *testing.T) {
|
||||
|
||||
/* TODO Fix this
|
||||
r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil)
|
||||
beegoInput.Request = r
|
||||
beegoInput.Context.Request = r
|
||||
if beegoInput.SubDomains() != "" {
|
||||
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
|
||||
}
|
||||
|
@ -21,8 +21,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -72,10 +75,11 @@ func (output *BeegoOutput) Body(content []byte) error {
|
||||
if output.Status != 0 {
|
||||
output.Context.ResponseWriter.WriteHeader(output.Status)
|
||||
output.Status = 0
|
||||
} else {
|
||||
output.Context.ResponseWriter.Started = true
|
||||
}
|
||||
|
||||
_, err := output.Context.ResponseWriter.Copy(buf)
|
||||
return err
|
||||
io.Copy(output.Context.ResponseWriter, buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cookie sets cookie value via given key.
|
||||
@ -142,18 +146,12 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
|
||||
}
|
||||
|
||||
// default false. for session cookie default true
|
||||
httponly := false
|
||||
if len(others) > 4 {
|
||||
if v, ok := others[4].(bool); ok && v {
|
||||
// HttpOnly = true
|
||||
httponly = true
|
||||
fmt.Fprintf(&b, "; HttpOnly")
|
||||
}
|
||||
}
|
||||
|
||||
if httponly {
|
||||
fmt.Fprintf(&b, "; HttpOnly")
|
||||
}
|
||||
|
||||
output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
|
||||
}
|
||||
|
||||
@ -208,7 +206,8 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
|
||||
if callback == "" {
|
||||
return errors.New(`"callback" parameter required`)
|
||||
}
|
||||
callbackContent := bytes.NewBufferString(" " + template.JSEscapeString(callback))
|
||||
callback = template.JSEscapeString(callback)
|
||||
callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback)
|
||||
callbackContent.WriteString("(")
|
||||
callbackContent.Write(content)
|
||||
callbackContent.WriteString(");\r\n")
|
||||
@ -235,13 +234,21 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
|
||||
// Download forces response for download file.
|
||||
// it prepares the download response header automatically.
|
||||
func (output *BeegoOutput) Download(file string, filename ...string) {
|
||||
// check get file error, file not found or other error.
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
|
||||
return
|
||||
}
|
||||
|
||||
var fName string
|
||||
if len(filename) > 0 && filename[0] != "" {
|
||||
fName = filename[0]
|
||||
} else {
|
||||
fName = filepath.Base(file)
|
||||
}
|
||||
output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName))
|
||||
output.Header("Content-Description", "File Transfer")
|
||||
output.Header("Content-Type", "application/octet-stream")
|
||||
if len(filename) > 0 && filename[0] != "" {
|
||||
output.Header("Content-Disposition", "attachment; filename="+filename[0])
|
||||
} else {
|
||||
output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file))
|
||||
}
|
||||
output.Header("Content-Transfer-Encoding", "binary")
|
||||
output.Header("Expires", "0")
|
||||
output.Header("Cache-Control", "must-revalidate")
|
||||
@ -269,55 +276,55 @@ func (output *BeegoOutput) SetStatus(status int) {
|
||||
|
||||
// IsCachable returns boolean of this request is cached.
|
||||
// HTTP 304 means cached.
|
||||
func (output *BeegoOutput) IsCachable(status int) bool {
|
||||
func (output *BeegoOutput) IsCachable() bool {
|
||||
return output.Status >= 200 && output.Status < 300 || output.Status == 304
|
||||
}
|
||||
|
||||
// IsEmpty returns boolean of this request is empty.
|
||||
// HTTP 201,204 and 304 means empty.
|
||||
func (output *BeegoOutput) IsEmpty(status int) bool {
|
||||
func (output *BeegoOutput) IsEmpty() bool {
|
||||
return output.Status == 201 || output.Status == 204 || output.Status == 304
|
||||
}
|
||||
|
||||
// IsOk returns boolean of this request runs well.
|
||||
// HTTP 200 means ok.
|
||||
func (output *BeegoOutput) IsOk(status int) bool {
|
||||
func (output *BeegoOutput) IsOk() bool {
|
||||
return output.Status == 200
|
||||
}
|
||||
|
||||
// IsSuccessful returns boolean of this request runs successfully.
|
||||
// HTTP 2xx means ok.
|
||||
func (output *BeegoOutput) IsSuccessful(status int) bool {
|
||||
func (output *BeegoOutput) IsSuccessful() bool {
|
||||
return output.Status >= 200 && output.Status < 300
|
||||
}
|
||||
|
||||
// IsRedirect returns boolean of this request is redirection header.
|
||||
// HTTP 301,302,307 means redirection.
|
||||
func (output *BeegoOutput) IsRedirect(status int) bool {
|
||||
func (output *BeegoOutput) IsRedirect() bool {
|
||||
return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
|
||||
}
|
||||
|
||||
// IsForbidden returns boolean of this request is forbidden.
|
||||
// HTTP 403 means forbidden.
|
||||
func (output *BeegoOutput) IsForbidden(status int) bool {
|
||||
func (output *BeegoOutput) IsForbidden() bool {
|
||||
return output.Status == 403
|
||||
}
|
||||
|
||||
// IsNotFound returns boolean of this request is not found.
|
||||
// HTTP 404 means forbidden.
|
||||
func (output *BeegoOutput) IsNotFound(status int) bool {
|
||||
func (output *BeegoOutput) IsNotFound() bool {
|
||||
return output.Status == 404
|
||||
}
|
||||
|
||||
// IsClientError returns boolean of this request client sends error data.
|
||||
// HTTP 4xx means forbidden.
|
||||
func (output *BeegoOutput) IsClientError(status int) bool {
|
||||
func (output *BeegoOutput) IsClientError() bool {
|
||||
return output.Status >= 400 && output.Status < 500
|
||||
}
|
||||
|
||||
// IsServerError returns boolean of this server handler errors.
|
||||
// HTTP 5xx means server internal error.
|
||||
func (output *BeegoOutput) IsServerError(status int) bool {
|
||||
func (output *BeegoOutput) IsServerError() bool {
|
||||
return output.Status >= 500 && output.Status < 600
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,7 @@ type Controller struct {
|
||||
TplName string
|
||||
Layout string
|
||||
LayoutSections map[string]string // the key is the section name and the value is the template name
|
||||
TplPrefix string
|
||||
TplExt string
|
||||
EnableRender bool
|
||||
|
||||
@ -208,7 +209,7 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
continue
|
||||
}
|
||||
buf.Reset()
|
||||
err = executeTemplate(&buf, sectionTpl, c.Data)
|
||||
err = ExecuteTemplate(&buf, sectionTpl, c.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -217,7 +218,7 @@ func (c *Controller) RenderBytes() ([]byte, error) {
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
executeTemplate(&buf, c.Layout, c.Data)
|
||||
ExecuteTemplate(&buf, c.Layout, c.Data)
|
||||
}
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
@ -227,6 +228,9 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) {
|
||||
if c.TplName == "" {
|
||||
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
|
||||
}
|
||||
if c.TplPrefix != "" {
|
||||
c.TplName = c.TplPrefix + c.TplName
|
||||
}
|
||||
if BConfig.RunMode == DEV {
|
||||
buildFiles := []string{c.TplName}
|
||||
if c.Layout != "" {
|
||||
@ -242,7 +246,7 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) {
|
||||
}
|
||||
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
|
||||
}
|
||||
return buf, executeTemplate(&buf, c.TplName, c.Data)
|
||||
return buf, ExecuteTemplate(&buf, c.TplName, c.Data)
|
||||
}
|
||||
|
||||
// Redirect sends the redirection response to url with status code.
|
||||
@ -261,12 +265,13 @@ 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.Output.Status = status
|
||||
// first panic from ErrorMaps, is is user defined error functions.
|
||||
// first panic from ErrorMaps, it is user defined error functions.
|
||||
if _, ok := ErrorMaps[body]; ok {
|
||||
c.Ctx.Output.Status = status
|
||||
panic(body)
|
||||
}
|
||||
// last panic user string
|
||||
c.Ctx.ResponseWriter.WriteHeader(status)
|
||||
c.Ctx.ResponseWriter.Write([]byte(body))
|
||||
panic(ErrAbort)
|
||||
}
|
||||
|
218
error.go
218
error.go
@ -210,159 +210,139 @@ var ErrorMaps = make(map[string]*errorInfo, 10)
|
||||
|
||||
// show 401 unauthorized error.
|
||||
func unauthorized(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(401),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested can't be authorized." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br>The credentials you supplied are incorrect" +
|
||||
"<br>There are errors in the website address" +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
401,
|
||||
"<br>The page you have requested can't be authorized."+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br>The credentials you supplied are incorrect"+
|
||||
"<br>There are errors in the website address"+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 402 Payment Required
|
||||
func paymentRequired(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(402),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested Payment Required." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br>The credentials you supplied are incorrect" +
|
||||
"<br>There are errors in the website address" +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
402,
|
||||
"<br>The page you have requested Payment Required."+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br>The credentials you supplied are incorrect"+
|
||||
"<br>There are errors in the website address"+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 403 forbidden error.
|
||||
func forbidden(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(403),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested is forbidden." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br>Your address may be blocked" +
|
||||
"<br>The site may be disabled" +
|
||||
"<br>You need to log in" +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
403,
|
||||
"<br>The page you have requested is forbidden."+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br>Your address may be blocked"+
|
||||
"<br>The site may be disabled"+
|
||||
"<br>You need to log in"+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 404 notfound error.
|
||||
// show 404 not found error.
|
||||
func notFound(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(404),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested has flown the coop." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br>The page has moved" +
|
||||
"<br>The page no longer exists" +
|
||||
"<br>You were looking for your puppy and got lost" +
|
||||
"<br>You like 404 pages" +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
404,
|
||||
"<br>The page you have requested has flown the coop."+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br>The page has moved"+
|
||||
"<br>The page no longer exists"+
|
||||
"<br>You were looking for your puppy and got lost"+
|
||||
"<br>You like 404 pages"+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 405 Method Not Allowed
|
||||
func methodNotAllowed(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(405),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The method you have requested Not Allowed." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI" +
|
||||
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource." +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
405,
|
||||
"<br>The method you have requested Not Allowed."+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI"+
|
||||
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource."+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 500 internal server error.
|
||||
func internalServerError(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(500),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
|
||||
"<br><br><ul>" +
|
||||
"<br>Please try again later and report the error to the website administrator" +
|
||||
"<br></ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
500,
|
||||
"<br>The page you have requested is down right now."+
|
||||
"<br><br><ul>"+
|
||||
"<br>Please try again later and report the error to the website administrator"+
|
||||
"<br></ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 501 Not Implemented.
|
||||
func notImplemented(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(504),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested is Not Implemented." +
|
||||
"<br><br><ul>" +
|
||||
"<br>Please try again later and report the error to the website administrator" +
|
||||
"<br></ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
501,
|
||||
"<br>The page you have requested is Not Implemented."+
|
||||
"<br><br><ul>"+
|
||||
"<br>Please try again later and report the error to the website administrator"+
|
||||
"<br></ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 502 Bad Gateway.
|
||||
func badGateway(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(502),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
|
||||
"<br><br><ul>" +
|
||||
"<br>The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request." +
|
||||
"<br>Please try again later and report the error to the website administrator" +
|
||||
"<br></ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
502,
|
||||
"<br>The page you have requested is down right now."+
|
||||
"<br><br><ul>"+
|
||||
"<br>The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."+
|
||||
"<br>Please try again later and report the error to the website administrator"+
|
||||
"<br></ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 503 service unavailable error.
|
||||
func serviceUnavailable(rw http.ResponseWriter, r *http.Request) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(503),
|
||||
"BeegoVersion": VERSION,
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested is unavailable." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br><br>The page is overloaded" +
|
||||
"<br>Please try again later." +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
responseError(rw, r,
|
||||
503,
|
||||
"<br>The page you have requested is unavailable."+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br><br>The page is overloaded"+
|
||||
"<br>Please try again later."+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
// show 504 Gateway Timeout.
|
||||
func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
|
||||
responseError(rw, r,
|
||||
504,
|
||||
"<br>The page you have requested is unavailable"+
|
||||
"<br>Perhaps you are here because:"+
|
||||
"<br><br><ul>"+
|
||||
"<br><br>The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI."+
|
||||
"<br>Please try again later."+
|
||||
"</ul>",
|
||||
)
|
||||
}
|
||||
|
||||
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
|
||||
t, _ := template.New("beegoerrortemp").Parse(errtpl)
|
||||
data := map[string]interface{}{
|
||||
"Title": http.StatusText(504),
|
||||
"Title": http.StatusText(errCode),
|
||||
"BeegoVersion": VERSION,
|
||||
"Content": template.HTML(errContent),
|
||||
}
|
||||
data["Content"] = template.HTML("<br>The page you have requested is unavailable." +
|
||||
"<br>Perhaps you are here because:" +
|
||||
"<br><br><ul>" +
|
||||
"<br><br>The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI." +
|
||||
"<br>Please try again later." +
|
||||
"</ul>")
|
||||
t.Execute(rw, data)
|
||||
}
|
||||
|
||||
@ -400,6 +380,11 @@ func ErrorController(c ControllerInterface) *App {
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
// Exception Write HttpStatus with errCode and Exec error handler if exist.
|
||||
func Exception(errCode uint64, ctx *context.Context) {
|
||||
exception(strconv.FormatUint(errCode, 10), ctx)
|
||||
}
|
||||
|
||||
// show error string as simple text message.
|
||||
// if error string is empty, show 503 or 500 error as default.
|
||||
func exception(errCode string, ctx *context.Context) {
|
||||
@ -408,7 +393,10 @@ func exception(errCode string, ctx *context.Context) {
|
||||
if err == nil {
|
||||
return v
|
||||
}
|
||||
return 503
|
||||
if ctx.Output.Status == 0 {
|
||||
return 503
|
||||
}
|
||||
return ctx.Output.Status
|
||||
}
|
||||
|
||||
for _, ec := range []string{errCode, "503", "500"} {
|
||||
|
88
error_test.go
Normal file
88
error_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
// 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 beego
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type errorTestController struct {
|
||||
Controller
|
||||
}
|
||||
|
||||
const parseCodeError = "parse code error"
|
||||
|
||||
func (ec *errorTestController) Get() {
|
||||
errorCode, err := ec.GetInt("code")
|
||||
if err != nil {
|
||||
ec.Abort(parseCodeError)
|
||||
}
|
||||
if errorCode != 0 {
|
||||
ec.CustomAbort(errorCode, ec.GetString("code"))
|
||||
}
|
||||
ec.Abort("404")
|
||||
}
|
||||
|
||||
func TestErrorCode_01(t *testing.T) {
|
||||
registerDefaultErrorHandler()
|
||||
for k := range ErrorMaps {
|
||||
r, _ := http.NewRequest("GET", "/error?code="+k, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler := NewControllerRegister()
|
||||
handler.Add("/error", &errorTestController{})
|
||||
handler.ServeHTTP(w, r)
|
||||
code, _ := strconv.Atoi(k)
|
||||
if w.Code != code {
|
||||
t.Fail()
|
||||
}
|
||||
if !strings.Contains(string(w.Body.Bytes()), http.StatusText(code)) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorCode_02(t *testing.T) {
|
||||
registerDefaultErrorHandler()
|
||||
r, _ := http.NewRequest("GET", "/error?code=0", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler := NewControllerRegister()
|
||||
handler.Add("/error", &errorTestController{})
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != 404 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorCode_03(t *testing.T) {
|
||||
registerDefaultErrorHandler()
|
||||
r, _ := http.NewRequest("GET", "/error?code=panic", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler := NewControllerRegister()
|
||||
handler.Add("/error", &errorTestController{})
|
||||
handler.ServeHTTP(w, r)
|
||||
if w.Code != 200 {
|
||||
t.Fail()
|
||||
}
|
||||
if string(w.Body.Bytes()) != parseCodeError {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ type FilterRouter struct {
|
||||
tree *Tree
|
||||
pattern string
|
||||
returnOnOutput bool
|
||||
resetParams bool
|
||||
}
|
||||
|
||||
// ValidRouter checks if the current request is matched by this filter.
|
||||
|
@ -20,14 +20,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
BeeLogger = logs.NewLogger(10000)
|
||||
BeeLogger.SetLogger("console", "")
|
||||
}
|
||||
|
||||
var FilterUser = func(ctx *context.Context) {
|
||||
ctx.Output.Body([]byte("i am " + ctx.Input.Param(":last") + ctx.Input.Param(":first")))
|
||||
}
|
||||
|
50
hooks.go
50
hooks.go
@ -6,6 +6,8 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/session"
|
||||
)
|
||||
|
||||
@ -43,23 +45,24 @@ func registerSession() error {
|
||||
if BConfig.WebConfig.Session.SessionOn {
|
||||
var err error
|
||||
sessionConfig := AppConfig.String("sessionConfig")
|
||||
conf := new(session.ManagerConfig)
|
||||
if sessionConfig == "" {
|
||||
conf := map[string]interface{}{
|
||||
"cookieName": BConfig.WebConfig.Session.SessionName,
|
||||
"gclifetime": BConfig.WebConfig.Session.SessionGCMaxLifetime,
|
||||
"providerConfig": filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig),
|
||||
"secure": BConfig.Listen.EnableHTTPS,
|
||||
"enableSetCookie": BConfig.WebConfig.Session.SessionAutoSetCookie,
|
||||
"domain": BConfig.WebConfig.Session.SessionDomain,
|
||||
"cookieLifeTime": BConfig.WebConfig.Session.SessionCookieLifeTime,
|
||||
}
|
||||
confBytes, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
conf.CookieName = BConfig.WebConfig.Session.SessionName
|
||||
conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||
conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime
|
||||
conf.Secure = BConfig.Listen.EnableHTTPS
|
||||
conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||
conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
|
||||
conf.Domain = BConfig.WebConfig.Session.SessionDomain
|
||||
conf.EnableSidInHttpHeader = BConfig.WebConfig.Session.EnableSidInHttpHeader
|
||||
conf.SessionNameInHttpHeader = BConfig.WebConfig.Session.SessionNameInHttpHeader
|
||||
conf.EnableSidInUrlQuery = BConfig.WebConfig.Session.EnableSidInUrlQuery
|
||||
} else {
|
||||
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
|
||||
return err
|
||||
}
|
||||
sessionConfig = string(confBytes)
|
||||
}
|
||||
if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, sessionConfig); err != nil {
|
||||
if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, conf); err != nil {
|
||||
return err
|
||||
}
|
||||
go GlobalSessions.GC()
|
||||
@ -70,24 +73,27 @@ func registerSession() error {
|
||||
func registerTemplate() error {
|
||||
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
|
||||
if BConfig.RunMode == DEV {
|
||||
Warn(err)
|
||||
logs.Warn(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerDocs() error {
|
||||
if BConfig.WebConfig.EnableDocs {
|
||||
Get("/docs", serverDocs)
|
||||
Get("/docs/*", serverDocs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerAdmin() error {
|
||||
if BConfig.Listen.EnableAdmin {
|
||||
go beeAdminApp.Run()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerGzip() error {
|
||||
if BConfig.EnableGzip {
|
||||
context.InitGzip(
|
||||
AppConfig.DefaultInt("gzipMinLength", -1),
|
||||
AppConfig.DefaultInt("gzipCompressLevel", -1),
|
||||
AppConfig.DefaultStrings("includedMethods", []string{"GET"}),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -409,9 +409,10 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
|
||||
if trans == nil {
|
||||
// create default transport
|
||||
trans = &http.Transport{
|
||||
TLSClientConfig: b.setting.TLSClientConfig,
|
||||
Proxy: b.setting.Proxy,
|
||||
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
||||
TLSClientConfig: b.setting.TLSClientConfig,
|
||||
Proxy: b.setting.Proxy,
|
||||
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
|
||||
MaxIdleConnsPerHost: -1,
|
||||
}
|
||||
} else {
|
||||
// if b.transport is *http.Transport then set the settings.
|
||||
|
35
log.go
35
log.go
@ -33,82 +33,77 @@ const (
|
||||
)
|
||||
|
||||
// BeeLogger references the used application logger.
|
||||
var BeeLogger = logs.NewLogger(100)
|
||||
var BeeLogger = logs.GetBeeLogger()
|
||||
|
||||
// SetLevel sets the global log level used by the simple logger.
|
||||
func SetLevel(l int) {
|
||||
BeeLogger.SetLevel(l)
|
||||
logs.SetLevel(l)
|
||||
}
|
||||
|
||||
// SetLogFuncCall set the CallDepth, default is 3
|
||||
func SetLogFuncCall(b bool) {
|
||||
BeeLogger.EnableFuncCallDepth(b)
|
||||
BeeLogger.SetLogFuncCallDepth(3)
|
||||
logs.SetLogFuncCall(b)
|
||||
}
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(adaptername string, config string) error {
|
||||
err := BeeLogger.SetLogger(adaptername, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return logs.SetLogger(adaptername, config)
|
||||
}
|
||||
|
||||
// Emergency logs a message at emergency level.
|
||||
func Emergency(v ...interface{}) {
|
||||
BeeLogger.Emergency(generateFmtStr(len(v)), v...)
|
||||
logs.Emergency(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Alert logs a message at alert level.
|
||||
func Alert(v ...interface{}) {
|
||||
BeeLogger.Alert(generateFmtStr(len(v)), v...)
|
||||
logs.Alert(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(v ...interface{}) {
|
||||
BeeLogger.Critical(generateFmtStr(len(v)), v...)
|
||||
logs.Critical(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(v ...interface{}) {
|
||||
BeeLogger.Error(generateFmtStr(len(v)), v...)
|
||||
logs.Error(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warning(v ...interface{}) {
|
||||
BeeLogger.Warning(generateFmtStr(len(v)), v...)
|
||||
logs.Warning(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Warn compatibility alias for Warning()
|
||||
func Warn(v ...interface{}) {
|
||||
BeeLogger.Warn(generateFmtStr(len(v)), v...)
|
||||
logs.Warn(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Notice logs a message at notice level.
|
||||
func Notice(v ...interface{}) {
|
||||
BeeLogger.Notice(generateFmtStr(len(v)), v...)
|
||||
logs.Notice(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Informational logs a message at info level.
|
||||
func Informational(v ...interface{}) {
|
||||
BeeLogger.Informational(generateFmtStr(len(v)), v...)
|
||||
logs.Informational(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Info compatibility alias for Warning()
|
||||
func Info(v ...interface{}) {
|
||||
BeeLogger.Info(generateFmtStr(len(v)), v...)
|
||||
logs.Info(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(v ...interface{}) {
|
||||
BeeLogger.Debug(generateFmtStr(len(v)), v...)
|
||||
logs.Debug(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
// compatibility alias for Warning()
|
||||
func Trace(v ...interface{}) {
|
||||
BeeLogger.Trace(generateFmtStr(len(v)), v...)
|
||||
logs.Trace(generateFmtStr(len(v)), v...)
|
||||
}
|
||||
|
||||
func generateFmtStr(n int) string {
|
||||
|
@ -12,28 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package beego
|
||||
// +build !windows
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego/context"
|
||||
)
|
||||
package logs
|
||||
|
||||
// GlobalDocAPI store the swagger api documents
|
||||
var GlobalDocAPI = make(map[string]interface{})
|
||||
import "io"
|
||||
|
||||
func serverDocs(ctx *context.Context) {
|
||||
var obj interface{}
|
||||
if splat := ctx.Input.Param(":splat"); splat == "" {
|
||||
obj = GlobalDocAPI["Root"]
|
||||
} else {
|
||||
if v, ok := GlobalDocAPI[splat]; ok {
|
||||
obj = v
|
||||
}
|
||||
}
|
||||
if obj != nil {
|
||||
ctx.Output.Header("Access-Control-Allow-Origin", "*")
|
||||
ctx.Output.JSON(obj, false, false)
|
||||
return
|
||||
}
|
||||
ctx.Output.SetStatus(404)
|
||||
type ansiColorWriter struct {
|
||||
w io.Writer
|
||||
mode outputMode
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
||||
return cw.w.Write(p)
|
||||
}
|
428
logs/color_windows.go
Normal file
428
logs/color_windows.go
Normal file
@ -0,0 +1,428 @@
|
||||
// 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
csiState int
|
||||
parseResult int
|
||||
)
|
||||
|
||||
const (
|
||||
outsideCsiCode csiState = iota
|
||||
firstCsiCode
|
||||
secondCsiCode
|
||||
)
|
||||
|
||||
const (
|
||||
noConsole parseResult = iota
|
||||
changedColor
|
||||
unknown
|
||||
)
|
||||
|
||||
type ansiColorWriter struct {
|
||||
w io.Writer
|
||||
mode outputMode
|
||||
state csiState
|
||||
paramStartBuf bytes.Buffer
|
||||
paramBuf bytes.Buffer
|
||||
}
|
||||
|
||||
const (
|
||||
firstCsiChar byte = '\x1b'
|
||||
secondeCsiChar byte = '['
|
||||
separatorChar byte = ';'
|
||||
sgrCode byte = 'm'
|
||||
)
|
||||
|
||||
const (
|
||||
foregroundBlue = uint16(0x0001)
|
||||
foregroundGreen = uint16(0x0002)
|
||||
foregroundRed = uint16(0x0004)
|
||||
foregroundIntensity = uint16(0x0008)
|
||||
backgroundBlue = uint16(0x0010)
|
||||
backgroundGreen = uint16(0x0020)
|
||||
backgroundRed = uint16(0x0040)
|
||||
backgroundIntensity = uint16(0x0080)
|
||||
underscore = uint16(0x8000)
|
||||
|
||||
foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity
|
||||
backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity
|
||||
)
|
||||
|
||||
const (
|
||||
ansiReset = "0"
|
||||
ansiIntensityOn = "1"
|
||||
ansiIntensityOff = "21"
|
||||
ansiUnderlineOn = "4"
|
||||
ansiUnderlineOff = "24"
|
||||
ansiBlinkOn = "5"
|
||||
ansiBlinkOff = "25"
|
||||
|
||||
ansiForegroundBlack = "30"
|
||||
ansiForegroundRed = "31"
|
||||
ansiForegroundGreen = "32"
|
||||
ansiForegroundYellow = "33"
|
||||
ansiForegroundBlue = "34"
|
||||
ansiForegroundMagenta = "35"
|
||||
ansiForegroundCyan = "36"
|
||||
ansiForegroundWhite = "37"
|
||||
ansiForegroundDefault = "39"
|
||||
|
||||
ansiBackgroundBlack = "40"
|
||||
ansiBackgroundRed = "41"
|
||||
ansiBackgroundGreen = "42"
|
||||
ansiBackgroundYellow = "43"
|
||||
ansiBackgroundBlue = "44"
|
||||
ansiBackgroundMagenta = "45"
|
||||
ansiBackgroundCyan = "46"
|
||||
ansiBackgroundWhite = "47"
|
||||
ansiBackgroundDefault = "49"
|
||||
|
||||
ansiLightForegroundGray = "90"
|
||||
ansiLightForegroundRed = "91"
|
||||
ansiLightForegroundGreen = "92"
|
||||
ansiLightForegroundYellow = "93"
|
||||
ansiLightForegroundBlue = "94"
|
||||
ansiLightForegroundMagenta = "95"
|
||||
ansiLightForegroundCyan = "96"
|
||||
ansiLightForegroundWhite = "97"
|
||||
|
||||
ansiLightBackgroundGray = "100"
|
||||
ansiLightBackgroundRed = "101"
|
||||
ansiLightBackgroundGreen = "102"
|
||||
ansiLightBackgroundYellow = "103"
|
||||
ansiLightBackgroundBlue = "104"
|
||||
ansiLightBackgroundMagenta = "105"
|
||||
ansiLightBackgroundCyan = "106"
|
||||
ansiLightBackgroundWhite = "107"
|
||||
)
|
||||
|
||||
type drawType int
|
||||
|
||||
const (
|
||||
foreground drawType = iota
|
||||
background
|
||||
)
|
||||
|
||||
type winColor struct {
|
||||
code uint16
|
||||
drawType drawType
|
||||
}
|
||||
|
||||
var colorMap = map[string]winColor{
|
||||
ansiForegroundBlack: {0, foreground},
|
||||
ansiForegroundRed: {foregroundRed, foreground},
|
||||
ansiForegroundGreen: {foregroundGreen, foreground},
|
||||
ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground},
|
||||
ansiForegroundBlue: {foregroundBlue, foreground},
|
||||
ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground},
|
||||
ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground},
|
||||
ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
||||
ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
||||
|
||||
ansiBackgroundBlack: {0, background},
|
||||
ansiBackgroundRed: {backgroundRed, background},
|
||||
ansiBackgroundGreen: {backgroundGreen, background},
|
||||
ansiBackgroundYellow: {backgroundRed | backgroundGreen, background},
|
||||
ansiBackgroundBlue: {backgroundBlue, background},
|
||||
ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background},
|
||||
ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background},
|
||||
ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background},
|
||||
ansiBackgroundDefault: {0, background},
|
||||
|
||||
ansiLightForegroundGray: {foregroundIntensity, foreground},
|
||||
ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground},
|
||||
ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground},
|
||||
ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground},
|
||||
ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground},
|
||||
ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground},
|
||||
ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground},
|
||||
ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground},
|
||||
|
||||
ansiLightBackgroundGray: {backgroundIntensity, background},
|
||||
ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background},
|
||||
ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background},
|
||||
ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background},
|
||||
ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background},
|
||||
ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background},
|
||||
ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background},
|
||||
ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background},
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
defaultAttr *textAttributes
|
||||
)
|
||||
|
||||
func init() {
|
||||
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo != nil {
|
||||
colorMap[ansiForegroundDefault] = winColor{
|
||||
screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue),
|
||||
foreground,
|
||||
}
|
||||
colorMap[ansiBackgroundDefault] = winColor{
|
||||
screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue),
|
||||
background,
|
||||
}
|
||||
defaultAttr = convertTextAttr(screenInfo.WAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
type coord struct {
|
||||
X, Y int16
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
Left, Top, Right, Bottom int16
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
DwSize coord
|
||||
DwCursorPosition coord
|
||||
WAttributes uint16
|
||||
SrWindow smallRect
|
||||
DwMaximumWindowSize coord
|
||||
}
|
||||
|
||||
func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo {
|
||||
var csbi consoleScreenBufferInfo
|
||||
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
|
||||
hConsoleOutput,
|
||||
uintptr(unsafe.Pointer(&csbi)))
|
||||
if ret == 0 {
|
||||
return nil
|
||||
}
|
||||
return &csbi
|
||||
}
|
||||
|
||||
func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool {
|
||||
ret, _, _ := procSetConsoleTextAttribute.Call(
|
||||
hConsoleOutput,
|
||||
uintptr(wAttributes))
|
||||
return ret != 0
|
||||
}
|
||||
|
||||
type textAttributes struct {
|
||||
foregroundColor uint16
|
||||
backgroundColor uint16
|
||||
foregroundIntensity uint16
|
||||
backgroundIntensity uint16
|
||||
underscore uint16
|
||||
otherAttributes uint16
|
||||
}
|
||||
|
||||
func convertTextAttr(winAttr uint16) *textAttributes {
|
||||
fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue)
|
||||
bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue)
|
||||
fgIntensity := winAttr & foregroundIntensity
|
||||
bgIntensity := winAttr & backgroundIntensity
|
||||
underline := winAttr & underscore
|
||||
otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore)
|
||||
return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes}
|
||||
}
|
||||
|
||||
func convertWinAttr(textAttr *textAttributes) uint16 {
|
||||
var winAttr uint16
|
||||
winAttr |= textAttr.foregroundColor
|
||||
winAttr |= textAttr.backgroundColor
|
||||
winAttr |= textAttr.foregroundIntensity
|
||||
winAttr |= textAttr.backgroundIntensity
|
||||
winAttr |= textAttr.underscore
|
||||
winAttr |= textAttr.otherAttributes
|
||||
return winAttr
|
||||
}
|
||||
|
||||
func changeColor(param []byte) parseResult {
|
||||
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo == nil {
|
||||
return noConsole
|
||||
}
|
||||
|
||||
winAttr := convertTextAttr(screenInfo.WAttributes)
|
||||
strParam := string(param)
|
||||
if len(strParam) <= 0 {
|
||||
strParam = "0"
|
||||
}
|
||||
csiParam := strings.Split(strParam, string(separatorChar))
|
||||
for _, p := range csiParam {
|
||||
c, ok := colorMap[p]
|
||||
switch {
|
||||
case !ok:
|
||||
switch p {
|
||||
case ansiReset:
|
||||
winAttr.foregroundColor = defaultAttr.foregroundColor
|
||||
winAttr.backgroundColor = defaultAttr.backgroundColor
|
||||
winAttr.foregroundIntensity = defaultAttr.foregroundIntensity
|
||||
winAttr.backgroundIntensity = defaultAttr.backgroundIntensity
|
||||
winAttr.underscore = 0
|
||||
winAttr.otherAttributes = 0
|
||||
case ansiIntensityOn:
|
||||
winAttr.foregroundIntensity = foregroundIntensity
|
||||
case ansiIntensityOff:
|
||||
winAttr.foregroundIntensity = 0
|
||||
case ansiUnderlineOn:
|
||||
winAttr.underscore = underscore
|
||||
case ansiUnderlineOff:
|
||||
winAttr.underscore = 0
|
||||
case ansiBlinkOn:
|
||||
winAttr.backgroundIntensity = backgroundIntensity
|
||||
case ansiBlinkOff:
|
||||
winAttr.backgroundIntensity = 0
|
||||
default:
|
||||
// unknown code
|
||||
}
|
||||
case c.drawType == foreground:
|
||||
winAttr.foregroundColor = c.code
|
||||
case c.drawType == background:
|
||||
winAttr.backgroundColor = c.code
|
||||
}
|
||||
}
|
||||
winTextAttribute := convertWinAttr(winAttr)
|
||||
setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute)
|
||||
|
||||
return changedColor
|
||||
}
|
||||
|
||||
func parseEscapeSequence(command byte, param []byte) parseResult {
|
||||
if defaultAttr == nil {
|
||||
return noConsole
|
||||
}
|
||||
|
||||
switch command {
|
||||
case sgrCode:
|
||||
return changeColor(param)
|
||||
default:
|
||||
return unknown
|
||||
}
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) flushBuffer() (int, error) {
|
||||
return cw.flushTo(cw.w)
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) resetBuffer() (int, error) {
|
||||
return cw.flushTo(nil)
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) flushTo(w io.Writer) (int, error) {
|
||||
var n1, n2 int
|
||||
var err error
|
||||
|
||||
startBytes := cw.paramStartBuf.Bytes()
|
||||
cw.paramStartBuf.Reset()
|
||||
if w != nil {
|
||||
n1, err = cw.w.Write(startBytes)
|
||||
if err != nil {
|
||||
return n1, err
|
||||
}
|
||||
} else {
|
||||
n1 = len(startBytes)
|
||||
}
|
||||
paramBytes := cw.paramBuf.Bytes()
|
||||
cw.paramBuf.Reset()
|
||||
if w != nil {
|
||||
n2, err = cw.w.Write(paramBytes)
|
||||
if err != nil {
|
||||
return n1 + n2, err
|
||||
}
|
||||
} else {
|
||||
n2 = len(paramBytes)
|
||||
}
|
||||
return n1 + n2, nil
|
||||
}
|
||||
|
||||
func isParameterChar(b byte) bool {
|
||||
return ('0' <= b && b <= '9') || b == separatorChar
|
||||
}
|
||||
|
||||
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
||||
r, nw, first, last := 0, 0, 0, 0
|
||||
if cw.mode != DiscardNonColorEscSeq {
|
||||
cw.state = outsideCsiCode
|
||||
cw.resetBuffer()
|
||||
}
|
||||
|
||||
var err error
|
||||
for i, ch := range p {
|
||||
switch cw.state {
|
||||
case outsideCsiCode:
|
||||
if ch == firstCsiChar {
|
||||
cw.paramStartBuf.WriteByte(ch)
|
||||
cw.state = firstCsiCode
|
||||
}
|
||||
case firstCsiCode:
|
||||
switch ch {
|
||||
case firstCsiChar:
|
||||
cw.paramStartBuf.WriteByte(ch)
|
||||
break
|
||||
case secondeCsiChar:
|
||||
cw.paramStartBuf.WriteByte(ch)
|
||||
cw.state = secondCsiCode
|
||||
last = i - 1
|
||||
default:
|
||||
cw.resetBuffer()
|
||||
cw.state = outsideCsiCode
|
||||
}
|
||||
case secondCsiCode:
|
||||
if isParameterChar(ch) {
|
||||
cw.paramBuf.WriteByte(ch)
|
||||
} else {
|
||||
nw, err = cw.w.Write(p[first:last])
|
||||
r += nw
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
first = i + 1
|
||||
result := parseEscapeSequence(ch, cw.paramBuf.Bytes())
|
||||
if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) {
|
||||
cw.paramBuf.WriteByte(ch)
|
||||
nw, err := cw.flushBuffer()
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r += nw
|
||||
} else {
|
||||
n, _ := cw.resetBuffer()
|
||||
// Add one more to the size of the buffer for the last ch
|
||||
r += n + 1
|
||||
}
|
||||
|
||||
cw.state = outsideCsiCode
|
||||
}
|
||||
default:
|
||||
cw.state = outsideCsiCode
|
||||
}
|
||||
}
|
||||
|
||||
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
|
||||
nw, err = cw.w.Write(p[first:])
|
||||
r += nw
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
294
logs/color_windows_test.go
Normal file
294
logs/color_windows_test.go
Normal file
@ -0,0 +1,294 @@
|
||||
// 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var GetConsoleScreenBufferInfo = getConsoleScreenBufferInfo
|
||||
|
||||
func ChangeColor(color uint16) {
|
||||
setConsoleTextAttribute(uintptr(syscall.Stdout), color)
|
||||
}
|
||||
|
||||
func ResetColor() {
|
||||
ChangeColor(uint16(0x0007))
|
||||
}
|
||||
|
||||
func TestWritePlanText(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
expected := "plain text"
|
||||
fmt.Fprintf(w, expected)
|
||||
actual := inner.String()
|
||||
if actual != expected {
|
||||
t.Errorf("Get %q, want %q", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteParseText(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
|
||||
inputTail := "\x1b[0mtail text"
|
||||
expectedTail := "tail text"
|
||||
fmt.Fprintf(w, inputTail)
|
||||
actualTail := inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputHead := "head text\x1b[0m"
|
||||
expectedHead := "head text"
|
||||
fmt.Fprintf(w, inputHead)
|
||||
actualHead := inner.String()
|
||||
inner.Reset()
|
||||
if actualHead != expectedHead {
|
||||
t.Errorf("Get %q, want %q", actualHead, expectedHead)
|
||||
}
|
||||
|
||||
inputBothEnds := "both ends \x1b[0m text"
|
||||
expectedBothEnds := "both ends text"
|
||||
fmt.Fprintf(w, inputBothEnds)
|
||||
actualBothEnds := inner.String()
|
||||
inner.Reset()
|
||||
if actualBothEnds != expectedBothEnds {
|
||||
t.Errorf("Get %q, want %q", actualBothEnds, expectedBothEnds)
|
||||
}
|
||||
|
||||
inputManyEsc := "\x1b\x1b\x1b\x1b[0m many esc"
|
||||
expectedManyEsc := "\x1b\x1b\x1b many esc"
|
||||
fmt.Fprintf(w, inputManyEsc)
|
||||
actualManyEsc := inner.String()
|
||||
inner.Reset()
|
||||
if actualManyEsc != expectedManyEsc {
|
||||
t.Errorf("Get %q, want %q", actualManyEsc, expectedManyEsc)
|
||||
}
|
||||
|
||||
expectedSplit := "split text"
|
||||
for _, ch := range "split \x1b[0m text" {
|
||||
fmt.Fprintf(w, string(ch))
|
||||
}
|
||||
actualSplit := inner.String()
|
||||
inner.Reset()
|
||||
if actualSplit != expectedSplit {
|
||||
t.Errorf("Get %q, want %q", actualSplit, expectedSplit)
|
||||
}
|
||||
}
|
||||
|
||||
type screenNotFoundError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func writeAnsiColor(expectedText, colorCode string) (actualText string, actualAttributes uint16, err error) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
fmt.Fprintf(w, "\x1b[%sm%s", colorCode, expectedText)
|
||||
|
||||
actualText = inner.String()
|
||||
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo != nil {
|
||||
actualAttributes = screenInfo.WAttributes
|
||||
} else {
|
||||
err = &screenNotFoundError{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type testParam struct {
|
||||
text string
|
||||
attributes uint16
|
||||
ansiColor string
|
||||
}
|
||||
|
||||
func TestWriteAnsiColorText(t *testing.T) {
|
||||
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
|
||||
if screenInfo == nil {
|
||||
t.Fatal("Could not get ConsoleScreenBufferInfo")
|
||||
}
|
||||
defer ChangeColor(screenInfo.WAttributes)
|
||||
defaultFgColor := screenInfo.WAttributes & uint16(0x0007)
|
||||
defaultBgColor := screenInfo.WAttributes & uint16(0x0070)
|
||||
defaultFgIntensity := screenInfo.WAttributes & uint16(0x0008)
|
||||
defaultBgIntensity := screenInfo.WAttributes & uint16(0x0080)
|
||||
|
||||
fgParam := []testParam{
|
||||
{"foreground black ", uint16(0x0000 | 0x0000), "30"},
|
||||
{"foreground red ", uint16(0x0004 | 0x0000), "31"},
|
||||
{"foreground green ", uint16(0x0002 | 0x0000), "32"},
|
||||
{"foreground yellow ", uint16(0x0006 | 0x0000), "33"},
|
||||
{"foreground blue ", uint16(0x0001 | 0x0000), "34"},
|
||||
{"foreground magenta", uint16(0x0005 | 0x0000), "35"},
|
||||
{"foreground cyan ", uint16(0x0003 | 0x0000), "36"},
|
||||
{"foreground white ", uint16(0x0007 | 0x0000), "37"},
|
||||
{"foreground default", defaultFgColor | 0x0000, "39"},
|
||||
{"foreground light gray ", uint16(0x0000 | 0x0008 | 0x0000), "90"},
|
||||
{"foreground light red ", uint16(0x0004 | 0x0008 | 0x0000), "91"},
|
||||
{"foreground light green ", uint16(0x0002 | 0x0008 | 0x0000), "92"},
|
||||
{"foreground light yellow ", uint16(0x0006 | 0x0008 | 0x0000), "93"},
|
||||
{"foreground light blue ", uint16(0x0001 | 0x0008 | 0x0000), "94"},
|
||||
{"foreground light magenta", uint16(0x0005 | 0x0008 | 0x0000), "95"},
|
||||
{"foreground light cyan ", uint16(0x0003 | 0x0008 | 0x0000), "96"},
|
||||
{"foreground light white ", uint16(0x0007 | 0x0008 | 0x0000), "97"},
|
||||
}
|
||||
|
||||
bgParam := []testParam{
|
||||
{"background black ", uint16(0x0007 | 0x0000), "40"},
|
||||
{"background red ", uint16(0x0007 | 0x0040), "41"},
|
||||
{"background green ", uint16(0x0007 | 0x0020), "42"},
|
||||
{"background yellow ", uint16(0x0007 | 0x0060), "43"},
|
||||
{"background blue ", uint16(0x0007 | 0x0010), "44"},
|
||||
{"background magenta", uint16(0x0007 | 0x0050), "45"},
|
||||
{"background cyan ", uint16(0x0007 | 0x0030), "46"},
|
||||
{"background white ", uint16(0x0007 | 0x0070), "47"},
|
||||
{"background default", uint16(0x0007) | defaultBgColor, "49"},
|
||||
{"background light gray ", uint16(0x0007 | 0x0000 | 0x0080), "100"},
|
||||
{"background light red ", uint16(0x0007 | 0x0040 | 0x0080), "101"},
|
||||
{"background light green ", uint16(0x0007 | 0x0020 | 0x0080), "102"},
|
||||
{"background light yellow ", uint16(0x0007 | 0x0060 | 0x0080), "103"},
|
||||
{"background light blue ", uint16(0x0007 | 0x0010 | 0x0080), "104"},
|
||||
{"background light magenta", uint16(0x0007 | 0x0050 | 0x0080), "105"},
|
||||
{"background light cyan ", uint16(0x0007 | 0x0030 | 0x0080), "106"},
|
||||
{"background light white ", uint16(0x0007 | 0x0070 | 0x0080), "107"},
|
||||
}
|
||||
|
||||
resetParam := []testParam{
|
||||
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, "0"},
|
||||
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, ""},
|
||||
}
|
||||
|
||||
boldParam := []testParam{
|
||||
{"bold on", uint16(0x0007 | 0x0008), "1"},
|
||||
{"bold off", uint16(0x0007), "21"},
|
||||
}
|
||||
|
||||
underscoreParam := []testParam{
|
||||
{"underscore on", uint16(0x0007 | 0x8000), "4"},
|
||||
{"underscore off", uint16(0x0007), "24"},
|
||||
}
|
||||
|
||||
blinkParam := []testParam{
|
||||
{"blink on", uint16(0x0007 | 0x0080), "5"},
|
||||
{"blink off", uint16(0x0007), "25"},
|
||||
}
|
||||
|
||||
mixedParam := []testParam{
|
||||
{"both black, bold, underline, blink", uint16(0x0000 | 0x0000 | 0x0008 | 0x8000 | 0x0080), "30;40;1;4;5"},
|
||||
{"both red, bold, underline, blink", uint16(0x0004 | 0x0040 | 0x0008 | 0x8000 | 0x0080), "31;41;1;4;5"},
|
||||
{"both green, bold, underline, blink", uint16(0x0002 | 0x0020 | 0x0008 | 0x8000 | 0x0080), "32;42;1;4;5"},
|
||||
{"both yellow, bold, underline, blink", uint16(0x0006 | 0x0060 | 0x0008 | 0x8000 | 0x0080), "33;43;1;4;5"},
|
||||
{"both blue, bold, underline, blink", uint16(0x0001 | 0x0010 | 0x0008 | 0x8000 | 0x0080), "34;44;1;4;5"},
|
||||
{"both magenta, bold, underline, blink", uint16(0x0005 | 0x0050 | 0x0008 | 0x8000 | 0x0080), "35;45;1;4;5"},
|
||||
{"both cyan, bold, underline, blink", uint16(0x0003 | 0x0030 | 0x0008 | 0x8000 | 0x0080), "36;46;1;4;5"},
|
||||
{"both white, bold, underline, blink", uint16(0x0007 | 0x0070 | 0x0008 | 0x8000 | 0x0080), "37;47;1;4;5"},
|
||||
{"both default, bold, underline, blink", uint16(defaultFgColor | defaultBgColor | 0x0008 | 0x8000 | 0x0080), "39;49;1;4;5"},
|
||||
}
|
||||
|
||||
assertTextAttribute := func(expectedText string, expectedAttributes uint16, ansiColor string) {
|
||||
actualText, actualAttributes, err := writeAnsiColor(expectedText, ansiColor)
|
||||
if actualText != expectedText {
|
||||
t.Errorf("Get %q, want %q", actualText, expectedText)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("Could not get ConsoleScreenBufferInfo")
|
||||
}
|
||||
if actualAttributes != expectedAttributes {
|
||||
t.Errorf("Text: %q, Get 0x%04x, want 0x%04x", expectedText, actualAttributes, expectedAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range fgParam {
|
||||
ResetColor()
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
for _, v := range bgParam {
|
||||
ChangeColor(uint16(0x0070 | 0x0007))
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
for _, v := range resetParam {
|
||||
ChangeColor(uint16(0x0000 | 0x0070 | 0x0008))
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
ResetColor()
|
||||
for _, v := range boldParam {
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
ResetColor()
|
||||
for _, v := range underscoreParam {
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
ResetColor()
|
||||
for _, v := range blinkParam {
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
|
||||
for _, v := range mixedParam {
|
||||
ResetColor()
|
||||
assertTextAttribute(v.text, v.attributes, v.ansiColor)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreUnknownSequences(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewModeAnsiColorWriter(inner, OutputNonColorEscSeq)
|
||||
|
||||
inputText := "\x1b[=decpath mode"
|
||||
expectedTail := inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail := inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputText = "\x1b[=tailing esc and bracket\x1b["
|
||||
expectedTail = inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail = inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputText = "\x1b[?tailing esc\x1b"
|
||||
expectedTail = inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail = inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
|
||||
inputText = "\x1b[1h;3punended color code invalid\x1b3"
|
||||
expectedTail = inputText
|
||||
fmt.Fprintf(w, inputText)
|
||||
actualTail = inner.String()
|
||||
inner.Reset()
|
||||
if actualTail != expectedTail {
|
||||
t.Errorf("Get %q, want %q", actualTail, expectedTail)
|
||||
}
|
||||
}
|
@ -113,5 +113,5 @@ func (c *connWriter) needToConnectOnMsg() bool {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("conn", NewConn)
|
||||
Register(AdapterConn, NewConn)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func NewConsole() Logger {
|
||||
cw := &consoleWriter{
|
||||
lg: newLogWriter(os.Stdout),
|
||||
Level: LevelDebug,
|
||||
Colorful: true,
|
||||
Colorful: runtime.GOOS != "windows",
|
||||
}
|
||||
return cw
|
||||
}
|
||||
@ -97,5 +97,5 @@ func (c *consoleWriter) Flush() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("console", NewConsole)
|
||||
Register(AdapterConsole, NewConsole)
|
||||
}
|
||||
|
@ -76,5 +76,5 @@ func (el *esLogger) Flush() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
logs.Register("es", NewES)
|
||||
logs.Register(logs.AdapterEs, NewES)
|
||||
}
|
||||
|
94
logs/file.go
94
logs/file.go
@ -22,6 +22,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -30,7 +31,7 @@ import (
|
||||
// fileLogWriter implements LoggerInterface.
|
||||
// It writes messages by lines limit, file size limit, or time frequency.
|
||||
type fileLogWriter struct {
|
||||
sync.Mutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
|
||||
// The opened file
|
||||
Filename string `json:"filename"`
|
||||
fileWriter *os.File
|
||||
@ -47,12 +48,13 @@ type fileLogWriter struct {
|
||||
Daily bool `json:"daily"`
|
||||
MaxDays int64 `json:"maxdays"`
|
||||
dailyOpenDate int
|
||||
dailyOpenTime time.Time
|
||||
|
||||
Rotate bool `json:"rotate"`
|
||||
|
||||
Level int `json:"level"`
|
||||
|
||||
Perm os.FileMode `json:"perm"`
|
||||
Perm string `json:"perm"`
|
||||
|
||||
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
|
||||
}
|
||||
@ -60,14 +62,11 @@ type fileLogWriter struct {
|
||||
// newFileWriter create a FileLogWriter returning as LoggerInterface.
|
||||
func newFileWriter() Logger {
|
||||
w := &fileLogWriter{
|
||||
Filename: "",
|
||||
MaxLines: 1000000,
|
||||
MaxSize: 1 << 28, //256 MB
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: 0660,
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
}
|
||||
return w
|
||||
}
|
||||
@ -77,11 +76,11 @@ func newFileWriter() Logger {
|
||||
// {
|
||||
// "filename":"logs/beego.log",
|
||||
// "maxLines":10000,
|
||||
// "maxsize":1<<30,
|
||||
// "maxsize":1024,
|
||||
// "daily":true,
|
||||
// "maxDays":15,
|
||||
// "rotate":true,
|
||||
// "perm":0600
|
||||
// "perm":"0600"
|
||||
// }
|
||||
func (w *fileLogWriter) Init(jsonConfig string) error {
|
||||
err := json.Unmarshal([]byte(jsonConfig), w)
|
||||
@ -128,7 +127,9 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
h, d := formatTimeHeader(when)
|
||||
msg = string(h) + msg + "\n"
|
||||
if w.Rotate {
|
||||
w.RLock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
w.RUnlock()
|
||||
w.Lock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
@ -136,6 +137,8 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
} else {
|
||||
w.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,7 +154,15 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
|
||||
func (w *fileLogWriter) createLogFile() (*os.File, error) {
|
||||
// Open the log file
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, w.Perm)
|
||||
perm, err := strconv.ParseInt(w.Perm, 8, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
|
||||
if err == nil {
|
||||
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
|
||||
os.Chmod(w.Filename, os.FileMode(perm))
|
||||
}
|
||||
return fd, err
|
||||
}
|
||||
|
||||
@ -162,8 +173,12 @@ func (w *fileLogWriter) initFd() error {
|
||||
return fmt.Errorf("get stat err: %s\n", err)
|
||||
}
|
||||
w.maxSizeCurSize = int(fInfo.Size())
|
||||
w.dailyOpenDate = time.Now().Day()
|
||||
w.dailyOpenTime = time.Now()
|
||||
w.dailyOpenDate = w.dailyOpenTime.Day()
|
||||
w.maxLinesCurLines = 0
|
||||
if w.Daily {
|
||||
go w.dailyRotate(w.dailyOpenTime)
|
||||
}
|
||||
if fInfo.Size() > 0 {
|
||||
count, err := w.lines()
|
||||
if err != nil {
|
||||
@ -174,6 +189,22 @@ func (w *fileLogWriter) initFd() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
|
||||
y, m, d := openTime.Add(24 * time.Hour).Date()
|
||||
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
|
||||
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
|
||||
select {
|
||||
case <-tm.C:
|
||||
w.Lock()
|
||||
if w.needRotate(0, time.Now().Day()) {
|
||||
if err := w.doRotate(time.Now()); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *fileLogWriter) lines() (int, error) {
|
||||
fd, err := os.Open(w.Filename)
|
||||
if err != nil {
|
||||
@ -204,22 +235,29 @@ func (w *fileLogWriter) lines() (int, error) {
|
||||
// DoRotate means it need to write file in new file.
|
||||
// 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
|
||||
}
|
||||
// file exists
|
||||
// Find the next available number
|
||||
num := 1
|
||||
fName := ""
|
||||
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
//even if the file is not exist or other ,we should RESTART the logger
|
||||
goto RESTART_LOGGER
|
||||
}
|
||||
|
||||
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)
|
||||
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
if err == nil {
|
||||
@ -231,16 +269,18 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
|
||||
// Rename the file to its new found name
|
||||
// even if occurs error,we MUST guarantee to restart new logger
|
||||
renameErr := os.Rename(w.Filename, fName)
|
||||
err = os.Rename(w.Filename, fName)
|
||||
// re-start logger
|
||||
RESTART_LOGGER:
|
||||
|
||||
startLoggerErr := w.startLogger()
|
||||
go w.deleteOldLog()
|
||||
|
||||
if startLoggerErr != nil {
|
||||
return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
|
||||
}
|
||||
if renameErr != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", renameErr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Rotate: %s\n", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -255,8 +295,12 @@ func (w *fileLogWriter) deleteOldLog() {
|
||||
}
|
||||
}()
|
||||
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) {
|
||||
if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) &&
|
||||
if info == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
|
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
|
||||
strings.HasSuffix(filepath.Base(path), w.suffix) {
|
||||
os.Remove(path)
|
||||
}
|
||||
@ -278,5 +322,5 @@ func (w *fileLogWriter) Flush() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("file", newFileWriter)
|
||||
Register(AdapterFile, newFileWriter)
|
||||
}
|
||||
|
@ -17,12 +17,35 @@ package logs
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFilePerm(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
// use 0666 as test perm cause the default umask is 022
|
||||
log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`)
|
||||
log.Debug("debug")
|
||||
log.Informational("info")
|
||||
log.Notice("notice")
|
||||
log.Warning("warning")
|
||||
log.Error("error")
|
||||
log.Alert("alert")
|
||||
log.Critical("critical")
|
||||
log.Emergency("emergency")
|
||||
file, err := os.Stat("test.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if file.Mode() != 0666 {
|
||||
t.Fatal("unexpected log file permission")
|
||||
}
|
||||
os.Remove("test.log")
|
||||
}
|
||||
|
||||
func TestFile1(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test.log"}`)
|
||||
@ -89,7 +112,7 @@ func TestFile2(t *testing.T) {
|
||||
os.Remove("test2.log")
|
||||
}
|
||||
|
||||
func TestFileRotate(t *testing.T) {
|
||||
func TestFileRotate_01(t *testing.T) {
|
||||
log := NewLogger(10000)
|
||||
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
|
||||
log.Debug("debug")
|
||||
@ -110,6 +133,90 @@ func TestFileRotate(t *testing.T) {
|
||||
os.Remove("test3.log")
|
||||
}
|
||||
|
||||
func TestFileRotate_02(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
testFileRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileRotate_03(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func TestFileRotate_04(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
}
|
||||
|
||||
func TestFileRotate_05(t *testing.T) {
|
||||
fn1 := "rotate_day.log"
|
||||
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
|
||||
os.Create(fn)
|
||||
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
|
||||
testFileDailyRotate(t, fn1, fn2)
|
||||
os.Remove(fn)
|
||||
}
|
||||
|
||||
func testFileRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
}
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
|
||||
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
|
||||
fw := &fileLogWriter{
|
||||
Daily: true,
|
||||
MaxDays: 7,
|
||||
Rotate: true,
|
||||
Level: LevelTrace,
|
||||
Perm: "0660",
|
||||
}
|
||||
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
|
||||
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
|
||||
fw.dailyOpenDate = fw.dailyOpenTime.Day()
|
||||
today, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), fw.dailyOpenTime.Location())
|
||||
today = today.Add(-1 * time.Second)
|
||||
fw.dailyRotate(today)
|
||||
for _, file := range []string{fn1, fn2} {
|
||||
_, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
if len(content) > 0 {
|
||||
t.FailNow()
|
||||
}
|
||||
os.Remove(file)
|
||||
}
|
||||
fw.Destroy()
|
||||
}
|
||||
|
||||
func exists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
|
329
logs/log.go
329
logs/log.go
@ -35,10 +35,12 @@ package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -55,16 +57,28 @@ const (
|
||||
LevelDebug
|
||||
)
|
||||
|
||||
// Legacy loglevel constants to ensure backwards compatibility.
|
||||
//
|
||||
// Deprecated: will be removed in 1.5.0.
|
||||
// levelLogLogger is defined to implement log.Logger
|
||||
// the real log level will be LevelEmergency
|
||||
const levelLoggerImpl = -1
|
||||
|
||||
// Name for adapter with beego official support
|
||||
const (
|
||||
AdapterConsole = "console"
|
||||
AdapterFile = "file"
|
||||
AdapterMultiFile = "multifile"
|
||||
AdapterMail = "stmp"
|
||||
AdapterConn = "conn"
|
||||
AdapterEs = "es"
|
||||
)
|
||||
|
||||
// Legacy log level constants to ensure backwards compatibility.
|
||||
const (
|
||||
LevelInfo = LevelInformational
|
||||
LevelTrace = LevelDebug
|
||||
LevelWarn = LevelWarning
|
||||
)
|
||||
|
||||
type loggerType func() Logger
|
||||
type newLoggerFunc func() Logger
|
||||
|
||||
// Logger defines the behavior of a log provider.
|
||||
type Logger interface {
|
||||
@ -74,12 +88,13 @@ type Logger interface {
|
||||
Flush()
|
||||
}
|
||||
|
||||
var adapters = make(map[string]loggerType)
|
||||
var adapters = make(map[string]newLoggerFunc)
|
||||
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
|
||||
|
||||
// Register makes a log provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
func Register(name string, log loggerType) {
|
||||
func Register(name string, log newLoggerFunc) {
|
||||
if log == nil {
|
||||
panic("logs: Register provide is nil")
|
||||
}
|
||||
@ -94,15 +109,19 @@ func Register(name string, log loggerType) {
|
||||
type BeeLogger struct {
|
||||
lock sync.Mutex
|
||||
level int
|
||||
init bool
|
||||
enableFuncCallDepth bool
|
||||
loggerFuncCallDepth int
|
||||
asynchronous bool
|
||||
msgChanLen int64
|
||||
msgChan chan *logMsg
|
||||
signalChan chan string
|
||||
wg sync.WaitGroup
|
||||
outputs []*nameLogger
|
||||
}
|
||||
|
||||
const defaultAsyncMsgLen = 1e3
|
||||
|
||||
type nameLogger struct {
|
||||
Logger
|
||||
name string
|
||||
@ -119,18 +138,31 @@ var logMsgPool *sync.Pool
|
||||
// NewLogger returns a new BeeLogger.
|
||||
// channelLen means the number of messages in chan(used where asynchronous is true).
|
||||
// if the buffering chan is full, logger adapters write to file or other way.
|
||||
func NewLogger(channelLen int64) *BeeLogger {
|
||||
func NewLogger(channelLens ...int64) *BeeLogger {
|
||||
bl := new(BeeLogger)
|
||||
bl.level = LevelDebug
|
||||
bl.loggerFuncCallDepth = 2
|
||||
bl.msgChan = make(chan *logMsg, channelLen)
|
||||
bl.msgChanLen = append(channelLens, 0)[0]
|
||||
if bl.msgChanLen <= 0 {
|
||||
bl.msgChanLen = defaultAsyncMsgLen
|
||||
}
|
||||
bl.signalChan = make(chan string, 1)
|
||||
bl.setLogger(AdapterConsole)
|
||||
return bl
|
||||
}
|
||||
|
||||
// Async set the log to asynchronous and start the goroutine
|
||||
func (bl *BeeLogger) Async() *BeeLogger {
|
||||
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if bl.asynchronous {
|
||||
return bl
|
||||
}
|
||||
bl.asynchronous = true
|
||||
if len(msgLen) > 0 && msgLen[0] > 0 {
|
||||
bl.msgChanLen = msgLen[0]
|
||||
}
|
||||
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
|
||||
logMsgPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &logMsg{}
|
||||
@ -143,16 +175,14 @@ func (bl *BeeLogger) Async() *BeeLogger {
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
func (bl *BeeLogger) SetLogger(adapterName string, config string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
|
||||
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
|
||||
config := append(configs, "{}")[0]
|
||||
for _, l := range bl.outputs {
|
||||
if l.name == adapterName {
|
||||
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log, ok := adapters[adapterName]
|
||||
if !ok {
|
||||
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
|
||||
@ -168,6 +198,18 @@ func (bl *BeeLogger) SetLogger(adapterName string, config string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLogger provides a given logger adapter into BeeLogger with config string.
|
||||
// config need to be correct JSON as string: {"interval":360}.
|
||||
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
|
||||
bl.lock.Lock()
|
||||
defer bl.lock.Unlock()
|
||||
if !bl.init {
|
||||
bl.outputs = []*nameLogger{}
|
||||
bl.init = true
|
||||
}
|
||||
return bl.setLogger(adapterName, configs...)
|
||||
}
|
||||
|
||||
// DelLogger remove a logger adapter in BeeLogger.
|
||||
func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||
bl.lock.Lock()
|
||||
@ -196,7 +238,32 @@ func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
// writeMsg will always add a '\n' character
|
||||
if p[len(p)-1] == '\n' {
|
||||
p = p[0 : len(p)-1]
|
||||
}
|
||||
// set levelLoggerImpl to ensure all log message will be write out
|
||||
err = bl.writeMsg(levelLoggerImpl, string(p))
|
||||
if err == nil {
|
||||
return len(p), err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
|
||||
if !bl.init {
|
||||
bl.lock.Lock()
|
||||
bl.setLogger(AdapterConsole)
|
||||
bl.lock.Unlock()
|
||||
}
|
||||
|
||||
if len(v) > 0 {
|
||||
msg = fmt.Sprintf(msg, v...)
|
||||
}
|
||||
when := time.Now()
|
||||
if bl.enableFuncCallDepth {
|
||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||
@ -205,8 +272,17 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
line = 0
|
||||
}
|
||||
_, filename := path.Split(file)
|
||||
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "]" + msg
|
||||
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg
|
||||
}
|
||||
|
||||
//set level info in front of filename info
|
||||
if logLevel == levelLoggerImpl {
|
||||
// set to emergency to ensure all log will be print out correctly
|
||||
logLevel = LevelEmergency
|
||||
} else {
|
||||
msg = levelPrefix[logLevel] + msg
|
||||
}
|
||||
|
||||
if bl.asynchronous {
|
||||
lm := logMsgPool.Get().(*logMsg)
|
||||
lm.level = logLevel
|
||||
@ -273,8 +349,7 @@ func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
|
||||
if LevelEmergency > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[M] "+format, v...)
|
||||
bl.writeMsg(LevelEmergency, msg)
|
||||
bl.writeMsg(LevelEmergency, format, v...)
|
||||
}
|
||||
|
||||
// Alert Log ALERT level message.
|
||||
@ -282,8 +357,7 @@ func (bl *BeeLogger) Alert(format string, v ...interface{}) {
|
||||
if LevelAlert > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[A] "+format, v...)
|
||||
bl.writeMsg(LevelAlert, msg)
|
||||
bl.writeMsg(LevelAlert, format, v...)
|
||||
}
|
||||
|
||||
// Critical Log CRITICAL level message.
|
||||
@ -291,8 +365,7 @@ func (bl *BeeLogger) Critical(format string, v ...interface{}) {
|
||||
if LevelCritical > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[C] "+format, v...)
|
||||
bl.writeMsg(LevelCritical, msg)
|
||||
bl.writeMsg(LevelCritical, format, v...)
|
||||
}
|
||||
|
||||
// Error Log ERROR level message.
|
||||
@ -300,17 +373,12 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) {
|
||||
if LevelError > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[E] "+format, v...)
|
||||
bl.writeMsg(LevelError, msg)
|
||||
bl.writeMsg(LevelError, format, v...)
|
||||
}
|
||||
|
||||
// Warning Log WARNING level message.
|
||||
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
|
||||
if LevelWarning > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writeMsg(LevelWarning, msg)
|
||||
bl.Warn(format, v...)
|
||||
}
|
||||
|
||||
// Notice Log NOTICE level message.
|
||||
@ -318,17 +386,12 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) {
|
||||
if LevelNotice > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[N] "+format, v...)
|
||||
bl.writeMsg(LevelNotice, msg)
|
||||
bl.writeMsg(LevelNotice, format, v...)
|
||||
}
|
||||
|
||||
// Informational Log INFORMATIONAL level message.
|
||||
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
|
||||
if LevelInformational > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writeMsg(LevelInformational, msg)
|
||||
bl.Info(format, v...)
|
||||
}
|
||||
|
||||
// Debug Log DEBUG level message.
|
||||
@ -336,38 +399,31 @@ func (bl *BeeLogger) Debug(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writeMsg(LevelDebug, msg)
|
||||
bl.writeMsg(LevelDebug, format, v...)
|
||||
}
|
||||
|
||||
// Warn Log WARN level message.
|
||||
// compatibility alias for Warning()
|
||||
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
|
||||
if LevelWarning > bl.level {
|
||||
if LevelWarn > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[W] "+format, v...)
|
||||
bl.writeMsg(LevelWarning, msg)
|
||||
bl.writeMsg(LevelWarn, format, v...)
|
||||
}
|
||||
|
||||
// Info Log INFO level message.
|
||||
// compatibility alias for Informational()
|
||||
func (bl *BeeLogger) Info(format string, v ...interface{}) {
|
||||
if LevelInformational > bl.level {
|
||||
if LevelInfo > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[I] "+format, v...)
|
||||
bl.writeMsg(LevelInformational, msg)
|
||||
bl.writeMsg(LevelInfo, format, v...)
|
||||
}
|
||||
|
||||
// Trace Log TRACE level message.
|
||||
// compatibility alias for Debug()
|
||||
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
|
||||
if LevelDebug > bl.level {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf("[D] "+format, v...)
|
||||
bl.writeMsg(LevelDebug, msg)
|
||||
bl.Debug(format, v...)
|
||||
}
|
||||
|
||||
// Flush flush all chan data.
|
||||
@ -386,6 +442,7 @@ func (bl *BeeLogger) Close() {
|
||||
if bl.asynchronous {
|
||||
bl.signalChan <- "close"
|
||||
bl.wg.Wait()
|
||||
close(bl.msgChan)
|
||||
} else {
|
||||
bl.flush()
|
||||
for _, l := range bl.outputs {
|
||||
@ -393,7 +450,6 @@ func (bl *BeeLogger) Close() {
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
close(bl.msgChan)
|
||||
close(bl.signalChan)
|
||||
}
|
||||
|
||||
@ -407,16 +463,175 @@ func (bl *BeeLogger) Reset() {
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) flush() {
|
||||
for {
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
if bl.asynchronous {
|
||||
for {
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
for _, l := range bl.outputs {
|
||||
l.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// beeLogger references the used application logger.
|
||||
var beeLogger *BeeLogger = NewLogger()
|
||||
|
||||
// GetLogger returns the default BeeLogger
|
||||
func GetBeeLogger() *BeeLogger {
|
||||
return beeLogger
|
||||
}
|
||||
|
||||
var beeLoggerMap = struct {
|
||||
sync.RWMutex
|
||||
logs map[string]*log.Logger
|
||||
}{
|
||||
logs: map[string]*log.Logger{},
|
||||
}
|
||||
|
||||
// GetLogger returns the default BeeLogger
|
||||
func GetLogger(prefixes ...string) *log.Logger {
|
||||
prefix := append(prefixes, "")[0]
|
||||
if prefix != "" {
|
||||
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
|
||||
}
|
||||
beeLoggerMap.RLock()
|
||||
l, ok := beeLoggerMap.logs[prefix]
|
||||
if ok {
|
||||
beeLoggerMap.RUnlock()
|
||||
return l
|
||||
}
|
||||
beeLoggerMap.RUnlock()
|
||||
beeLoggerMap.Lock()
|
||||
defer beeLoggerMap.Unlock()
|
||||
l, ok = beeLoggerMap.logs[prefix]
|
||||
if !ok {
|
||||
l = log.New(beeLogger, prefix, 0)
|
||||
beeLoggerMap.logs[prefix] = l
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Reset will remove all the adapter
|
||||
func Reset() {
|
||||
beeLogger.Reset()
|
||||
}
|
||||
|
||||
func Async(msgLen ...int64) *BeeLogger {
|
||||
return beeLogger.Async(msgLen...)
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level used by the simple logger.
|
||||
func SetLevel(l int) {
|
||||
beeLogger.SetLevel(l)
|
||||
}
|
||||
|
||||
// EnableFuncCallDepth enable log funcCallDepth
|
||||
func EnableFuncCallDepth(b bool) {
|
||||
beeLogger.enableFuncCallDepth = b
|
||||
}
|
||||
|
||||
// SetLogFuncCall set the CallDepth, default is 4
|
||||
func SetLogFuncCall(b bool) {
|
||||
beeLogger.EnableFuncCallDepth(b)
|
||||
beeLogger.SetLogFuncCallDepth(4)
|
||||
}
|
||||
|
||||
// SetLogFuncCallDepth set log funcCallDepth
|
||||
func SetLogFuncCallDepth(d int) {
|
||||
beeLogger.loggerFuncCallDepth = d
|
||||
}
|
||||
|
||||
// SetLogger sets a new logger.
|
||||
func SetLogger(adapter string, config ...string) error {
|
||||
err := beeLogger.SetLogger(adapter, config...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Emergency logs a message at emergency level.
|
||||
func Emergency(f interface{}, v ...interface{}) {
|
||||
beeLogger.Emergency(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Alert logs a message at alert level.
|
||||
func Alert(f interface{}, v ...interface{}) {
|
||||
beeLogger.Alert(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Critical logs a message at critical level.
|
||||
func Critical(f interface{}, v ...interface{}) {
|
||||
beeLogger.Critical(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Error logs a message at error level.
|
||||
func Error(f interface{}, v ...interface{}) {
|
||||
beeLogger.Error(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Warning logs a message at warning level.
|
||||
func Warning(f interface{}, v ...interface{}) {
|
||||
beeLogger.Warn(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Warn compatibility alias for Warning()
|
||||
func Warn(f interface{}, v ...interface{}) {
|
||||
beeLogger.Warn(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Notice logs a message at notice level.
|
||||
func Notice(f interface{}, v ...interface{}) {
|
||||
beeLogger.Notice(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Informational logs a message at info level.
|
||||
func Informational(f interface{}, v ...interface{}) {
|
||||
beeLogger.Info(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Info compatibility alias for Warning()
|
||||
func Info(f interface{}, v ...interface{}) {
|
||||
beeLogger.Info(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Debug logs a message at debug level.
|
||||
func Debug(f interface{}, v ...interface{}) {
|
||||
beeLogger.Debug(formatLog(f, v...))
|
||||
}
|
||||
|
||||
// Trace logs a message at trace level.
|
||||
// compatibility alias for Warning()
|
||||
func Trace(f interface{}, v ...interface{}) {
|
||||
beeLogger.Trace(formatLog(f, v...))
|
||||
}
|
||||
|
||||
func formatLog(f interface{}, v ...interface{}) string {
|
||||
var msg string
|
||||
switch f.(type) {
|
||||
case string:
|
||||
msg = f.(string)
|
||||
if len(v) == 0 {
|
||||
return msg
|
||||
}
|
||||
if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
|
||||
//format string
|
||||
} else {
|
||||
//do not contain format char
|
||||
msg += strings.Repeat(" %v", len(v))
|
||||
}
|
||||
default:
|
||||
msg = fmt.Sprint(f)
|
||||
if len(v) == 0 {
|
||||
return msg
|
||||
}
|
||||
msg += strings.Repeat(" %v", len(v))
|
||||
}
|
||||
return fmt.Sprintf(msg, v...)
|
||||
}
|
||||
|
166
logs/logger.go
166
logs/logger.go
@ -12,11 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@ -37,44 +38,151 @@ func (lg *logWriter) println(when time.Time, msg string) {
|
||||
lg.Unlock()
|
||||
}
|
||||
|
||||
type outputMode int
|
||||
|
||||
// DiscardNonColorEscSeq supports the divided color escape sequence.
|
||||
// But non-color escape sequence is not output.
|
||||
// Please use the OutputNonColorEscSeq If you want to output a non-color
|
||||
// escape sequences such as ncurses. However, it does not support the divided
|
||||
// color escape sequence.
|
||||
const (
|
||||
_ outputMode = iota
|
||||
DiscardNonColorEscSeq
|
||||
OutputNonColorEscSeq
|
||||
)
|
||||
|
||||
// NewAnsiColorWriter creates and initializes a new ansiColorWriter
|
||||
// using io.Writer w as its initial contents.
|
||||
// In the console of Windows, which change the foreground and background
|
||||
// colors of the text by the escape sequence.
|
||||
// In the console of other systems, which writes to w all text.
|
||||
func NewAnsiColorWriter(w io.Writer) io.Writer {
|
||||
return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq)
|
||||
}
|
||||
|
||||
// NewModeAnsiColorWriter create and initializes a new ansiColorWriter
|
||||
// by specifying the outputMode.
|
||||
func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer {
|
||||
if _, ok := w.(*ansiColorWriter); !ok {
|
||||
return &ansiColorWriter{
|
||||
w: w,
|
||||
mode: mode,
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
const (
|
||||
y1 = `0123456789`
|
||||
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
|
||||
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
|
||||
mo1 = `000000000111`
|
||||
mo2 = `123456789012`
|
||||
d1 = `0000000001111111111222222222233`
|
||||
d2 = `1234567890123456789012345678901`
|
||||
h1 = `000000000011111111112222`
|
||||
h2 = `012345678901234567890123`
|
||||
mi1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||
mi2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||
s1 = `000000000011111111112222222222333333333344444444445555555555`
|
||||
s2 = `012345678901234567890123456789012345678901234567890123456789`
|
||||
)
|
||||
|
||||
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
|
||||
//len("2006/01/02 15:04:05 ")==20
|
||||
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[0] = y1[y/1000%10]
|
||||
buf[1] = y2[y/100]
|
||||
buf[2] = y3[y-y/100*100]
|
||||
buf[3] = y4[y-y/100*100]
|
||||
buf[4] = '/'
|
||||
if mo > 9 {
|
||||
buf[5] = '1'
|
||||
buf[6] = byte('0' + mo - 9)
|
||||
} else {
|
||||
buf[5] = '0'
|
||||
buf[6] = byte('0' + mo)
|
||||
}
|
||||
buf[5] = mo1[mo-1]
|
||||
buf[6] = mo2[mo-1]
|
||||
buf[7] = '/'
|
||||
t = d / 10
|
||||
buf[8] = byte('0' + t)
|
||||
buf[9] = byte('0' + d - t*10)
|
||||
buf[8] = d1[d-1]
|
||||
buf[9] = d2[d-1]
|
||||
buf[10] = ' '
|
||||
t = h / 10
|
||||
buf[11] = byte('0' + t)
|
||||
buf[12] = byte('0' + h - t*10)
|
||||
buf[11] = h1[h]
|
||||
buf[12] = h2[h]
|
||||
buf[13] = ':'
|
||||
t = mi / 10
|
||||
buf[14] = byte('0' + t)
|
||||
buf[15] = byte('0' + mi - t*10)
|
||||
buf[14] = mi1[mi]
|
||||
buf[15] = mi2[mi]
|
||||
buf[16] = ':'
|
||||
t = s / 10
|
||||
buf[17] = byte('0' + t)
|
||||
buf[18] = byte('0' + s - t*10)
|
||||
buf[17] = s1[s]
|
||||
buf[18] = s2[s]
|
||||
buf[19] = ' '
|
||||
|
||||
return buf[0:], d
|
||||
}
|
||||
|
||||
var (
|
||||
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
|
||||
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
|
||||
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
|
||||
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
|
||||
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
|
||||
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
|
||||
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
|
||||
|
||||
w32Green = string([]byte{27, 91, 52, 50, 109})
|
||||
w32White = string([]byte{27, 91, 52, 55, 109})
|
||||
w32Yellow = string([]byte{27, 91, 52, 51, 109})
|
||||
w32Red = string([]byte{27, 91, 52, 49, 109})
|
||||
w32Blue = string([]byte{27, 91, 52, 52, 109})
|
||||
w32Magenta = string([]byte{27, 91, 52, 53, 109})
|
||||
w32Cyan = string([]byte{27, 91, 52, 54, 109})
|
||||
|
||||
reset = string([]byte{27, 91, 48, 109})
|
||||
)
|
||||
|
||||
func ColorByStatus(cond bool, code int) string {
|
||||
switch {
|
||||
case code >= 200 && code < 300:
|
||||
return map[bool]string{true: green, false: w32Green}[cond]
|
||||
case code >= 300 && code < 400:
|
||||
return map[bool]string{true: white, false: w32White}[cond]
|
||||
case code >= 400 && code < 500:
|
||||
return map[bool]string{true: yellow, false: w32Yellow}[cond]
|
||||
default:
|
||||
return map[bool]string{true: red, false: w32Red}[cond]
|
||||
}
|
||||
}
|
||||
|
||||
func ColorByMethod(cond bool, method string) string {
|
||||
switch method {
|
||||
case "GET":
|
||||
return map[bool]string{true: blue, false: w32Blue}[cond]
|
||||
case "POST":
|
||||
return map[bool]string{true: cyan, false: w32Cyan}[cond]
|
||||
case "PUT":
|
||||
return map[bool]string{true: yellow, false: w32Yellow}[cond]
|
||||
case "DELETE":
|
||||
return map[bool]string{true: red, false: w32Red}[cond]
|
||||
case "PATCH":
|
||||
return map[bool]string{true: green, false: w32Green}[cond]
|
||||
case "HEAD":
|
||||
return map[bool]string{true: magenta, false: w32Magenta}[cond]
|
||||
case "OPTIONS":
|
||||
return map[bool]string{true: white, false: w32White}[cond]
|
||||
default:
|
||||
return reset
|
||||
}
|
||||
}
|
||||
|
||||
// Guard Mutex to guarantee atomicity of W32Debug(string) function
|
||||
var mu sync.Mutex
|
||||
|
||||
// Helper method to output colored logs in Windows terminals
|
||||
func W32Debug(msg string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
current := time.Now()
|
||||
w := NewAnsiColorWriter(os.Stdout)
|
||||
|
||||
fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg)
|
||||
}
|
||||
|
75
logs/logger_test.go
Normal file
75
logs/logger_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// 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 logs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFormatHeader_0(t *testing.T) {
|
||||
tm := time.Now()
|
||||
if tm.Year() >= 2100 {
|
||||
t.FailNow()
|
||||
}
|
||||
dur := time.Second
|
||||
for {
|
||||
if tm.Year() >= 2100 {
|
||||
break
|
||||
}
|
||||
h, _ := formatTimeHeader(tm)
|
||||
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
|
||||
t.Log(tm)
|
||||
t.FailNow()
|
||||
}
|
||||
tm = tm.Add(dur)
|
||||
dur *= 2
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatHeader_1(t *testing.T) {
|
||||
tm := time.Now()
|
||||
year := tm.Year()
|
||||
dur := time.Second
|
||||
for {
|
||||
if tm.Year() >= year+1 {
|
||||
break
|
||||
}
|
||||
h, _ := formatTimeHeader(tm)
|
||||
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
|
||||
t.Log(tm)
|
||||
t.FailNow()
|
||||
}
|
||||
tm = tm.Add(dur)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAnsiColor1(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w := NewAnsiColorWriter(inner)
|
||||
if w == inner {
|
||||
t.Errorf("Get %#v, want %#v", w, inner)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewAnsiColor2(t *testing.T) {
|
||||
inner := bytes.NewBufferString("")
|
||||
w1 := NewAnsiColorWriter(inner)
|
||||
w2 := NewAnsiColorWriter(w1)
|
||||
if w1 != w2 {
|
||||
t.Errorf("Get %#v, want %#v", w1, w2)
|
||||
}
|
||||
}
|
@ -112,5 +112,5 @@ func newFilesWriter() Logger {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("multifile", newFilesWriter)
|
||||
Register(AdapterMultiFile, newFilesWriter)
|
||||
}
|
||||
|
@ -156,5 +156,5 @@ func (s *SMTPWriter) Destroy() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("smtp", newSMTPWriter)
|
||||
Register(AdapterMail, newSMTPWriter)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/orm"
|
||||
)
|
||||
|
||||
@ -90,7 +90,7 @@ func (m *Migration) Reset() {
|
||||
func (m *Migration) Exec(name, status string) error {
|
||||
o := orm.NewOrm()
|
||||
for _, s := range m.sqls {
|
||||
beego.Info("exec sql:", s)
|
||||
logs.Info("exec sql:", s)
|
||||
r := o.Raw(s)
|
||||
_, err := r.Exec()
|
||||
if err != nil {
|
||||
@ -144,20 +144,20 @@ func Upgrade(lasttime int64) error {
|
||||
i := 0
|
||||
for _, v := range sm {
|
||||
if v.created > lasttime {
|
||||
beego.Info("start upgrade", v.name)
|
||||
logs.Info("start upgrade", v.name)
|
||||
v.m.Reset()
|
||||
v.m.Up()
|
||||
err := v.m.Exec(v.name, "up")
|
||||
if err != nil {
|
||||
beego.Error("execute error:", err)
|
||||
logs.Error("execute error:", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
return err
|
||||
}
|
||||
beego.Info("end upgrade:", v.name)
|
||||
logs.Info("end upgrade:", v.name)
|
||||
i++
|
||||
}
|
||||
}
|
||||
beego.Info("total success upgrade:", i, " migration")
|
||||
logs.Info("total success upgrade:", i, " migration")
|
||||
time.Sleep(2 * time.Second)
|
||||
return nil
|
||||
}
|
||||
@ -165,20 +165,20 @@ func Upgrade(lasttime int64) error {
|
||||
// Rollback rollback the migration by the name
|
||||
func Rollback(name string) error {
|
||||
if v, ok := migrationMap[name]; ok {
|
||||
beego.Info("start rollback")
|
||||
logs.Info("start rollback")
|
||||
v.Reset()
|
||||
v.Down()
|
||||
err := v.Exec(name, "down")
|
||||
if err != nil {
|
||||
beego.Error("execute error:", err)
|
||||
logs.Error("execute error:", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
return err
|
||||
}
|
||||
beego.Info("end rollback")
|
||||
logs.Info("end rollback")
|
||||
time.Sleep(2 * time.Second)
|
||||
return nil
|
||||
}
|
||||
beego.Error("not exist the migrationMap name:" + name)
|
||||
logs.Error("not exist the migrationMap name:" + name)
|
||||
time.Sleep(2 * time.Second)
|
||||
return errors.New("not exist the migrationMap name:" + name)
|
||||
}
|
||||
@ -191,23 +191,23 @@ func Reset() error {
|
||||
for j := len(sm) - 1; j >= 0; j-- {
|
||||
v := sm[j]
|
||||
if isRollBack(v.name) {
|
||||
beego.Info("skip the", v.name)
|
||||
logs.Info("skip the", v.name)
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
beego.Info("start reset:", v.name)
|
||||
logs.Info("start reset:", v.name)
|
||||
v.m.Reset()
|
||||
v.m.Down()
|
||||
err := v.m.Exec(v.name, "down")
|
||||
if err != nil {
|
||||
beego.Error("execute error:", err)
|
||||
logs.Error("execute error:", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
return err
|
||||
}
|
||||
i++
|
||||
beego.Info("end reset:", v.name)
|
||||
logs.Info("end reset:", v.name)
|
||||
}
|
||||
beego.Info("total success reset:", i, " migration")
|
||||
logs.Info("total success reset:", i, " migration")
|
||||
time.Sleep(2 * time.Second)
|
||||
return nil
|
||||
}
|
||||
@ -216,7 +216,7 @@ func Reset() error {
|
||||
func Refresh() error {
|
||||
err := Reset()
|
||||
if err != nil {
|
||||
beego.Error("execute error:", err)
|
||||
logs.Error("execute error:", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
return err
|
||||
}
|
||||
@ -265,7 +265,7 @@ func isRollBack(name string) bool {
|
||||
var maps []orm.Params
|
||||
num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps)
|
||||
if err != nil {
|
||||
beego.Info("get name has error", err)
|
||||
logs.Info("get name has error", err)
|
||||
return false
|
||||
}
|
||||
if num <= 0 {
|
||||
|
2
mime.go
2
mime.go
@ -339,7 +339,7 @@ var mimemaps = map[string]string{
|
||||
".pvu": "paleovu/x-pv",
|
||||
".pwz": "application/vndms-powerpoint",
|
||||
".py": "text/x-scriptphyton",
|
||||
".pyc": "applicaiton/x-bytecodepython",
|
||||
".pyc": "application/x-bytecodepython",
|
||||
".qcp": "audio/vndqcelp",
|
||||
".qd3": "x-world/x-3dmf",
|
||||
".qd3d": "x-world/x-3dmf",
|
||||
|
@ -44,7 +44,7 @@ func NewNamespace(prefix string, params ...LinkNamespace) *Namespace {
|
||||
return ns
|
||||
}
|
||||
|
||||
// Cond set condtion function
|
||||
// Cond set condition function
|
||||
// if cond return true can run this namespace, else can't
|
||||
// usage:
|
||||
// ns.Cond(func (ctx *context.Context) bool{
|
||||
@ -60,7 +60,7 @@ func (n *Namespace) Cond(cond namespaceCond) *Namespace {
|
||||
exception("405", ctx)
|
||||
}
|
||||
}
|
||||
if v, ok := n.handlers.filters[BeforeRouter]; ok {
|
||||
if v := n.handlers.filters[BeforeRouter]; len(v) > 0 {
|
||||
mr := new(FilterRouter)
|
||||
mr.tree = NewTree()
|
||||
mr.pattern = "*"
|
||||
|
@ -61,8 +61,8 @@ func TestNamespaceNest(t *testing.T) {
|
||||
ns.Namespace(
|
||||
NewNamespace("/admin").
|
||||
Get("/order", func(ctx *context.Context) {
|
||||
ctx.Output.Body([]byte("order"))
|
||||
}),
|
||||
ctx.Output.Body([]byte("order"))
|
||||
}),
|
||||
)
|
||||
AddNamespace(ns)
|
||||
BeeApp.Handlers.ServeHTTP(w, r)
|
||||
@ -79,8 +79,8 @@ func TestNamespaceNestParam(t *testing.T) {
|
||||
ns.Namespace(
|
||||
NewNamespace("/admin").
|
||||
Get("/order/:id", func(ctx *context.Context) {
|
||||
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
|
||||
}),
|
||||
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
|
||||
}),
|
||||
)
|
||||
AddNamespace(ns)
|
||||
BeeApp.Handlers.ServeHTTP(w, r)
|
||||
@ -124,8 +124,8 @@ func TestNamespaceFilter(t *testing.T) {
|
||||
ctx.Output.Body([]byte("this is Filter"))
|
||||
}).
|
||||
Get("/user/:id", func(ctx *context.Context) {
|
||||
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
|
||||
})
|
||||
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
|
||||
})
|
||||
AddNamespace(ns)
|
||||
BeeApp.Handlers.ServeHTTP(w, r)
|
||||
if w.Body.String() != "this is Filter" {
|
||||
|
@ -52,9 +52,15 @@ checkColumn:
|
||||
case TypeBooleanField:
|
||||
col = T["bool"]
|
||||
case TypeCharField:
|
||||
col = fmt.Sprintf(T["string"], fieldSize)
|
||||
if al.Driver == DRPostgres && fi.toText {
|
||||
col = T["string-text"]
|
||||
} else {
|
||||
col = fmt.Sprintf(T["string"], fieldSize)
|
||||
}
|
||||
case TypeTextField:
|
||||
col = T["string-text"]
|
||||
case TypeTimeField:
|
||||
col = T["time.Time-clock"]
|
||||
case TypeDateField:
|
||||
col = T["time.Time-date"]
|
||||
case TypeDateTimeField:
|
||||
@ -88,6 +94,18 @@ checkColumn:
|
||||
} else {
|
||||
col = fmt.Sprintf(s, fi.digits, fi.decimals)
|
||||
}
|
||||
case TypeJSONField:
|
||||
if al.Driver != DRPostgres {
|
||||
fieldType = TypeCharField
|
||||
goto checkColumn
|
||||
}
|
||||
col = T["json"]
|
||||
case TypeJsonbField:
|
||||
if al.Driver != DRPostgres {
|
||||
fieldType = TypeCharField
|
||||
goto checkColumn
|
||||
}
|
||||
col = T["jsonb"]
|
||||
case RelForeignKey, RelOneToOne:
|
||||
fieldType = fi.relModelInfo.fields.pk.fieldType
|
||||
fieldSize = fi.relModelInfo.fields.pk.size
|
||||
@ -264,7 +282,7 @@ func getColumnDefault(fi *fieldInfo) string {
|
||||
|
||||
// These defaults will be useful if there no config value orm:"default" and NOT NULL is on
|
||||
switch fi.fieldType {
|
||||
case TypeDateField, TypeDateTimeField, TypeTextField:
|
||||
case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField:
|
||||
return v
|
||||
|
||||
case TypeBitField, TypeSmallIntegerField, TypeIntegerField,
|
||||
@ -276,6 +294,8 @@ func getColumnDefault(fi *fieldInfo) string {
|
||||
case TypeBooleanField:
|
||||
t = " DEFAULT %s "
|
||||
d = "FALSE"
|
||||
case TypeJSONField, TypeJsonbField:
|
||||
d = "{}"
|
||||
}
|
||||
|
||||
if fi.colDefault {
|
||||
|
244
orm/db.go
244
orm/db.go
@ -24,6 +24,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
formatTime = "15:04:05"
|
||||
formatDate = "2006-01-02"
|
||||
formatDateTime = "2006-01-02 15:04:05"
|
||||
)
|
||||
@ -71,12 +72,12 @@ type dbBase struct {
|
||||
var _ dbBaser = new(dbBase)
|
||||
|
||||
// get struct columns values as interface slice.
|
||||
func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string, skipAuto bool, insert bool, names *[]string, tz *time.Location) (values []interface{}, err error) {
|
||||
var columns []string
|
||||
|
||||
if names != nil {
|
||||
columns = *names
|
||||
func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string, skipAuto bool, insert bool, names *[]string, tz *time.Location) (values []interface{}, autoFields []string, err error) {
|
||||
if names == nil {
|
||||
ns := make([]string, 0, len(cols))
|
||||
names = &ns
|
||||
}
|
||||
values = make([]interface{}, 0, len(cols))
|
||||
|
||||
for _, column := range cols {
|
||||
var fi *fieldInfo
|
||||
@ -90,18 +91,24 @@ func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string,
|
||||
}
|
||||
value, err := d.collectFieldValue(mi, fi, ind, insert, tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if names != nil {
|
||||
columns = append(columns, column)
|
||||
// ignore empty value auto field
|
||||
if insert && fi.auto {
|
||||
if fi.fieldType&IsPositiveIntegerField > 0 {
|
||||
if vu, ok := value.(uint64); !ok || vu == 0 {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if vu, ok := value.(int64); !ok || vu == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
autoFields = append(autoFields, fi.column)
|
||||
}
|
||||
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
if names != nil {
|
||||
*names = columns
|
||||
*names, values = append(*names, column), append(values, value)
|
||||
}
|
||||
|
||||
return
|
||||
@ -134,7 +141,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
||||
} else {
|
||||
value = field.Bool()
|
||||
}
|
||||
case TypeCharField, TypeTextField:
|
||||
case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
|
||||
if ns, ok := field.Interface().(sql.NullString); ok {
|
||||
value = nil
|
||||
if ns.Valid {
|
||||
@ -169,7 +176,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
||||
value = field.Float()
|
||||
}
|
||||
}
|
||||
case TypeDateField, TypeDateTimeField:
|
||||
case TypeTimeField, TypeDateField, TypeDateTimeField:
|
||||
value = field.Interface()
|
||||
if t, ok := value.(time.Time); ok {
|
||||
d.ins.TimeToDB(&t, tz)
|
||||
@ -181,7 +188,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
||||
}
|
||||
default:
|
||||
switch {
|
||||
case fi.fieldType&IsPostiveIntegerField > 0:
|
||||
case fi.fieldType&IsPositiveIntegerField > 0:
|
||||
if field.Kind() == reflect.Ptr {
|
||||
if field.IsNil() {
|
||||
value = nil
|
||||
@ -223,7 +230,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
||||
}
|
||||
}
|
||||
switch fi.fieldType {
|
||||
case TypeDateField, TypeDateTimeField:
|
||||
case TypeTimeField, TypeDateField, TypeDateTimeField:
|
||||
if fi.autoNow || fi.autoNowAdd && insert {
|
||||
if insert {
|
||||
if t, ok := value.(time.Time); ok && !t.IsZero() {
|
||||
@ -236,10 +243,21 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
|
||||
if fi.isFielder {
|
||||
f := field.Addr().Interface().(Fielder)
|
||||
f.SetRaw(tnow.In(DefaultTimeLoc))
|
||||
} else if field.Kind() == reflect.Ptr {
|
||||
v := tnow.In(DefaultTimeLoc)
|
||||
field.Set(reflect.ValueOf(&v))
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(tnow.In(DefaultTimeLoc)))
|
||||
}
|
||||
}
|
||||
case TypeJSONField, TypeJsonbField:
|
||||
if s, ok := value.(string); (ok && len(s) == 0) || value == nil {
|
||||
if fi.colDefault && fi.initial.Exist() {
|
||||
value = fi.initial.String()
|
||||
} else {
|
||||
value = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
@ -273,7 +291,7 @@ func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string,
|
||||
|
||||
// insert struct with prepared statement and given struct reflect value.
|
||||
func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
|
||||
values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
|
||||
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -300,7 +318,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
|
||||
if len(cols) > 0 {
|
||||
var err error
|
||||
whereCols = make([]string, 0, len(cols))
|
||||
args, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
|
||||
args, _, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -349,13 +367,21 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
|
||||
|
||||
// execute insert sql dbQuerier with given struct reflect.Value.
|
||||
func (d *dbBase) Insert(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
|
||||
names := make([]string, 0, len(mi.fields.dbcols)-1)
|
||||
values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz)
|
||||
names := make([]string, 0, len(mi.fields.dbcols))
|
||||
values, autoFields, err := d.collectValues(mi, ind, mi.fields.dbcols, false, true, &names, tz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return d.InsertValue(q, mi, false, names, values)
|
||||
id, err := d.InsertValue(q, mi, false, names, values)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(autoFields) > 0 {
|
||||
err = d.ins.setval(q, mi, autoFields)
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// multi-insert sql with given slice struct reflect.Value.
|
||||
@ -369,7 +395,7 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
|
||||
|
||||
// typ := reflect.Indirect(mi.addrField).Type()
|
||||
|
||||
length := sind.Len()
|
||||
length, autoFields := sind.Len(), make([]string, 0, 1)
|
||||
|
||||
for i := 1; i <= length; i++ {
|
||||
|
||||
@ -381,16 +407,18 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
|
||||
// }
|
||||
|
||||
if i == 1 {
|
||||
vus, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz)
|
||||
var (
|
||||
vus []interface{}
|
||||
err error
|
||||
)
|
||||
vus, autoFields, err = d.collectValues(mi, ind, mi.fields.dbcols, false, true, &names, tz)
|
||||
if err != nil {
|
||||
return cnt, err
|
||||
}
|
||||
values = make([]interface{}, bulk*len(vus))
|
||||
nums += copy(values, vus)
|
||||
|
||||
} else {
|
||||
|
||||
vus, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
|
||||
vus, _, err := d.collectValues(mi, ind, mi.fields.dbcols, false, true, nil, tz)
|
||||
if err != nil {
|
||||
return cnt, err
|
||||
}
|
||||
@ -412,7 +440,12 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
|
||||
}
|
||||
}
|
||||
|
||||
return cnt, nil
|
||||
var err error
|
||||
if len(autoFields) > 0 {
|
||||
err = d.ins.setval(q, mi, autoFields)
|
||||
}
|
||||
|
||||
return cnt, err
|
||||
}
|
||||
|
||||
// execute insert sql with given struct and given values.
|
||||
@ -455,6 +488,110 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
|
||||
return id, err
|
||||
}
|
||||
|
||||
// InsertOrUpdate a row
|
||||
// If your primary key or unique column conflict will update
|
||||
// If no will insert
|
||||
func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
|
||||
args0 := ""
|
||||
iouStr := ""
|
||||
argsMap := map[string]string{}
|
||||
switch a.Driver {
|
||||
case DRMySQL:
|
||||
iouStr = "ON DUPLICATE KEY UPDATE"
|
||||
case DRPostgres:
|
||||
if len(args) == 0 {
|
||||
return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName)
|
||||
} else {
|
||||
args0 = strings.ToLower(args[0])
|
||||
iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
|
||||
}
|
||||
|
||||
//Get on the key-value pairs
|
||||
for _, v := range args {
|
||||
kv := strings.Split(v, "=")
|
||||
if len(kv) == 2 {
|
||||
argsMap[strings.ToLower(kv[0])] = kv[1]
|
||||
}
|
||||
}
|
||||
|
||||
isMulti := false
|
||||
names := make([]string, 0, len(mi.fields.dbcols)-1)
|
||||
Q := d.ins.TableQuote()
|
||||
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
marks := make([]string, len(names))
|
||||
updateValues := make([]interface{}, 0)
|
||||
updates := make([]string, len(names))
|
||||
var conflitValue interface{}
|
||||
for i, v := range names {
|
||||
marks[i] = "?"
|
||||
valueStr := argsMap[strings.ToLower(v)]
|
||||
if v == args0 {
|
||||
conflitValue = values[i]
|
||||
}
|
||||
if valueStr != "" {
|
||||
switch a.Driver {
|
||||
case DRMySQL:
|
||||
updates[i] = v + "=" + valueStr
|
||||
case DRPostgres:
|
||||
if conflitValue != nil {
|
||||
//postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
|
||||
updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args0)
|
||||
updateValues = append(updateValues, conflitValue)
|
||||
} else {
|
||||
return 0, fmt.Errorf("`%s` must be in front of `%s` in your struct", args0, v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updates[i] = v + "=?"
|
||||
updateValues = append(updateValues, values[i])
|
||||
}
|
||||
}
|
||||
|
||||
values = append(values, updateValues...)
|
||||
|
||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||
qmarks := strings.Join(marks, ", ")
|
||||
qupdates := strings.Join(updates, ", ")
|
||||
columns := strings.Join(names, sep)
|
||||
|
||||
multi := len(values) / len(names)
|
||||
|
||||
if isMulti {
|
||||
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
|
||||
}
|
||||
//conflitValue maybe is a int,can`t use fmt.Sprintf
|
||||
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
if isMulti || !d.ins.HasReturningID(mi, &query) {
|
||||
res, err := q.Exec(query, values...)
|
||||
if err == nil {
|
||||
if isMulti {
|
||||
return res.RowsAffected()
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
row := q.QueryRow(query, values...)
|
||||
var id int64
|
||||
err = row.Scan(&id)
|
||||
if err.Error() == `pq: syntax error at or near "ON"` {
|
||||
err = fmt.Errorf("postgres version must 9.5 or higher")
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// execute update sql dbQuerier with given struct reflect.Value.
|
||||
func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
|
||||
pkName, pkValue, ok := getExistPk(mi, ind)
|
||||
@ -472,7 +609,7 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
||||
setNames = make([]string, 0, len(cols))
|
||||
}
|
||||
|
||||
setValues, err := d.collectValues(mi, ind, cols, true, false, &setNames, tz)
|
||||
setValues, _, err := d.collectValues(mi, ind, cols, true, false, &setNames, tz)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -516,7 +653,7 @@ 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 {
|
||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0)
|
||||
} else {
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
|
||||
@ -927,12 +1064,17 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
|
||||
tables.parseRelated(qs.related, qs.relDepth)
|
||||
|
||||
where, args := tables.getCondSQL(cond, false, tz)
|
||||
groupBy := tables.getGroupSQL(qs.groups)
|
||||
tables.getOrderSQL(qs.orders)
|
||||
join := tables.getJoinSQL()
|
||||
|
||||
Q := d.ins.TableQuote()
|
||||
|
||||
query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s", Q, mi.table, Q, join, where)
|
||||
query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s", Q, mi.table, Q, join, where, groupBy)
|
||||
|
||||
if groupBy != "" {
|
||||
query = fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS T", query)
|
||||
}
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
@ -1071,13 +1213,13 @@ setValue:
|
||||
}
|
||||
value = b
|
||||
}
|
||||
case fieldType == TypeCharField || fieldType == TypeTextField:
|
||||
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
||||
if str == nil {
|
||||
value = ToStr(val)
|
||||
} else {
|
||||
value = str.String()
|
||||
}
|
||||
case fieldType == TypeDateField || fieldType == TypeDateTimeField:
|
||||
case fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField:
|
||||
if str == nil {
|
||||
switch t := val.(type) {
|
||||
case time.Time:
|
||||
@ -1097,15 +1239,20 @@ setValue:
|
||||
if len(s) >= 19 {
|
||||
s = s[:19]
|
||||
t, err = time.ParseInLocation(formatDateTime, s, tz)
|
||||
} else {
|
||||
} else if len(s) >= 10 {
|
||||
if len(s) > 10 {
|
||||
s = s[:10]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatDate, s, tz)
|
||||
} else if len(s) >= 8 {
|
||||
if len(s) > 8 {
|
||||
s = s[:8]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatTime, s, tz)
|
||||
}
|
||||
t = t.In(DefaultTimeLoc)
|
||||
|
||||
if err != nil && s != "0000-00-00" && s != "0000-00-00 00:00:00" {
|
||||
if err != nil && s != "00:00:00" && s != "0000-00-00" && s != "0000-00-00 00:00:00" {
|
||||
tErr = err
|
||||
goto end
|
||||
}
|
||||
@ -1140,7 +1287,7 @@ setValue:
|
||||
tErr = err
|
||||
goto end
|
||||
}
|
||||
if fieldType&IsPostiveIntegerField > 0 {
|
||||
if fieldType&IsPositiveIntegerField > 0 {
|
||||
v, _ := str.Uint64()
|
||||
value = v
|
||||
} else {
|
||||
@ -1212,7 +1359,7 @@ setValue:
|
||||
field.SetBool(value.(bool))
|
||||
}
|
||||
}
|
||||
case fieldType == TypeCharField || fieldType == TypeTextField:
|
||||
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
|
||||
if isNative {
|
||||
if ns, ok := field.Interface().(sql.NullString); ok {
|
||||
if value == nil {
|
||||
@ -1234,12 +1381,18 @@ setValue:
|
||||
field.SetString(value.(string))
|
||||
}
|
||||
}
|
||||
case fieldType == TypeDateField || fieldType == TypeDateTimeField:
|
||||
case fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField:
|
||||
if isNative {
|
||||
if value == nil {
|
||||
value = time.Time{}
|
||||
} else if field.Kind() == reflect.Ptr {
|
||||
if value != nil {
|
||||
v := value.(time.Time)
|
||||
field.Set(reflect.ValueOf(&v))
|
||||
}
|
||||
} else {
|
||||
field.Set(reflect.ValueOf(value))
|
||||
}
|
||||
field.Set(reflect.ValueOf(value))
|
||||
}
|
||||
case fieldType == TypePositiveBitField && field.Kind() == reflect.Ptr:
|
||||
if value != nil {
|
||||
@ -1292,7 +1445,7 @@ setValue:
|
||||
field.Set(reflect.ValueOf(&v))
|
||||
}
|
||||
case fieldType&IsIntegerField > 0:
|
||||
if fieldType&IsPostiveIntegerField > 0 {
|
||||
if fieldType&IsPositiveIntegerField > 0 {
|
||||
if isNative {
|
||||
if value == nil {
|
||||
value = uint64(0)
|
||||
@ -1440,7 +1593,11 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
|
||||
|
||||
sels := strings.Join(cols, ", ")
|
||||
|
||||
query := fmt.Sprintf("SELECT %s FROM %s%s%s T0 %s%s%s%s%s", sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
|
||||
sqlSelect := "SELECT"
|
||||
if qs.distinct {
|
||||
sqlSelect += " DISTINCT"
|
||||
}
|
||||
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
|
||||
|
||||
d.ins.ReplaceMarks(&query)
|
||||
|
||||
@ -1562,6 +1719,11 @@ func (d *dbBase) HasReturningID(*modelInfo, *string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// sync auto key
|
||||
func (d *dbBase) setval(db dbQuerier, mi *modelInfo, autoFields []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert time from db.
|
||||
func (d *dbBase) TimeFromDB(t *time.Time, tz *time.Location) {
|
||||
*t = t.In(tz)
|
||||
|
@ -56,6 +56,8 @@ var postgresTypes = map[string]string{
|
||||
"uint64": `bigint CHECK("%COL%" >= 0)`,
|
||||
"float64": "double precision",
|
||||
"float64-decimal": "numeric(%d, %d)",
|
||||
"json": "json",
|
||||
"jsonb": "jsonb",
|
||||
}
|
||||
|
||||
// postgresql dbBaser.
|
||||
@ -123,14 +125,35 @@ func (d *dbBasePostgres) ReplaceMarks(query *string) {
|
||||
}
|
||||
|
||||
// make returning sql support for postgresql.
|
||||
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) (has bool) {
|
||||
if mi.fields.pk.auto {
|
||||
if query != nil {
|
||||
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, mi.fields.pk.column)
|
||||
}
|
||||
has = true
|
||||
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) bool {
|
||||
fi := mi.fields.pk
|
||||
if fi.fieldType&IsPositiveIntegerField == 0 && fi.fieldType&IsIntegerField == 0 {
|
||||
return false
|
||||
}
|
||||
return
|
||||
|
||||
if query != nil {
|
||||
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, fi.column)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// sync auto key
|
||||
func (d *dbBasePostgres) setval(db dbQuerier, mi *modelInfo, autoFields []string) error {
|
||||
if len(autoFields) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
Q := d.ins.TableQuote()
|
||||
for _, name := range autoFields {
|
||||
query := fmt.Sprintf("SELECT setval(pg_get_serial_sequence('%s', '%s'), (SELECT MAX(%s%s%s) FROM %s%s%s));",
|
||||
mi.table, name,
|
||||
Q, name, Q,
|
||||
Q, mi.table, Q)
|
||||
if _, err := db.Exec(query); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// show table sql for postgresql.
|
||||
|
@ -33,13 +33,13 @@ func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interfac
|
||||
fi := mi.fields.pk
|
||||
|
||||
v := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsPostiveIntegerField > 0 {
|
||||
if fi.fieldType&IsPositiveIntegerField > 0 {
|
||||
vu := v.Uint()
|
||||
exist = vu > 0
|
||||
value = vu
|
||||
} else if fi.fieldType&IsIntegerField > 0 {
|
||||
vu := v.Int()
|
||||
exist = vu > 0
|
||||
exist = true
|
||||
value = vu
|
||||
} else {
|
||||
vu := v.String()
|
||||
@ -74,24 +74,32 @@ outFor:
|
||||
case reflect.String:
|
||||
v := val.String()
|
||||
if fi != nil {
|
||||
if fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
|
||||
if fi.fieldType == TypeTimeField || fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
|
||||
var t time.Time
|
||||
var err error
|
||||
if len(v) >= 19 {
|
||||
s := v[:19]
|
||||
t, err = time.ParseInLocation(formatDateTime, s, DefaultTimeLoc)
|
||||
} else {
|
||||
} else if len(v) >= 10 {
|
||||
s := v
|
||||
if len(v) > 10 {
|
||||
s = v[:10]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatDate, s, tz)
|
||||
} else {
|
||||
s := v
|
||||
if len(s) > 8 {
|
||||
s = v[:8]
|
||||
}
|
||||
t, err = time.ParseInLocation(formatTime, s, tz)
|
||||
}
|
||||
if err == nil {
|
||||
if fi.fieldType == TypeDateField {
|
||||
v = t.In(tz).Format(formatDate)
|
||||
} else {
|
||||
} else if fi.fieldType == TypeDateTimeField {
|
||||
v = t.In(tz).Format(formatDateTime)
|
||||
} else {
|
||||
v = t.In(tz).Format(formatTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,8 +145,10 @@ outFor:
|
||||
if v, ok := arg.(time.Time); ok {
|
||||
if fi != nil && fi.fieldType == TypeDateField {
|
||||
arg = v.In(tz).Format(formatDate)
|
||||
} else {
|
||||
} else if fi.fieldType == TypeDateTimeField {
|
||||
arg = v.In(tz).Format(formatDateTime)
|
||||
} else {
|
||||
arg = v.In(tz).Format(formatTime)
|
||||
}
|
||||
} else {
|
||||
typ := val.Type()
|
||||
|
@ -66,7 +66,7 @@ func registerModel(prefix string, model interface{}) {
|
||||
}
|
||||
|
||||
if info.fields.pk == nil {
|
||||
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field\n", name)
|
||||
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field, default use 'id' if not set\n", name)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ const (
|
||||
TypeBooleanField = 1 << iota
|
||||
TypeCharField
|
||||
TypeTextField
|
||||
TypeTimeField
|
||||
TypeDateField
|
||||
TypeDateTimeField
|
||||
TypeBitField
|
||||
@ -37,6 +38,8 @@ const (
|
||||
TypePositiveBigIntegerField
|
||||
TypeFloatField
|
||||
TypeDecimalField
|
||||
TypeJSONField
|
||||
TypeJsonbField
|
||||
RelForeignKey
|
||||
RelOneToOne
|
||||
RelManyToMany
|
||||
@ -46,10 +49,10 @@ const (
|
||||
|
||||
// Define some logic enum
|
||||
const (
|
||||
IsIntegerField = ^-TypePositiveBigIntegerField >> 4 << 5
|
||||
IsPostiveIntegerField = ^-TypePositiveBigIntegerField >> 8 << 9
|
||||
IsRelField = ^-RelReverseMany >> 14 << 15
|
||||
IsFieldType = ^-RelReverseMany<<1 + 1
|
||||
IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6
|
||||
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10
|
||||
IsRelField = ^-RelReverseMany >> 17 << 18
|
||||
IsFieldType = ^-RelReverseMany<<1 + 1
|
||||
)
|
||||
|
||||
// BooleanField A true/false field.
|
||||
@ -145,6 +148,65 @@ func (e *CharField) RawValue() interface{} {
|
||||
// verify CharField implement Fielder
|
||||
var _ Fielder = new(CharField)
|
||||
|
||||
// TimeField A time, represented in go by a time.Time instance.
|
||||
// only time values like 10:00:00
|
||||
// Has a few extra, optional attr tag:
|
||||
//
|
||||
// auto_now:
|
||||
// Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps.
|
||||
// Note that the current date is always used; it’s not just a default value that you can override.
|
||||
//
|
||||
// auto_now_add:
|
||||
// Automatically set the field to now when the object is first created. Useful for creation of timestamps.
|
||||
// Note that the current date is always used; it’s not just a default value that you can override.
|
||||
//
|
||||
// eg: `orm:"auto_now"` or `orm:"auto_now_add"`
|
||||
type TimeField time.Time
|
||||
|
||||
// Value return the time.Time
|
||||
func (e TimeField) Value() time.Time {
|
||||
return time.Time(e)
|
||||
}
|
||||
|
||||
// Set set the TimeField's value
|
||||
func (e *TimeField) Set(d time.Time) {
|
||||
*e = TimeField(d)
|
||||
}
|
||||
|
||||
// String convert time to string
|
||||
func (e *TimeField) String() string {
|
||||
return e.Value().String()
|
||||
}
|
||||
|
||||
// FieldType return enum type Date
|
||||
func (e *TimeField) FieldType() int {
|
||||
return TypeDateField
|
||||
}
|
||||
|
||||
// SetRaw convert the interface to time.Time. Allow string and time.Time
|
||||
func (e *TimeField) SetRaw(value interface{}) error {
|
||||
switch d := value.(type) {
|
||||
case time.Time:
|
||||
e.Set(d)
|
||||
case string:
|
||||
v, err := timeParse(d, formatTime)
|
||||
if err != nil {
|
||||
e.Set(v)
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("<TimeField.SetRaw> unknown value `%s`", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RawValue return time value
|
||||
func (e *TimeField) RawValue() interface{} {
|
||||
return e.Value()
|
||||
}
|
||||
|
||||
var _ Fielder = new(TimeField)
|
||||
|
||||
// DateField A date, represented in go by a time.Time instance.
|
||||
// only date values like 2006-01-02
|
||||
// Has a few extra, optional attr tag:
|
||||
@ -627,3 +689,87 @@ func (e *TextField) RawValue() interface{} {
|
||||
|
||||
// verify TextField implement Fielder
|
||||
var _ Fielder = new(TextField)
|
||||
|
||||
// JSONField postgres json field.
|
||||
type JSONField string
|
||||
|
||||
// Value return JSONField value
|
||||
func (j JSONField) Value() string {
|
||||
return string(j)
|
||||
}
|
||||
|
||||
// Set the JSONField value
|
||||
func (j *JSONField) Set(d string) {
|
||||
*j = JSONField(d)
|
||||
}
|
||||
|
||||
// String convert JSONField to string
|
||||
func (j *JSONField) String() string {
|
||||
return j.Value()
|
||||
}
|
||||
|
||||
// FieldType return enum type
|
||||
func (j *JSONField) FieldType() int {
|
||||
return TypeJSONField
|
||||
}
|
||||
|
||||
// SetRaw convert interface string to string
|
||||
func (j *JSONField) SetRaw(value interface{}) error {
|
||||
switch d := value.(type) {
|
||||
case string:
|
||||
j.Set(d)
|
||||
default:
|
||||
return fmt.Errorf("<JSONField.SetRaw> unknown value `%s`", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RawValue return JSONField value
|
||||
func (j *JSONField) RawValue() interface{} {
|
||||
return j.Value()
|
||||
}
|
||||
|
||||
// verify JSONField implement Fielder
|
||||
var _ Fielder = new(JSONField)
|
||||
|
||||
// JsonbField postgres json field.
|
||||
type JsonbField string
|
||||
|
||||
// Value return JsonbField value
|
||||
func (j JsonbField) Value() string {
|
||||
return string(j)
|
||||
}
|
||||
|
||||
// Set the JsonbField value
|
||||
func (j *JsonbField) Set(d string) {
|
||||
*j = JsonbField(d)
|
||||
}
|
||||
|
||||
// String convert JsonbField to string
|
||||
func (j *JsonbField) String() string {
|
||||
return j.Value()
|
||||
}
|
||||
|
||||
// FieldType return enum type
|
||||
func (j *JsonbField) FieldType() int {
|
||||
return TypeJsonbField
|
||||
}
|
||||
|
||||
// SetRaw convert interface string to string
|
||||
func (j *JsonbField) SetRaw(value interface{}) error {
|
||||
switch d := value.(type) {
|
||||
case string:
|
||||
j.Set(d)
|
||||
default:
|
||||
return fmt.Errorf("<JsonbField.SetRaw> unknown value `%s`", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RawValue return JsonbField value
|
||||
func (j *JsonbField) RawValue() interface{} {
|
||||
return j.Value()
|
||||
}
|
||||
|
||||
// verify JsonbField implement Fielder
|
||||
var _ Fielder = new(JsonbField)
|
||||
|
@ -119,6 +119,7 @@ type fieldInfo struct {
|
||||
colDefault bool
|
||||
initial StrTo
|
||||
size int
|
||||
toText bool
|
||||
autoNow bool
|
||||
autoNowAdd bool
|
||||
rel bool
|
||||
@ -239,8 +240,15 @@ checkType:
|
||||
if err != nil {
|
||||
goto end
|
||||
}
|
||||
if fieldType == TypeCharField && tags["type"] == "text" {
|
||||
fieldType = TypeTextField
|
||||
if fieldType == TypeCharField {
|
||||
switch tags["type"] {
|
||||
case "text":
|
||||
fieldType = TypeTextField
|
||||
case "json":
|
||||
fieldType = TypeJSONField
|
||||
case "jsonb":
|
||||
fieldType = TypeJsonbField
|
||||
}
|
||||
}
|
||||
if fieldType == TypeFloatField && (digits != "" || decimals != "") {
|
||||
fieldType = TypeDecimalField
|
||||
@ -248,6 +256,9 @@ checkType:
|
||||
if fieldType == TypeDateTimeField && tags["type"] == "date" {
|
||||
fieldType = TypeDateField
|
||||
}
|
||||
if fieldType == TypeTimeField && tags["type"] == "time" {
|
||||
fieldType = TypeTimeField
|
||||
}
|
||||
}
|
||||
|
||||
switch fieldType {
|
||||
@ -339,7 +350,7 @@ checkType:
|
||||
|
||||
switch fieldType {
|
||||
case TypeBooleanField:
|
||||
case TypeCharField:
|
||||
case TypeCharField, TypeJSONField, TypeJsonbField:
|
||||
if size != "" {
|
||||
v, e := StrTo(size).Int32()
|
||||
if e != nil {
|
||||
@ -349,11 +360,12 @@ checkType:
|
||||
}
|
||||
} else {
|
||||
fi.size = 255
|
||||
fi.toText = true
|
||||
}
|
||||
case TypeTextField:
|
||||
fi.index = false
|
||||
fi.unique = false
|
||||
case TypeDateField, TypeDateTimeField:
|
||||
case TypeTimeField, TypeDateField, TypeDateTimeField:
|
||||
if attrs["auto_now"] {
|
||||
fi.autoNow = true
|
||||
} else if attrs["auto_now_add"] {
|
||||
@ -406,7 +418,7 @@ checkType:
|
||||
fi.index = false
|
||||
}
|
||||
|
||||
if fi.auto || fi.pk || fi.unique || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
||||
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
||||
// can not set default
|
||||
initial.Clear()
|
||||
}
|
||||
|
@ -78,40 +78,43 @@ func (e *SliceStringField) RawValue() interface{} {
|
||||
var _ Fielder = new(SliceStringField)
|
||||
|
||||
// A json field.
|
||||
type JSONField struct {
|
||||
type JSONFieldTest struct {
|
||||
Name string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (e *JSONField) String() string {
|
||||
func (e *JSONFieldTest) String() string {
|
||||
data, _ := json.Marshal(e)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (e *JSONField) FieldType() int {
|
||||
func (e *JSONFieldTest) FieldType() int {
|
||||
return TypeTextField
|
||||
}
|
||||
|
||||
func (e *JSONField) SetRaw(value interface{}) error {
|
||||
func (e *JSONFieldTest) SetRaw(value interface{}) error {
|
||||
switch d := value.(type) {
|
||||
case string:
|
||||
return json.Unmarshal([]byte(d), e)
|
||||
default:
|
||||
return fmt.Errorf("<JsonField.SetRaw> unknown value `%v`", value)
|
||||
return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *JSONField) RawValue() interface{} {
|
||||
func (e *JSONFieldTest) RawValue() interface{} {
|
||||
return e.String()
|
||||
}
|
||||
|
||||
var _ Fielder = new(JSONField)
|
||||
var _ Fielder = new(JSONFieldTest)
|
||||
|
||||
type Data struct {
|
||||
ID int `orm:"column(id)"`
|
||||
Boolean bool
|
||||
Char string `orm:"size(50)"`
|
||||
Text string `orm:"type(text)"`
|
||||
JSON string `orm:"type(json);default({\"name\":\"json\"})"`
|
||||
Jsonb string `orm:"type(jsonb)"`
|
||||
Time time.Time `orm:"type(time)"`
|
||||
Date time.Time `orm:"type(date)"`
|
||||
DateTime time.Time `orm:"column(datetime)"`
|
||||
Byte byte
|
||||
@ -136,6 +139,9 @@ type DataNull struct {
|
||||
Boolean bool `orm:"null"`
|
||||
Char string `orm:"null;size(50)"`
|
||||
Text string `orm:"null;type(text)"`
|
||||
JSON string `orm:"type(json);null"`
|
||||
Jsonb string `orm:"type(jsonb);null"`
|
||||
Time time.Time `orm:"null;type(time)"`
|
||||
Date time.Time `orm:"null;type(date)"`
|
||||
DateTime time.Time `orm:"null;column(datetime)"`
|
||||
Byte byte `orm:"null"`
|
||||
@ -175,6 +181,9 @@ type DataNull struct {
|
||||
Float32Ptr *float32 `orm:"null"`
|
||||
Float64Ptr *float64 `orm:"null"`
|
||||
DecimalPtr *float64 `orm:"digits(8);decimals(4);null"`
|
||||
TimePtr *time.Time `orm:"null;type(time)"`
|
||||
DatePtr *time.Time `orm:"null;type(date)"`
|
||||
DateTimePtr *time.Time `orm:"null"`
|
||||
}
|
||||
|
||||
type String string
|
||||
@ -237,7 +246,7 @@ type User struct {
|
||||
ShouldSkip string `orm:"-"`
|
||||
Nums int
|
||||
Langs SliceStringField `orm:"size(100)"`
|
||||
Extra JSONField `orm:"type(text)"`
|
||||
Extra JSONFieldTest `orm:"type(text)"`
|
||||
unexport bool `orm:"-"`
|
||||
unexportBool bool
|
||||
}
|
||||
@ -375,6 +384,28 @@ func NewInLine() *InLine {
|
||||
return new(InLine)
|
||||
}
|
||||
|
||||
type InLineOneToOne struct {
|
||||
// Common Fields
|
||||
ModelBase
|
||||
|
||||
Note string
|
||||
InLine *InLine `orm:"rel(fk);column(inline)"`
|
||||
}
|
||||
|
||||
func NewInLineOneToOne() *InLineOneToOne {
|
||||
return new(InLineOneToOne)
|
||||
}
|
||||
|
||||
type IntegerPk struct {
|
||||
ID int64 `orm:"pk"`
|
||||
Value string
|
||||
}
|
||||
|
||||
type UintPk struct {
|
||||
ID uint32 `orm:"pk"`
|
||||
Name string
|
||||
}
|
||||
|
||||
var DBARGS = struct {
|
||||
Driver string
|
||||
Source string
|
||||
|
@ -137,6 +137,8 @@ func getFieldType(val reflect.Value) (ft int, err error) {
|
||||
ft = TypeBooleanField
|
||||
case reflect.TypeOf(new(string)):
|
||||
ft = TypeCharField
|
||||
case reflect.TypeOf(new(time.Time)):
|
||||
ft = TypeDateTimeField
|
||||
default:
|
||||
elm := reflect.Indirect(val)
|
||||
switch elm.Kind() {
|
||||
@ -192,10 +194,10 @@ func parseStructTag(data string, attrs *map[string]bool, tags *map[string]string
|
||||
tag := make(map[string]string)
|
||||
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
||||
v = strings.TrimSpace(v)
|
||||
if supportTag[v] == 1 {
|
||||
attr[v] = true
|
||||
if t := strings.ToLower(v); supportTag[t] == 1 {
|
||||
attr[t] = true
|
||||
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
||||
name := v[:i]
|
||||
name := t[:i]
|
||||
if supportTag[name] == 2 {
|
||||
v = v[i+1 : len(v)-1]
|
||||
tag[name] = v
|
||||
|
26
orm/orm.go
26
orm/orm.go
@ -140,7 +140,14 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i
|
||||
return (err == nil), id, err
|
||||
}
|
||||
|
||||
return false, ind.FieldByIndex(mi.fields.pk.fieldIndex).Int(), err
|
||||
id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex)
|
||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
id = int64(vid.Uint())
|
||||
} else {
|
||||
id = vid.Int()
|
||||
}
|
||||
|
||||
return false, id, err
|
||||
}
|
||||
|
||||
// insert model data to database
|
||||
@ -159,7 +166,7 @@ func (o *orm) Insert(md interface{}) (int64, error) {
|
||||
// set auto pk field
|
||||
func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) {
|
||||
if mi.fields.pk.auto {
|
||||
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
|
||||
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
} else {
|
||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
|
||||
@ -184,7 +191,7 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||
|
||||
if bulk <= 1 {
|
||||
for i := 0; i < sind.Len(); i++ {
|
||||
ind := sind.Index(i)
|
||||
ind := reflect.Indirect(sind.Index(i))
|
||||
mi, _ := o.getMiInd(ind.Interface(), false)
|
||||
id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ)
|
||||
if err != nil {
|
||||
@ -202,6 +209,19 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) {
|
||||
return cnt, nil
|
||||
}
|
||||
|
||||
// InsertOrUpdate data to database
|
||||
func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
|
||||
mi, ind := o.getMiInd(md, true)
|
||||
id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
o.setPk(mi, ind, id)
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// update model to database.
|
||||
// cols set the columns those want to update.
|
||||
func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
|
||||
|
@ -31,7 +31,7 @@ type Log struct {
|
||||
// NewLog set io.Writer to create a Logger.
|
||||
func NewLog(out io.Writer) *Log {
|
||||
d := new(Log)
|
||||
d.Logger = log.New(out, "[ORM]", 1e9)
|
||||
d.Logger = log.New(out, "[ORM]", log.LstdFlags)
|
||||
return d
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ 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 {
|
||||
if o.mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
|
||||
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
|
||||
} else {
|
||||
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id)
|
||||
|
@ -192,16 +192,18 @@ func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
|
||||
// query one row data and map to containers.
|
||||
// cols means the columns when querying.
|
||||
func (o *querySet) One(container interface{}, cols ...string) error {
|
||||
o.limit = 1
|
||||
num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if num > 1 {
|
||||
return ErrMultiRows
|
||||
}
|
||||
if num == 0 {
|
||||
return ErrNoRows
|
||||
}
|
||||
|
||||
if num > 1 {
|
||||
return ErrMultiRows
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -342,7 +342,13 @@ 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(fi.fieldIndex), value)
|
||||
field := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsRelField > 0 {
|
||||
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||
field.Set(mf)
|
||||
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||
}
|
||||
o.setFieldValue(field, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -480,7 +486,13 @@ 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(fi.fieldIndex), value)
|
||||
field := ind.FieldByIndex(fi.fieldIndex)
|
||||
if fi.fieldType&IsRelField > 0 {
|
||||
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
|
||||
field.Set(mf)
|
||||
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
|
||||
}
|
||||
o.setFieldValue(field, value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
316
orm/orm_test.go
316
orm/orm_test.go
@ -19,6 +19,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@ -33,6 +34,7 @@ var _ = os.PathSeparator
|
||||
var (
|
||||
testDate = formatDate + " -0700"
|
||||
testDateTime = formatDateTime + " -0700"
|
||||
testTime = formatTime + " -0700"
|
||||
)
|
||||
|
||||
type argAny []interface{}
|
||||
@ -188,6 +190,9 @@ func TestSyncDb(t *testing.T) {
|
||||
RegisterModel(new(Permission))
|
||||
RegisterModel(new(GroupPermissions))
|
||||
RegisterModel(new(InLine))
|
||||
RegisterModel(new(InLineOneToOne))
|
||||
RegisterModel(new(IntegerPk))
|
||||
RegisterModel(new(UintPk))
|
||||
|
||||
err := RunSyncdb("default", true, Debug)
|
||||
throwFail(t, err)
|
||||
@ -208,6 +213,9 @@ func TestRegisterModels(t *testing.T) {
|
||||
RegisterModel(new(Permission))
|
||||
RegisterModel(new(GroupPermissions))
|
||||
RegisterModel(new(InLine))
|
||||
RegisterModel(new(InLineOneToOne))
|
||||
RegisterModel(new(IntegerPk))
|
||||
RegisterModel(new(UintPk))
|
||||
|
||||
BootStrap()
|
||||
|
||||
@ -233,6 +241,9 @@ var DataValues = map[string]interface{}{
|
||||
"Boolean": true,
|
||||
"Char": "char",
|
||||
"Text": "text",
|
||||
"JSON": `{"name":"json"}`,
|
||||
"Jsonb": `{"name": "jsonb"}`,
|
||||
"Time": time.Now(),
|
||||
"Date": time.Now(),
|
||||
"DateTime": time.Now(),
|
||||
"Byte": byte(1<<8 - 1),
|
||||
@ -257,10 +268,12 @@ func TestDataTypes(t *testing.T) {
|
||||
ind := reflect.Indirect(reflect.ValueOf(&d))
|
||||
|
||||
for name, value := range DataValues {
|
||||
if name == "JSON" {
|
||||
continue
|
||||
}
|
||||
e := ind.FieldByName(name)
|
||||
e.Set(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
id, err := dORM.Insert(&d)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(id, 1))
|
||||
@ -281,6 +294,9 @@ func TestDataTypes(t *testing.T) {
|
||||
case "DateTime":
|
||||
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime)
|
||||
value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime)
|
||||
case "Time":
|
||||
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime)
|
||||
value = value.(time.Time).In(DefaultTimeLoc).Format(testTime)
|
||||
}
|
||||
throwFail(t, AssertIs(vu == value, true), value, vu)
|
||||
}
|
||||
@ -299,10 +315,18 @@ func TestNullDataTypes(t *testing.T) {
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(id, 1))
|
||||
|
||||
data := `{"ok":1,"data":{"arr":[1,2],"msg":"gopher"}}`
|
||||
d = DataNull{ID: 1, JSON: data}
|
||||
num, err := dORM.Update(&d)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 1))
|
||||
|
||||
d = DataNull{ID: 1}
|
||||
err = dORM.Read(&d)
|
||||
throwFail(t, err)
|
||||
|
||||
throwFail(t, AssertIs(d.JSON, data))
|
||||
|
||||
throwFail(t, AssertIs(d.NullBool.Valid, false))
|
||||
throwFail(t, AssertIs(d.NullString.Valid, false))
|
||||
throwFail(t, AssertIs(d.NullInt64.Valid, false))
|
||||
@ -326,6 +350,9 @@ func TestNullDataTypes(t *testing.T) {
|
||||
throwFail(t, AssertIs(d.Float32Ptr, nil))
|
||||
throwFail(t, AssertIs(d.Float64Ptr, nil))
|
||||
throwFail(t, AssertIs(d.DecimalPtr, nil))
|
||||
throwFail(t, AssertIs(d.TimePtr, nil))
|
||||
throwFail(t, AssertIs(d.DatePtr, nil))
|
||||
throwFail(t, AssertIs(d.DateTimePtr, nil))
|
||||
|
||||
_, err = dORM.Raw(`INSERT INTO data_null (boolean) VALUES (?)`, nil).Exec()
|
||||
throwFail(t, err)
|
||||
@ -352,6 +379,9 @@ func TestNullDataTypes(t *testing.T) {
|
||||
float32Ptr := float32(42.0)
|
||||
float64Ptr := float64(42.0)
|
||||
decimalPtr := float64(42.0)
|
||||
timePtr := time.Now()
|
||||
datePtr := time.Now()
|
||||
dateTimePtr := time.Now()
|
||||
|
||||
d = DataNull{
|
||||
DateTime: time.Now(),
|
||||
@ -377,6 +407,9 @@ func TestNullDataTypes(t *testing.T) {
|
||||
Float32Ptr: &float32Ptr,
|
||||
Float64Ptr: &float64Ptr,
|
||||
DecimalPtr: &decimalPtr,
|
||||
TimePtr: &timePtr,
|
||||
DatePtr: &datePtr,
|
||||
DateTimePtr: &dateTimePtr,
|
||||
}
|
||||
|
||||
id, err = dORM.Insert(&d)
|
||||
@ -417,6 +450,9 @@ func TestNullDataTypes(t *testing.T) {
|
||||
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
|
||||
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
|
||||
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
|
||||
throwFail(t, AssertIs((*d.TimePtr).Format(testTime), timePtr.Format(testTime)))
|
||||
throwFail(t, AssertIs((*d.DatePtr).Format(testDate), datePtr.Format(testDate)))
|
||||
throwFail(t, AssertIs((*d.DateTimePtr).Format(testDateTime), dateTimePtr.Format(testDateTime)))
|
||||
}
|
||||
|
||||
func TestDataCustomTypes(t *testing.T) {
|
||||
@ -969,12 +1005,19 @@ func TestOne(t *testing.T) {
|
||||
var user User
|
||||
qs := dORM.QueryTable("user")
|
||||
err := qs.One(&user)
|
||||
throwFail(t, AssertIs(err, ErrMultiRows))
|
||||
throwFail(t, err)
|
||||
|
||||
user = User{}
|
||||
err = qs.OrderBy("Id").Limit(1).One(&user)
|
||||
throwFailNow(t, err)
|
||||
throwFail(t, AssertIs(user.UserName, "slene"))
|
||||
throwFail(t, AssertNot(err, ErrMultiRows))
|
||||
|
||||
user = User{}
|
||||
err = qs.OrderBy("-Id").Limit(100).One(&user)
|
||||
throwFailNow(t, err)
|
||||
throwFail(t, AssertIs(user.UserName, "nobody"))
|
||||
throwFail(t, AssertNot(err, ErrMultiRows))
|
||||
|
||||
err = qs.Filter("user_name", "nothing").One(&user)
|
||||
throwFail(t, AssertIs(err, ErrNoRows))
|
||||
@ -1514,6 +1557,7 @@ func TestRawQueryRow(t *testing.T) {
|
||||
Boolean bool
|
||||
Char string
|
||||
Text string
|
||||
Time time.Time
|
||||
Date time.Time
|
||||
DateTime time.Time
|
||||
Byte byte
|
||||
@ -1542,14 +1586,14 @@ func TestRawQueryRow(t *testing.T) {
|
||||
Q := dDbBaser.TableQuote()
|
||||
|
||||
cols := []string{
|
||||
"id", "boolean", "char", "text", "date", "datetime", "byte", "rune", "int", "int8", "int16", "int32",
|
||||
"id", "boolean", "char", "text", "time", "date", "datetime", "byte", "rune", "int", "int8", "int16", "int32",
|
||||
"int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "decimal",
|
||||
}
|
||||
sep := fmt.Sprintf("%s, %s", Q, Q)
|
||||
query := fmt.Sprintf("SELECT %s%s%s FROM data WHERE id = ?", Q, strings.Join(cols, sep), Q)
|
||||
var id int
|
||||
values := []interface{}{
|
||||
&id, &Boolean, &Char, &Text, &Date, &DateTime, &Byte, &Rune, &Int, &Int8, &Int16, &Int32,
|
||||
&id, &Boolean, &Char, &Text, &Time, &Date, &DateTime, &Byte, &Rune, &Int, &Int8, &Int16, &Int32,
|
||||
&Int64, &Uint, &Uint8, &Uint16, &Uint32, &Uint64, &Float32, &Float64, &Decimal,
|
||||
}
|
||||
err := dORM.Raw(query, 1).QueryRow(values...)
|
||||
@ -1560,6 +1604,10 @@ func TestRawQueryRow(t *testing.T) {
|
||||
switch col {
|
||||
case "id":
|
||||
throwFail(t, AssertIs(id, 1))
|
||||
case "time":
|
||||
v = v.(time.Time).In(DefaultTimeLoc)
|
||||
value := dataValues[col].(time.Time).In(DefaultTimeLoc)
|
||||
throwFail(t, AssertIs(v, value, testTime))
|
||||
case "date":
|
||||
v = v.(time.Time).In(DefaultTimeLoc)
|
||||
value := dataValues[col].(time.Time).In(DefaultTimeLoc)
|
||||
@ -1607,6 +1655,9 @@ func TestQueryRows(t *testing.T) {
|
||||
e := ind.FieldByName(name)
|
||||
vu := e.Interface()
|
||||
switch name {
|
||||
case "Time":
|
||||
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime)
|
||||
value = value.(time.Time).In(DefaultTimeLoc).Format(testTime)
|
||||
case "Date":
|
||||
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate)
|
||||
value = value.(time.Time).In(DefaultTimeLoc).Format(testDate)
|
||||
@ -1631,6 +1682,9 @@ func TestQueryRows(t *testing.T) {
|
||||
e := ind.FieldByName(name)
|
||||
vu := e.Interface()
|
||||
switch name {
|
||||
case "Time":
|
||||
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime)
|
||||
value = value.(time.Time).In(DefaultTimeLoc).Format(testTime)
|
||||
case "Date":
|
||||
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate)
|
||||
value = value.(time.Time).In(DefaultTimeLoc).Format(testDate)
|
||||
@ -1952,3 +2006,257 @@ func TestInLine(t *testing.T) {
|
||||
throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate))
|
||||
throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime))
|
||||
}
|
||||
|
||||
func TestInLineOneToOne(t *testing.T) {
|
||||
name := "121"
|
||||
email := "121@go.com"
|
||||
inline := NewInLine()
|
||||
inline.Name = name
|
||||
inline.Email = email
|
||||
|
||||
id, err := dORM.Insert(inline)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(id, 2))
|
||||
|
||||
note := "one2one"
|
||||
il121 := NewInLineOneToOne()
|
||||
il121.Note = note
|
||||
il121.InLine = inline
|
||||
_, err = dORM.Insert(il121)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(il121.ID, 1))
|
||||
|
||||
il := NewInLineOneToOne()
|
||||
err = dORM.QueryTable(il).Filter("Id", 1).RelatedSel().One(il)
|
||||
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(il.Note, note))
|
||||
throwFail(t, AssertIs(il.InLine.ID, id))
|
||||
throwFail(t, AssertIs(il.InLine.Name, name))
|
||||
throwFail(t, AssertIs(il.InLine.Email, email))
|
||||
|
||||
rinline := NewInLine()
|
||||
err = dORM.QueryTable(rinline).Filter("InLineOneToOne__Id", 1).One(rinline)
|
||||
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(rinline.ID, id))
|
||||
throwFail(t, AssertIs(rinline.Name, name))
|
||||
throwFail(t, AssertIs(rinline.Email, email))
|
||||
}
|
||||
|
||||
func TestIntegerPk(t *testing.T) {
|
||||
its := []IntegerPk{
|
||||
{ID: math.MinInt64, Value: "-"},
|
||||
{ID: 0, Value: "0"},
|
||||
{ID: math.MaxInt64, Value: "+"},
|
||||
}
|
||||
|
||||
num, err := dORM.InsertMulti(len(its), its)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, len(its)))
|
||||
|
||||
for _, intPk := range its {
|
||||
out := IntegerPk{ID: intPk.ID}
|
||||
err = dORM.Read(&out)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(out.Value, intPk.Value))
|
||||
}
|
||||
|
||||
num, err = dORM.InsertMulti(1, []*IntegerPk{{
|
||||
ID: 1, Value: "ok",
|
||||
}})
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 1))
|
||||
}
|
||||
|
||||
func TestInsertAuto(t *testing.T) {
|
||||
u := &User{
|
||||
UserName: "autoPre",
|
||||
Email: "autoPre@gmail.com",
|
||||
}
|
||||
|
||||
id, err := dORM.Insert(u)
|
||||
throwFail(t, err)
|
||||
|
||||
id += 100
|
||||
su := &User{
|
||||
ID: int(id),
|
||||
UserName: "auto",
|
||||
Email: "auto@gmail.com",
|
||||
}
|
||||
|
||||
nid, err := dORM.Insert(su)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(nid, id))
|
||||
|
||||
users := []User{
|
||||
{ID: int(id + 100), UserName: "auto_100"},
|
||||
{ID: int(id + 110), UserName: "auto_110"},
|
||||
{ID: int(id + 120), UserName: "auto_120"},
|
||||
}
|
||||
num, err := dORM.InsertMulti(100, users)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(num, 3))
|
||||
|
||||
u = &User{
|
||||
UserName: "auto_121",
|
||||
}
|
||||
|
||||
nid, err = dORM.Insert(u)
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(nid, id+120+1))
|
||||
}
|
||||
|
||||
func TestUintPk(t *testing.T) {
|
||||
name := "go"
|
||||
u := &UintPk{
|
||||
ID: 8,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
created, pk, err := dORM.ReadOrCreate(u, "ID")
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(created, true))
|
||||
throwFail(t, AssertIs(u.Name, name))
|
||||
|
||||
nu := &UintPk{ID: 8}
|
||||
created, pk, err = dORM.ReadOrCreate(nu, "ID")
|
||||
throwFail(t, err)
|
||||
throwFail(t, AssertIs(created, false))
|
||||
throwFail(t, AssertIs(nu.ID, u.ID))
|
||||
throwFail(t, AssertIs(pk, u.ID))
|
||||
throwFail(t, AssertIs(nu.Name, name))
|
||||
|
||||
dORM.Delete(u)
|
||||
}
|
||||
|
||||
func TestSnake(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"i": "i",
|
||||
"I": "i",
|
||||
"iD": "i_d",
|
||||
"ID": "i_d",
|
||||
"NO": "n_o",
|
||||
"NOO": "n_o_o",
|
||||
"NOOooOOoo": "n_o_ooo_o_ooo",
|
||||
"OrderNO": "order_n_o",
|
||||
"tagName": "tag_name",
|
||||
"tag_Name": "tag__name",
|
||||
"tag_name": "tag_name",
|
||||
"_tag_name": "_tag_name",
|
||||
"tag_666name": "tag_666name",
|
||||
"tag_666Name": "tag_666_name",
|
||||
}
|
||||
for name, want := range cases {
|
||||
got := snakeString(name)
|
||||
throwFail(t, AssertIs(got, want))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreCaseTag(t *testing.T) {
|
||||
type testTagModel struct {
|
||||
ID int `orm:"pk"`
|
||||
NOO string `orm:"column(n)"`
|
||||
Name01 string `orm:"NULL"`
|
||||
Name02 string `orm:"COLUMN(Name)"`
|
||||
Name03 string `orm:"Column(name)"`
|
||||
}
|
||||
modelCache.clean()
|
||||
RegisterModel(&testTagModel{})
|
||||
info, ok := modelCache.get("test_tag_model")
|
||||
throwFail(t, AssertIs(ok, true))
|
||||
throwFail(t, AssertNot(info, nil))
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
throwFail(t, AssertIs(info.fields.GetByName("NOO").column, "n"))
|
||||
throwFail(t, AssertIs(info.fields.GetByName("Name01").null, true))
|
||||
throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name"))
|
||||
throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name"))
|
||||
}
|
||||
func TestInsertOrUpdate(t *testing.T) {
|
||||
RegisterModel(new(User))
|
||||
user := User{UserName: "unique_username133", Status: 1, Password: "o"}
|
||||
user1 := User{UserName: "unique_username133", Status: 2, Password: "o"}
|
||||
user2 := User{UserName: "unique_username133", Status: 3, Password: "oo"}
|
||||
dORM.Insert(&user)
|
||||
test := User{UserName: "unique_username133"}
|
||||
fmt.Println(dORM.Driver().Name())
|
||||
if dORM.Driver().Name() == "sqlite3" {
|
||||
fmt.Println("sqlite3 is nonsupport")
|
||||
return
|
||||
}
|
||||
//test1
|
||||
_, err := dORM.InsertOrUpdate(&user1, "user_name")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
|
||||
} else {
|
||||
throwFailNow(t, err)
|
||||
}
|
||||
} else {
|
||||
dORM.Read(&test, "user_name")
|
||||
throwFailNow(t, AssertIs(user1.Status, test.Status))
|
||||
}
|
||||
//test2
|
||||
_, err = dORM.InsertOrUpdate(&user2, "user_name")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
|
||||
} else {
|
||||
throwFailNow(t, err)
|
||||
}
|
||||
} else {
|
||||
dORM.Read(&test, "user_name")
|
||||
throwFailNow(t, AssertIs(user2.Status, test.Status))
|
||||
throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password)))
|
||||
}
|
||||
//test3 +
|
||||
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
|
||||
} else {
|
||||
throwFailNow(t, err)
|
||||
}
|
||||
} else {
|
||||
dORM.Read(&test, "user_name")
|
||||
throwFailNow(t, AssertIs(user2.Status+1, test.Status))
|
||||
}
|
||||
//test4 -
|
||||
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status-1")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
|
||||
} else {
|
||||
throwFailNow(t, err)
|
||||
}
|
||||
} else {
|
||||
dORM.Read(&test, "user_name")
|
||||
throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status))
|
||||
}
|
||||
//test5 *
|
||||
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status*3")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
|
||||
} else {
|
||||
throwFailNow(t, err)
|
||||
}
|
||||
} else {
|
||||
dORM.Read(&test, "user_name")
|
||||
throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status))
|
||||
}
|
||||
//test6 /
|
||||
_, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
|
||||
} else {
|
||||
throwFailNow(t, err)
|
||||
}
|
||||
} else {
|
||||
dORM.Read(&test, "user_name")
|
||||
throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status))
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,11 @@ type Ormer interface {
|
||||
// id, err = Ormer.Insert(user)
|
||||
// user must a pointer and Insert will set user's pk field
|
||||
Insert(interface{}) (int64, error)
|
||||
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
|
||||
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
|
||||
// postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value")
|
||||
// if colu type is integer : can use(+-*/), string : colu || "value"
|
||||
InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)
|
||||
// insert some models to database
|
||||
InsertMulti(bulk int, mds interface{}) (int64, error)
|
||||
// update model to database.
|
||||
@ -391,6 +396,7 @@ type txEnder interface {
|
||||
type dbBaser interface {
|
||||
Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) error
|
||||
Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||
InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
|
||||
InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
|
||||
InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
|
||||
InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||
@ -420,4 +426,5 @@ type dbBaser interface {
|
||||
ShowColumnsQuery(string) string
|
||||
IndexExists(dbQuerier, string, string) bool
|
||||
collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error)
|
||||
setval(dbQuerier, *modelInfo, []string) error
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ func ToInt64(value interface{}) (d int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// snake string, XxYy to xx_yy
|
||||
// snake string, XxYy to xx_yy , XxYY to xx_yy
|
||||
func snakeString(s string) string {
|
||||
data := make([]byte, 0, len(s)*2)
|
||||
j := false
|
||||
|
48
parser.go
48
parser.go
@ -23,10 +23,11 @@ import (
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
@ -55,10 +56,11 @@ func init() {
|
||||
}
|
||||
|
||||
func parserPkg(pkgRealpath, pkgpath string) error {
|
||||
rep := strings.NewReplacer("/", "_", ".", "_")
|
||||
commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go"
|
||||
rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
|
||||
commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
|
||||
commentFilename = coomentPrefix + rep.Replace(commentFilename) + ".go"
|
||||
if !compareFile(pkgRealpath) {
|
||||
Info(pkgRealpath + " no changed")
|
||||
logs.Info(pkgRealpath + " no changed")
|
||||
return nil
|
||||
}
|
||||
genInfoList = make(map[string][]ControllerComments)
|
||||
@ -86,7 +88,7 @@ func parserPkg(pkgRealpath, pkgpath string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
genRouterCode()
|
||||
genRouterCode(pkgRealpath)
|
||||
savetoFile(pkgRealpath)
|
||||
return nil
|
||||
}
|
||||
@ -99,7 +101,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
|
||||
elements := strings.TrimLeft(t, "@router ")
|
||||
e1 := strings.SplitN(elements, " ", 2)
|
||||
if len(e1) < 1 {
|
||||
return errors.New("you should has router infomation")
|
||||
return errors.New("you should has router information")
|
||||
}
|
||||
key := pkgpath + ":" + controllerName
|
||||
cc := ControllerComments{}
|
||||
@ -129,9 +131,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
|
||||
return nil
|
||||
}
|
||||
|
||||
func genRouterCode() {
|
||||
os.Mkdir(path.Join(AppPath, "routers"), 0755)
|
||||
Info("generate router from comments")
|
||||
func genRouterCode(pkgRealpath string) {
|
||||
os.Mkdir(getRouterDir(pkgRealpath), 0755)
|
||||
logs.Info("generate router from comments")
|
||||
var (
|
||||
globalinfo string
|
||||
sortKey []string
|
||||
@ -164,15 +166,15 @@ func genRouterCode() {
|
||||
globalinfo = globalinfo + `
|
||||
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
|
||||
beego.ControllerComments{
|
||||
"` + strings.TrimSpace(c.Method) + `",
|
||||
` + "`" + c.Router + "`" + `,
|
||||
` + allmethod + `,
|
||||
` + params + `})
|
||||
Method: "` + strings.TrimSpace(c.Method) + `",
|
||||
` + "Router: `" + c.Router + "`" + `,
|
||||
AllowHTTPMethods: ` + allmethod + `,
|
||||
Params: ` + params + `})
|
||||
`
|
||||
}
|
||||
}
|
||||
if globalinfo != "" {
|
||||
f, err := os.Create(path.Join(AppPath, "routers", commentFilename))
|
||||
f, err := os.Create(filepath.Join(getRouterDir(pkgRealpath), commentFilename))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -182,7 +184,7 @@ func genRouterCode() {
|
||||
}
|
||||
|
||||
func compareFile(pkgRealpath string) bool {
|
||||
if !utils.FileExists(path.Join(AppPath, "routers", commentFilename)) {
|
||||
if !utils.FileExists(filepath.Join(getRouterDir(pkgRealpath), commentFilename)) {
|
||||
return true
|
||||
}
|
||||
if utils.FileExists(lastupdateFilename) {
|
||||
@ -229,3 +231,19 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) {
|
||||
}
|
||||
return lastupdate, nil
|
||||
}
|
||||
|
||||
func getRouterDir(pkgRealpath string) string {
|
||||
dir := filepath.Dir(pkgRealpath)
|
||||
for {
|
||||
d := filepath.Join(dir, "routers")
|
||||
if utils.FileExists(d) {
|
||||
return d
|
||||
}
|
||||
|
||||
if r, _ := filepath.Rel(dir, AppPath); r == "." {
|
||||
return d
|
||||
}
|
||||
// Parent dir.
|
||||
dir = filepath.Dir(dir)
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
//
|
||||
// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360))
|
||||
//
|
||||
// Infomation:
|
||||
// Information:
|
||||
//
|
||||
// In the request user should include these params in the query
|
||||
//
|
||||
|
415
router.go
415
router.go
@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
beecontext "github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/toolbox"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
@ -114,7 +115,7 @@ type controllerInfo struct {
|
||||
type ControllerRegister struct {
|
||||
routers map[string]*Tree
|
||||
enableFilter bool
|
||||
filters map[int][]*FilterRouter
|
||||
filters [FinishRouter + 1][]*FilterRouter
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
@ -122,7 +123,6 @@ type ControllerRegister struct {
|
||||
func NewControllerRegister() *ControllerRegister {
|
||||
cr := &ControllerRegister{
|
||||
routers: make(map[string]*Tree),
|
||||
filters: make(map[int][]*FilterRouter),
|
||||
}
|
||||
cr.pool.New = func() interface{} {
|
||||
return beecontext.NewContext()
|
||||
@ -406,29 +406,39 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface)
|
||||
}
|
||||
|
||||
// InsertFilter Add a FilterFunc with pattern rule and action constant.
|
||||
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
|
||||
// params is for:
|
||||
// 1. setting the returnOnOutput value (false allows multiple filters to execute)
|
||||
// 2. determining whether or not params need to be reset.
|
||||
func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error {
|
||||
|
||||
mr := new(FilterRouter)
|
||||
mr.tree = NewTree()
|
||||
mr.pattern = pattern
|
||||
mr.filterFunc = filter
|
||||
if !BConfig.RouterCaseSensitive {
|
||||
pattern = strings.ToLower(pattern)
|
||||
mr := &FilterRouter{
|
||||
tree: NewTree(),
|
||||
pattern: pattern,
|
||||
filterFunc: filter,
|
||||
returnOnOutput: true,
|
||||
}
|
||||
if len(params) == 0 {
|
||||
mr.returnOnOutput = true
|
||||
} else {
|
||||
if !BConfig.RouterCaseSensitive {
|
||||
mr.pattern = strings.ToLower(pattern)
|
||||
}
|
||||
|
||||
paramsLen := len(params)
|
||||
if paramsLen > 0 {
|
||||
mr.returnOnOutput = params[0]
|
||||
}
|
||||
if paramsLen > 1 {
|
||||
mr.resetParams = params[1]
|
||||
}
|
||||
mr.tree.AddRouter(pattern, true)
|
||||
return p.insertFilterRouter(pos, mr)
|
||||
}
|
||||
|
||||
// add Filter into
|
||||
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) error {
|
||||
p.filters[pos] = append(p.filters[pos], mr)
|
||||
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) {
|
||||
if pos < BeforeStatic || pos > FinishRouter {
|
||||
err = fmt.Errorf("can not find your filter position")
|
||||
return
|
||||
}
|
||||
p.enableFilter = true
|
||||
p.filters[pos] = append(p.filters[pos], mr)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -437,11 +447,11 @@ func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) error
|
||||
func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) string {
|
||||
paths := strings.Split(endpoint, ".")
|
||||
if len(paths) <= 1 {
|
||||
Warn("urlfor endpoint must like path.controller.method")
|
||||
logs.Warn("urlfor endpoint must like path.controller.method")
|
||||
return ""
|
||||
}
|
||||
if len(values)%2 != 0 {
|
||||
Warn("urlfor params must key-value pair")
|
||||
logs.Warn("urlfor params must key-value pair")
|
||||
return ""
|
||||
}
|
||||
params := make(map[string]string)
|
||||
@ -577,21 +587,27 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (p *ControllerRegister) execFilter(context *beecontext.Context, pos int, urlPath string) (started bool) {
|
||||
if p.enableFilter {
|
||||
if l, ok := p.filters[pos]; ok {
|
||||
for _, filterR := range l {
|
||||
if filterR.returnOnOutput && context.ResponseWriter.Started {
|
||||
return true
|
||||
}
|
||||
if ok := filterR.ValidRouter(urlPath, context); ok {
|
||||
filterR.filterFunc(context)
|
||||
}
|
||||
if filterR.returnOnOutput && context.ResponseWriter.Started {
|
||||
return true
|
||||
func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath string, pos int) (started bool) {
|
||||
var preFilterParams map[string]string
|
||||
for _, filterR := range p.filters[pos] {
|
||||
if filterR.returnOnOutput && context.ResponseWriter.Started {
|
||||
return true
|
||||
}
|
||||
if filterR.resetParams {
|
||||
preFilterParams = context.Input.Params()
|
||||
}
|
||||
if ok := filterR.ValidRouter(urlPath, context); ok {
|
||||
filterR.filterFunc(context)
|
||||
if filterR.resetParams {
|
||||
context.Input.ResetParams()
|
||||
for k, v := range preFilterParams {
|
||||
context.Input.SetParam(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if filterR.returnOnOutput && context.ResponseWriter.Started {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -604,6 +620,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
findRouter bool
|
||||
runMethod string
|
||||
routerInfo *controllerInfo
|
||||
isRunnable bool
|
||||
)
|
||||
context := p.pool.Get().(*beecontext.Context)
|
||||
context.Reset(rw, r)
|
||||
@ -617,11 +634,10 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
context.Output.Header("Server", BConfig.ServerName)
|
||||
}
|
||||
|
||||
var urlPath string
|
||||
var urlPath = r.URL.Path
|
||||
|
||||
if !BConfig.RouterCaseSensitive {
|
||||
urlPath = strings.ToLower(r.URL.Path)
|
||||
} else {
|
||||
urlPath = r.URL.Path
|
||||
urlPath = strings.ToLower(urlPath)
|
||||
}
|
||||
|
||||
// filter wrong http method
|
||||
@ -631,11 +647,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// filter for static file
|
||||
if p.execFilter(context, BeforeStatic, urlPath) {
|
||||
if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) {
|
||||
goto Admin
|
||||
}
|
||||
|
||||
serverStaticRouter(context)
|
||||
|
||||
if context.ResponseWriter.Started {
|
||||
findRouter = true
|
||||
goto Admin
|
||||
@ -653,9 +670,9 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
var err error
|
||||
context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r)
|
||||
if err != nil {
|
||||
Error(err)
|
||||
logs.Error(err)
|
||||
exception("503", context)
|
||||
return
|
||||
goto Admin
|
||||
}
|
||||
defer func() {
|
||||
if context.Input.CruSession != nil {
|
||||
@ -663,148 +680,139 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if p.execFilter(context, BeforeRouter, urlPath) {
|
||||
if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) {
|
||||
goto Admin
|
||||
}
|
||||
|
||||
if !findRouter {
|
||||
httpMethod := r.Method
|
||||
if t, ok := p.routers[httpMethod]; ok {
|
||||
runObject := t.Match(urlPath, context)
|
||||
if r, ok := runObject.(*controllerInfo); ok {
|
||||
routerInfo = r
|
||||
findRouter = true
|
||||
if splat := context.Input.Param(":splat"); splat != "" {
|
||||
for k, v := range strings.Split(splat, "/") {
|
||||
context.Input.SetParam(strconv.Itoa(k), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
routerInfo, findRouter = p.FindRouter(context)
|
||||
//if no matches to url, throw a not found exception
|
||||
if !findRouter {
|
||||
exception("404", context)
|
||||
goto Admin
|
||||
}
|
||||
|
||||
if findRouter {
|
||||
//execute middleware filters
|
||||
if p.execFilter(context, BeforeExec, urlPath) {
|
||||
goto Admin
|
||||
}
|
||||
isRunnable := false
|
||||
if routerInfo != nil {
|
||||
if routerInfo.routerType == routerTypeRESTFul {
|
||||
if _, ok := routerInfo.methods[r.Method]; ok {
|
||||
isRunnable = true
|
||||
routerInfo.runFunction(context)
|
||||
} else {
|
||||
exception("405", context)
|
||||
goto Admin
|
||||
}
|
||||
} else if routerInfo.routerType == routerTypeHandler {
|
||||
isRunnable = true
|
||||
routerInfo.handler.ServeHTTP(rw, r)
|
||||
} else {
|
||||
runRouter = routerInfo.controllerType
|
||||
method := r.Method
|
||||
if r.Method == "POST" && context.Input.Query("_method") == "PUT" {
|
||||
method = "PUT"
|
||||
}
|
||||
if r.Method == "POST" && context.Input.Query("_method") == "DELETE" {
|
||||
method = "DELETE"
|
||||
}
|
||||
if m, ok := routerInfo.methods[method]; ok {
|
||||
runMethod = m
|
||||
} else if m, ok = routerInfo.methods["*"]; ok {
|
||||
runMethod = m
|
||||
} else {
|
||||
runMethod = method
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also defined runRouter & runMethod from filter
|
||||
if !isRunnable {
|
||||
//Invoke the request handler
|
||||
vc := reflect.New(runRouter)
|
||||
execController, ok := vc.Interface().(ControllerInterface)
|
||||
if !ok {
|
||||
panic("controller is not ControllerInterface")
|
||||
}
|
||||
|
||||
//call the controller init function
|
||||
execController.Init(context, runRouter.Name(), runMethod, vc.Interface())
|
||||
|
||||
//call prepare function
|
||||
execController.Prepare()
|
||||
|
||||
//if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
|
||||
if BConfig.WebConfig.EnableXSRF {
|
||||
execController.XSRFToken()
|
||||
if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
|
||||
(r.Method == "POST" && (context.Input.Query("_method") == "DELETE" || context.Input.Query("_method") == "PUT")) {
|
||||
execController.CheckXSRFCookie()
|
||||
}
|
||||
}
|
||||
|
||||
execController.URLMapping()
|
||||
|
||||
if !context.ResponseWriter.Started {
|
||||
//exec main logic
|
||||
switch runMethod {
|
||||
case "GET":
|
||||
execController.Get()
|
||||
case "POST":
|
||||
execController.Post()
|
||||
case "DELETE":
|
||||
execController.Delete()
|
||||
case "PUT":
|
||||
execController.Put()
|
||||
case "HEAD":
|
||||
execController.Head()
|
||||
case "PATCH":
|
||||
execController.Patch()
|
||||
case "OPTIONS":
|
||||
execController.Options()
|
||||
default:
|
||||
if !execController.HandlerFunc(runMethod) {
|
||||
var in []reflect.Value
|
||||
method := vc.MethodByName(runMethod)
|
||||
method.Call(in)
|
||||
}
|
||||
}
|
||||
|
||||
//render template
|
||||
if !context.ResponseWriter.Started && context.Output.Status == 0 {
|
||||
if BConfig.WebConfig.AutoRender {
|
||||
if err := execController.Render(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finish all runRouter. release resource
|
||||
execController.Finish()
|
||||
}
|
||||
|
||||
//execute middleware filters
|
||||
if p.execFilter(context, AfterExec, urlPath) {
|
||||
goto Admin
|
||||
if splat := context.Input.Param(":splat"); splat != "" {
|
||||
for k, v := range strings.Split(splat, "/") {
|
||||
context.Input.SetParam(strconv.Itoa(k), v)
|
||||
}
|
||||
}
|
||||
|
||||
p.execFilter(context, FinishRouter, urlPath)
|
||||
//store router pattern into context
|
||||
context.Input.SetData("RouterPattern", routerInfo.pattern)
|
||||
|
||||
//execute middleware filters
|
||||
if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
|
||||
goto Admin
|
||||
}
|
||||
|
||||
if routerInfo != nil {
|
||||
if routerInfo.routerType == routerTypeRESTFul {
|
||||
if _, ok := routerInfo.methods[r.Method]; ok {
|
||||
isRunnable = true
|
||||
routerInfo.runFunction(context)
|
||||
} else {
|
||||
exception("405", context)
|
||||
goto Admin
|
||||
}
|
||||
} else if routerInfo.routerType == routerTypeHandler {
|
||||
isRunnable = true
|
||||
routerInfo.handler.ServeHTTP(rw, r)
|
||||
} else {
|
||||
runRouter = routerInfo.controllerType
|
||||
method := r.Method
|
||||
if r.Method == "POST" && context.Input.Query("_method") == "PUT" {
|
||||
method = "PUT"
|
||||
}
|
||||
if r.Method == "POST" && context.Input.Query("_method") == "DELETE" {
|
||||
method = "DELETE"
|
||||
}
|
||||
if m, ok := routerInfo.methods[method]; ok {
|
||||
runMethod = m
|
||||
} else if m, ok = routerInfo.methods["*"]; ok {
|
||||
runMethod = m
|
||||
} else {
|
||||
runMethod = method
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also defined runRouter & runMethod from filter
|
||||
if !isRunnable {
|
||||
//Invoke the request handler
|
||||
vc := reflect.New(runRouter)
|
||||
execController, ok := vc.Interface().(ControllerInterface)
|
||||
if !ok {
|
||||
panic("controller is not ControllerInterface")
|
||||
}
|
||||
|
||||
//call the controller init function
|
||||
execController.Init(context, runRouter.Name(), runMethod, vc.Interface())
|
||||
|
||||
//call prepare function
|
||||
execController.Prepare()
|
||||
|
||||
//if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
|
||||
if BConfig.WebConfig.EnableXSRF {
|
||||
execController.XSRFToken()
|
||||
if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
|
||||
(r.Method == "POST" && (context.Input.Query("_method") == "DELETE" || context.Input.Query("_method") == "PUT")) {
|
||||
execController.CheckXSRFCookie()
|
||||
}
|
||||
}
|
||||
|
||||
execController.URLMapping()
|
||||
|
||||
if !context.ResponseWriter.Started {
|
||||
//exec main logic
|
||||
switch runMethod {
|
||||
case "GET":
|
||||
execController.Get()
|
||||
case "POST":
|
||||
execController.Post()
|
||||
case "DELETE":
|
||||
execController.Delete()
|
||||
case "PUT":
|
||||
execController.Put()
|
||||
case "HEAD":
|
||||
execController.Head()
|
||||
case "PATCH":
|
||||
execController.Patch()
|
||||
case "OPTIONS":
|
||||
execController.Options()
|
||||
default:
|
||||
if !execController.HandlerFunc(runMethod) {
|
||||
var in []reflect.Value
|
||||
method := vc.MethodByName(runMethod)
|
||||
method.Call(in)
|
||||
}
|
||||
}
|
||||
|
||||
//render template
|
||||
if !context.ResponseWriter.Started && context.Output.Status == 0 {
|
||||
if BConfig.WebConfig.AutoRender {
|
||||
if err := execController.Render(); err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finish all runRouter. release resource
|
||||
execController.Finish()
|
||||
}
|
||||
|
||||
//execute middleware filters
|
||||
if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) {
|
||||
goto Admin
|
||||
}
|
||||
|
||||
if len(p.filters[FinishRouter]) > 0 && p.execFilter(context, urlPath, FinishRouter) {
|
||||
goto Admin
|
||||
}
|
||||
|
||||
Admin:
|
||||
timeDur := time.Since(startTime)
|
||||
//admin module record QPS
|
||||
if BConfig.Listen.EnableAdmin {
|
||||
timeDur := time.Since(startTime)
|
||||
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur) {
|
||||
if runRouter != nil {
|
||||
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
|
||||
@ -815,18 +823,36 @@ Admin:
|
||||
}
|
||||
|
||||
if BConfig.RunMode == DEV || BConfig.Log.AccessLogs {
|
||||
timeDur := time.Since(startTime)
|
||||
var devInfo string
|
||||
|
||||
statusCode := context.ResponseWriter.Status
|
||||
if statusCode == 0 {
|
||||
statusCode = 200
|
||||
}
|
||||
|
||||
iswin := (runtime.GOOS == "windows")
|
||||
statusColor := logs.ColorByStatus(iswin, statusCode)
|
||||
methodColor := logs.ColorByMethod(iswin, r.Method)
|
||||
resetColor := logs.ColorByMethod(iswin, "")
|
||||
|
||||
if findRouter {
|
||||
if routerInfo != nil {
|
||||
devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, timeDur.String(), "match", routerInfo.pattern)
|
||||
devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode,
|
||||
resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path,
|
||||
routerInfo.pattern)
|
||||
} else {
|
||||
devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeDur.String(), "match")
|
||||
devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor,
|
||||
timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path)
|
||||
}
|
||||
} else {
|
||||
devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeDur.String(), "notmatch")
|
||||
devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor,
|
||||
timeDur.String(), "nomatch", methodColor, resetColor, r.Method, r.URL.Path)
|
||||
}
|
||||
if DefaultAccessLogFilter == nil || !DefaultAccessLogFilter.Filter(context) {
|
||||
Debug(devInfo)
|
||||
if iswin {
|
||||
logs.W32Debug(devInfo)
|
||||
} else {
|
||||
logs.Debug(devInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,6 +862,22 @@ Admin:
|
||||
}
|
||||
}
|
||||
|
||||
// FindRouter Find Router info for URL
|
||||
func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *controllerInfo, isFind bool) {
|
||||
var urlPath = context.Input.URL()
|
||||
if !BConfig.RouterCaseSensitive {
|
||||
urlPath = strings.ToLower(urlPath)
|
||||
}
|
||||
httpMethod := context.Input.Method()
|
||||
if t, ok := p.routers[httpMethod]; ok {
|
||||
runObject := t.Match(urlPath, context)
|
||||
if r, ok := runObject.(*controllerInfo); ok {
|
||||
return r, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ControllerRegister) recoverPanic(context *beecontext.Context) {
|
||||
if err := recover(); err != nil {
|
||||
if err == ErrAbort {
|
||||
@ -843,27 +885,26 @@ func (p *ControllerRegister) recoverPanic(context *beecontext.Context) {
|
||||
}
|
||||
if !BConfig.RecoverPanic {
|
||||
panic(err)
|
||||
} else {
|
||||
if BConfig.EnableErrorsShow {
|
||||
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
||||
exception(fmt.Sprint(err), context)
|
||||
return
|
||||
}
|
||||
}
|
||||
if BConfig.EnableErrorsShow {
|
||||
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
||||
exception(fmt.Sprint(err), context)
|
||||
return
|
||||
}
|
||||
var stack string
|
||||
Critical("the request url is ", context.Input.URL())
|
||||
Critical("Handler crashed with error", err)
|
||||
for i := 1; ; i++ {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
Critical(fmt.Sprintf("%s:%d", file, line))
|
||||
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||
}
|
||||
if BConfig.RunMode == DEV {
|
||||
showErr(err, context, stack)
|
||||
}
|
||||
var stack string
|
||||
logs.Critical("the request url is ", context.Input.URL())
|
||||
logs.Critical("Handler crashed with error", err)
|
||||
for i := 1; ; i++ {
|
||||
_, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
logs.Critical(fmt.Sprintf("%s:%d", file, line))
|
||||
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||
}
|
||||
if BConfig.RunMode == DEV {
|
||||
showErr(err, context, stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
type TestController struct {
|
||||
@ -94,7 +95,7 @@ func TestUrlFor(t *testing.T) {
|
||||
handler.Add("/api/list", &TestController{}, "*:List")
|
||||
handler.Add("/person/:last/:first", &TestController{}, "*:Param")
|
||||
if a := handler.URLFor("TestController.List"); a != "/api/list" {
|
||||
Info(a)
|
||||
logs.Info(a)
|
||||
t.Errorf("TestController.List must equal to /api/list")
|
||||
}
|
||||
if a := handler.URLFor("TestController.Param", ":last", "xie", ":first", "asta"); a != "/person/xie/asta" {
|
||||
@ -120,24 +121,24 @@ func TestUrlFor2(t *testing.T) {
|
||||
handler.Add("/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", &TestController{}, "*:Param")
|
||||
handler.Add("/:year:int/:month:int/:title/:entid", &TestController{})
|
||||
if handler.URLFor("TestController.GetURL", ":username", "astaxie") != "/v1/astaxie/edit" {
|
||||
Info(handler.URLFor("TestController.GetURL"))
|
||||
logs.Info(handler.URLFor("TestController.GetURL"))
|
||||
t.Errorf("TestController.List must equal to /v1/astaxie/edit")
|
||||
}
|
||||
|
||||
if handler.URLFor("TestController.List", ":v", "za", ":id", "12", ":page", "123") !=
|
||||
"/v1/za/cms_12_123.html" {
|
||||
Info(handler.URLFor("TestController.List"))
|
||||
logs.Info(handler.URLFor("TestController.List"))
|
||||
t.Errorf("TestController.List must equal to /v1/za/cms_12_123.html")
|
||||
}
|
||||
if handler.URLFor("TestController.Param", ":v", "za", ":id", "12", ":page", "123") !=
|
||||
"/v1/za_cms/ttt_12_123.html" {
|
||||
Info(handler.URLFor("TestController.Param"))
|
||||
logs.Info(handler.URLFor("TestController.Param"))
|
||||
t.Errorf("TestController.List must equal to /v1/za_cms/ttt_12_123.html")
|
||||
}
|
||||
if handler.URLFor("TestController.Get", ":year", "1111", ":month", "11",
|
||||
":title", "aaaa", ":entid", "aaaa") !=
|
||||
"/1111/11/aaaa/aaaa" {
|
||||
Info(handler.URLFor("TestController.Get"))
|
||||
logs.Info(handler.URLFor("TestController.Get"))
|
||||
t.Errorf("TestController.Get must equal to /1111/11/aaaa/aaaa")
|
||||
}
|
||||
}
|
||||
@ -419,6 +420,74 @@ func testRequest(method, path string) (*httptest.ResponseRecorder, *http.Request
|
||||
return recorder, request
|
||||
}
|
||||
|
||||
// Expectation: A Filter with the correct configuration should be created given
|
||||
// specific parameters.
|
||||
func TestInsertFilter(t *testing.T) {
|
||||
testName := "TestInsertFilter"
|
||||
|
||||
mux := NewControllerRegister()
|
||||
mux.InsertFilter("*", BeforeRouter, func(*context.Context) {})
|
||||
if !mux.filters[BeforeRouter][0].returnOnOutput {
|
||||
t.Errorf(
|
||||
"%s: passing no variadic params should set returnOnOutput to true",
|
||||
testName)
|
||||
}
|
||||
if mux.filters[BeforeRouter][0].resetParams {
|
||||
t.Errorf(
|
||||
"%s: passing no variadic params should set resetParams to false",
|
||||
testName)
|
||||
}
|
||||
|
||||
mux = NewControllerRegister()
|
||||
mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, false)
|
||||
if mux.filters[BeforeRouter][0].returnOnOutput {
|
||||
t.Errorf(
|
||||
"%s: passing false as 1st variadic param should set returnOnOutput to false",
|
||||
testName)
|
||||
}
|
||||
|
||||
mux = NewControllerRegister()
|
||||
mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, true, true)
|
||||
if !mux.filters[BeforeRouter][0].resetParams {
|
||||
t.Errorf(
|
||||
"%s: passing true as 2nd variadic param should set resetParams to true",
|
||||
testName)
|
||||
}
|
||||
}
|
||||
|
||||
// Expectation: the second variadic arg should cause the execution of the filter
|
||||
// to preserve the parameters from before its execution.
|
||||
func TestParamResetFilter(t *testing.T) {
|
||||
testName := "TestParamResetFilter"
|
||||
route := "/beego/*" // splat
|
||||
path := "/beego/routes/routes"
|
||||
|
||||
mux := NewControllerRegister()
|
||||
|
||||
mux.InsertFilter("*", BeforeExec, beegoResetParams, true, true)
|
||||
|
||||
mux.Get(route, beegoHandleResetParams)
|
||||
|
||||
rw, r := testRequest("GET", path)
|
||||
mux.ServeHTTP(rw, r)
|
||||
|
||||
// The two functions, `beegoResetParams` and `beegoHandleResetParams` add
|
||||
// a response header of `Splat`. The expectation here is that that Header
|
||||
// value should match what the _request's_ router set, not the filter's.
|
||||
|
||||
headers := rw.HeaderMap
|
||||
if len(headers["Splat"]) != 1 {
|
||||
t.Errorf(
|
||||
"%s: There was an error in the test. Splat param not set in Header",
|
||||
testName)
|
||||
}
|
||||
if headers["Splat"][0] != "routes/routes" {
|
||||
t.Errorf(
|
||||
"%s: expected `:splat` param to be [routes/routes] but it was [%s]",
|
||||
testName, headers["Splat"][0])
|
||||
}
|
||||
}
|
||||
|
||||
// Execution point: BeforeRouter
|
||||
// expectation: only BeforeRouter function is executed, notmatch output as router doesn't handle
|
||||
func TestFilterBeforeRouter(t *testing.T) {
|
||||
@ -611,3 +680,10 @@ func beegoFinishRouter1(ctx *context.Context) {
|
||||
func beegoFinishRouter2(ctx *context.Context) {
|
||||
ctx.WriteString("|FinishRouter2")
|
||||
}
|
||||
func beegoResetParams(ctx *context.Context) {
|
||||
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
|
||||
}
|
||||
|
||||
func beegoHandleResetParams(ctx *context.Context) {
|
||||
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
|
||||
}
|
||||
|
@ -115,7 +115,6 @@ func (st *SessionStore) SessionRelease(w http.ResponseWriter) {
|
||||
}
|
||||
st.c.Exec("UPDATE "+TableName+" set `session_data`=?, `session_expiry`=? where session_key=?",
|
||||
b, time.Now().Unix(), st.sid)
|
||||
|
||||
}
|
||||
|
||||
// Provider mysql session provider
|
||||
|
@ -15,6 +15,7 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@ -23,7 +24,11 @@ import (
|
||||
|
||||
func TestCookie(t *testing.T) {
|
||||
config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
|
||||
globalSessions, err := NewManager("cookie", config)
|
||||
conf := new(ManagerConfig)
|
||||
if err := json.Unmarshal([]byte(config), conf); err != nil {
|
||||
t.Fatal("json decode error", err)
|
||||
}
|
||||
globalSessions, err := NewManager("cookie", conf)
|
||||
if err != nil {
|
||||
t.Fatal("init cookie session err", err)
|
||||
}
|
||||
@ -56,7 +61,11 @@ func TestCookie(t *testing.T) {
|
||||
|
||||
func TestDestorySessionCookie(t *testing.T) {
|
||||
config := `{"cookieName":"gosessionid","enableSetCookie":true,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
|
||||
globalSessions, err := NewManager("cookie", config)
|
||||
conf := new(ManagerConfig)
|
||||
if err := json.Unmarshal([]byte(config), conf); err != nil {
|
||||
t.Fatal("json decode error", err)
|
||||
}
|
||||
globalSessions, err := NewManager("cookie", conf)
|
||||
if err != nil {
|
||||
t.Fatal("init cookie session err", err)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -82,14 +81,17 @@ func (fs *FileSessionStore) SessionID() string {
|
||||
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
|
||||
b, err := EncodeGob(fs.values)
|
||||
if err != nil {
|
||||
SLogger.Println(err)
|
||||
return
|
||||
}
|
||||
_, err = os.Stat(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
|
||||
var f *os.File
|
||||
if err == nil {
|
||||
f, err = os.OpenFile(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid), os.O_RDWR, 0777)
|
||||
SLogger.Println(err)
|
||||
} else if os.IsNotExist(err) {
|
||||
f, err = os.Create(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
|
||||
SLogger.Println(err)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@ -123,7 +125,7 @@ func (fp *FileProvider) SessionRead(sid string) (Store, error) {
|
||||
|
||||
err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
SLogger.Println(err.Error())
|
||||
}
|
||||
_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
|
||||
var f *os.File
|
||||
@ -191,7 +193,7 @@ func (fp *FileProvider) SessionAll() int {
|
||||
return a.visit(path, f, err)
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("filepath.Walk() returned %v\n", err)
|
||||
SLogger.Printf("filepath.Walk() returned %v\n", err)
|
||||
return 0
|
||||
}
|
||||
return a.total
|
||||
@ -205,11 +207,11 @@ func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
|
||||
|
||||
err := os.MkdirAll(path.Join(fp.savePath, string(oldsid[0]), string(oldsid[1])), 0777)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
SLogger.Println(err.Error())
|
||||
}
|
||||
err = os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
SLogger.Println(err.Error())
|
||||
}
|
||||
_, err = os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
|
||||
var newf *os.File
|
||||
|
@ -15,6 +15,7 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@ -22,7 +23,12 @@ import (
|
||||
)
|
||||
|
||||
func TestMem(t *testing.T) {
|
||||
globalSessions, _ := NewManager("memory", `{"cookieName":"gosessionid","gclifetime":10}`)
|
||||
config := `{"cookieName":"gosessionid","gclifetime":10, "enableSetCookie":true}`
|
||||
conf := new(ManagerConfig)
|
||||
if err := json.Unmarshal([]byte(config), conf); err != nil {
|
||||
t.Fatal("json decode error", err)
|
||||
}
|
||||
globalSessions, _ := NewManager("memory", conf)
|
||||
go globalSessions.GC()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
@ -89,7 +89,7 @@ func TestCookieEncodeDecode(t *testing.T) {
|
||||
|
||||
func TestParseConfig(t *testing.T) {
|
||||
s := `{"cookieName":"gosessionid","gclifetime":3600}`
|
||||
cf := new(managerConfig)
|
||||
cf := new(ManagerConfig)
|
||||
cf.EnableSetCookie = true
|
||||
err := json.Unmarshal([]byte(s), cf)
|
||||
if err != nil {
|
||||
@ -103,7 +103,7 @@ func TestParseConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
cc := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
|
||||
cf2 := new(managerConfig)
|
||||
cf2 := new(ManagerConfig)
|
||||
cf2.EnableSetCookie = true
|
||||
err = json.Unmarshal([]byte(cc), cf2)
|
||||
if err != nil {
|
||||
|
@ -30,10 +30,14 @@ package session
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -61,6 +65,9 @@ type Provider interface {
|
||||
|
||||
var provides = make(map[string]Provider)
|
||||
|
||||
// SLogger a helpful variable to log information about session
|
||||
var SLogger = NewSessionLog(os.Stderr)
|
||||
|
||||
// Register makes a session provide available by the provided name.
|
||||
// If Register is called twice with the same name or if driver is nil,
|
||||
// it panics.
|
||||
@ -74,22 +81,25 @@ func Register(name string, provide Provider) {
|
||||
provides[name] = provide
|
||||
}
|
||||
|
||||
type managerConfig struct {
|
||||
CookieName string `json:"cookieName"`
|
||||
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
|
||||
Gclifetime int64 `json:"gclifetime"`
|
||||
Maxlifetime int64 `json:"maxLifetime"`
|
||||
Secure bool `json:"secure"`
|
||||
CookieLifeTime int `json:"cookieLifeTime"`
|
||||
ProviderConfig string `json:"providerConfig"`
|
||||
Domain string `json:"domain"`
|
||||
SessionIDLength int64 `json:"sessionIDLength"`
|
||||
type ManagerConfig struct {
|
||||
CookieName string `json:"cookieName"`
|
||||
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
|
||||
Gclifetime int64 `json:"gclifetime"`
|
||||
Maxlifetime int64 `json:"maxLifetime"`
|
||||
Secure bool `json:"secure"`
|
||||
CookieLifeTime int `json:"cookieLifeTime"`
|
||||
ProviderConfig string `json:"providerConfig"`
|
||||
Domain string `json:"domain"`
|
||||
SessionIDLength int64 `json:"sessionIDLength"`
|
||||
EnableSidInHttpHeader bool `json:"enableSidInHttpHeader"`
|
||||
SessionNameInHttpHeader string `json:"sessionNameInHttpHeader"`
|
||||
EnableSidInUrlQuery bool `json:"enableSidInUrlQuery"`
|
||||
}
|
||||
|
||||
// Manager contains Provider and its configuration.
|
||||
type Manager struct {
|
||||
provider Provider
|
||||
config *managerConfig
|
||||
config *ManagerConfig
|
||||
}
|
||||
|
||||
// NewManager Create new Manager with provider name and json config string.
|
||||
@ -104,21 +114,29 @@ type Manager struct {
|
||||
// 2. hashfunc default sha1
|
||||
// 3. hashkey default beegosessionkey
|
||||
// 4. maxage default is none
|
||||
func NewManager(provideName, config string) (*Manager, error) {
|
||||
func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) {
|
||||
provider, ok := provides[provideName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
|
||||
}
|
||||
cf := new(managerConfig)
|
||||
cf.EnableSetCookie = true
|
||||
err := json.Unmarshal([]byte(config), cf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cf.Maxlifetime == 0 {
|
||||
cf.Maxlifetime = cf.Gclifetime
|
||||
}
|
||||
err = provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig)
|
||||
|
||||
if cf.EnableSidInHttpHeader {
|
||||
if cf.SessionNameInHttpHeader == "" {
|
||||
panic(errors.New("SessionNameInHttpHeader is empty"))
|
||||
}
|
||||
|
||||
strMimeHeader := textproto.CanonicalMIMEHeaderKey(cf.SessionNameInHttpHeader)
|
||||
if cf.SessionNameInHttpHeader != strMimeHeader {
|
||||
strErrMsg := "SessionNameInHttpHeader (" + cf.SessionNameInHttpHeader + ") has the wrong format, it should be like this : " + strMimeHeader
|
||||
panic(errors.New(strErrMsg))
|
||||
}
|
||||
}
|
||||
|
||||
err := provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -142,13 +160,25 @@ func NewManager(provideName, config string) (*Manager, error) {
|
||||
// otherwise return an valid session id.
|
||||
func (manager *Manager) getSid(r *http.Request) (string, error) {
|
||||
cookie, errs := r.Cookie(manager.config.CookieName)
|
||||
if errs != nil || cookie.Value == "" || cookie.MaxAge < 0 {
|
||||
errs := r.ParseForm()
|
||||
if errs != nil {
|
||||
return "", errs
|
||||
if errs != nil || cookie.Value == "" {
|
||||
var sid string
|
||||
if manager.config.EnableSidInUrlQuery {
|
||||
errs := r.ParseForm()
|
||||
if errs != nil {
|
||||
return "", errs
|
||||
}
|
||||
|
||||
sid = r.FormValue(manager.config.CookieName)
|
||||
}
|
||||
|
||||
// if not found in Cookie / param, then read it from request headers
|
||||
if manager.config.EnableSidInHttpHeader && sid == "" {
|
||||
sids, isFound := r.Header[manager.config.SessionNameInHttpHeader]
|
||||
if isFound && len(sids) != 0 {
|
||||
return sids[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
sid := r.FormValue(manager.config.CookieName)
|
||||
return sid, nil
|
||||
}
|
||||
|
||||
@ -175,6 +205,9 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
|
||||
}
|
||||
|
||||
session, err = manager.provider.SessionRead(sid)
|
||||
if err != nil {
|
||||
return nil, errs
|
||||
}
|
||||
cookie := &http.Cookie{
|
||||
Name: manager.config.CookieName,
|
||||
Value: url.QueryEscape(sid),
|
||||
@ -192,11 +225,21 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
|
||||
}
|
||||
r.AddCookie(cookie)
|
||||
|
||||
if manager.config.EnableSidInHttpHeader {
|
||||
r.Header.Set(manager.config.SessionNameInHttpHeader, sid)
|
||||
w.Header().Set(manager.config.SessionNameInHttpHeader, sid)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SessionDestroy Destroy session by its id in http request cookie.
|
||||
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
|
||||
if manager.config.EnableSidInHttpHeader {
|
||||
r.Header.Del(manager.config.SessionNameInHttpHeader)
|
||||
w.Header().Del(manager.config.SessionNameInHttpHeader)
|
||||
}
|
||||
|
||||
cookie, err := r.Cookie(manager.config.CookieName)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return
|
||||
@ -261,6 +304,12 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
r.AddCookie(cookie)
|
||||
|
||||
if manager.config.EnableSidInHttpHeader {
|
||||
r.Header.Set(manager.config.SessionNameInHttpHeader, sid)
|
||||
w.Header().Set(manager.config.SessionNameInHttpHeader, sid)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -296,3 +345,15 @@ func (manager *Manager) isSecure(req *http.Request) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Log implement the log.Logger
|
||||
type Log struct {
|
||||
*log.Logger
|
||||
}
|
||||
|
||||
// NewSessionLog set io.Writer to create a Logger for session.
|
||||
func NewSessionLog(out io.Writer) *Log {
|
||||
sl := new(Log)
|
||||
sl.Logger = log.New(out, "[SESSION]", 1e9)
|
||||
return sl
|
||||
}
|
||||
|
192
session/ssdb/sess_ssdb.go
Normal file
192
session/ssdb/sess_ssdb.go
Normal file
@ -0,0 +1,192 @@
|
||||
package ssdb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/astaxie/beego/session"
|
||||
"github.com/ssdb/gossdb/ssdb"
|
||||
)
|
||||
|
||||
var ssdbProvider = &SsdbProvider{}
|
||||
|
||||
type SsdbProvider struct {
|
||||
client *ssdb.Client
|
||||
host string
|
||||
port int
|
||||
maxLifetime int64
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) connectInit() error {
|
||||
var err error
|
||||
if p.host == "" || p.port == 0 {
|
||||
return errors.New("SessionInit First")
|
||||
}
|
||||
p.client, err = ssdb.Connect(p.host, p.port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) SessionInit(maxLifetime int64, savePath string) error {
|
||||
var e error = nil
|
||||
p.maxLifetime = maxLifetime
|
||||
address := strings.Split(savePath, ":")
|
||||
p.host = address[0]
|
||||
p.port, e = strconv.Atoi(address[1])
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
err := p.connectInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) SessionRead(sid string) (session.Store, error) {
|
||||
if p.client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var kv map[interface{}]interface{}
|
||||
value, err := p.client.Get(sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if value == nil || len(value.(string)) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob([]byte(value.(string)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rs := &SessionStore{sid: sid, values: kv, maxLifetime: p.maxLifetime, client: p.client}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) SessionExist(sid string) bool {
|
||||
if p.client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
value, err := p.client.Get(sid)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if value == nil || len(value.(string)) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
func (p *SsdbProvider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
|
||||
//conn.Do("setx", key, v, ttl)
|
||||
if p.client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
value, err := p.client.Get(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var kv map[interface{}]interface{}
|
||||
if value == nil || len(value.(string)) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob([]byte(value.(string)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = p.client.Del(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, e := p.client.Do("setx", sid, value, p.maxLifetime)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
rs := &SessionStore{sid: sid, values: kv, maxLifetime: p.maxLifetime, client: p.client}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) SessionDestroy(sid string) error {
|
||||
if p.client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := p.client.Del(sid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) SessionGC() {
|
||||
return
|
||||
}
|
||||
|
||||
func (p *SsdbProvider) SessionAll() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type SessionStore struct {
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
values map[interface{}]interface{}
|
||||
maxLifetime int64
|
||||
client *ssdb.Client
|
||||
}
|
||||
|
||||
func (s *SessionStore) Set(key, value interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.values[key] = value
|
||||
return nil
|
||||
}
|
||||
func (s *SessionStore) Get(key interface{}) interface{} {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if value, ok := s.values[key]; ok {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SessionStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
delete(s.values, key)
|
||||
return nil
|
||||
}
|
||||
func (s *SessionStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.values = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
func (s *SessionStore) SessionID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
func (s *SessionStore) SessionRelease(w http.ResponseWriter) {
|
||||
b, err := session.EncodeGob(s.values)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.client.Do("setx", s.sid, string(b), s.maxLifetime)
|
||||
|
||||
}
|
||||
func init() {
|
||||
session.Register("ssdb", ssdbProvider)
|
||||
}
|
@ -27,6 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
var errNotStaticRequest = errors.New("request not a static file request")
|
||||
@ -48,14 +49,23 @@ func serverStaticRouter(ctx *context.Context) {
|
||||
|
||||
if filePath == "" || fileInfo == nil {
|
||||
if BConfig.RunMode == DEV {
|
||||
Warn("Can't find/open the file:", filePath, err)
|
||||
logs.Warn("Can't find/open the file:", filePath, err)
|
||||
}
|
||||
http.NotFound(ctx.ResponseWriter, ctx.Request)
|
||||
return
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
//serveFile will list dir
|
||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, filePath)
|
||||
requestURL := ctx.Input.URL()
|
||||
if requestURL[len(requestURL)-1] != '/' {
|
||||
redirectURL := requestURL + "/"
|
||||
if ctx.Request.URL.RawQuery != "" {
|
||||
redirectURL = redirectURL + "?" + ctx.Request.URL.RawQuery
|
||||
}
|
||||
ctx.Redirect(302, redirectURL)
|
||||
} else {
|
||||
//serveFile will list dir
|
||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, filePath)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -67,7 +77,7 @@ func serverStaticRouter(ctx *context.Context) {
|
||||
b, n, sch, err := openFile(filePath, fileInfo, acceptEncoding)
|
||||
if err != nil {
|
||||
if BConfig.RunMode == DEV {
|
||||
Warn("Can't compress the file:", filePath, err)
|
||||
logs.Warn("Can't compress the file:", filePath, err)
|
||||
}
|
||||
http.NotFound(ctx.ResponseWriter, ctx.Request)
|
||||
return
|
||||
@ -157,13 +167,10 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) {
|
||||
return filePath, fi, nil
|
||||
}
|
||||
}
|
||||
return "", nil, errors.New(requestPath + " file not find")
|
||||
return "", nil, errNotStaticRequest
|
||||
}
|
||||
|
||||
for prefix, staticDir := range BConfig.WebConfig.StaticDir {
|
||||
if len(prefix) == 0 {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(requestPath, prefix) {
|
||||
continue
|
||||
}
|
||||
|
@ -1,160 +0,0 @@
|
||||
// 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 swagger struct definition
|
||||
package swagger
|
||||
|
||||
// SwaggerVersion show the current swagger version
|
||||
const SwaggerVersion = "1.2"
|
||||
|
||||
// ResourceListing list the resource
|
||||
type ResourceListing struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2
|
||||
// BasePath string `json:"basePath"` obsolete in 1.1
|
||||
APIs []APIRef `json:"apis"`
|
||||
Info Information `json:"info"`
|
||||
}
|
||||
|
||||
// APIRef description the api path and description
|
||||
type APIRef struct {
|
||||
Path string `json:"path"` // relative or absolute, must start with /
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// Information show the API Information
|
||||
type Information struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Contact string `json:"contact,omitempty"`
|
||||
TermsOfServiceURL string `json:"termsOfServiceUrl,omitempty"`
|
||||
License string `json:"license,omitempty"`
|
||||
LicenseURL string `json:"licenseUrl,omitempty"`
|
||||
}
|
||||
|
||||
// APIDeclaration see https://github.com/wordnik/swagger-core/blob/scala_2.10-1.3-RC3/schemas/api-declaration-schema.json
|
||||
type APIDeclaration struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
SwaggerVersion string `json:"swaggerVersion"`
|
||||
BasePath string `json:"basePath"`
|
||||
ResourcePath string `json:"resourcePath"` // must start with /
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
APIs []API `json:"apis,omitempty"`
|
||||
Models map[string]Model `json:"models,omitempty"`
|
||||
}
|
||||
|
||||
// API show tha API struct
|
||||
type API struct {
|
||||
Path string `json:"path"` // relative or absolute, must start with /
|
||||
Description string `json:"description"`
|
||||
Operations []Operation `json:"operations,omitempty"`
|
||||
}
|
||||
|
||||
// Operation desc the Operation
|
||||
type Operation struct {
|
||||
HTTPMethod string `json:"httpMethod"`
|
||||
Nickname string `json:"nickname"`
|
||||
Type string `json:"type"` // in 1.1 = DataType
|
||||
// ResponseClass string `json:"responseClass"` obsolete in 1.2
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty"`
|
||||
ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Authorizations []Authorization `json:"authorizations,omitempty"`
|
||||
Protocols []Protocol `json:"protocols,omitempty"`
|
||||
}
|
||||
|
||||
// Protocol support which Protocol
|
||||
type Protocol struct {
|
||||
}
|
||||
|
||||
// ResponseMessage Show the
|
||||
type ResponseMessage struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
ResponseModel string `json:"responseModel"`
|
||||
}
|
||||
|
||||
// Parameter desc the request parameters
|
||||
type Parameter struct {
|
||||
ParamType string `json:"paramType"` // path,query,body,header,form
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DataType string `json:"dataType"` // 1.2 needed?
|
||||
Type string `json:"type"` // integer
|
||||
Format string `json:"format"` // int64
|
||||
AllowMultiple bool `json:"allowMultiple"`
|
||||
Required bool `json:"required"`
|
||||
Minimum int `json:"minimum"`
|
||||
Maximum int `json:"maximum"`
|
||||
}
|
||||
|
||||
// ErrorResponse desc response
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// Model define the data model
|
||||
type Model struct {
|
||||
ID string `json:"id"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
Properties map[string]ModelProperty `json:"properties"`
|
||||
}
|
||||
|
||||
// ModelProperty define the properties
|
||||
type ModelProperty struct {
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Items map[string]string `json:"items,omitempty"`
|
||||
Format string `json:"format"`
|
||||
}
|
||||
|
||||
// Authorization see https://github.com/wordnik/swagger-core/wiki/authorizations
|
||||
type Authorization struct {
|
||||
LocalOAuth OAuth `json:"local-oauth"`
|
||||
APIKey APIKey `json:"apiKey"`
|
||||
}
|
||||
|
||||
// OAuth see https://github.com/wordnik/swagger-core/wiki/authorizations
|
||||
type OAuth struct {
|
||||
Type string `json:"type"` // e.g. oauth2
|
||||
Scopes []string `json:"scopes"` // e.g. PUBLIC
|
||||
GrantTypes map[string]GrantType `json:"grantTypes"`
|
||||
}
|
||||
|
||||
// GrantType see https://github.com/wordnik/swagger-core/wiki/authorizations
|
||||
type GrantType struct {
|
||||
LoginEndpoint Endpoint `json:"loginEndpoint"`
|
||||
TokenName string `json:"tokenName"` // e.g. access_code
|
||||
TokenRequestEndpoint Endpoint `json:"tokenRequestEndpoint"`
|
||||
TokenEndpoint Endpoint `json:"tokenEndpoint"`
|
||||
}
|
||||
|
||||
// Endpoint see https://github.com/wordnik/swagger-core/wiki/authorizations
|
||||
type Endpoint struct {
|
||||
URL string `json:"url"`
|
||||
ClientIDName string `json:"clientIdName"`
|
||||
ClientSecretName string `json:"clientSecretName"`
|
||||
TokenName string `json:"tokenName"`
|
||||
}
|
||||
|
||||
// APIKey see https://github.com/wordnik/swagger-core/wiki/authorizations
|
||||
type APIKey struct {
|
||||
Type string `json:"type"` // e.g. apiKey
|
||||
PassAs string `json:"passAs"` // e.g. header
|
||||
}
|
170
swagger/swagger.go
Normal file
170
swagger/swagger.go
Normal file
@ -0,0 +1,170 @@
|
||||
// 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.
|
||||
//
|
||||
// Swagger™ is a project used to describe and document RESTful APIs.
|
||||
//
|
||||
// The Swagger specification defines a set of files required to describe such an API. These files can then be used by the Swagger-UI project to display the API and Swagger-Codegen to generate clients in various languages. Additional utilities can also take advantage of the resulting files, such as testing tools.
|
||||
// Now in version 2.0, Swagger is more enabling than ever. And it's 100% open source software.
|
||||
|
||||
// Package swagger struct definition
|
||||
package swagger
|
||||
|
||||
// Swagger list the resource
|
||||
type Swagger struct {
|
||||
SwaggerVersion string `json:"swagger,omitempty"`
|
||||
Infos Information `json:"info"`
|
||||
Host string `json:"host,omitempty"`
|
||||
BasePath string `json:"basePath,omitempty"`
|
||||
Schemes []string `json:"schemes,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Paths map[string]*Item `json:"paths"`
|
||||
Definitions map[string]Schema `json:"definitions,omitempty"`
|
||||
SecurityDefinitions map[string]Scurity `json:"securityDefinitions,omitempty"`
|
||||
Security map[string][]string `json:"security,omitempty"`
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
// Information Provides metadata about the API. The metadata can be used by the clients if needed.
|
||||
type Information struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
TermsOfService string `json:"termsOfService,omitempty"`
|
||||
|
||||
Contact Contact `json:"contact,omitempty"`
|
||||
License License `json:"license,omitempty"`
|
||||
}
|
||||
|
||||
// Contact information for the exposed API.
|
||||
type Contact struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
EMail string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// License information for the exposed API.
|
||||
type License struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Item Describes the operations available on a single path.
|
||||
type Item struct {
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Get *Operation `json:"get,omitempty"`
|
||||
Put *Operation `json:"put,omitempty"`
|
||||
Post *Operation `json:"post,omitempty"`
|
||||
Delete *Operation `json:"delete,omitempty"`
|
||||
Options *Operation `json:"options,omitempty"`
|
||||
Head *Operation `json:"head,omitempty"`
|
||||
Patch *Operation `json:"patch,omitempty"`
|
||||
}
|
||||
|
||||
// Operation Describes a single API operation on a path.
|
||||
type Operation struct {
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
OperationID string `json:"operationId,omitempty"`
|
||||
Consumes []string `json:"consumes,omitempty"`
|
||||
Produces []string `json:"produces,omitempty"`
|
||||
Schemes []string `json:"schemes,omitempty"`
|
||||
Parameters []Parameter `json:"parameters,omitempty"`
|
||||
Responses map[string]Response `json:"responses,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
}
|
||||
|
||||
// Parameter Describes a single operation parameter.
|
||||
type Parameter struct {
|
||||
In string `json:"in,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Schema *Schema `json:"schema,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Items *ParameterItems `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body".
|
||||
// http://swagger.io/specification/#itemsObject
|
||||
type ParameterItems struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Items []*ParameterItems `json:"items,omitempty"` //Required if type is "array". Describes the type of items in the array.
|
||||
CollectionFormat string `json:"collectionFormat,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
// Schema Object allows the definition of input and output data types.
|
||||
type Schema struct {
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Items *Schema `json:"items,omitempty"`
|
||||
Properties map[string]Propertie `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
|
||||
type Propertie struct {
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Default string `json:"default,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Example string `json:"example,omitempty"`
|
||||
Required []string `json:"required,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
Properties map[string]Propertie `json:"properties,omitempty"`
|
||||
Items *Propertie `json:"items,omitempty"`
|
||||
AdditionalProperties *Propertie `json:"additionalProperties,omitempty"`
|
||||
}
|
||||
|
||||
// Response as they are returned from executing this operation.
|
||||
type Response struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Schema *Schema `json:"schema,omitempty"`
|
||||
Ref string `json:"$ref,omitempty"`
|
||||
}
|
||||
|
||||
// Scurity Allows the definition of a security scheme that can be used by the operations
|
||||
type Scurity struct {
|
||||
Type string `json:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2".
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
In string `json:"in,omitempty"` // Valid values are "query" or "header".
|
||||
Flow string `json:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode".
|
||||
AuthorizationURL string `json:"authorizationUrl,omitempty"`
|
||||
TokenURL string `json:"tokenUrl,omitempty"`
|
||||
Scopes map[string]string `json:"scopes,omitempty"` // The available scopes for the OAuth2 security scheme.
|
||||
}
|
||||
|
||||
// Tag Allows adding meta data to a single tag that is used by the Operation Object
|
||||
type Tag struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
||||
}
|
||||
|
||||
// ExternalDocs include Additional external documentation
|
||||
type ExternalDocs struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
44
template.go
44
template.go
@ -26,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
@ -36,17 +37,27 @@ var (
|
||||
templatesLock sync.RWMutex
|
||||
// beeTemplateExt stores the template extension which will build
|
||||
beeTemplateExt = []string{"tpl", "html"}
|
||||
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
|
||||
beeTemplateEngines = map[string]templatePreProcessor{}
|
||||
)
|
||||
|
||||
func executeTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
// ExecuteTemplate applies the template with name to the specified data object,
|
||||
// writing the output to wr.
|
||||
// A template will be executed safely in parallel.
|
||||
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)
|
||||
var err error
|
||||
if t.Lookup(name) != nil {
|
||||
err = t.ExecuteTemplate(wr, name, data)
|
||||
} else {
|
||||
err = t.Execute(wr, data)
|
||||
}
|
||||
if err != nil {
|
||||
Trace("template Execute err:", err)
|
||||
logs.Trace("template Execute err:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -88,6 +99,8 @@ func AddFuncMap(key string, fn interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error)
|
||||
|
||||
type templateFile struct {
|
||||
root string
|
||||
files map[string][]string
|
||||
@ -156,13 +169,22 @@ func BuildTemplate(dir string, files ...string) error {
|
||||
fmt.Printf("filepath.Walk() returned %v\n", err)
|
||||
return err
|
||||
}
|
||||
buildAllFiles := len(files) == 0
|
||||
for _, v := range self.files {
|
||||
for _, file := range v {
|
||||
if len(files) == 0 || utils.InSlice(file, files) {
|
||||
if buildAllFiles || utils.InSlice(file, files) {
|
||||
templatesLock.Lock()
|
||||
t, err := getTemplate(self.root, file, v...)
|
||||
ext := filepath.Ext(file)
|
||||
var t *template.Template
|
||||
if len(ext) == 0 {
|
||||
t, err = getTemplate(self.root, file, v...)
|
||||
} else if fn, ok := beeTemplateEngines[ext[1:]]; ok {
|
||||
t, err = fn(self.root, file, beegoTplFuncMap)
|
||||
} else {
|
||||
t, err = getTemplate(self.root, file, v...)
|
||||
}
|
||||
if err != nil {
|
||||
Trace("parse template err:", file, err)
|
||||
logs.Trace("parse template err:", file, err)
|
||||
} else {
|
||||
beeTemplates[file] = t
|
||||
}
|
||||
@ -240,7 +262,7 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others
|
||||
var subMods1 [][]string
|
||||
t, subMods1, err = getTplDeep(root, otherFile, "", t)
|
||||
if err != nil {
|
||||
Trace("template parse file err:", err)
|
||||
logs.Trace("template parse file err:", err)
|
||||
} else if subMods1 != nil && len(subMods1) > 0 {
|
||||
t, err = _getTemplate(t, root, subMods1, others...)
|
||||
}
|
||||
@ -261,7 +283,7 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others
|
||||
var subMods1 [][]string
|
||||
t, subMods1, err = getTplDeep(root, otherFile, "", t)
|
||||
if err != nil {
|
||||
Trace("template parse file err:", err)
|
||||
logs.Trace("template parse file err:", err)
|
||||
} else if subMods1 != nil && len(subMods1) > 0 {
|
||||
t, err = _getTemplate(t, root, subMods1, others...)
|
||||
}
|
||||
@ -305,3 +327,9 @@ func DelStaticPath(url string) *App {
|
||||
delete(BConfig.WebConfig.StaticDir, url)
|
||||
return BeeApp
|
||||
}
|
||||
|
||||
func AddTemplateEngine(extension string, fn templatePreProcessor) *App {
|
||||
AddTemplateExt(extension)
|
||||
beeTemplateEngines[extension] = fn
|
||||
return BeeApp
|
||||
}
|
||||
|
@ -421,18 +421,18 @@ func RenderForm(obj interface{}) template.HTML {
|
||||
|
||||
fieldT := objT.Field(i)
|
||||
|
||||
label, name, fType, id, class, ignored := parseFormTag(fieldT)
|
||||
label, name, fType, id, class, ignored, required := parseFormTag(fieldT)
|
||||
if ignored {
|
||||
continue
|
||||
}
|
||||
|
||||
raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class))
|
||||
raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class, required))
|
||||
}
|
||||
return template.HTML(strings.Join(raw, "</br>"))
|
||||
}
|
||||
|
||||
// renderFormField returns a string containing HTML of a single form field.
|
||||
func renderFormField(label, name, fType string, value interface{}, id string, class string) string {
|
||||
func renderFormField(label, name, fType string, value interface{}, id string, class string, required bool) string {
|
||||
if id != "" {
|
||||
id = " id=\"" + id + "\""
|
||||
}
|
||||
@ -441,11 +441,16 @@ func renderFormField(label, name, fType string, value interface{}, id string, cl
|
||||
class = " class=\"" + class + "\""
|
||||
}
|
||||
|
||||
if isValidForInput(fType) {
|
||||
return fmt.Sprintf(`%v<input%v%v name="%v" type="%v" value="%v">`, label, id, class, name, fType, value)
|
||||
requiredString := ""
|
||||
if required {
|
||||
requiredString = " required"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`%v<%v%v%v name="%v">%v</%v>`, label, fType, id, class, name, value, fType)
|
||||
if isValidForInput(fType) {
|
||||
return fmt.Sprintf(`%v<input%v%v name="%v" type="%v" value="%v"%v>`, label, id, class, name, fType, value, requiredString)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`%v<%v%v%v name="%v"%v>%v</%v>`, label, fType, id, class, name, requiredString, value, fType)
|
||||
}
|
||||
|
||||
// isValidForInput checks if fType is a valid value for the `type` property of an HTML input element.
|
||||
@ -461,7 +466,7 @@ func isValidForInput(fType string) bool {
|
||||
|
||||
// parseFormTag takes the stuct-tag of a StructField and parses the `form` value.
|
||||
// returned are the form label, name-property, type and wether the field should be ignored.
|
||||
func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool) {
|
||||
func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool, required bool) {
|
||||
tags := strings.Split(fieldT.Tag.Get("form"), ",")
|
||||
label = fieldT.Name + ": "
|
||||
name = fieldT.Name
|
||||
@ -470,6 +475,12 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str
|
||||
id = fieldT.Tag.Get("id")
|
||||
class = fieldT.Tag.Get("class")
|
||||
|
||||
required = false
|
||||
required_field := fieldT.Tag.Get("required")
|
||||
if required_field != "-" && required_field != "" {
|
||||
required, _ = strconv.ParseBool(required_field)
|
||||
}
|
||||
|
||||
switch len(tags) {
|
||||
case 1:
|
||||
if tags[0] == "-" {
|
||||
@ -496,6 +507,7 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str
|
||||
label = tags[2]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -195,54 +195,78 @@ func TestRenderForm(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderFormField(t *testing.T) {
|
||||
html := renderFormField("Label: ", "Name", "text", "Value", "", "")
|
||||
html := renderFormField("Label: ", "Name", "text", "Value", "", "", false)
|
||||
if html != `Label: <input name="Name" type="text" value="Value">` {
|
||||
t.Errorf("Wrong html output for input[type=text]: %v ", html)
|
||||
}
|
||||
|
||||
html = renderFormField("Label: ", "Name", "textarea", "Value", "", "")
|
||||
html = renderFormField("Label: ", "Name", "textarea", "Value", "", "", false)
|
||||
if html != `Label: <textarea name="Name">Value</textarea>` {
|
||||
t.Errorf("Wrong html output for textarea: %v ", html)
|
||||
}
|
||||
|
||||
html = renderFormField("Label: ", "Name", "textarea", "Value", "", "", true)
|
||||
if html != `Label: <textarea name="Name" required>Value</textarea>` {
|
||||
t.Errorf("Wrong html output for textarea: %v ", html)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFormTag(t *testing.T) {
|
||||
// create struct to contain field with different types of struct-tag `form`
|
||||
type user struct {
|
||||
All int `form:"name,text,年龄:"`
|
||||
NoName int `form:",hidden,年龄:"`
|
||||
OnlyLabel int `form:",,年龄:"`
|
||||
OnlyName int `form:"name" id:"name" class:"form-name"`
|
||||
Ignored int `form:"-"`
|
||||
All int `form:"name,text,年龄:"`
|
||||
NoName int `form:",hidden,年龄:"`
|
||||
OnlyLabel int `form:",,年龄:"`
|
||||
OnlyName int `form:"name" id:"name" class:"form-name"`
|
||||
Ignored int `form:"-"`
|
||||
Required int `form:"name" required:"true"`
|
||||
IgnoreRequired int `form:"name"`
|
||||
NotRequired int `form:"name" required:"false"`
|
||||
}
|
||||
|
||||
objT := reflect.TypeOf(&user{}).Elem()
|
||||
|
||||
label, name, fType, id, class, ignored := parseFormTag(objT.Field(0))
|
||||
label, name, fType, id, class, ignored, required := parseFormTag(objT.Field(0))
|
||||
if !(name == "name" && label == "年龄:" && fType == "text" && ignored == false) {
|
||||
t.Errorf("Form Tag with name, label and type was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored = parseFormTag(objT.Field(1))
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(1))
|
||||
if !(name == "NoName" && label == "年龄:" && fType == "hidden" && ignored == false) {
|
||||
t.Errorf("Form Tag with label and type but without name was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored = parseFormTag(objT.Field(2))
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(2))
|
||||
if !(name == "OnlyLabel" && label == "年龄:" && fType == "text" && ignored == false) {
|
||||
t.Errorf("Form Tag containing only label was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored = parseFormTag(objT.Field(3))
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(3))
|
||||
if !(name == "name" && label == "OnlyName: " && fType == "text" && ignored == false &&
|
||||
id == "name" && class == "form-name") {
|
||||
t.Errorf("Form Tag containing only name was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored = parseFormTag(objT.Field(4))
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(4))
|
||||
if ignored == false {
|
||||
t.Errorf("Form Tag that should be ignored was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(5))
|
||||
if !(name == "name" && required == true) {
|
||||
t.Errorf("Form Tag containing only name and required was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(6))
|
||||
if !(name == "name" && required == false) {
|
||||
t.Errorf("Form Tag containing only name and ignore required was not correctly parsed.")
|
||||
}
|
||||
|
||||
label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(7))
|
||||
if !(name == "name" && required == false) {
|
||||
t.Errorf("Form Tag containing only name and not required was not correctly parsed.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMapGet(t *testing.T) {
|
||||
|
@ -389,6 +389,10 @@ func dayMatches(s *Schedule, t time.Time) bool {
|
||||
|
||||
// StartTask start all tasks
|
||||
func StartTask() {
|
||||
if isstart {
|
||||
//If already started, no need to start another goroutine.
|
||||
return
|
||||
}
|
||||
isstart = true
|
||||
go run()
|
||||
}
|
||||
@ -432,8 +436,11 @@ func run() {
|
||||
|
||||
// StopTask stop all tasks
|
||||
func StopTask() {
|
||||
isstart = false
|
||||
stop <- true
|
||||
if isstart {
|
||||
isstart = false
|
||||
stop <- true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// AddTask add task with name
|
||||
|
4
tree.go
4
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
|
||||
}
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/cache"
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/utils"
|
||||
)
|
||||
|
||||
@ -139,7 +140,7 @@ func (c *Captcha) Handler(ctx *context.Context) {
|
||||
if err := c.store.Put(key, chars, c.Expiration); err != nil {
|
||||
ctx.Output.SetStatus(500)
|
||||
ctx.WriteString("captcha reload error")
|
||||
beego.Error("Reload Create Captcha Error:", err)
|
||||
logs.Error("Reload Create Captcha Error:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@ -154,7 +155,7 @@ func (c *Captcha) Handler(ctx *context.Context) {
|
||||
|
||||
img := NewImage(chars, c.StdWidth, c.StdHeight)
|
||||
if _, err := img.WriteTo(ctx.ResponseWriter); err != nil {
|
||||
beego.Error("Write Captcha Image Error:", err)
|
||||
logs.Error("Write Captcha Image Error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +163,7 @@ func (c *Captcha) Handler(ctx *context.Context) {
|
||||
func (c *Captcha) CreateCaptchaHTML() template.HTML {
|
||||
value, err := c.CreateCaptcha()
|
||||
if err != nil {
|
||||
beego.Error("Create Captcha Error:", err)
|
||||
logs.Error("Create Captcha Error:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func TestFileExists(t *testing.T) {
|
||||
}
|
||||
|
||||
if FileExists(noExistedFile) {
|
||||
t.Errorf("Wierd, how could this file exists: %s", noExistedFile)
|
||||
t.Errorf("Weird, how could this file exists: %s", noExistedFile)
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ func TestSearchFile(t *testing.T) {
|
||||
}
|
||||
t.Log(path)
|
||||
|
||||
path, err = SearchFile(noExistedFile, ".")
|
||||
_, err = SearchFile(noExistedFile, ".")
|
||||
if err == nil {
|
||||
t.Errorf("err shouldnot be nil, got path: %s", SelfDir())
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"github.com/astaxie/beego/context"
|
||||
)
|
||||
|
||||
// SetPaginator Instantiates a Paginator and assigns it to context.Input.Data["paginator"].
|
||||
// SetPaginator Instantiates a Paginator and assigns it to context.Input.Data("paginator").
|
||||
func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) {
|
||||
paginator = NewPaginator(context.Request, per, nums)
|
||||
context.Input.SetData("paginator", &paginator)
|
||||
|
@ -20,28 +20,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var alphaNum = []byte(`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`)
|
||||
|
||||
// RandomCreateBytes generate random []byte by specify chars.
|
||||
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
if len(alphabets) == 0 {
|
||||
alphabets = alphaNum
|
||||
}
|
||||
var bytes = make([]byte, n)
|
||||
var randby bool
|
||||
var randBy bool
|
||||
if num, err := rand.Read(bytes); num != n || err != nil {
|
||||
r.Seed(time.Now().UnixNano())
|
||||
randby = true
|
||||
randBy = true
|
||||
}
|
||||
for i, b := range bytes {
|
||||
if len(alphabets) == 0 {
|
||||
if randby {
|
||||
bytes[i] = alphanum[r.Intn(len(alphanum))]
|
||||
} else {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
if randBy {
|
||||
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||
} else {
|
||||
if randby {
|
||||
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||
} else {
|
||||
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||
}
|
||||
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||
}
|
||||
}
|
||||
return bytes
|
||||
|
33
utils/rand_test.go
Normal file
33
utils/rand_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// 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 utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRand_01(t *testing.T) {
|
||||
bs0 := RandomCreateBytes(16)
|
||||
bs1 := RandomCreateBytes(16)
|
||||
|
||||
t.Log(string(bs0), string(bs1))
|
||||
if string(bs0) == string(bs1) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
bs0 = RandomCreateBytes(4, []byte(`a`)...)
|
||||
|
||||
if string(bs0) != "aaaa" {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
@ -73,6 +73,10 @@ func (e *Error) String() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Implement Error interface.
|
||||
// Return e.String()
|
||||
func (e *Error) Error() string { return e.String() }
|
||||
|
||||
// Result is returned from every validation method.
|
||||
// It provides an indication of success, and a pointer to the Error (if any).
|
||||
type Result struct {
|
||||
|
Reference in New Issue
Block a user