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

532 Commits

Author SHA1 Message Date
b0e2bbce2a Merge pull request #3391 from astaxie/develop
V1.11.0
2018-11-19 14:40:43 +08:00
7a50ea7e36 Merge pull request #3395 from astaxie/AddTestForSnakeString
#3192 AddTestSnakeString
2018-11-18 21:59:48 +08:00
3070cfc60b Merge pull request #3396 from astaxie/FixAnnotationOnSnakeString
Fix #3192
2018-11-18 21:59:35 +08:00
c4c3067a31 Update utils.go 2018-11-15 15:16:41 +08:00
81346fe641 Update utils_test.go 2018-11-15 14:50:38 +08:00
1a66ad56c6 Merge pull request #3317 from HSoshiant/master
Lock
2018-11-14 19:24:23 +08:00
31f2adb79d Update session.go 2018-11-14 19:24:10 +08:00
f514ae309b Update session.go 2018-11-14 19:23:10 +08:00
277d3d98e3 v1.11.0 2018-11-14 17:01:12 +08:00
2c46877b36 Merge pull request #3390 from astaxie/FixDataRaceOnCache
Fix  #3354
2018-11-14 15:58:20 +08:00
cfe54a02c5 Merge pull request #3388 from s00500/develop
Closes #2515 Autodetect timezone in NewOrmWithDB()
2018-11-13 16:48:18 +08:00
55f390d08a Merge pull request #3387 from wuyumin/master
dynamically add task #2708
2018-11-13 14:51:41 +08:00
cf31222643 Merge pull request #3386 from DennisMao/AddTransportSetting
add transport setting
2018-11-13 14:50:28 +08:00
9e036bcab5 Update memory.go 2018-11-13 14:43:23 +08:00
d02170e3cb Closes #2515 Autodetect timezone in NewOrmWithDB() 2018-11-12 10:18:05 +01:00
2d6f1af1a5 dynamically add task 2018-11-12 17:17:06 +08:00
430457609f Update httplib_test.go 2018-11-12 13:04:17 +08:00
0333e26b3e Merge pull request #3382 from lxShaDoWxl/add_custom_fs_template
Add custom fs template
2018-11-10 22:44:28 +08:00
d0d28566b9 chore(GoMod): add dependency go-bindata-assetfs in vendor dir 2018-11-10 13:41:47 +06:00
872b787e6c refactor(FileSystem): add comments function 2018-11-10 12:34:53 +06:00
01a99edf80 chore(GoMod): add dependency go-bindata-assetfs 2018-11-10 12:26:19 +06:00
6050d37d2a Merge remote-tracking branch 'me/develop' into add_custom_fs_template 2018-11-10 11:39:28 +06:00
876dce8e54 fix the routerInfo is nil 2018-11-09 18:03:26 +08:00
24885c28f2 fix the comments update 2018-11-09 17:54:20 +08:00
5ea04bdfd3 update mod 2018-11-09 12:37:28 +08:00
9fdc1eaf3a Merge pull request #3352 from SongLiangChen/develop
add sessionid prefix
2018-11-08 23:29:05 +08:00
6b5a70d246 Merge pull request #3378 from nukc/develop
orm: support filter raw sql
2018-11-08 23:28:30 +08:00
8391d26220 Merge pull request #3383 from LockGit/develop
security question, fix arbitrary file read
2018-11-08 23:21:18 +08:00
f193e313a3 refactor(FileSystem): using single-line if 2018-11-07 20:21:34 +06:00
9ac4928113 refactor(Template): a detailed description of the error 2018-11-07 20:20:10 +06:00
9865779f14 security question, fix arbitrary file read 2018-11-07 11:31:27 +08:00
aa6d0f9f0b fix(Template): correct check error 2018-11-06 21:40:43 +06:00
68b0bd98fd fix(Template): error handling when reading files 2018-11-06 20:41:05 +06:00
3447798494 fix(Template): dependencies in travis 2018-11-06 20:18:48 +06:00
ca1b96f986 feat(Template): use interface http.FileSystem 2018-11-06 20:06:21 +06:00
771fe35431 feat(Template): testing fs bindata 2018-11-05 22:58:59 +06:00
2f00ad1602 fix(Template): close open file 2018-11-05 22:51:20 +06:00
f740b71ded fix(Template): remove duplicate check open/exists 2018-11-05 21:08:03 +06:00
7aae58a543 feat(Template): create interface FileSystem for to create custom fs 2018-11-05 21:05:19 +06:00
c8da875f83 add sessionId prefix 2018-11-05 09:51:27 +08:00
501d8a97f6 add sessionId prefix 2018-11-05 09:50:19 +08:00
736e66fcda orm: support filter raw sql 2018-11-05 03:47:21 +08:00
d3ad810f16 add sessionId prefix 2018-10-29 13:35:31 +08:00
abc8b78065 add sessionId prefix 2018-10-29 12:18:06 +08:00
f64e6b72e9 Merge pull request #3362 from SmartBrave/develop
modify qbs to qps
2018-10-28 20:01:23 +08:00
4e83b4400a Merge pull request #3371 from HaraldNordgren/go_versions
Bump Go versions and use 1.n.x to get latest minor versions
2018-10-28 19:49:27 +08:00
f6f61513a1 Bump Go versions and use 1.n.x to get latest minor versions 2018-10-28 00:58:36 +02:00
8217817a0b modify qbs to qps 2018-10-24 10:38:59 +08:00
833f54d818 Merge pull request #3319 from Colstuwjx/annotated-filter
Annotated filter
2018-10-23 13:45:18 +08:00
706c086bc5 Merge pull request #3345 from dingyuanhong/develop
fix / can use dynamic directory
2018-10-23 13:43:45 +08:00
187add9b84 add sessionid prefix 2018-10-10 11:02:45 +08:00
5b8e468a13 Merge pull request #3344 from akhedrane/patch-1
duo to #3278 numRow should be 0
2018-10-05 21:16:18 +08:00
dff9c8f5fa fix / can use dynamic directory 2018-10-01 15:16:35 +08:00
21a8623002 duo to #3278 numRow should be 0 2018-09-30 10:45:18 +01:00
ea9c5822e6 Merge pull request #3292 from SongLiangChen/master
Read over 4096 length values
2018-09-30 16:03:45 +08:00
ad0d166d46 Merge pull request #3295 from GNURub/feature/outputWithformat
feature/outputwithformat
2018-09-30 16:01:07 +08:00
2c2ace9a60 Merge pull request #3313 from oiooj/pr-module
support go modules
2018-09-30 15:55:57 +08:00
8134a89e81 Merge pull request #3333 from akhedrane/patch-1
Return error when wrong filtering field
2018-09-30 15:45:23 +08:00
e342a0099f Merge pull request #3340 from GNURub/feature/update-travis
Add go 1.11 version
2018-09-30 15:44:46 +08:00
c4ed5030da Merge pull request #3339 from GNURub/hotfix/redis-uri
Support redis URI format
2018-09-30 15:43:41 +08:00
7b9c24567d Merge pull request #3335 from GNURub/hotfix/ranking-response-times
Added link to time ranking
2018-09-30 15:42:05 +08:00
6906c5ce30 Updated travis go version 2018-09-27 18:27:38 +02:00
e4605f232b Support redis URI 2018-09-26 18:05:09 +02:00
6092e737a1 Added link to ranking 2018-09-24 18:33:56 +02:00
0e4d954fa7 Return error when wrong filtering field
When end user put wrong filtering field ORM should return error instead of Panic()
so developers can handle this error.
2018-09-23 12:18:15 +02:00
1cbba4d56f Add annotated filter, support @Import, @Filter.
Signed-off-by: Colstuwjx <Colstuwjx@gmail.com>
2018-09-07 15:59:57 +08:00
kun
cf5d1f3f3c support go modules 2018-09-05 14:05:16 +08:00
1097ac3682 GetProvider 2018-08-28 15:12:28 -04:00
755cc98ef7 Fix content type 2018-08-21 12:32:16 +02:00
5c407ff2e3 Add map shortcut and ServeFormatted method in output 2018-08-20 22:55:50 +02:00
8f455ef199 Read over 4096 length values 2018-08-17 11:40:00 +08:00
7e0649d661 Merge pull request #3289 from nezorflame/patch-1
Remove panic from Redirect()
2018-08-16 09:22:22 +08:00
1a3dcb4f84 Remove panic from Redirect()
This `panic(ErrAbort)` is unnecessary in `Redirect` function and causes problems in the production code.
2018-08-12 05:11:03 +03:00
b606f1f73f Merge pull request #3283 from JessonChan/develop
typo fixed
2018-08-09 17:05:25 +08:00
8241f219fd Merge pull request #3278 from hurisheng/revert-3247-develop
Revert "send ErrNoRows if the query returns zero rows ... in method orm_query…"
2018-08-09 17:05:01 +08:00
dea45a3d6c fix TestAll() 2018-08-07 16:36:27 +08:00
34a812d45f typo fixed
#3260
2018-08-07 12:08:15 +08:00
842336834f Merge pull request #3275 from WiFeng/master
fix bug of tasks that only some but not all are executed
2018-08-07 10:07:29 +08:00
58fe012446 Merge pull request #3274 from gombaniro/improvement/make-TestSet-on-map-self-contained
breaks dependency on TestNewBeeMap Testcase
2018-08-07 10:02:29 +08:00
bf0d40bca6 Merge pull request #3280 from GNURub/feature/pusher
Add access to pusher
2018-08-07 09:58:56 +08:00
48e6658eca Add access to pusher 2018-08-03 12:33:46 +02:00
1bd3fb7a33 Revert "send ErrNoRows if the query returns zero rows ... in method orm_query…" 2018-08-03 13:35:48 +08:00
d86410a631 fix bug of tasks that only some but not all are executed 2018-08-01 11:20:22 +08:00
6b62502b99 breaks rely on TestNewBeeMap 2018-08-01 10:24:39 +08:00
053a075344 Merge pull request #3271 from astaxie/develop
add vendor gopkg.in/yaml.v2
2018-07-31 21:18:48 +08:00
9dd7d19ce7 add vendor gopkg.in/yaml.v2 2018-07-31 21:18:07 +08:00
efe0f67388 Merge pull request #3267 from astaxie/develop
beego 1.10.1
2018-07-31 20:52:47 +08:00
de66d2bdfd beego 1.10.1 2018-07-31 20:09:08 +08:00
0e4fe4d177 Merge pull request #3270 from astaxie/revert-3269-master
Revert "hible"
2018-07-31 20:07:18 +08:00
6d84db1e93 Revert "hible" 2018-07-31 20:07:03 +08:00
2486f3826a Merge pull request #3269 from 514366607/master
hible
2018-07-31 20:06:55 +08:00
d7b8aa8b52 only add golang.org vendor 2018-07-31 19:25:43 +08:00
c7c0b01ec5 hible
cache add function
// IncrBy increase counter by num.
IncrBy(key string, num int)
// DecrBy decrease counter by num.
DecrBy(key string, num int)
2018-07-31 17:19:09 +08:00
6d69047fff update go vet 2018-07-30 15:13:04 +08:00
787ab12605 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2018-07-30 15:01:58 +08:00
f4112accef update go vet 2018-07-30 15:01:49 +08:00
42c394e28b Merge pull request #3263 from guomao545/master
Support return middle level value
2018-07-30 12:27:05 +08:00
5051d902fb Merge pull request #2986 from oxgo/hourlylog
add hourly log rotate
2018-07-30 12:26:37 +08:00
48acfa08be add vendor 2018-07-30 12:05:51 +08:00
39fc30b8b2 Support return middle level value
fix multilevel yaml config can't correct return middle level value bug
2018-07-27 15:33:24 +08:00
two
046cb248e0 edit test case 2018-07-26 15:08:14 +08:00
two
31c746d9d7 fix all confict 2018-07-26 14:34:25 +08:00
two
38a2f32252 fix one confict 2018-07-26 14:29:26 +08:00
d55f54a8ab Merge pull request #3149 from liaoishere/feature/support-begintx
Support DB.BeginTx in go 1.8
2018-07-23 20:25:17 +08:00
feb0e67fd7 upgrade go version from 1.9.2 to 1.9.7 in test env.
upgrade to avoid bug: https://github.com/golang/go/issues/22976

Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-23 15:58:12 +08:00
a09bafbf2a Merge pull request #3233 from astaxie/develop
beego 1.10.0
2018-07-21 15:55:28 +08:00
03de7456ca Merge pull request #3250 from 0x0400/develop
acquire lock when access config data
2018-07-21 15:25:23 +08:00
78f2fd8d14 acquire lock when access config data 2018-07-21 14:56:09 +08:00
a048ed51a7 ready to release 1.10.0 2018-07-21 09:29:00 +08:00
164a9231e8 Merge pull request #3249 from GNURub/feature/autocert
Feature/autocert
2018-07-21 09:20:07 +08:00
aaa7e33778 Autocert ok 2018-07-20 19:54:25 +02:00
f7008e2877 Removed patch 2018-07-20 19:02:09 +02:00
cf6e825547 Domains 2018-07-20 18:59:45 +02:00
38f9a3c49e AutoCert 2018-07-20 18:53:57 +02:00
f18283a517 Merge pull request #3181 from GNURub/feature/YAML
Feature/yaml
2018-07-20 23:41:15 +08:00
61aec396e0 Update .travis.yml 2018-07-20 23:40:11 +08:00
5ba9e63086 Merge branch 'develop' into feature/YAML 2018-07-20 23:24:51 +08:00
5acc56648d Merge branch 'develop' into feature/support-begintx 2018-07-20 23:16:12 +08:00
bc773039ca Merge pull request #2997 from DennisMao/master
fix the model can not be registered correctly on Ubuntu 32bit
2018-07-20 23:14:41 +08:00
868fc2a29f fix go1.10.3 orm test failed 2018-07-20 22:45:44 +08:00
81f69f12ab Merge branch 'develop' of https://github.com/astaxie/beego into develop 2018-07-20 22:30:25 +08:00
0711c3289f fix the orm test 2018-07-20 19:58:56 +08:00
b8868d6d2d remove unnecessary conversion
Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-20 17:07:17 +08:00
30bbc81a2e fix test case that calls All()
Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-20 16:51:36 +08:00
1a3f1d66c1 rename orm_go18.go to orm.go
Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-20 16:36:06 +08:00
6bdd152d91 upgrade postgres in travis
Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-20 16:36:06 +08:00
443c77b303 support DB.BeginTx of golang 1.8
Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-20 16:36:06 +08:00
0dff771707 fix unquoted identifier that may be misleading in postgres
Signed-off-by: Penghui Liao <liaoishere@gmail.com>
2018-07-20 16:36:06 +08:00
c9b6e4f825 Merge pull request #2981 from TankTheFrank/fix_template_render_with_automatic_parameter_routing
fixes template rendering with automatic mapped parameters (see #2979)
2018-07-20 15:39:10 +08:00
abd02c7de4 Merge pull request #2985 from terryding77/fix_orm_Field_SetRaw_function_error_judge_problem
fix orm fields SetRaw function error judge problem
2018-07-20 15:36:40 +08:00
eb4e0e4030 Merge pull request #3022 from chenpeiyuan/develop
do html escape before display path, avoid xss
2018-07-20 15:33:12 +08:00
96dffcd27f Merge pull request #3105 from ckahi/hotfix_log_dir
auto create log dir
2018-07-20 15:32:42 +08:00
0d0d87f600 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2018-07-20 15:25:04 +08:00
2c779a4287 Merge pull request #3141 from gadelkareem/patch-2
Improve access log
2018-07-20 15:20:15 +08:00
f25893832f Merge pull request #3142 from amitash1109/fix_response_http_code
Fix response http code
2018-07-20 15:18:54 +08:00
af73a2d515 Merge branch 'develop' into fix_response_http_code 2018-07-20 15:18:30 +08:00
67a6b8723c Merge pull request #3146 from Wang-Kai/master
Add code style for logs README
2018-07-20 15:17:31 +08:00
fdccd85330 Merge pull request #3152 from joshtechnologygroup/staticfile_unexpected_eof_bug_gix
[#2973] Fix Unexpected EOF bug in staticfile
2018-07-20 15:09:07 +08:00
ca394fc8ab Merge pull request #3182 from GNURub/feature/autobind
Add method to set the data depending on the accepted
2018-07-20 15:05:28 +08:00
9c9ba0129f Merge pull request #3226 from jianjianzhu/master
Fix the wrong status code in prod
2018-07-20 14:53:08 +08:00
b61c91d93d remove unnecessary conversion 2018-07-20 14:47:11 +08:00
f15732798f Merge pull request #3239 from wilhelmguo/develop
add session redis IdleTimeout config
2018-07-20 14:44:07 +08:00
efbe655d6a Merge pull request #3245 from 0x0400/patch-1
fix typo
2018-07-20 14:43:38 +08:00
27ced1d9c3 Merge pull request #3247 from mohan2808/develop
send ErrNoRows if the query returns zero rows ... in method orm_query…
2018-07-20 14:34:23 +08:00
8f6bce3b87 fix test case 2018-07-20 14:26:43 +08:00
be75f93d43 add miss dep 2018-07-20 12:14:27 +08:00
541fb181fe Merge branch 'master' into develop 2018-07-20 12:00:53 +08:00
293b54192f send ErrNoRows if the query returns zero rows ... in method orm_queryset.All() 2018-07-19 18:51:16 +05:30
0e0718d110 fix typo
hasReuired --> hasRequired
2018-07-17 23:32:11 +08:00
6fec0a7831 add session redis IdleTimeout config 2018-07-12 10:48:50 +08:00
654ebebe3c Merge pull request #3217 from jinxjinxagain/develop
fix: When multiply comment routers on one func
2018-07-08 16:13:21 +08:00
08c3ca642e Merge pull request #3230 from Colstuwjx/fix/correct-httplib-maxidleconnection-default-value
Fix: correct MaxIdleConnsPerHost value to net/http default 100.
2018-07-08 16:11:35 +08:00
b3c46a87ac Fix: correct MaxIdleConnsPerHost value to net/http default 100. 2018-07-05 19:15:42 +08:00
464d080518 fix httpcode in prod 2018-07-02 11:21:06 +08:00
227c04c9e6 fix: When multiply comment routers on one func, only generates the last one controller 2018-06-28 15:54:17 +08:00
e5d68aceed Merge pull request #3185 from kaka89/master
Fix defaut value bug, and add config for maxfiles
2018-06-23 22:54:35 +08:00
67d9241abc Merge pull request #3171 from whomm/master
debug stringsToJSON
2018-06-23 22:50:11 +08:00
110dbcb31f Merge pull request #3208 from hurisheng/qs_forupdate
add 'FOR UPDATE' support for querySet
2018-06-23 22:49:01 +08:00
740bf72f0c Merge pull request #3202 from openset/develop
Update: Htmlquote Htmlunquote
2018-06-23 22:43:53 +08:00
6b3b8607a0 Merge branch 'develop' into develop 2018-06-23 22:43:45 +08:00
b21c59ee70 Merge pull request #3206 from whilei/gofmt-2018-Jun-17-00-39
gofmt
2018-06-23 22:38:07 +08:00
fc2c96a177 add 'FOR UPDATE' support for querySet 2018-06-23 22:25:05 +08:00
ia
87ba3f3cd3 all: gofmt
Run standard gofmt command on project root.

- go version go1.10.3 darwin/amd64

Signed-off-by: ia <isaac.ardis@gmail.com>
2018-06-17 00:47:51 +02:00
b80b7b06fc Update: Redundant semicolon disableEscapeHTML 2018-06-14 11:55:07 +08:00
ad6c97ec1b Update: Htmlquote Htmlunquote 2018-06-13 15:43:01 +08:00
d3d97de312 Merge pull request #3200 from openset/master
Update: use PathEscape replace QueryEscape
2018-06-12 22:45:14 +08:00
bf915c3280 Update: use PathEscape replace QueryEscape
If filename contain space(" "), QueryEscape use "+" instead.
2018-06-12 16:15:20 +08:00
19c5cd130d Merge pull request #3190 from NSObjects/develop
add field comment on create table
2018-06-07 11:06:23 +08:00
1df2662924 add field comment on create table 2018-06-06 12:33:28 +08:00
f979050a45 Fix defaut value bug, and add default maxfiles
1. add default value for maxlines(100000), maxsize(1 << 28)
2. add maxfiles for configure, just like `maxlines` & maxsize
2018-06-03 23:08:03 +08:00
45b68d444d Add method to set the data depending on the accepted 2018-06-01 19:11:01 +02:00
732f79e758 Add dep travis 2018-05-31 14:52:47 +02:00
4e954e32b8 Test YAML 2018-05-31 13:48:24 +02:00
92e81ccf50 Merge branch 'develop' into feature/YAML 2018-05-31 13:35:44 +02:00
91f2005067 Test YAML 2018-05-31 13:35:23 +02:00
7c80bf6f9d Add YAML 2018-05-30 16:06:40 +02:00
cc2c98c112 update travis 2018-05-30 22:01:59 +08:00
c3c0adbf55 Merge pull request #3175 from mo0feng/master
Create redis_cluster.go
2018-05-28 16:48:06 +08:00
jz
04c305f273 fix use it comments
fix  use it comments
2018-05-24 15:14:56 +08:00
jz
8c8cf46b55 Create redis_cluster.go
super redis cluster
2018-05-23 17:30:13 +08:00
e96ae0c24a debug stringsToJSON
json char: \u four-hex-digits number(http://json.org/)
2018-05-21 15:18:18 +08:00
98a3cda260 Fix Unexpected EOF bug in staticfile 2018-05-07 13:51:05 +05:30
1fd7fa5df7 make example runable 2018-05-03 22:04:49 +08:00
3d3f2ed4c5 Merge pull request #3127 from kaka89/master
Refactor yaml config for support multilevel
2018-05-03 14:07:59 +08:00
0f73050567 add code style for logs README 2018-05-03 12:05:59 +08:00
a40899e6be Merge pull request #3145 from gadelkareem/develop
Allow log prefix
2018-05-03 11:27:50 +08:00
a9a15e2c54 Allow log prefix 2018-05-02 00:24:09 +02:00
896c258e44 Log redirects and abort after redirect 2018-04-30 17:48:01 +02:00
6df42d63e2 Fix response http code 2018-04-29 15:12:32 +03:00
33bf80b052 Merge branch 'pr/3141' into patch-2 2018-04-28 20:07:23 +02:00
d5c1c0e9a4 log errors in access log and make static request logging optional 2018-04-28 20:03:39 +02:00
8e61a6a6de Allow access log regardless of the log level 2018-04-28 17:15:19 +02:00
ccaa2dd9e0 Update yaml.go
delete white line.
2018-04-20 19:44:22 +08:00
507ea757d7 Merge pull request #3039 from cloudzhou/patch-1
execElem.FieldByName as local variable
2018-04-20 19:41:07 +08:00
9d526dfd50 Merge pull request #3100 from godcong/master
change github.com/garyburd/redigo to newest branch github.com/gomodul…
2018-04-20 19:40:30 +08:00
ba89253e4a Update yaml.go
add support for multilevel yaml config
2018-04-20 19:40:06 +08:00
0d6f190e72 Merge pull request #3107 from sergeylanzman/patch-1
Update .travis.yml golang 1.10
2018-04-20 19:33:50 +08:00
91b9a65db0 Merge pull request #3109 from Jeff885/Jeff885-patch-1
When log maxSize set big int,FileWrite Init fail
2018-04-20 19:33:33 +08:00
e96a5fb3ca Merge pull request #3115 from m4grio/minor-typo
Amend a very minor typo in a variable name
2018-04-20 19:26:42 +08:00
f5f70f386d Merge pull request #3126 from aruhi/master
In dev mode, template parse error cause program lock
2018-04-20 19:26:24 +08:00
242efcf7fa Merge pull request #3103 from qshuai/master
fix typo
2018-04-20 19:26:00 +08:00
51cc6fc257 Update template.go 2018-04-20 19:42:50 +09:00
5fb29cb772 Amend a very minor typo in a variable name 2018-04-10 12:19:50 +08:00
2da894d4a7 When log maxSize set big int,FileWrite Init fail
example:
beego.SetLogger("multifile", {"filename":"logs/liverelay.log","separate":[ "emergency", "error", "info", "debug"],"maxsize":250000000}).

json: cannot unmarshal number 2.5e+08 into Go value of type int

The err should return and show the developer.
2018-04-06 00:50:35 +08:00
2623b15ce0 Update .travis.yml 2018-04-05 16:30:08 +03:00
6db9ad7002 auto create log dir 2018-04-04 15:59:52 +08:00
889408136b fix typo 2018-03-28 00:26:06 +08:00
886fefe738 change github.com/garyburd/redigo to newest branch github.com/gomodule/redigo 2018-03-26 16:59:01 +08:00
768406f134 Merge pull request #3076 from gadelkareem/patch-1
Set default Beego RunMode to production
2018-03-11 16:26:00 +08:00
075e63b2bd Merge pull request #2999 from moririnson/fix_unable_to_add_column
fix the issue #2998
2018-03-11 16:18:50 +08:00
0057c08a90 Merge pull request #3085 from WUMUXIAN/swagger
Swagger: Allow example values with different types, allow example for enum.
2018-03-11 10:48:48 +08:00
09b073356d Swagger:
1. Allow example value to be given based on the field type, previously it's a string.
2. Allow to give example values for Enum values.

This is for the beego/bee pull request to work well.
2018-03-10 17:15:24 +08:00
3c9ed48630 Set default Beego RunMode to production 2018-03-02 18:23:20 +01:00
65d8b4f544 Merge pull request #3064 from takeo-lvgs/dev
fix the issue #3063
2018-02-22 17:22:39 +08:00
6d18d4dcdd Merge pull request #3066 from takeo-lvgs/fix_3065
fix the issue #3065
2018-02-22 17:16:38 +08:00
21fe2d519e fix the issue #3065 2018-02-20 17:40:18 +09:00
9a7554fa01 fix the issue #3063 2018-02-20 11:39:29 +09:00
37d1c13603 Merge pull request #3046 from aspacca/master
Handle pointer validation
2018-02-02 18:57:13 +08:00
5ed112e946 added CanSkipAlso 2018-02-02 10:22:43 +01:00
453f112094 added more test case 2018-02-01 18:31:04 +01:00
faa3341603 Fix after test failure 2018-01-28 18:19:27 +01:00
ee9cf05796 Handle pointer validation 2018-01-28 17:40:05 +01:00
6de538b136 execElem.FieldByName as local variable
execElem.FieldByName(fieldType.Name) as local variable
2018-01-25 17:52:09 +08:00
47c1072b78 do html escape before display path, avoid xss 2018-01-08 19:35:53 +08:00
e81f1e53bf Merge pull request #3017 from Medicean/develop
Update: Fix migration generate SQL
2018-01-05 00:10:16 +08:00
cf92d2c6ef Update: Fix migration generate SQL 2018-01-04 10:42:39 +08:00
0507076c3f Merge pull request #3004 from pcallewaert/develop
redis cache: make MaxIdle configurable
2017-12-30 13:54:11 +08:00
59fd3952b7 bug: restore the default value 2017-12-26 11:48:34 +01:00
7fd80e6aa1 feat(redis.go): make MaxIdle configurable 2017-12-26 11:48:08 +01:00
24fa6189b5 fix the issue #2998 2017-12-23 16:41:56 +09:00
0bde9cbd91 fix the issue #2995 2017-12-22 16:21:23 +08:00
122414d789 Merge pull request #2992 from priteshgudge/develop
Update Documentation in Output.go
2017-12-21 17:08:10 +08:00
aac69674ad Update Documentation in Output.go
Fix Documentation for HTTP status codes descriptions.
2017-12-21 13:50:28 +05:30
1a42154c64 add file test 2017-12-20 17:54:40 +08:00
e81cca304b add file test 2017-12-20 16:19:58 +08:00
07aa97aa9a add hourly rotate file.go 2017-12-20 15:56:36 +08:00
94fba0b2aa fix orm fields SetRaw function error judge problem 2017-12-20 14:53:00 +08:00
80aa47f605 Merge pull request #2976 from szyhf/develop
Fix #2975
2017-12-19 23:34:17 +08:00
f16688817a Merge pull request #2978 from BorisBorshevsky/fix_reflection_bug
fix bug #2972
2017-12-18 19:18:59 +08:00
2670a86005 fix #2979 2017-12-14 17:55:08 +02:00
0e369e6df8 fix bug 2017-12-13 15:27:32 +02:00
84443b9c05 Fix #2975
修复AccessLog输出会多换一行的bug
2017-12-12 12:21:55 +08:00
33be6803a3 Merge pull request #2970 from gcy3y/master
update log.go add GetLevel Function to Log
2017-12-10 20:26:46 +08:00
aef2f1c66e Merge pull request #2971 from PureWhiteWu/fix_typo
fix a typo
2017-12-10 18:47:54 +08:00
619cd2d908 fix a typo 2017-12-08 23:01:21 +08:00
4613acd88e update log.go add GetLevel Function to Log 2017-12-08 15:35:12 +08:00
bf5c5626ab Merge pull request #2943 from astaxie/develop
1.9.2
2017-12-06 23:37:36 +08:00
0fbbc67c3d Merge pull request #2961 from zhlicen/master
Add lock while releasing session
2017-12-06 14:44:25 +08:00
3e1916ec3c Merge pull request #2953 from szyhf/develop
Proposal to #2952
2017-12-06 14:43:12 +08:00
b068a676dd Merge pull request #2950 from huhaocc/change_httpmethod_type
Change the type of HTTPMETHOD
2017-12-06 14:42:35 +08:00
ed73bdcfab Add lock while releasing session
Solve the problem of SessionRead failure while ReleaseSession is in progress
2017-12-05 16:18:56 +08:00
ae94b705ea Merge pull request #2954 from axetroy/master
test: Improve test case for utils/safemap, make it 100% cover
2017-12-01 23:33:46 +08:00
08fb921053 test: Improve test case for utils/safemap, make it 100% cover 2017-12-01 01:04:15 +08:00
e67e57f8fb orm: 修复logic enum因为type enum改变而产生的位错位。 2017-11-30 20:26:34 +08:00
b30969704a Proposal to #2952 2017-11-30 18:12:49 +08:00
646acc423e Change HTTPMETHOD type 2017-11-30 01:43:50 +08:00
c3a81a23f9 beego 1.9.2 2017-11-27 15:52:31 +08:00
103dd22151 remove mysq travis 2017-11-27 15:42:18 +08:00
ec6cb43711 fix ci failed 2017-11-27 14:07:05 +08:00
84cb9a5986 update to go1.9.2 2017-11-26 14:35:37 +08:00
9b8bc2aef7 Merge pull request #2941 from BorisBorshevsky/report-card
add report card to readme.md
2017-11-26 14:32:57 +08:00
9710d9e961 Merge pull request #2940 from BorisBorshevsky/golint
fix golint comments
2017-11-26 14:32:40 +08:00
58bb49a78c add report card to readme.md 2017-11-25 19:23:46 +02:00
df37739c7d fix golint comments 2017-11-25 19:18:37 +02:00
a5dd5d161d Merge pull request #2659 from ansiz/master
to close issue #1899(redis cache doubt)
2017-11-19 11:13:42 +08:00
6827107177 Merge pull request #2706 from fat4lix/bugfix
Fix run controller if it set by RumController and RunMethod in Filterfunc
2017-11-19 11:12:48 +08:00
a30a89e57e Merge pull request #2849 from iclinux/grace-patch
Make parent process exit gracefully.
2017-11-19 11:09:13 +08:00
d352e4abcb Merge pull request #2852 from Radar8/master
validation: fix ErrorMap, added AddError(key, message)
2017-11-19 11:08:31 +08:00
a948d4c1e1 set default to apache format 2017-11-19 11:07:57 +08:00
b7eb3963f5 Merge pull request #2863 from gadelkareem/develop
Add JSON or Apache access log formatting option to config:  AccessLogsFormat = JSON_FORMAT or APACHE_FORMAT  ref #2738
2017-11-19 11:05:12 +08:00
f7afb3cb75 Merge pull request #2878 from silviucm/master
Add the ability to unregister fixed routes
2017-11-19 10:41:40 +08:00
348bf51a42 Merge pull request #2914 from AbelZhou/master
Add sys env feature
2017-11-19 10:41:22 +08:00
dfea2cc5f3 Merge pull request #2928 from ilylia/develop
add Enum field to Schema
2017-11-19 10:27:11 +08:00
532eab8e1d Merge pull request #2932 from lotus-wu/Branch_v1.9.0
1.Add Mutual HTTPS  Option!
2017-11-19 10:26:59 +08:00
3872382a4b 1.Add Mutual HTTPS Option! 2017-11-15 22:42:30 +08:00
4018693fbd add Enum field to Schema 2017-11-13 14:36:08 +08:00
3b829504f6 Merge pull request #2920 from chenpeiyuan/develop
avoid unnecessary read of large log file
2017-11-07 09:35:09 -06:00
80fa51468c avoid unnecessary read of large log file
If w.MaxLines is not set, there is no need to calc large log file’s lines. It may takes more than 10mins to calc a 10G file.
2017-11-07 22:58:39 +08:00
3504d2a4da Add host env feature. 2017-10-31 19:19:14 +08:00
229d8b9530 Add host env feature. 2017-10-30 13:54:36 +08:00
9536d460d0 Create test file for the route unregistering functionality 2017-10-28 14:49:41 -04:00
e211e4839c Merge pull request #2906 from mlgd/patch-1
Update for MySQL timezone detection bug
2017-10-27 09:47:09 -05:00
3332dbe595 Update for MySQL timezone detection bug
Use "DefaultTimeLoc" for "al.TZ" default value
Don't set TZ on alias when t.Location() is empty

You can set your MySQL server timezone on main.go init function.
Example :
orm.DefaultTimeLoc, _ = time.LoadLocation("Europe/Paris")
2017-10-26 14:32:42 +02:00
b9c8c08c03 Add host env feature. 2017-10-26 19:25:05 +08:00
663f22d849 Merge pull request #2893 from melotusme/master
Typo fixed
2017-10-23 06:48:46 -05:00
fbaa4d1233 Merge pull request #2898 from skOak/swagger
remove omitempty for swagger.Response.Description, because it's Required according to Swagger2.0 Spec
2017-10-23 06:48:22 -05:00
7dc8991140 Merge pull request #2894 from skOak/develop
add custom middleware options for beego.Run()
2017-10-23 06:48:03 -05:00
0ce70b8c99 remove omitempty for swagger.Response.Description, because it's Required according to Swagger2.0 Spec 2017-10-18 22:34:24 +08:00
b169ea4b63 Merge pull request #2896 from tinycedar/master
misc: fix typos
2017-10-17 21:22:27 +08:00
72ec4df679 Merge branch 'master' into master 2017-10-17 04:30:59 -05:00
b91263a254 misc: fix typos 2017-10-17 17:27:03 +08:00
e91afb1938 add custom middleware options for beego.Run() 2017-10-16 14:55:08 +08:00
74eb613919 Typo fixed 2017-10-16 00:06:20 +08:00
32d4310861 Merge pull request #2886 from hurisheng/patch-1
add XMLBody method for httplib
2017-10-14 07:57:02 -05:00
c56704f3fd Merge branch 'master' into develop 2017-10-14 17:53:40 +08:00
c5118e9535 Merge pull request #2889 from lidaobing/fix_typo
fix typo
2017-10-14 03:28:56 -05:00
9b57566963 fix typo 2017-10-11 14:35:31 +08:00
8d59e7afd1 for the root path, all methods must be covered
use continue instead of return
2017-10-07 13:16:36 -04:00
fd733f76f0 simplify replacement of the base (root) tree 2017-10-07 12:14:28 -04:00
37d4bb3df5 add XMLBody method for httplib 2017-10-05 14:34:52 -05:00
5697c6d7cc Remove redundant return to pass gosimple Travis check 2017-09-25 23:17:57 -04:00
51a6162363 Update app.go 2017-09-25 21:45:42 -04:00
5a12b3d020 Add the ability to unregister fixed routes
Certain web application inherit most of the routes from a base application by using the underscore import 
(e.g. import _ "myoldapp.com/routers").
The new application might want to only overwrite certain pages, such as "/about" or "/faq"

The proposed new UnregisterFixedRoute method allows unregistering of the specified paths.
 Usage (replace "GET" with "*" for all methods):
  beego.UnregisterFixedRoute("/yourpreviouspath", "GET")
  beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage")
The children paths are left intact.
For example, /yourpreviouspath/oldchildsubpath should still continue to function in legacy mode.
2017-09-25 20:52:19 -04:00
bebd2c469d Remove typo 2017-09-13 22:15:40 +02:00
d15e66a4ff check if SetEscapeHTML exists instead of checking Go version 2017-09-13 22:14:41 +02:00
c04d43695c fix dependency go-version for travis 2017-09-13 03:16:33 +02:00
d813334a24 Merge branch 'develop' of github.com:gadelkareem/beego into develop 2017-09-13 03:10:08 +02:00
9fef2f2eb4 Do not escape html if Golang version < 1.7 2017-09-13 03:09:53 +02:00
0c746f4547 Merge branch 'master' into develop 2017-09-13 02:16:50 +02:00
f5c8b1c6ac Merge branch 'master' of github.com:gadelkareem/beego 2017-09-13 02:05:15 +02:00
4921014c64 Add JSON or Apache access log formatting option to config: AccessLogsFormat = JSON_FORMAT or APACHE_FORMAT ref #2738 2017-09-13 02:03:46 +02:00
520753415f Merge pull request #2846 from hikenote/master
return template build error rather than log error
2017-09-09 06:42:02 +08:00
3162da131d Merge pull request #2858 from astaxie/revert-2854-master
Revert "should use time.Since instead of time.Now().Sub"
2017-09-09 06:29:59 +08:00
a7354d2d08 Revert "should use time.Since instead of time.Now().Sub" 2017-09-09 06:29:38 +08:00
07a9a2d0f3 Merge pull request #2854 from wgliang/master
should use time.Since instead of time.Now().Sub
2017-09-09 06:29:31 +08:00
c8c25549e7 should use time.Since instead of time.Now().Sub
Signed-off-by: wgliang <liangcszzu@163.com>
2017-09-07 19:01:34 +08:00
6641a436a2 validation: fix ErrorMap, added func AddError(key, message) 2017-09-06 16:49:21 +08:00
1dd50fb65f Make parent process exit gracefully.
With beego.BConfig.Listen.Graceful  enabled, when received SIGHUP, we'll fork a child process.
But the parent process still have jobs to finish, So we can't kill the parent process directly.
2017-09-05 11:53:42 +08:00
4bc4f77c29 return template build error 2017-09-02 17:55:26 +08:00
ef36ecd376 avoid some proxy not support select command 2017-08-31 20:26:32 +08:00
c6cef853c7 Merge pull request #2813 from mlgd/develop
Add IPV6 compatibility
2017-08-23 16:54:59 +08:00
33ad8d5db4 Merge pull request #2815 from wisererik/patch-1
Fix the quick start section of the orm/README.md
2017-08-23 16:53:41 +08:00
33e6d57754 Merge pull request #2820 from crazcalm/doc_error
fixed mispelled word
2017-08-22 23:18:31 +08:00
afa57ca1f2 fixed mispelled word 2017-08-20 23:32:11 +08:00
510dd02a06 Fix the quick start section of the orm/README.md
Increase the init method by adding the `RunSyncdb` method to resolve the problem that the table is not created.
2017-08-11 11:34:18 +08:00
166e88c103 Update input.go 2017-08-09 21:05:06 +02:00
51b6adeb24 Add IPV6 compatibility 2017-08-09 10:23:03 +02:00
51c19c374a Merge pull request #2801 from nightlyone/patch-1
fix bad error code in seesion handling
2017-08-06 22:29:05 +08:00
b23452dc3f Merge pull request #2805 from ninjacn/master
comment edit
2017-08-06 22:28:36 +08:00
f61038e6bd Merge pull request #2803 from iamzhout/log_add_milliseconds
add millisecond to timestamp in log output
2017-08-06 22:28:19 +08:00
b9117e2ff1 add millisecond to timestamp in log output 2017-08-04 00:11:59 +08:00
5a7a3da909 comment edit 2017-08-03 19:15:32 +08:00
3f4502990a fix bad error code
By providing a unique error code instead of a format specifier without using an error formatting function.
2017-08-03 01:52:24 +02:00
e14113aa0e Merge pull request #2656 from BorisBorshevsky/develop
Allow injecting dependencies to controllers
2017-07-30 23:36:52 +08:00
d96289a81b Merge pull request #2771 from astaxie/develop
v1.9.0
2017-07-19 00:56:48 +08:00
4fc95b0d69 gofmt and golint 2017-07-19 00:52:27 +08:00
aa3d6c5363 fix the gosimple 2017-07-19 00:37:42 +08:00
5ac0cb929c v1.9.0 2017-07-18 23:58:22 +08:00
b27ab53017 fix issue for runMethod and runRouter from context 2017-07-18 23:41:50 +08:00
1aba294405 Merge pull request #2740 from xlwcom/master
fix the bugs in the "ParseBool" function in the file of config.go
2017-07-18 13:39:45 +08:00
657e55ed59 Merge pull request #2692 from jerson/master
added statusCode and pattern to FilterMonitorFunc
2017-07-17 11:06:09 +08:00
621c25396e Merge pull request #2766 from yangsf5/master
sort ControllerComments
2017-07-17 11:02:28 +08:00
715ba918f0 Merge pull request #2744 from gnanakeethan/feature/database-migration
[Proposal] Database Migrations;
2017-07-17 10:54:44 +08:00
8bb0a70847 Update: Fix in SQL Generation
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-16 08:48:44 +05:30
94e79eddcf Update: removing remnant of revert commit ( a call to function
m.DDLSpec() )

Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-16 08:37:27 +05:30
749a4028b4 Revert "Update: removing the need to call DDLSpec in the migration file"
The odds of getting this perfectly up is not good.

This reverts commit d58ad2ee36.
2017-07-16 08:11:10 +05:30
fc55c2b57c Update: missed to call DDLSpec in Down migration
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-16 07:30:53 +05:30
d58ad2ee36 Update: removing the need to call DDLSpec in the migration file
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-16 07:24:58 +05:30
cb38ab4f85 Update: fixing a SQL generation code
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-16 07:10:09 +05:30
fc86f6422d sort ControllerComments 2017-07-15 17:26:20 +08:00
d453242e48 Update: moving package to bottom
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-14 14:42:56 +05:30
7c2ec075a4 Update: fixing some methods and adding documentation
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-13 21:03:30 +05:30
c903de41e4 updated sample for FilterMonitorFunc
added pattern to sample
2017-07-12 09:51:37 -05:00
e8c8366308 Merge pull request #2754 from imiskolee/develop
supported gzip for req.Header has `Content-Encoding: gzip`
2017-07-12 19:57:29 +08:00
4901567bba Merge pull request #2749 from satng/patch-4
oracle插入占位符
2017-07-12 19:49:32 +08:00
29bcd31b27 supported gzip for req.Header has Content-Encoding: gzip 2017-07-10 21:27:54 +08:00
83a563c0ab oracle插入占位符 2017-07-09 12:25:51 +08:00
e888fee4e0 Update: Foreign Key & Comments
Summary: Foreign Key functions are now available

Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-06 20:26:37 +05:30
c1ba11f531 Fixing typo
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-06 14:58:40 +05:30
ed558a0e70 Fix: typo due to find and replace migration renamed to m
Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-06 07:45:07 +05:30
6b9c3f4824 [Proposal] Database Migrations;
Summary: The database migrations now can be created using the methods on
the migration struct. it does not break any existing migration features.
it upgrades the migration struct and adds few more struct types so that
the migrations can be efficiently generated for create, alter, reverse,
drop.

Current Features:
* Supports creation of columns
   * `m.NewCol("name").SetDataType("VARCHAR(10)").SetNullable("true")`
   * **NOTE** `SetNullable` & `SetDefault` methods should not be called on
   same column for consistency
* Supports addition of primary keys
   * `m.PriCol("id").SetDataType("INT(10)").SetNullable("true")`
   * **NOTE** `setAuto(true)` can be only called on Primary keys
* Supports addition of unique keys
   * `m.UniCol("unique_index","column_name").SetDataType("VARCHAR(23)").SetNullable("true")`
   * **NOTE** `UniCol` can be called again with the same index name to
   add column to the index
* Supports rename of columns
   * `m.RenameColumn("from_name","to_name")`
   * Allows standard column methods and methods such that, `SetOldDefault` allows
   reversibility of renames

* TODO:
   * ForeignKey

Signed-off-by: Gnanakeethan Balasubramaniam <gnanakeethan@gmail.com>
2017-07-06 07:44:48 +05:30
7ec819deed fix #2725 big form 2017-07-04 21:16:59 +08:00
4cfb3678f8 Merge pull request #2741 from miraclesu/validation
validation: support required option for some struct tag valids
2017-07-04 15:49:36 +08:00
3c17e2a7e6 remove the comments 2017-07-04 11:03:49 +08:00
e72b02b7cc validation: support required option for some struct tag valids 2017-07-03 16:26:23 +08:00
82586c70e9 Merge pull request #2683 from jialijelly/master
Provide permission to access old files to everyone
2017-07-03 11:23:49 +08:00
cb86bcc9e8 Merge pull request #2728 from miraclesu/validation
validation: support int64 int32 int16 and int8 type on 64-bit platform
2017-07-01 15:35:48 +08:00
234708062a fix the bug in the "ParseBool" function in the file of config.go 2017-06-29 13:32:40 +08:00
6e34f43721 Fix break API change
support int64 on 64-bit platform
2017-06-28 16:56:37 +08:00
3249ec8ebf Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-27 10:49:10 +08:00
31b2b21dbc Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-27 10:48:52 +08:00
d0c1936922 Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-22 19:18:49 +08:00
338a23a12b Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-22 19:18:34 +08:00
932def1ed2 Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-22 18:55:37 +08:00
16b5a11484 Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-22 18:55:25 +08:00
547fbce86c Merge branch 'master' of https://github.com/jialijelly/beego 2017-06-22 18:53:29 +08:00
5a2eea07cb Provide permission to access old log files to everyone 2017-06-22 18:51:52 +08:00
2231841d74 validation: support int64 int32 int16 and int8 type 2017-06-21 14:13:30 +08:00
805a674825 Merge pull request #2712 from eyalpost/develop
incorrect error rendering (wrong status)
2017-06-16 13:34:28 +08:00
f2925978f1 Merge pull request #2717 from huwenbo/master
fix panic sync: negative WaitGroup counter
2017-06-16 13:26:36 +08:00
fe3a224a23 Merge pull request #2724 from JessonChan/develop
AddAPPStartHook func modify
2017-06-16 08:20:45 +08:00
2754edc849 Merge pull request #2726 from moqiancong/develop
fix cache/memory fatal error: concurrent map iteration and map write
2017-06-16 08:20:03 +08:00
79f60274a0 fix cache/memory fatal error: concurrent map iteration and map write 2017-06-16 01:26:55 +08:00
a87c1c5e8e AddAPPStartHook func modify 2017-06-15 17:36:37 +08:00
2b00b7d66d fix panic: sync: negative WaitGroup counter 2017-06-13 20:15:43 +08:00
3d9286f089 fix panic: sync: negative WaitGroup counter 2017-06-13 15:34:57 +08:00
55e6c15073 fix panic: sync: negative WaitGroup counter 2017-06-13 15:19:51 +08:00
8b504e7d51 incorrect error rendering (wrong status) 2017-06-12 21:05:40 +03:00
47ef2b343e Fix run controller if it set by RumController and RunMethod in Filterfunc 2017-06-09 15:53:03 +03:00
80dcdb8645 Fix run controller if it set by RumController and RunMethod in FilterFunc 2017-06-09 15:27:26 +03:00
d1c3bd8416 Merge pull request #2701 from eyalpost/develop
correctly handle multiple params with same type
2017-06-09 15:33:37 +08:00
0240e182c6 correctly handle multiple params with same type 2017-06-09 10:15:36 +03:00
7f2e3feb3c added pattern to FilterMonitorFunc 2017-06-05 18:21:31 -05:00
d15dd2795c added statusCode in FilterMonitorFunc 2017-06-03 15:24:45 -05:00
0ea34fff27 Provide permission to access old log files to everyone 2017-06-02 10:56:55 +08:00
4e8f212069 refactor: #1899 redis cache module refactor
redis cache module refactor, to view the discussion:

  https://github.com/astaxie/beego/issues/1899
2017-05-20 19:56:38 +08:00
eb71d0ea7f Merge remote-tracking branch 'upstream/master' 2017-05-20 19:39:24 +08:00
b24ddb953c merge from upstram + resolve conflicts 2017-05-20 02:42:00 +03:00
12f8fbe37f Allow injecting dependencies to controllers 2017-05-20 02:06:28 +03:00
5e8312bc23 Merge pull request #2654 from casbin/master
Fix the new repo address for casbin.
2017-05-19 23:34:58 +08:00
88d07058a5 Fix the new repo address for casbin. 2017-05-19 22:19:31 +08:00
cab8458c1c Merge pull request #2651 from astaxie/develop
v1.8.3
2017-05-19 21:19:18 +08:00
f0b95c552b Merge pull request #2315 from sch00lb0y/master
issue no:#2261 fix for xsrf panic error
2017-05-19 18:50:56 +08:00
ce677202e5 issue no:#2261 fix for xsrf panic error 2017-05-19 14:02:14 +05:30
720c323e20 Merge pull request #2652 from gouyang/gouyang/dev
Support timeformat "2006-01-02T15:04:05"
2017-05-19 09:48:35 +08:00
47e351e11d Support timeformat "2006-01-02T15:04:05"
Fixes #2649

Signed-off-by: Guohua Ouyang <guohuaouyang@gmail.com>
2017-05-19 09:22:27 +08:00
248beab557 v1.8.3 2017-05-18 22:55:10 +08:00
388a5610fa Merge pull request #2365 from chesedo/RequiredValidationCatchSpaces
[WIP]Have Required validator trim strings to fix #2361
2017-05-18 22:44:15 +08:00
41498758fe Merge pull request #2620 from hsluoyz/authz
Add an authorization plugin that supports ACL, RBAC based on casbin.
2017-05-18 18:18:12 +08:00
655484b4df Merge pull request #2586 from eyalpost/develop
Automatic Parameter Router
2017-05-18 18:14:49 +08:00
11b4bf8aaa move to context 2017-05-18 10:38:12 +03:00
2513bcf584 remove Redirect to avoid confusion 2017-05-18 10:32:51 +03:00
3e51823c0f move response 2017-05-18 09:05:49 +03:00
e32a18203b fix gosimple 2017-05-17 21:27:32 +03:00
ee1d8bc30e fix gosimple 2017-05-17 20:50:41 +03:00
828cbbdf5d Refactor a bit to consolidate packages 2017-05-17 20:38:59 +03:00
d54cd4fa5f Merge remote-tracking branch 'upstream/develop' into develop 2017-05-17 20:02:40 +03:00
7747e9ec8b Merge branch 'develop' of https://github.com/astaxie/beego into develop 2017-05-17 20:52:53 +08:00
9765519f38 Merge pull request #2637 from alexsunxl/develop
allow o.Raw(sql).QueryRows(&container) pass nested struct
2017-05-17 16:45:14 +08:00
69f0b94745 fix gosimple 2017-05-16 22:21:43 +08:00
3c9b6c99b7 Merge pull request #2643 from rbw0/master
Spelling fixes
2017-05-16 11:26:16 +08:00
b5c6eb54d2 Missing PK error spelling fix 2017-05-16 00:58:20 +02:00
e1c90bfc09 Table not found spelling fixes 2017-05-16 00:27:57 +02:00
91400f10b0 Merge pull request #2640 from franzwilhelm/master
Moved Security to Operation struct to support swagger API security auth
2017-05-14 19:15:20 +08:00
c814893d65 add support for global security 2017-05-14 12:13:35 +02:00
2325090101 add test case that used nested struct test QueryRows 2017-05-14 12:03:34 +08:00
40bc52b844 fix security struct placement and formatting 2017-05-14 00:42:09 +02:00
589f3755f0 允许o.Raw(sql).QueryRows(&container) 传入的container包含结构的嵌套 2017-05-12 18:11:42 +08:00
1004678005 popular status codes 2017-05-12 09:57:56 +03:00
0ac2e47162 location=>paramType 2017-05-12 09:28:46 +03:00
b6a35a8944 more tests 2017-05-12 09:25:12 +03:00
74dc3c7500 tests 2017-05-11 19:32:44 +03:00
cb4f252a06 defValue -> defaultValue 2017-05-11 17:58:25 +03:00
bceefc9075 Merge pull request #2636 from guanly/master
ISSUE2630 使用sqlite,orm中通过filter后的delete删除不成功
2017-05-11 22:04:27 +08:00
10cd1070f4 使用sqlite,orm中通过filter后的delete删除不成功
https://github.com/astaxie/beego/issues/2630
2017-05-11 21:45:38 +08:00
9b01b1c63d ISSUE2630 使用sqlite,orm中通过filter后的delete删除不成功
https://github.com/astaxie/beego/issues/2630
2017-05-11 14:49:01 +08:00
b2e7720fcd Add an authorization plugin that supports ACL, RBAC based on casbin. It requires the built-in HTTP basic authentication by default. 2017-05-04 14:02:21 +08:00
83814a76cc hotfix: err nil 2017-05-02 12:47:15 +08:00
d3a16dca85 Redirect should returns error 2017-05-01 08:57:57 +03:00
7452151bee Merge pull request #2611 from astaxie/develop
1.8.2
2017-05-01 13:01:59 +08:00
1b8f05cef1 golint fixes 2017-04-30 19:28:26 +03:00
cfb2f68dd6 Merge remote-tracking branch 'upstream/develop' into develop 2017-04-30 18:59:50 +03:00
e76423e6dc revert #2518, fix #2605 2017-04-30 23:59:38 +08:00
947980b5eb beego 1.8.2 2017-04-30 23:43:46 +08:00
44bdf1df63 ignore NilErr 2017-04-30 23:38:48 +08:00
79b66ef053 fix the beego ORM test 2017-04-30 22:55:39 +08:00
a91e2e9950 add golint check and fix all golints 2017-04-30 22:41:23 +08:00
ea3d0690cf golint 2017-04-29 09:13:28 +08:00
1c32c011a1 fix misspell 2017-04-28 23:37:40 +08:00
64b475d7d6 fix ReadOrCreate test case 2017-04-28 22:58:17 +08:00
aa8f7bc146 fix ineffectual 2017-04-28 22:36:28 +08:00
3e29078f68 add check ineffect and gofmt 2017-04-28 21:38:08 +08:00
a1bc94e648 dont generate comment if router not found 2017-04-26 01:00:25 +03:00
4cba78afd9 small fixes 2017-04-25 23:42:35 +03:00
f311ae9ebe feature: Export function printTree (#2597)
This exports PrintTree function which allow to set Role Based Access Control, Create our own requests statistics...etc
2017-04-25 18:12:03 +02:00
cbd831042a move under context 2017-04-25 18:39:42 +03:00
522b3a4a70 Merge pull request #2596 from astaxie/develop
beego 1.8.1
2017-04-25 22:51:23 +08:00
cd67f13bf9 add template layout test #2481 2017-04-25 22:21:27 +08:00
0f554d9b1a update travis to latest go version 2017-04-25 22:21:08 +08:00
9b79437778 all types working + controller comments generation 2017-04-25 16:00:49 +03:00
3742d1178c httplib support delete params fix #2593 2017-04-25 20:42:43 +08:00
b9e3cbbf44 feat: export function printTree
we can do more thing with an exported PrintTree function, such as
  set role base access control,create our own requests statistics.
2017-04-25 12:42:36 +08:00
3c0c87f473 Merge pull request #1 from astaxie/master
merge from origin repo
2017-04-25 12:38:32 +08:00
3b29a9c12a Merge remote-tracking branch 'upstream/develop' into develop 2017-04-24 18:23:58 +03:00
d03285a0ee Merge pull request #2555 from Liaodd/master
Update ini.go: change the key to lowercase when set a new key for ini configer
2017-04-24 22:37:25 +08:00
e810f2e930 add more oracle alias 2017-04-24 21:36:07 +08:00
41aac79ac0 Merge pull request #2590 from amrfaissal/fix-2587
Fix warnings raised by gometalinter and gosimple
2017-04-24 21:14:00 +08:00
52f916a28a support Go1.8 default GOPATH 2017-04-24 21:10:03 +08:00
864693d2f8 mall fixes 2017-04-24 02:35:04 +03:00
08ea9b3339 Merge remote-tracking branch 'upstream/develop' into develop 2017-04-23 22:07:46 +03:00
19f4a6ac0b slice support 2017-04-23 21:37:09 +03:00
bf6bd6b292 Fixes #2587
Fixes warnings and errors raised by gometalinter and gosimple.
2017-04-23 19:19:05 +02:00
89e01d125c all types implemented 2017-04-23 01:33:50 +03:00
3bb4ca5adc Merge pull request #2583 from OlegFX/develop
Fixed InsertOrUpdate bug
2017-04-22 11:19:15 +08:00
712df81c99 Fixed InsertOrUpdate bug 2017-04-21 19:57:04 +03:00
3d7ef599cc Merge pull request #2 from astaxie/develop
Update
2017-04-21 19:50:33 +03:00
9aedb4d05a phase #1 2017-04-21 15:26:41 +03:00
453691728a gofmt simplify 2017-04-20 10:56:09 +08:00
e7e3ca77ad Merge pull request #2491 from chendx79/master
Fix routing bug for splat matching
2017-04-19 21:20:30 +08:00
d3f3956def Merge pull request #2574 from extrlibs/master
Fix the following template reference
2017-04-19 21:18:26 +08:00
7206214105 Merge pull request #2575 from PaulChen2016/master
Beego 运行过程中动态增减定时任务时,now的时间需要更新,否则等待时间会不正确
2017-04-19 21:17:38 +08:00
b08ace7532 Merge pull request #2577 from ggicci/develop
Fix ini parsing error for multiple users on one machine.
2017-04-19 20:17:45 +08:00
d1a2583972 Fix ini parsing error for multiple users on one machine.
If there were multiple users working on one machine, it's common that
"/tmp/beego" will be owned by one of them, and the others won't be able
to access to it. So, it's better to add an "id-like" postfix to the
temporary directory.
2017-04-19 19:50:11 +08:00
0cb8de4218 Beego 运行过程中动态增减定时任务时,now的时间需要更新,否则等待时间会不正确
beego 启动时,执行toolbox.StartTask()
运行过程中,动态添加定时任务(这个是now时间已经不是starttask的时间了,需要刷新)     
     case <-changed:
			now = time.Now().Local()
			continue
2017-04-19 16:22:58 +08:00
0cd31a247f fix Template nesting problem 2017-04-19 07:47:05 +08:00
7886e69236 Update sess_file.go 2017-04-17 12:37:54 -04:00
405c170d45 Merge pull request #2556 from zjjott/master
fix: log mode should be 0440 should not be 440
2017-04-10 21:14:34 +08:00
932019770d fix: log mode 0440 should not be 440 2017-04-10 17:37:55 +08:00
d5c03f5b8f Update ini.go
change the key to lowercase when set a new key for ini configer
2017-04-10 11:30:23 +08:00
3d20c0b8f4 Merge pull request #2543 from Bobochka/fix_route_hander_docs
Fix example for Hander func
2017-04-07 23:04:38 +08:00
a4fb4c6a03 Merge pull request #2547 from TrueFurby/patch-1
Update README.md
2017-04-03 20:25:41 +02:00
a9941b6edc Merge branch 'develop' into patch-1 2017-04-02 13:46:06 +02:00
6a32b048bd Update README.md 2017-04-02 11:04:08 +02:00
fb04d3cff1 Fix example for Hander func 2017-03-30 12:53:12 +03:00
5c7673e73d update to 1.8.1 2017-03-29 10:22:27 +08:00
54b05377d9 Merge pull request #2466 from gouyang/gouyang/develop
Parse form time by its length
2017-03-29 10:01:03 +08:00
f49f3f92ec Merge pull request #2533 from cnxh/redis-session-poolsize
redis poolsize could set to zero
2017-03-29 10:00:28 +08:00
b18b94f03b Merge pull request #2539 from astaxie/revert-2421-develop
Revert "close mysql connection"
2017-03-29 09:57:54 +08:00
bf469f0b55 Revert "close mysql connection" 2017-03-29 09:57:18 +08:00
c12709dbc9 Merge pull request #2536 from ChristophPech/master
Fix for IndexExists in SQLite driver
2017-03-29 09:53:55 +08:00
2808a13f07 Fix for IndexExists in SQLite driver, they added the "origin" and "partial" columns to the index_list pragma.
see: https://www.sqlite.org/src/info/2743846cdba572f6
2017-03-28 12:38:27 +02:00
a8d48aa5ea Merge pull request #2535 from ketanhwr/patch-1
Update README.md
2017-03-28 10:49:52 +02:00
fdb2660a2a Update README.md 2017-03-28 11:29:20 +05:30
7c3a997735 poolsize could set to zero
sometimes we may want disable the redis pool
2017-03-27 23:34:58 +08:00
1760f0ceca Merge pull request #2532 from vbalien/patch-1
Fix markdown formatting
2017-03-27 13:34:19 +08:00
c387aeeb36 fix markdown formatting 2017-03-27 14:30:42 +09:00
83d4385f1f Support time.RFC3339
Signed-off-by: Guohua Ouyang <guohuaouyang@gmail.com>
2017-03-25 07:52:43 +08:00
ae0a75c464 console debug use diffrent color 2017-03-21 23:55:40 +08:00
a05e5a7c09 Merge branches 'master' and 'develop' of https://github.com/astaxie/beego into develop 2017-03-21 23:47:32 +08:00
75ca7b77b6 Merge pull request #2518 from sicojuy/develop
Fix set cookie bug, zero max age is not valid.
2017-03-21 09:44:37 +08:00
b0e2012a17 Fix set cookie bug, zero max age is not valid. 2017-03-21 09:15:30 +08:00
12dff072fa Merge pull request #2509 from sergeylanzman/add-gosimple
add go simple support
2017-03-18 13:37:31 +08:00
d956444965 Merge branch 'develop' into add-gosimple 2017-03-18 11:20:30 +08:00
8dbf9eb0bf Merge pull request #2512 from sergeylanzman/add-unconvert
add unconverted support
2017-03-18 11:05:28 +08:00
faa981fb6a Merge pull request #2511 from sergeylanzman/drop-go1.4-1.5-travis
drop support go1.4/5 in travis yml
2017-03-18 11:02:39 +08:00
1ea3c13ff5 Merge branch 'develop' into drop-go1.4-1.5-travis 2017-03-18 11:02:15 +08:00
46d8fef0ad Merge pull request #2510 from sergeylanzman/travis-add-1.7-1.8
add support go1.7/8 to travis yml
2017-03-18 11:01:27 +08:00
37c1ffc57a add go simple support 2017-03-17 20:22:20 +02:00
856fde28dc add unconverted support 2017-03-17 19:45:30 +02:00
3204d7631b drop support go1.4/5 in travis yml 2017-03-17 19:33:54 +02:00
f5fc2edfd3 add support go1.7/8 to travis yml 2017-03-17 19:32:31 +02:00
21d1267c14 Merge pull request #2501 from sergeylanzman/change-template-error-log
temple parse error write in log as trace. change to error
2017-03-14 20:04:42 +08:00
3e37b97549 temple parse error write in log as trace. change to error 2017-03-14 12:55:40 +02:00
206f736819 Merge pull request #2496 from eyalpost/develop
Don't panic during AddViewPath if adding the same path twice
2017-03-14 12:54:48 +08:00
24d4a27842 Don't panic during AddViewPath if adding the same path twice 2017-03-13 08:46:57 +02:00
46f3ea4f43 Merge pull request #2494 from miraclesu/develop
validation: fix email valid
2017-03-12 22:26:25 +08:00
c9cc642d37 validation: amend email test case 2017-03-12 20:27:09 +08:00
b34853f8cc validattion: add test case for email valid 2017-03-12 19:30:23 +08:00
d41f4c0a3a validation: fix email valid 2017-03-12 19:23:46 +08:00
8e46decc8e fix routing bug for splat 2017-03-10 09:28:25 +08:00
49fffe3ebe Parse form time by its length
Fix #2451

Signed-off-by: Guohua Ouyang <guohuaouyang@gmail.com>
2017-02-27 14:43:16 +08:00
a8a2dffc59 Have Required validator trim strings to fix #2361
This will cause the Required validator not to consider fields that has
only spaces or new lines to be regarded as valid. This is done by
checking if the trimmed version of the string is valid.
2017-01-06 10:12:22 +02:00
748 changed files with 361955 additions and 1435 deletions

4
.gosimpleignore Normal file
View File

@ -0,0 +1,4 @@
github.com/astaxie/beego/*/*:S1012
github.com/astaxie/beego/*:S1012
github.com/astaxie/beego/*/*:S1007
github.com/astaxie/beego/*:S1007

View File

@ -1,9 +1,9 @@
language: go language: go
go: go:
- 1.6 - "1.9.x"
- 1.5.3 - "1.10.x"
- 1.4.3 - "1.11.x"
services: services:
- redis-server - redis-server
- mysql - mysql
@ -11,7 +11,6 @@ services:
- memcached - memcached
env: env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
before_install: before_install:
- git clone git://github.com/ideawu/ssdb.git - git clone git://github.com/ideawu/ssdb.git
@ -23,23 +22,32 @@ install:
- go get github.com/go-sql-driver/mysql - go get github.com/go-sql-driver/mysql
- go get github.com/mattn/go-sqlite3 - go get github.com/mattn/go-sqlite3
- go get github.com/bradfitz/gomemcache/memcache - go get github.com/bradfitz/gomemcache/memcache
- go get github.com/garyburd/redigo/redis - go get github.com/gomodule/redigo/redis
- go get github.com/beego/x2j - go get github.com/beego/x2j
- go get github.com/couchbase/go-couchbase - go get github.com/couchbase/go-couchbase
- go get github.com/beego/goyaml2 - go get github.com/beego/goyaml2
- go get gopkg.in/yaml.v2
- go get github.com/belogik/goes - go get github.com/belogik/goes
- go get github.com/siddontang/ledisdb/config - go get github.com/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis - go get github.com/siddontang/ledisdb/ledis
- go get github.com/ssdb/gossdb/ssdb - go get github.com/ssdb/gossdb/ssdb
- go get github.com/cloudflare/golz4 - go get github.com/cloudflare/golz4
- go get github.com/gogo/protobuf/proto - go get github.com/gogo/protobuf/proto
- go get github.com/Knetic/govaluate
- go get github.com/casbin/casbin
- go get github.com/elazarl/go-bindata-assetfs
- go get -u honnef.co/go/tools/cmd/gosimple
- go get -u github.com/mdempsky/unconvert
- go get -u github.com/gordonklaus/ineffassign
- go get -u github.com/golang/lint/golint
- go get -u github.com/go-redis/redis
before_script: before_script:
- psql --version - psql --version
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" - 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' = '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 [ '$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 "go get github.com/golang/lint/golint; golint ./...;"
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi" - sh -c "go list ./... | grep -v vendor | xargs go vet -v"
- mkdir -p res/var - mkdir -p res/var
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
after_script: after_script:
@ -47,5 +55,10 @@ after_script:
- rm -rf ./res/var/* - rm -rf ./res/var/*
script: script:
- go test -v ./... - go test -v ./...
- gosimple -ignore "$(cat .gosimpleignore)" $(go list ./... | grep -v /vendor/)
- unconvert $(go list ./... | grep -v /vendor/)
- ineffassign .
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
- golint ./...
addons: addons:
postgresql: "9.4" postgresql: "9.6"

View File

@ -1,20 +1,20 @@
## Beego # Beego [![Build Status](https://travis-ci.org/astaxie/beego.svg?branch=master)](https://travis-ci.org/astaxie/beego) [![GoDoc](http://godoc.org/github.com/astaxie/beego?status.svg)](http://godoc.org/github.com/astaxie/beego) [![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org) [![Go Report Card](https://goreportcard.com/badge/github.com/astaxie/beego)](https://goreportcard.com/report/github.com/astaxie/beego)
[![Build Status](https://travis-ci.org/astaxie/beego.svg?branch=master)](https://travis-ci.org/astaxie/beego)
[![GoDoc](http://godoc.org/github.com/astaxie/beego?status.svg)](http://godoc.org/github.com/astaxie/beego)
[![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org)
beego is used for rapid development of RESTful APIs, web apps and backend services in Go. beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding. It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
More info [beego.me](http://beego.me) Response time ranking: [web-frameworks](https://github.com/the-benchmarker/web-frameworks).
###### More info at [beego.me](http://beego.me).
## Quick Start ## Quick Start
######Download and install
#### Download and install
go get github.com/astaxie/beego go get github.com/astaxie/beego
######Create file `hello.go` #### Create file `hello.go`
```go ```go
package main package main
@ -24,15 +24,16 @@ func main(){
beego.Run() beego.Run()
} }
``` ```
######Build and run #### Build and run
```bash
go build hello.go go build hello.go
./hello ./hello
```
######Congratulations! #### Go to [http://localhost:8080](http://localhost:8080)
You just built your first beego app.
Open your browser and visit `http://localhost:8080`. Congratulations! You've just built your first **beego** app.
Please see [Documentation](http://beego.me/docs) for more.
###### Please see [Documentation](http://beego.me/docs) for more.
## Features ## Features
@ -56,7 +57,7 @@ Please see [Documentation](http://beego.me/docs) for more.
* [http://beego.me/community](http://beego.me/community) * [http://beego.me/community](http://beego.me/community)
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232) * Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
## LICENSE ## License
beego source code is licensed under the Apache Licence, Version 2.0 beego source code is licensed under the Apache Licence, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0.html). (http://www.apache.org/licenses/LICENSE-2.0.html).

117
admin.go
View File

@ -35,9 +35,9 @@ import (
var beeAdminApp *adminApp var beeAdminApp *adminApp
// FilterMonitorFunc is default monitor filter when admin module is enable. // FilterMonitorFunc is default monitor filter when admin module is enable.
// if this func returns, admin module records qbs for this request by condition of this function logic. // if this func returns, admin module records qps for this request by condition of this function logic.
// usage: // usage:
// func MyFilterMonitor(method, requestPath string, t time.Duration) bool { // func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool {
// if method == "POST" { // if method == "POST" {
// return false // return false
// } // }
@ -50,7 +50,7 @@ var beeAdminApp *adminApp
// return true // return true
// } // }
// beego.FilterMonitorFunc = MyFilterMonitor. // beego.FilterMonitorFunc = MyFilterMonitor.
var FilterMonitorFunc func(string, string, time.Duration) bool var FilterMonitorFunc func(string, string, time.Duration, string, int) bool
func init() { func init() {
beeAdminApp = &adminApp{ beeAdminApp = &adminApp{
@ -62,20 +62,32 @@ func init() {
beeAdminApp.Route("/healthcheck", healthcheck) beeAdminApp.Route("/healthcheck", healthcheck)
beeAdminApp.Route("/task", taskStatus) beeAdminApp.Route("/task", taskStatus)
beeAdminApp.Route("/listconf", listConf) beeAdminApp.Route("/listconf", listConf)
FilterMonitorFunc = func(string, string, time.Duration) bool { return true } FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true }
} }
// AdminIndex is the default http.Handler for admin module. // AdminIndex is the default http.Handler for admin module.
// it matches url pattern "/". // it matches url pattern "/".
func adminIndex(rw http.ResponseWriter, r *http.Request) { func adminIndex(rw http.ResponseWriter, _ *http.Request) {
execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
} }
// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. // QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
// it's registered with url pattern "/qbs" in admin module. // it's registered with url pattern "/qps" in admin module.
func qpsIndex(rw http.ResponseWriter, r *http.Request) { func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
data := make(map[interface{}]interface{}) data := make(map[interface{}]interface{})
data["Content"] = toolbox.StatisticsMap.GetMap() data["Content"] = toolbox.StatisticsMap.GetMap()
// do html escape before display path, avoid xss
if content, ok := (data["Content"]).(M); ok {
if resultLists, ok := (content["Data"]).([][]string); ok {
for i := range resultLists {
if len(resultLists[i]) > 0 {
resultLists[i][0] = template.HTMLEscapeString(resultLists[i][0])
}
}
}
}
execTpl(rw, data, qpsTpl, defaultScriptsTpl) execTpl(rw, data, qpsTpl, defaultScriptsTpl)
} }
@ -92,7 +104,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
data := make(map[interface{}]interface{}) data := make(map[interface{}]interface{})
switch command { switch command {
case "conf": case "conf":
m := make(map[string]interface{}) m := make(M)
list("BConfig", BConfig, m) list("BConfig", BConfig, m)
m["AppConfigPath"] = appConfigPath m["AppConfigPath"] = appConfigPath
m["AppConfigProvider"] = appConfigProvider m["AppConfigProvider"] = appConfigProvider
@ -105,42 +117,25 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
tmpl.Execute(rw, data) tmpl.Execute(rw, data)
case "router": case "router":
var ( content := PrintTree()
content = map[string]interface{}{ content["Fields"] = []string{
"Fields": []string{
"Router Pattern", "Router Pattern",
"Methods", "Methods",
"Controller", "Controller",
},
} }
methods = []string{}
methodsData = make(map[string]interface{})
)
for method, t := range BeeApp.Handlers.routers {
resultList := new([][]string)
printTree(resultList, t)
methods = append(methods, method)
methodsData[method] = resultList
}
content["Data"] = methodsData
content["Methods"] = methods
data["Content"] = content data["Content"] = content
data["Title"] = "Routers" data["Title"] = "Routers"
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
case "filter": case "filter":
var ( var (
content = map[string]interface{}{ content = M{
"Fields": []string{ "Fields": []string{
"Router Pattern", "Router Pattern",
"Filter Function", "Filter Function",
}, },
} }
filterTypes = []string{} filterTypes = []string{}
filterTypeData = make(map[string]interface{}) filterTypeData = make(M)
) )
if BeeApp.Handlers.enableFilter { if BeeApp.Handlers.enableFilter {
@ -157,8 +152,8 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
resultList := new([][]string) resultList := new([][]string)
for _, f := range bf { for _, f := range bf {
var result = []string{ var result = []string{
fmt.Sprintf("%s", f.pattern), f.pattern,
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), utils.GetFuncName(f.filterFunc),
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} }
@ -178,7 +173,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
} }
} }
func list(root string, p interface{}, m map[string]interface{}) { func list(root string, p interface{}, m M) {
pt := reflect.TypeOf(p) pt := reflect.TypeOf(p)
pv := reflect.ValueOf(p) pv := reflect.ValueOf(p)
if pt.Kind() == reflect.Ptr { if pt.Kind() == reflect.Ptr {
@ -200,6 +195,28 @@ func list(root string, p interface{}, m map[string]interface{}) {
} }
} }
// PrintTree prints all registered routers.
func PrintTree() M {
var (
content = M{}
methods = []string{}
methodsData = make(M)
)
for method, t := range BeeApp.Handlers.routers {
resultList := new([][]string)
printTree(resultList, t)
methods = append(methods, method)
methodsData[method] = resultList
}
content["Data"] = methodsData
content["Methods"] = methods
return content
}
func printTree(resultList *[][]string, t *Tree) { func printTree(resultList *[][]string, t *Tree) {
for _, tr := range t.fixrouters { for _, tr := range t.fixrouters {
printTree(resultList, tr) printTree(resultList, tr)
@ -208,12 +225,12 @@ func printTree(resultList *[][]string, t *Tree) {
printTree(resultList, t.wildcard) printTree(resultList, t.wildcard)
} }
for _, l := range t.leaves { for _, l := range t.leaves {
if v, ok := l.runObject.(*controllerInfo); ok { if v, ok := l.runObject.(*ControllerInfo); ok {
if v.routerType == routerTypeBeego { if v.routerType == routerTypeBeego {
var result = []string{ var result = []string{
v.pattern, v.pattern,
fmt.Sprintf("%s", v.methods), fmt.Sprintf("%s", v.methods),
fmt.Sprintf("%s", v.controllerType), v.controllerType.String(),
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} else if v.routerType == routerTypeRESTFul { } else if v.routerType == routerTypeRESTFul {
@ -274,12 +291,12 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
// Healthcheck is a http.Handler calling health checking and showing the result. // Healthcheck is a http.Handler calling health checking and showing the result.
// it's in "/healthcheck" pattern in admin module. // it's in "/healthcheck" pattern in admin module.
func healthcheck(rw http.ResponseWriter, req *http.Request) { func healthcheck(rw http.ResponseWriter, _ *http.Request) {
var ( var (
result []string
data = make(map[interface{}]interface{}) data = make(map[interface{}]interface{})
result = []string{}
resultList = new([][]string) resultList = new([][]string)
content = map[string]interface{}{ content = M{
"Fields": []string{"Name", "Message", "Status"}, "Fields": []string{"Name", "Message", "Status"},
} }
) )
@ -287,21 +304,20 @@ func healthcheck(rw http.ResponseWriter, req *http.Request) {
for name, h := range toolbox.AdminCheckList { for name, h := range toolbox.AdminCheckList {
if err := h.Check(); err != nil { if err := h.Check(); err != nil {
result = []string{ result = []string{
fmt.Sprintf("error"), "error",
fmt.Sprintf("%s", name), name,
fmt.Sprintf("%s", err.Error()), err.Error(),
} }
} else { } else {
result = []string{ result = []string{
fmt.Sprintf("success"), "success",
fmt.Sprintf("%s", name), name,
fmt.Sprintf("OK"), "OK",
} }
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} }
content["Data"] = resultList content["Data"] = resultList
data["Content"] = content data["Content"] = content
data["Title"] = "Health Check" data["Title"] = "Health Check"
@ -328,9 +344,8 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
} }
// List Tasks // List Tasks
content := make(map[string]interface{}) content := make(M)
resultList := new([][]string) resultList := new([][]string)
var result = []string{}
var fields = []string{ var fields = []string{
"Task Name", "Task Name",
"Task Spec", "Task Spec",
@ -339,10 +354,10 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
"", "",
} }
for tname, tk := range toolbox.AdminTaskList { for tname, tk := range toolbox.AdminTaskList {
result = []string{ result := []string{
tname, tname,
fmt.Sprintf("%s", tk.GetSpec()), tk.GetSpec(),
fmt.Sprintf("%s", tk.GetStatus()), tk.GetStatus(),
tk.GetPrev().String(), tk.GetPrev().String(),
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)

View File

@ -6,7 +6,7 @@ import (
) )
func TestList_01(t *testing.T) { func TestList_01(t *testing.T) {
m := make(map[string]interface{}) m := make(M)
list("BConfig", BConfig, m) list("BConfig", BConfig, m)
t.Log(m) t.Log(m)
om := oldMap() om := oldMap()
@ -18,8 +18,8 @@ func TestList_01(t *testing.T) {
} }
} }
func oldMap() map[string]interface{} { func oldMap() M {
m := make(map[string]interface{}) m := make(M)
m["BConfig.AppName"] = BConfig.AppName m["BConfig.AppName"] = BConfig.AppName
m["BConfig.RunMode"] = BConfig.RunMode m["BConfig.RunMode"] = BConfig.RunMode
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
@ -67,6 +67,8 @@ func oldMap() map[string]interface{} {
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
m["BConfig.Log.EnableStaticLogs"] = BConfig.Log.EnableStaticLogs
m["BConfig.Log.AccessLogsFormat"] = BConfig.Log.AccessLogsFormat
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
return m return m

149
app.go
View File

@ -15,17 +15,22 @@
package beego package beego
import ( import (
"crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/fcgi" "net/http/fcgi"
"os" "os"
"path" "path"
"strings"
"time" "time"
"github.com/astaxie/beego/grace" "github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
"golang.org/x/crypto/acme/autocert"
) )
var ( var (
@ -51,8 +56,11 @@ func NewApp() *App {
return app return app
} }
// MiddleWare function for http.Handler
type MiddleWare func(http.Handler) http.Handler
// Run beego application. // Run beego application.
func (app *App) Run() { func (app *App) Run(mws ...MiddleWare) {
addr := BConfig.Listen.HTTPAddr addr := BConfig.Listen.HTTPAddr
if BConfig.Listen.HTTPPort != 0 { if BConfig.Listen.HTTPPort != 0 {
@ -94,6 +102,12 @@ func (app *App) Run() {
} }
app.Server.Handler = app.Handlers app.Server.Handler = app.Handlers
for i := len(mws) - 1; i >= 0; i-- {
if mws[i] == nil {
continue
}
app.Server.Handler = mws[i](app.Server.Handler)
}
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.ErrorLog = logs.GetLogger("HTTP") app.Server.ErrorLog = logs.GetLogger("HTTP")
@ -102,9 +116,9 @@ func (app *App) Run() {
if BConfig.Listen.Graceful { if BConfig.Listen.Graceful {
httpsAddr := BConfig.Listen.HTTPSAddr httpsAddr := BConfig.Listen.HTTPSAddr
app.Server.Addr = httpsAddr app.Server.Addr = httpsAddr
if BConfig.Listen.EnableHTTPS { if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
go func() { go func() {
time.Sleep(20 * time.Microsecond) time.Sleep(1000 * time.Microsecond)
if BConfig.Listen.HTTPSPort != 0 { if BConfig.Listen.HTTPSPort != 0 {
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
app.Server.Addr = httpsAddr app.Server.Addr = httpsAddr
@ -112,11 +126,28 @@ func (app *App) Run() {
server := grace.NewServer(httpsAddr, app.Handlers) server := grace.NewServer(httpsAddr, app.Handlers)
server.Server.ReadTimeout = app.Server.ReadTimeout server.Server.ReadTimeout = app.Server.ReadTimeout
server.Server.WriteTimeout = app.Server.WriteTimeout server.Server.WriteTimeout = app.Server.WriteTimeout
if BConfig.Listen.EnableMutualHTTPS {
if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
} else {
if BConfig.Listen.AutoTLS {
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
}
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
}
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid())) logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond) time.Sleep(100 * time.Microsecond)
endRunning <- true endRunning <- true
} }
}
}() }()
} }
if BConfig.Listen.EnableHTTP { if BConfig.Listen.EnableHTTP {
@ -139,22 +170,44 @@ func (app *App) Run() {
} }
// run normal mode // run normal mode
if BConfig.Listen.EnableHTTPS { if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
go func() { go func() {
time.Sleep(20 * time.Microsecond) time.Sleep(1000 * time.Microsecond)
if BConfig.Listen.HTTPSPort != 0 { if BConfig.Listen.HTTPSPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
} else if BConfig.Listen.EnableHTTP { } else if BConfig.Listen.EnableHTTP {
BeeLogger.Info("Start https server error, confict with http.Please reset https port") BeeLogger.Info("Start https server error, conflict with http. Please reset https port")
return return
} }
logs.Info("https server Running on https://%s", app.Server.Addr) logs.Info("https server Running on https://%s", app.Server.Addr)
if BConfig.Listen.AutoTLS {
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(BConfig.Listen.Domains...),
Cache: autocert.DirCache(BConfig.Listen.TLSCacheDir),
}
app.Server.TLSConfig = &tls.Config{GetCertificate: m.GetCertificate}
BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile = "", ""
} else if BConfig.Listen.EnableMutualHTTPS {
pool := x509.NewCertPool()
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
if err != nil {
BeeLogger.Info("MutualHTTPS should provide TrustCaFile")
return
}
pool.AppendCertsFromPEM(data)
app.Server.TLSConfig = &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
}
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil { if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err) logs.Critical("ListenAndServeTLS: ", err)
time.Sleep(100 * time.Microsecond) time.Sleep(100 * time.Microsecond)
endRunning <- true endRunning <- true
} }
}() }()
} }
if BConfig.Listen.EnableHTTP { if BConfig.Listen.EnableHTTP {
go func() { go func() {
@ -207,6 +260,84 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A
return BeeApp return BeeApp
} }
// UnregisterFixedRoute unregisters the route with the specified fixedRoute. It is particularly useful
// in web applications that inherit most routes from a base webapp via the underscore
// import, and aim to overwrite only certain paths.
// The method parameter can be empty or "*" for all HTTP methods, or a particular
// method type (e.g. "GET" or "POST") for selective removal.
//
// Usage (replace "GET" with "*" for all methods):
// beego.UnregisterFixedRoute("/yourpreviouspath", "GET")
// beego.Router("/yourpreviouspath", yourControllerAddress, "get:GetNewPage")
func UnregisterFixedRoute(fixedRoute string, method string) *App {
subPaths := splitPath(fixedRoute)
if method == "" || method == "*" {
for m := range HTTPMETHOD {
if _, ok := BeeApp.Handlers.routers[m]; !ok {
continue
}
if BeeApp.Handlers.routers[m].prefix == strings.Trim(fixedRoute, "/ ") {
findAndRemoveSingleTree(BeeApp.Handlers.routers[m])
continue
}
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[m], m)
}
return BeeApp
}
// Single HTTP method
um := strings.ToUpper(method)
if _, ok := BeeApp.Handlers.routers[um]; ok {
if BeeApp.Handlers.routers[um].prefix == strings.Trim(fixedRoute, "/ ") {
findAndRemoveSingleTree(BeeApp.Handlers.routers[um])
return BeeApp
}
findAndRemoveTree(subPaths, BeeApp.Handlers.routers[um], um)
}
return BeeApp
}
func findAndRemoveTree(paths []string, entryPointTree *Tree, method string) {
for i := range entryPointTree.fixrouters {
if entryPointTree.fixrouters[i].prefix == paths[0] {
if len(paths) == 1 {
if len(entryPointTree.fixrouters[i].fixrouters) > 0 {
// If the route had children subtrees, remove just the functional leaf,
// to allow children to function as before
if len(entryPointTree.fixrouters[i].leaves) > 0 {
entryPointTree.fixrouters[i].leaves[0] = nil
entryPointTree.fixrouters[i].leaves = entryPointTree.fixrouters[i].leaves[1:]
}
} else {
// Remove the *Tree from the fixrouters slice
entryPointTree.fixrouters[i] = nil
if i == len(entryPointTree.fixrouters)-1 {
entryPointTree.fixrouters = entryPointTree.fixrouters[:i]
} else {
entryPointTree.fixrouters = append(entryPointTree.fixrouters[:i], entryPointTree.fixrouters[i+1:len(entryPointTree.fixrouters)]...)
}
}
return
}
findAndRemoveTree(paths[1:], entryPointTree.fixrouters[i], method)
}
}
}
func findAndRemoveSingleTree(entryPointTree *Tree) {
if entryPointTree == nil {
return
}
if len(entryPointTree.fixrouters) > 0 {
// If the route had children subtrees, remove just the functional leaf,
// to allow children to function as before
if len(entryPointTree.leaves) > 0 {
entryPointTree.leaves[0] = nil
entryPointTree.leaves = entryPointTree.leaves[1:]
}
}
}
// Include will generate router file in the router/xxx.go from the controller's comments // Include will generate router file in the router/xxx.go from the controller's comments
// usage: // usage:
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) // beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
@ -348,9 +479,9 @@ func Any(rootpath string, f FilterFunc) *App {
// Handler used to register a Handler router // Handler used to register a Handler router
// usage: // usage:
// beego.Handler("/api", func(ctx *context.Context){ // beego.Handler("/api", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
// ctx.Output.Body("hello world") // fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
// }) // }))
func Handler(rootpath string, h http.Handler, options ...interface{}) *App { func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
BeeApp.Handlers.Handler(rootpath, h, options...) BeeApp.Handlers.Handler(rootpath, h, options...)
return BeeApp return BeeApp

View File

@ -23,7 +23,7 @@ import (
const ( const (
// VERSION represent beego web framework version. // VERSION represent beego web framework version.
VERSION = "1.8.0" VERSION = "1.11.0"
// DEV is for develop // DEV is for develop
DEV = "dev" DEV = "dev"
@ -31,7 +31,10 @@ const (
PROD = "prod" PROD = "prod"
) )
//hook function to run // M is Map shortcut
type M map[string]interface{}
// Hook function to run
type hookfunc func() error type hookfunc func() error
var ( var (
@ -40,9 +43,9 @@ var (
// AddAPPStartHook is used to register the hookfunc // AddAPPStartHook is used to register the hookfunc
// The hookfuncs will run in beego.Run() // The hookfuncs will run in beego.Run()
// such as sessionInit, middlerware start, buildtemplate, admin start // such as initiating session , starting middleware , building template, starting admin control and so on.
func AddAPPStartHook(hf hookfunc) { func AddAPPStartHook(hf ...hookfunc) {
hooks = append(hooks, hf) hooks = append(hooks, hf...)
} }
// Run beego application. // Run beego application.
@ -62,19 +65,39 @@ func Run(params ...string) {
if len(strs) > 1 && strs[1] != "" { if len(strs) > 1 && strs[1] != "" {
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1]) BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
} }
BConfig.Listen.Domains = params
} }
BeeApp.Run() BeeApp.Run()
} }
// RunWithMiddleWares Run beego application with middlewares.
func RunWithMiddleWares(addr string, mws ...MiddleWare) {
initBeforeHTTPRun()
strs := strings.Split(addr, ":")
if len(strs) > 0 && strs[0] != "" {
BConfig.Listen.HTTPAddr = strs[0]
BConfig.Listen.Domains = []string{strs[0]}
}
if len(strs) > 1 && strs[1] != "" {
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
}
BeeApp.Run(mws...)
}
func initBeforeHTTPRun() { func initBeforeHTTPRun() {
//init hooks //init hooks
AddAPPStartHook(registerMime) AddAPPStartHook(
AddAPPStartHook(registerDefaultErrorHandler) registerMime,
AddAPPStartHook(registerSession) registerDefaultErrorHandler,
AddAPPStartHook(registerTemplate) registerSession,
AddAPPStartHook(registerAdmin) registerTemplate,
AddAPPStartHook(registerGzip) registerAdmin,
registerGzip,
)
for _, hk := range hooks { for _, hk := range hooks {
if err := hk(); err != nil { if err := hk(); err != nil {

2
cache/README.md vendored
View File

@ -52,7 +52,7 @@ Configure like this:
## Redis adapter ## Redis adapter
Redis adapter use the [redigo](http://github.com/garyburd/redigo) client. Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.
Configure like this: Configure like this:

2
cache/cache.go vendored
View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package cache provide a Cache interface and some implemetn engine // Package cache provide a Cache interface and some implement engine
// Usage: // Usage:
// //
// import( // import(

2
cache/conv.go vendored
View File

@ -28,7 +28,7 @@ func GetString(v interface{}) string {
return string(result) return string(result)
default: default:
if v != nil { if v != nil {
return fmt.Sprintf("%v", result) return fmt.Sprint(result)
} }
} }
return "" return ""

6
cache/conv_test.go vendored
View File

@ -118,14 +118,14 @@ func TestGetFloat64(t *testing.T) {
func TestGetBool(t *testing.T) { func TestGetBool(t *testing.T) {
var t1 = true var t1 = true
if true != GetBool(t1) { if !GetBool(t1) {
t.Error("get bool from bool error") t.Error("get bool from bool error")
} }
var t2 = "true" var t2 = "true"
if true != GetBool(t2) { if !GetBool(t2) {
t.Error("get bool from string error") t.Error("get bool from string error")
} }
if false != GetBool(nil) { if GetBool(nil) {
t.Error("get bool from nil error") t.Error("get bool from nil error")
} }
} }

View File

@ -146,10 +146,7 @@ func (rc *Cache) IsExist(key string) bool {
} }
} }
_, err := rc.conn.Get(key) _, err := rc.conn.Get(key)
if err != nil { return !(err != nil)
return false
}
return true
} }
// ClearAll clear all cached in memcache. // ClearAll clear all cached in memcache.

41
cache/memory.go vendored
View File

@ -203,13 +203,17 @@ func (bc *MemoryCache) StartAndGC(config string) error {
dur := time.Duration(cf["interval"]) * time.Second dur := time.Duration(cf["interval"]) * time.Second
bc.Every = cf["interval"] bc.Every = cf["interval"]
bc.dur = dur bc.dur = dur
go bc.vaccuum() go bc.vacuum()
return nil return nil
} }
// check expiration. // check expiration.
func (bc *MemoryCache) vaccuum() { func (bc *MemoryCache) vacuum() {
if bc.Every < 1 { bc.RLock()
every := bc.Every
bc.RUnlock()
if every < 1 {
return return
} }
for { for {
@ -217,26 +221,31 @@ func (bc *MemoryCache) vaccuum() {
if bc.items == nil { if bc.items == nil {
return return
} }
for name := range bc.items { if keys := bc.expiredKeys(); len(keys) != 0 {
bc.itemExpired(name) bc.clearItems(keys)
} }
} }
} }
// itemExpired returns true if an item is expired. // expiredKeys returns key list which are expired.
func (bc *MemoryCache) itemExpired(name string) bool { func (bc *MemoryCache) expiredKeys() (keys []string) {
bc.RLock()
defer bc.RUnlock()
for key, itm := range bc.items {
if itm.isExpire() {
keys = append(keys, key)
}
}
return
}
// clearItems removes all the items which key in keys.
func (bc *MemoryCache) clearItems(keys []string) {
bc.Lock() bc.Lock()
defer bc.Unlock() defer bc.Unlock()
for _, key := range keys {
itm, ok := bc.items[name] delete(bc.items, key)
if !ok {
return true
} }
if itm.isExpire() {
delete(bc.items, name)
return true
}
return false
} }
func init() { func init() {

91
cache/redis/redis.go vendored
View File

@ -14,9 +14,9 @@
// Package redis for cache provider // Package redis for cache provider
// //
// depend on github.com/garyburd/redigo/redis // depend on github.com/gomodule/redigo/redis
// //
// go install github.com/garyburd/redigo/redis // go install github.com/gomodule/redigo/redis
// //
// Usage: // Usage:
// import( // import(
@ -32,12 +32,14 @@ package redis
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/garyburd/redigo/redis" "github.com/gomodule/redigo/redis"
"github.com/astaxie/beego/cache" "github.com/astaxie/beego/cache"
"strings"
) )
var ( var (
@ -52,6 +54,7 @@ type Cache struct {
dbNum int dbNum int
key string key string
password string password string
maxIdle int
} }
// NewRedisCache create new redis cache with default collection name. // NewRedisCache create new redis cache with default collection name.
@ -59,14 +62,23 @@ func NewRedisCache() cache.Cache {
return &Cache{key: DefaultKey} return &Cache{key: DefaultKey}
} }
// actually do the redis cmds // actually do the redis cmds, args[0] must be the key name.
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) { func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
if len(args) < 1 {
return nil, errors.New("missing required arguments")
}
args[0] = rc.associate(args[0])
c := rc.p.Get() c := rc.p.Get()
defer c.Close() defer c.Close()
return c.Do(commandName, args...) return c.Do(commandName, args...)
} }
// associate with config key.
func (rc *Cache) associate(originKey interface{}) string {
return fmt.Sprintf("%s:%s", rc.key, originKey)
}
// Get cache from redis. // Get cache from redis.
func (rc *Cache) Get(key string) interface{} { func (rc *Cache) Get(key string) interface{} {
if v, err := rc.do("GET", key); err == nil { if v, err := rc.do("GET", key); err == nil {
@ -77,57 +89,28 @@ func (rc *Cache) Get(key string) interface{} {
// GetMulti get cache from redis. // GetMulti get cache from redis.
func (rc *Cache) GetMulti(keys []string) []interface{} { func (rc *Cache) GetMulti(keys []string) []interface{} {
size := len(keys)
var rv []interface{}
c := rc.p.Get() c := rc.p.Get()
defer c.Close() defer c.Close()
var err error var args []interface{}
for _, key := range keys { for _, key := range keys {
err = c.Send("GET", key) args = append(args, rc.associate(key))
}
values, err := redis.Values(c.Do("MGET", args...))
if err != nil { if err != nil {
goto ERROR return nil
} }
} return values
if err = c.Flush(); err != nil {
goto ERROR
}
for i := 0; i < size; i++ {
if v, err := c.Receive(); err == nil {
rv = append(rv, v.([]byte))
} else {
rv = append(rv, err)
}
}
return rv
ERROR:
rv = rv[0:0]
for i := 0; i < size; i++ {
rv = append(rv, nil)
}
return rv
} }
// Put put cache to redis. // Put put cache to redis.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
var err error _, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
if _, err = rc.do("SETEX", key, int64(timeout/time.Second), val); err != nil {
return err
}
if _, err = rc.do("HSET", rc.key, key, true); err != nil {
return err
}
return err return err
} }
// Delete delete cache in redis. // Delete delete cache in redis.
func (rc *Cache) Delete(key string) error { func (rc *Cache) Delete(key string) error {
var err error _, err := rc.do("DEL", key)
if _, err = rc.do("DEL", key); err != nil {
return err
}
_, err = rc.do("HDEL", rc.key, key)
return err return err
} }
@ -137,11 +120,6 @@ func (rc *Cache) IsExist(key string) bool {
if err != nil { if err != nil {
return false return false
} }
if v == false {
if _, err = rc.do("HDEL", rc.key, key); err != nil {
return false
}
}
return v return v
} }
@ -159,16 +137,17 @@ func (rc *Cache) Decr(key string) error {
// ClearAll clean all cache in redis. delete this redis collection. // ClearAll clean all cache in redis. delete this redis collection.
func (rc *Cache) ClearAll() error { func (rc *Cache) ClearAll() error {
cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key)) c := rc.p.Get()
defer c.Close()
cachedKeys, err := redis.Strings(c.Do("KEYS", rc.key+":*"))
if err != nil { if err != nil {
return err return err
} }
for _, str := range cachedKeys { for _, str := range cachedKeys {
if _, err = rc.do("DEL", str); err != nil { if _, err = c.Do("DEL", str); err != nil {
return err return err
} }
} }
_, err = rc.do("DEL", rc.key)
return err return err
} }
@ -186,16 +165,28 @@ func (rc *Cache) StartAndGC(config string) error {
if _, ok := cf["conn"]; !ok { if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key") return errors.New("config has no conn key")
} }
// Format redis://<password>@<host>:<port>
cf["conn"] = strings.Replace(cf["conn"], "redis://", "", 1)
if i := strings.Index(cf["conn"], "@"); i > -1 {
cf["password"] = cf["conn"][0:i]
cf["conn"] = cf["conn"][i+1:]
}
if _, ok := cf["dbNum"]; !ok { if _, ok := cf["dbNum"]; !ok {
cf["dbNum"] = "0" cf["dbNum"] = "0"
} }
if _, ok := cf["password"]; !ok { if _, ok := cf["password"]; !ok {
cf["password"] = "" cf["password"] = ""
} }
if _, ok := cf["maxIdle"]; !ok {
cf["maxIdle"] = "3"
}
rc.key = cf["key"] rc.key = cf["key"]
rc.conninfo = cf["conn"] rc.conninfo = cf["conn"]
rc.dbNum, _ = strconv.Atoi(cf["dbNum"]) rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
rc.password = cf["password"] rc.password = cf["password"]
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
rc.connectInit() rc.connectInit()
@ -229,7 +220,7 @@ func (rc *Cache) connectInit() {
} }
// initialize a new pool // initialize a new pool
rc.p = &redis.Pool{ rc.p = &redis.Pool{
MaxIdle: 3, MaxIdle: rc.maxIdle,
IdleTimeout: 180 * time.Second, IdleTimeout: 180 * time.Second,
Dial: dialFunc, Dial: dialFunc,
} }

View File

@ -19,7 +19,7 @@ import (
"time" "time"
"github.com/astaxie/beego/cache" "github.com/astaxie/beego/cache"
"github.com/garyburd/redigo/redis" "github.com/gomodule/redigo/redis"
) )
func TestRedisCache(t *testing.T) { func TestRedisCache(t *testing.T) {

13
cache/ssdb/ssdb.go vendored
View File

@ -53,7 +53,7 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
resSize := len(res) resSize := len(res)
if err == nil { if err == nil {
for i := 1; i < resSize; i += 2 { for i := 1; i < resSize; i += 2 {
values = append(values, string(res[i+1])) values = append(values, res[i+1])
} }
return values return values
} }
@ -71,11 +71,8 @@ func (rc *Cache) DelMulti(keys []string) error {
} }
} }
_, err := rc.conn.Do("multi_del", keys) _, err := rc.conn.Do("multi_del", keys)
if err != nil {
return err return err
} }
return nil
}
// Put put value to memcache. only support string. // Put put value to memcache. only support string.
func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error { func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error {
@ -113,11 +110,8 @@ func (rc *Cache) Delete(key string) error {
} }
} }
_, err := rc.conn.Del(key) _, err := rc.conn.Del(key)
if err != nil {
return err return err
} }
return nil
}
// Incr increase counter. // Incr increase counter.
func (rc *Cache) Incr(key string) error { func (rc *Cache) Incr(key string) error {
@ -175,7 +169,7 @@ func (rc *Cache) ClearAll() error {
} }
keys := []string{} keys := []string{}
for i := 1; i < size; i += 2 { for i := 1; i < size; i += 2 {
keys = append(keys, string(resp[i])) keys = append(keys, resp[i])
} }
_, e := rc.conn.Do("multi_del", keys) _, e := rc.conn.Do("multi_del", keys)
if e != nil { if e != nil {
@ -229,11 +223,8 @@ func (rc *Cache) connectInit() error {
} }
var err error var err error
rc.conn, err = ssdb.Connect(host, port) rc.conn, err = ssdb.Connect(host, port)
if err != nil {
return err return err
} }
return nil
}
func init() { func init() {
cache.Register("ssdb", NewSsdbCache) cache.Register("ssdb", NewSsdbCache)

View File

@ -55,11 +55,16 @@ type Listen struct {
EnableHTTP bool EnableHTTP bool
HTTPAddr string HTTPAddr string
HTTPPort int HTTPPort int
AutoTLS bool
Domains []string
TLSCacheDir string
EnableHTTPS bool EnableHTTPS bool
EnableMutualHTTPS bool
HTTPSAddr string HTTPSAddr string
HTTPSPort int HTTPSPort int
HTTPSCertFile string HTTPSCertFile string
HTTPSKeyFile string HTTPSKeyFile string
TrustCaFile string
EnableAdmin bool EnableAdmin bool
AdminAddr string AdminAddr string
AdminPort int AdminPort int
@ -104,6 +109,8 @@ type SessionConfig struct {
// LogConfig holds Log related config // LogConfig holds Log related config
type LogConfig struct { type LogConfig struct {
AccessLogs bool AccessLogs bool
EnableStaticLogs bool //log static files requests default: false
AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string
FileLineNum bool FileLineNum bool
Outputs map[string]string // Store Adaptor : config Outputs map[string]string // Store Adaptor : config
} }
@ -134,9 +141,13 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
appConfigPath = filepath.Join(workPath, "conf", "app.conf") var filename = "app.conf"
if os.Getenv("BEEGO_RUNMODE") != "" {
filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
}
appConfigPath = filepath.Join(workPath, "conf", filename)
if !utils.FileExists(appConfigPath) { if !utils.FileExists(appConfigPath) {
appConfigPath = filepath.Join(AppPath, "conf", "app.conf") appConfigPath = filepath.Join(AppPath, "conf", filename)
if !utils.FileExists(appConfigPath) { if !utils.FileExists(appConfigPath) {
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()} AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
return return
@ -175,13 +186,18 @@ func recoverPanic(ctx *context.Context) {
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
showErr(err, ctx, stack) showErr(err, ctx, stack)
} }
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
} }
} }
func newBConfig() *Config { func newBConfig() *Config {
return &Config{ return &Config{
AppName: "beego", AppName: "beego",
RunMode: DEV, RunMode: PROD,
RouterCaseSensitive: true, RouterCaseSensitive: true,
ServerName: "beegoServer:" + VERSION, ServerName: "beegoServer:" + VERSION,
RecoverPanic: true, RecoverPanic: true,
@ -196,6 +212,9 @@ func newBConfig() *Config {
ServerTimeOut: 0, ServerTimeOut: 0,
ListenTCP4: false, ListenTCP4: false,
EnableHTTP: true, EnableHTTP: true,
AutoTLS: false,
Domains: []string{},
TLSCacheDir: ".",
HTTPAddr: "", HTTPAddr: "",
HTTPPort: 8080, HTTPPort: 8080,
EnableHTTPS: false, EnableHTTPS: false,
@ -240,6 +259,8 @@ func newBConfig() *Config {
}, },
Log: LogConfig{ Log: LogConfig{
AccessLogs: false, AccessLogs: false,
EnableStaticLogs: false,
AccessLogsFormat: "APACHE_FORMAT",
FileLineNum: true, FileLineNum: true,
Outputs: map[string]string{"console": ""}, Outputs: map[string]string{"console": ""},
}, },
@ -345,7 +366,7 @@ func assignSingleConfig(p interface{}, ac config.Configer) {
case reflect.String: case reflect.String:
pf.SetString(ac.DefaultString(name, pf.String())) pf.SetString(ac.DefaultString(name, pf.String()))
case reflect.Int, reflect.Int64: case reflect.Int, reflect.Int64:
pf.SetInt(int64(ac.DefaultInt64(name, pf.Int()))) pf.SetInt(ac.DefaultInt64(name, pf.Int()))
case reflect.Bool: case reflect.Bool:
pf.SetBool(ac.DefaultBool(name, pf.Bool())) pf.SetBool(ac.DefaultBool(name, pf.Bool()))
case reflect.Struct: case reflect.Struct:

View File

@ -150,12 +150,12 @@ func ExpandValueEnv(value string) (realValue string) {
} }
key := "" key := ""
defalutV := "" defaultV := ""
// value start with "${" // value start with "${"
for i := 2; i < vLen; i++ { for i := 2; i < vLen; i++ {
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') { if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
key = value[2:i] key = value[2:i]
defalutV = value[i+2 : vLen-1] // other string is default value. defaultV = value[i+2 : vLen-1] // other string is default value.
break break
} else if value[i] == '}' { } else if value[i] == '}' {
key = value[2:i] key = value[2:i]
@ -165,7 +165,7 @@ func ExpandValueEnv(value string) (realValue string) {
realValue = os.Getenv(key) realValue = os.Getenv(key)
if realValue == "" { if realValue == "" {
realValue = defalutV realValue = defaultV
} }
return return
@ -189,16 +189,16 @@ func ParseBool(val interface{}) (value bool, err error) {
return false, nil return false, nil
} }
case int8, int32, int64: case int8, int32, int64:
strV := fmt.Sprintf("%s", v) strV := fmt.Sprintf("%d", v)
if strV == "1" { if strV == "1" {
return true, nil return true, nil
} else if strV == "0" { } else if strV == "0" {
return false, nil return false, nil
} }
case float64: case float64:
if v == 1 { if v == 1.0 {
return true, nil return true, nil
} else if v == 0 { } else if v == 0.0 {
return false, nil return false, nil
} }
} }

2
config/env/env.go vendored
View File

@ -12,6 +12,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package env is used to parse environment.
package env package env
import ( import (

View File

@ -126,7 +126,7 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
var _ Configer = new(fakeConfigContainer) var _ Configer = new(fakeConfigContainer)
// NewFakeConfig return a fake Congiger // NewFakeConfig return a fake Configer
func NewFakeConfig() Configer { func NewFakeConfig() Configer {
return &fakeConfigContainer{ return &fakeConfigContainer{
data: make(map[string]string), data: make(map[string]string),

View File

@ -21,6 +21,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/user"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -77,15 +78,37 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e
} }
} }
section := defaultSection section := defaultSection
tmpBuf := bytes.NewBuffer(nil)
for { for {
line, _, err := buf.ReadLine() tmpBuf.Reset()
shouldBreak := false
for {
tmp, isPrefix, err := buf.ReadLine()
if err == io.EOF { if err == io.EOF {
shouldBreak = true
break break
} }
//It might be a good idea to throw a error on all unknonw errors? //It might be a good idea to throw a error on all unknonw errors?
if _, ok := err.(*os.PathError); ok { if _, ok := err.(*os.PathError); ok {
return nil, err return nil, err
} }
tmpBuf.Write(tmp)
if isPrefix {
continue
}
if !isPrefix {
break
}
}
if shouldBreak {
break
}
line := tmpBuf.Bytes()
line = bytes.TrimSpace(line) line = bytes.TrimSpace(line)
if bytes.Equal(line, bEmpty) { if bytes.Equal(line, bEmpty) {
continue continue
@ -184,10 +207,17 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e
// ParseData parse ini the data // ParseData parse ini the data
// When include other.conf,other.conf is either absolute directory // When include other.conf,other.conf is either absolute directory
// or under beego in default temporary directory(/tmp/beego). // or under beego in default temporary directory(/tmp/beego[-username]).
func (ini *IniConfig) ParseData(data []byte) (Configer, error) { func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
dir := filepath.Join(os.TempDir(), "beego") dir := "beego"
os.MkdirAll(dir, os.ModePerm) currentUser, err := user.Current()
if err == nil {
dir = "beego-" + currentUser.Username
}
dir = filepath.Join(os.TempDir(), dir)
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
return nil, err
}
return ini.parseData(dir, data) return ini.parseData(dir, data)
} }
@ -207,7 +237,7 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) {
} }
// DefaultBool returns the boolean value for a given key. // DefaultBool returns the boolean value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
v, err := c.Bool(key) v, err := c.Bool(key)
if err != nil { if err != nil {
@ -222,7 +252,7 @@ func (c *IniConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key) v, err := c.Int(key)
if err != nil { if err != nil {
@ -237,7 +267,7 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
v, err := c.Int64(key) v, err := c.Int64(key)
if err != nil { if err != nil {
@ -252,7 +282,7 @@ func (c *IniConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
v, err := c.Float(key) v, err := c.Float(key)
if err != nil { if err != nil {
@ -267,7 +297,7 @@ func (c *IniConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
v := c.String(key) v := c.String(key)
if v == "" { if v == "" {
@ -287,7 +317,7 @@ func (c *IniConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if v == nil { if v == nil {
@ -306,7 +336,7 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
// 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. // BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)
@ -317,7 +347,10 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Get section or key comments. Fixed #1607 // Get section or key comments. Fixed #1607
getCommentStr := func(section, key string) string { getCommentStr := func(section, key string) string {
comment, ok := "", false var (
comment string
ok bool
)
if len(key) == 0 { if len(key) == 0 {
comment, ok = c.sectionComment[section] comment, ok = c.sectionComment[section]
} else { } else {
@ -397,12 +430,9 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
} }
} }
} }
_, err = buf.WriteTo(f)
if _, err = buf.WriteTo(f); err != nil {
return err return err
} }
return nil
}
// Set writes a new value for key. // Set writes a new value for key.
// if write to one section, the key need be "section::key". // if write to one section, the key need be "section::key".
@ -416,7 +446,7 @@ func (c *IniConfigContainer) Set(key, value string) error {
var ( var (
section, k string section, k string
sectionKey = strings.Split(key, "::") sectionKey = strings.Split(strings.ToLower(key), "::")
) )
if len(sectionKey) >= 2 { if len(sectionKey) >= 2 {

View File

@ -181,7 +181,7 @@ name=mysql
cfgData := string(data) cfgData := string(data)
datas := strings.Split(saveResult, "\n") datas := strings.Split(saveResult, "\n")
for _, line := range datas { for _, line := range datas {
if strings.Contains(cfgData, line+"\n") == false { if !strings.Contains(cfgData, line+"\n") {
t.Fatalf("different after save ini config file. need contains %q", line) t.Fatalf("different after save ini config file. need contains %q", line)
} }
} }

View File

@ -101,7 +101,7 @@ func (c *JSONConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int { func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err == nil { if v, err := c.Int(key); err == nil {
return v return v
@ -122,7 +122,7 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err == nil { if v, err := c.Int64(key); err == nil {
return v return v
@ -143,7 +143,7 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err == nil { if v, err := c.Float(key); err == nil {
return v return v
@ -163,7 +163,7 @@ func (c *JSONConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string { func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
// TODO FIXME should not use "" to replace non existence // TODO FIXME should not use "" to replace non existence
if v := c.String(key); v != "" { if v := c.String(key); v != "" {
@ -182,7 +182,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); v != nil { if v := c.Strings(key); v != nil {
return v return v

View File

@ -216,7 +216,7 @@ func TestJson(t *testing.T) {
t.Error("unknown keys should return an error when expecting a Bool") t.Error("unknown keys should return an error when expecting a Bool")
} }
if !jsonconf.DefaultBool("unknow", true) { if !jsonconf.DefaultBool("unknown", true) {
t.Error("unknown keys with default value wrong") t.Error("unknown keys with default value wrong")
} }
} }

View File

@ -102,7 +102,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key) v, err := c.Int(key)
if err != nil { if err != nil {
@ -117,7 +117,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
v, err := c.Int64(key) v, err := c.Int64(key)
if err != nil { if err != nil {
@ -133,7 +133,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
v, err := c.Float(key) v, err := c.Float(key)
if err != nil { if err != nil {
@ -151,7 +151,7 @@ func (c *ConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultString(key string, defaultval string) string { func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
v := c.String(key) v := c.String(key)
if v == "" { if v == "" {
@ -170,7 +170,7 @@ func (c *ConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if v == nil { if v == nil {

View File

@ -119,7 +119,7 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
// ConfigContainer A Config represents the yaml configuration. // ConfigContainer A Config represents the yaml configuration.
type ConfigContainer struct { type ConfigContainer struct {
data map[string]interface{} data map[string]interface{}
sync.Mutex sync.RWMutex
} }
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
@ -154,7 +154,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key) v, err := c.Int(key)
if err != nil { if err != nil {
@ -174,7 +174,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
v, err := c.Int64(key) v, err := c.Int64(key)
if err != nil { if err != nil {
@ -198,7 +198,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
v, err := c.Float(key) v, err := c.Float(key)
if err != nil { if err != nil {
@ -218,7 +218,7 @@ func (c *ConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultString(key string, defaultval string) string { func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
v := c.String(key) v := c.String(key)
if v == "" { if v == "" {
@ -237,7 +237,7 @@ func (c *ConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if v == nil { if v == nil {
@ -285,10 +285,29 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
if len(key) == 0 { if len(key) == 0 {
return nil, errors.New("key is empty") return nil, errors.New("key is empty")
} }
c.RLock()
defer c.RUnlock()
if v, ok := c.data[key]; ok { keys := strings.Split(key, ".")
tmpData := c.data
for idx, k := range keys {
if v, ok := tmpData[k]; ok {
switch v.(type) {
case map[string]interface{}:
{
tmpData = v.(map[string]interface{})
if idx == len(keys) - 1 {
return tmpData, nil
}
}
default:
{
return v, nil return v, nil
} }
}
}
}
return nil, fmt.Errorf("not exist key %q", key) return nil, fmt.Errorf("not exist key %q", key)
} }

View File

@ -48,15 +48,15 @@ func TestAssignConfig_02(t *testing.T) {
_BConfig := &Config{} _BConfig := &Config{}
bs, _ := json.Marshal(newBConfig()) bs, _ := json.Marshal(newBConfig())
jsonMap := map[string]interface{}{} jsonMap := M{}
json.Unmarshal(bs, &jsonMap) json.Unmarshal(bs, &jsonMap)
configMap := map[string]interface{}{} configMap := M{}
for k, v := range jsonMap { for k, v := range jsonMap {
if reflect.TypeOf(v).Kind() == reflect.Map { if reflect.TypeOf(v).Kind() == reflect.Map {
for k1, v1 := range v.(map[string]interface{}) { for k1, v1 := range v.(M) {
if reflect.TypeOf(v1).Kind() == reflect.Map { if reflect.TypeOf(v1).Kind() == reflect.Map {
for k2, v2 := range v1.(map[string]interface{}) { for k2, v2 := range v1.(M) {
configMap[k2] = v2 configMap[k2] = v2
} }
} else { } else {
@ -75,7 +75,7 @@ func TestAssignConfig_02(t *testing.T) {
jcf := &config.JSONConfig{} jcf := &config.JSONConfig{}
bs, _ = json.Marshal(configMap) bs, _ = json.Marshal(configMap)
ac, _ := jcf.ParseData([]byte(bs)) ac, _ := jcf.ParseData(bs)
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} { for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
assignSingleConfig(i, ac) assignSingleConfig(i, ac)

View File

@ -39,6 +39,7 @@ var (
getMethodOnly bool getMethodOnly bool
) )
// InitGzip init the gzipcompress
func InitGzip(minLength, compressLevel int, methods []string) { func InitGzip(minLength, compressLevel int, methods []string) {
if minLength >= 0 { if minLength >= 0 {
gzipMinLength = minLength gzipMinLength = minLength

View File

@ -38,6 +38,14 @@ import (
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
//commonly used mime-types
const (
ApplicationJSON = "application/json"
ApplicationXML = "application/xml"
ApplicationYAML = "application/x-yaml"
TextXML = "text/xml"
)
// NewContext return the Context with Input and Output // NewContext return the Context with Input and Output
func NewContext() *Context { func NewContext() *Context {
return &Context{ return &Context{
@ -171,6 +179,22 @@ func (ctx *Context) CheckXSRFCookie() bool {
return true return true
} }
// RenderMethodResult renders the return value of a controller method to the output
func (ctx *Context) RenderMethodResult(result interface{}) {
if result != nil {
renderer, ok := result.(Renderer)
if !ok {
err, ok := result.(error)
if ok {
renderer = errorRenderer(err)
} else {
renderer = jsonRenderer(result)
}
}
renderer.Render(ctx)
}
}
//Response is a wrapper for the http.ResponseWriter //Response is a wrapper for the http.ResponseWriter
//started set to true if response was written to then don't execute other handler //started set to true if response was written to then don't execute other handler
type Response struct { type Response struct {
@ -228,3 +252,11 @@ func (r *Response) CloseNotify() <-chan bool {
} }
return nil return nil
} }
// Pusher http.Pusher
func (r *Response) Pusher() (pusher http.Pusher) {
if pusher, ok := r.ResponseWriter.(http.Pusher); ok {
return pusher
}
return nil
}

View File

@ -16,9 +16,12 @@ package context
import ( import (
"bytes" "bytes"
"compress/gzip"
"errors" "errors"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http"
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
@ -34,6 +37,7 @@ var (
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`) acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`) acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`) acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
acceptsYAMLRegex = regexp.MustCompile(`(application/x-yaml)(?:,|$)`)
maxParam = 50 maxParam = 50
) )
@ -113,9 +117,8 @@ func (input *BeegoInput) Domain() string {
// if no host info in request, return localhost. // if no host info in request, return localhost.
func (input *BeegoInput) Host() string { func (input *BeegoInput) Host() string {
if input.Context.Request.Host != "" { if input.Context.Request.Host != "" {
hostParts := strings.Split(input.Context.Request.Host, ":") if hostPart, _, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
if len(hostParts) > 0 { return hostPart
return hostParts[0]
} }
return input.Context.Request.Host return input.Context.Request.Host
} }
@ -201,23 +204,27 @@ func (input *BeegoInput) AcceptsXML() bool {
func (input *BeegoInput) AcceptsJSON() bool { func (input *BeegoInput) AcceptsJSON() bool {
return acceptsJSONRegex.MatchString(input.Header("Accept")) return acceptsJSONRegex.MatchString(input.Header("Accept"))
} }
// AcceptsYAML Checks if request accepts json response
func (input *BeegoInput) AcceptsYAML() bool {
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
}
// IP returns request client ip. // IP returns request client ip.
// if in proxy, return first proxy id. // if in proxy, return first proxy id.
// if error, return 127.0.0.1. // if error, return RemoteAddr.
func (input *BeegoInput) IP() string { func (input *BeegoInput) IP() string {
ips := input.Proxy() ips := input.Proxy()
if len(ips) > 0 && ips[0] != "" { if len(ips) > 0 && ips[0] != "" {
rip := strings.Split(ips[0], ":") rip, _, err := net.SplitHostPort(ips[0])
return rip[0] if err != nil {
rip = ips[0]
} }
ip := strings.Split(input.Context.Request.RemoteAddr, ":") return rip
if len(ip) > 0 {
if ip[0] != "[" {
return ip[0]
} }
if ip, _, err := net.SplitHostPort(input.Context.Request.RemoteAddr); err == nil {
return ip
} }
return "127.0.0.1" return input.Context.Request.RemoteAddr
} }
// Proxy returns proxy client ips slice. // Proxy returns proxy client ips slice.
@ -251,9 +258,8 @@ func (input *BeegoInput) SubDomains() string {
// Port returns request client port. // Port returns request client port.
// when error or empty, return 80. // when error or empty, return 80.
func (input *BeegoInput) Port() int { func (input *BeegoInput) Port() int {
parts := strings.Split(input.Context.Request.Host, ":") if _, portPart, err := net.SplitHostPort(input.Context.Request.Host); err == nil {
if len(parts) == 2 { port, _ := strconv.Atoi(portPart)
port, _ := strconv.Atoi(parts[1])
return port return port
} }
return 80 return 80
@ -349,11 +355,22 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
if input.Context.Request.Body == nil { if input.Context.Request.Body == nil {
return []byte{} return []byte{}
} }
var requestbody []byte
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory} safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
requestbody, _ := ioutil.ReadAll(safe) if input.Header("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(safe)
if err != nil {
return nil
}
requestbody, _ = ioutil.ReadAll(reader)
} else {
requestbody, _ = ioutil.ReadAll(safe)
}
input.Context.Request.Body.Close() input.Context.Request.Body.Close()
bf := bytes.NewBuffer(requestbody) bf := bytes.NewBuffer(requestbody)
input.Context.Request.Body = ioutil.NopCloser(bf) input.Context.Request.Body = http.MaxBytesReader(input.Context.ResponseWriter, ioutil.NopCloser(bf), MaxMemory)
input.RequestBody = requestbody input.RequestBody = requestbody
return requestbody return requestbody
} }

View File

@ -73,8 +73,8 @@ func TestBind(t *testing.T) {
{"/?human.ID=888&human.Nick=astaxie&human.Ms=true&human[Pwd]=pass", []testItem{{"human", Human{}, Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass"}}}}, {"/?human.ID=888&human.Nick=astaxie&human.Ms=true&human[Pwd]=pass", []testItem{{"human", Human{}, Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass"}}}},
{"/?human[0].ID=888&human[0].Nick=astaxie&human[0].Ms=true&human[0][Pwd]=pass01&human[1].ID=999&human[1].Nick=ysqi&human[1].Ms=On&human[1].Pwd=pass02", {"/?human[0].ID=888&human[0].Nick=astaxie&human[0].Ms=true&human[0][Pwd]=pass01&human[1].ID=999&human[1].Nick=ysqi&human[1].Ms=On&human[1].Pwd=pass02",
[]testItem{{"human", []Human{}, []Human{ []testItem{{"human", []Human{}, []Human{
Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass01"}, {ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass01"},
Human{ID: 999, Nick: "ysqi", Ms: true, Pwd: "pass02"}, {ID: 999, Nick: "ysqi", Ms: true, Pwd: "pass02"},
}}}}, }}}},
{ {

View File

@ -30,6 +30,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v2"
) )
// BeegoOutput does work for sending response header. // BeegoOutput does work for sending response header.
@ -168,9 +169,22 @@ func sanitizeValue(v string) string {
return cookieValueSanitizer.Replace(v) return cookieValueSanitizer.Replace(v)
} }
func jsonRenderer(value interface{}) Renderer {
return rendererFunc(func(ctx *Context) {
ctx.Output.JSON(value, false, false)
})
}
func errorRenderer(err error) Renderer {
return rendererFunc(func(ctx *Context) {
ctx.Output.SetStatus(500)
ctx.Output.Body([]byte(err.Error()))
})
}
// JSON writes json to response body. // JSON writes json to response body.
// if coding is true, it converts utf-8 to \u0000 type. // if encoding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error { func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
output.Header("Content-Type", "application/json; charset=utf-8") output.Header("Content-Type", "application/json; charset=utf-8")
var content []byte var content []byte
var err error var err error
@ -183,12 +197,26 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err return err
} }
if coding { if encoding {
content = []byte(stringsToJSON(string(content))) content = []byte(stringsToJSON(string(content)))
} }
return output.Body(content) return output.Body(content)
} }
// YAML writes yaml to response body.
func (output *BeegoOutput) YAML(data interface{}) error {
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
var content []byte
var err error
content, err = yaml.Marshal(data)
if err != nil {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
return output.Body(content)
}
// JSONP writes jsonp to response body. // JSONP writes jsonp to response body.
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/javascript; charset=utf-8") output.Header("Content-Type", "application/javascript; charset=utf-8")
@ -232,6 +260,19 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
return output.Body(content) return output.Body(content)
} }
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) {
accept := output.Context.Input.Header("Accept")
switch accept {
case ApplicationYAML:
output.YAML(data)
case ApplicationXML, TextXML:
output.XML(data, hasIndent)
default:
output.JSON(data, hasIndent, len(hasEncode) > 0 && hasEncode[0])
}
}
// Download forces response for download file. // Download forces response for download file.
// it prepares the download response header automatically. // it prepares the download response header automatically.
func (output *BeegoOutput) Download(file string, filename ...string) { func (output *BeegoOutput) Download(file string, filename ...string) {
@ -247,7 +288,7 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
} else { } else {
fName = filepath.Base(file) fName = filepath.Base(file)
} }
output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName)) output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
output.Header("Content-Description", "File Transfer") output.Header("Content-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream") output.Header("Content-Type", "application/octet-stream")
output.Header("Content-Transfer-Encoding", "binary") output.Header("Content-Transfer-Encoding", "binary")
@ -312,13 +353,13 @@ func (output *BeegoOutput) IsForbidden() bool {
} }
// IsNotFound returns boolean of this request is not found. // IsNotFound returns boolean of this request is not found.
// HTTP 404 means forbidden. // HTTP 404 means not found.
func (output *BeegoOutput) IsNotFound() bool { func (output *BeegoOutput) IsNotFound() bool {
return output.Status == 404 return output.Status == 404
} }
// IsClientError returns boolean of this request client sends error data. // IsClientError returns boolean of this request client sends error data.
// HTTP 4xx means forbidden. // HTTP 4xx means client error.
func (output *BeegoOutput) IsClientError() bool { func (output *BeegoOutput) IsClientError() bool {
return output.Status >= 400 && output.Status < 500 return output.Status >= 400 && output.Status < 500
} }
@ -330,14 +371,18 @@ func (output *BeegoOutput) IsServerError() bool {
} }
func stringsToJSON(str string) string { func stringsToJSON(str string) string {
rs := []rune(str)
var jsons bytes.Buffer var jsons bytes.Buffer
for _, r := range rs { for _, r := range str {
rint := int(r) rint := int(r)
if rint < 128 { if rint < 128 {
jsons.WriteRune(r) jsons.WriteRune(r)
} else { } else {
jsons.WriteString("\\u") jsons.WriteString("\\u")
if rint < 0x100 {
jsons.WriteString("00")
} else if rint < 0x1000 {
jsons.WriteString("0")
}
jsons.WriteString(strconv.FormatInt(int64(rint), 16)) jsons.WriteString(strconv.FormatInt(int64(rint), 16))
} }
} }

78
context/param/conv.go Normal file
View File

@ -0,0 +1,78 @@
package param
import (
"fmt"
"reflect"
beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
)
// ConvertParams converts http method params to values that will be passed to the method controller as arguments
func ConvertParams(methodParams []*MethodParam, methodType reflect.Type, ctx *beecontext.Context) (result []reflect.Value) {
result = make([]reflect.Value, 0, len(methodParams))
for i := 0; i < len(methodParams); i++ {
reflectValue := convertParam(methodParams[i], methodType.In(i), ctx)
result = append(result, reflectValue)
}
return
}
func convertParam(param *MethodParam, paramType reflect.Type, ctx *beecontext.Context) (result reflect.Value) {
paramValue := getParamValue(param, ctx)
if paramValue == "" {
if param.required {
ctx.Abort(400, fmt.Sprintf("Missing parameter %s", param.name))
} else {
paramValue = param.defaultValue
}
}
reflectValue, err := parseValue(param, paramValue, paramType)
if err != nil {
logs.Debug(fmt.Sprintf("Error converting param %s to type %s. Value: %v, Error: %s", param.name, paramType, paramValue, err))
ctx.Abort(400, fmt.Sprintf("Invalid parameter %s. Can not convert %v to type %s", param.name, paramValue, paramType))
}
return reflectValue
}
func getParamValue(param *MethodParam, ctx *beecontext.Context) string {
switch param.in {
case body:
return string(ctx.Input.RequestBody)
case header:
return ctx.Input.Header(param.name)
case path:
return ctx.Input.Query(":" + param.name)
default:
return ctx.Input.Query(param.name)
}
}
func parseValue(param *MethodParam, paramValue string, paramType reflect.Type) (result reflect.Value, err error) {
if paramValue == "" {
return reflect.Zero(paramType), nil
}
parser := getParser(param, paramType)
value, err := parser.parse(paramValue, paramType)
if err != nil {
return result, err
}
return safeConvert(reflect.ValueOf(value), paramType)
}
func safeConvert(value reflect.Value, t reflect.Type) (result reflect.Value, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
}
}()
result = value.Convert(t)
return
}

View File

@ -0,0 +1,69 @@
package param
import (
"fmt"
"strings"
)
//MethodParam keeps param information to be auto passed to controller methods
type MethodParam struct {
name string
in paramType
required bool
defaultValue string
}
type paramType byte
const (
param paramType = iota
path
body
header
)
//New creates a new MethodParam with name and specific options
func New(name string, opts ...MethodParamOption) *MethodParam {
return newParam(name, nil, opts)
}
func newParam(name string, parser paramParser, opts []MethodParamOption) (param *MethodParam) {
param = &MethodParam{name: name}
for _, option := range opts {
option(param)
}
return
}
//Make creates an array of MethodParmas or an empty array
func Make(list ...*MethodParam) []*MethodParam {
if len(list) > 0 {
return list
}
return nil
}
func (mp *MethodParam) String() string {
options := []string{}
result := "param.New(\"" + mp.name + "\""
if mp.required {
options = append(options, "param.IsRequired")
}
switch mp.in {
case path:
options = append(options, "param.InPath")
case body:
options = append(options, "param.InBody")
case header:
options = append(options, "param.InHeader")
}
if mp.defaultValue != "" {
options = append(options, fmt.Sprintf(`param.Default("%s")`, mp.defaultValue))
}
if len(options) > 0 {
result += ", "
}
result += strings.Join(options, ", ")
result += ")"
return result
}

37
context/param/options.go Normal file
View File

@ -0,0 +1,37 @@
package param
import (
"fmt"
)
// MethodParamOption defines a func which apply options on a MethodParam
type MethodParamOption func(*MethodParam)
// IsRequired indicates that this param is required and can not be omitted from the http request
var IsRequired MethodParamOption = func(p *MethodParam) {
p.required = true
}
// InHeader indicates that this param is passed via an http header
var InHeader MethodParamOption = func(p *MethodParam) {
p.in = header
}
// InPath indicates that this param is part of the URL path
var InPath MethodParamOption = func(p *MethodParam) {
p.in = path
}
// InBody indicates that this param is passed as an http request body
var InBody MethodParamOption = func(p *MethodParam) {
p.in = body
}
// Default provides a default value for the http param
func Default(defaultValue interface{}) MethodParamOption {
return func(p *MethodParam) {
if defaultValue != nil {
p.defaultValue = fmt.Sprint(defaultValue)
}
}
}

149
context/param/parsers.go Normal file
View File

@ -0,0 +1,149 @@
package param
import (
"encoding/json"
"reflect"
"strconv"
"strings"
"time"
)
type paramParser interface {
parse(value string, toType reflect.Type) (interface{}, error)
}
func getParser(param *MethodParam, t reflect.Type) paramParser {
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return intParser{}
case reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 { //treat []byte as string
return stringParser{}
}
if param.in == body {
return jsonParser{}
}
elemParser := getParser(param, t.Elem())
if elemParser == (jsonParser{}) {
return elemParser
}
return sliceParser(elemParser)
case reflect.Bool:
return boolParser{}
case reflect.String:
return stringParser{}
case reflect.Float32, reflect.Float64:
return floatParser{}
case reflect.Ptr:
elemParser := getParser(param, t.Elem())
if elemParser == (jsonParser{}) {
return elemParser
}
return ptrParser(elemParser)
default:
if t.PkgPath() == "time" && t.Name() == "Time" {
return timeParser{}
}
return jsonParser{}
}
}
type parserFunc func(value string, toType reflect.Type) (interface{}, error)
func (f parserFunc) parse(value string, toType reflect.Type) (interface{}, error) {
return f(value, toType)
}
type boolParser struct {
}
func (p boolParser) parse(value string, toType reflect.Type) (interface{}, error) {
return strconv.ParseBool(value)
}
type stringParser struct {
}
func (p stringParser) parse(value string, toType reflect.Type) (interface{}, error) {
return value, nil
}
type intParser struct {
}
func (p intParser) parse(value string, toType reflect.Type) (interface{}, error) {
return strconv.Atoi(value)
}
type floatParser struct {
}
func (p floatParser) parse(value string, toType reflect.Type) (interface{}, error) {
if toType.Kind() == reflect.Float32 {
res, err := strconv.ParseFloat(value, 32)
if err != nil {
return nil, err
}
return float32(res), nil
}
return strconv.ParseFloat(value, 64)
}
type timeParser struct {
}
func (p timeParser) parse(value string, toType reflect.Type) (result interface{}, err error) {
result, err = time.Parse(time.RFC3339, value)
if err != nil {
result, err = time.Parse("2006-01-02", value)
}
return
}
type jsonParser struct {
}
func (p jsonParser) parse(value string, toType reflect.Type) (interface{}, error) {
pResult := reflect.New(toType)
v := pResult.Interface()
err := json.Unmarshal([]byte(value), v)
if err != nil {
return nil, err
}
return pResult.Elem().Interface(), nil
}
func sliceParser(elemParser paramParser) paramParser {
return parserFunc(func(value string, toType reflect.Type) (interface{}, error) {
values := strings.Split(value, ",")
result := reflect.MakeSlice(toType, 0, len(values))
elemType := toType.Elem()
for _, v := range values {
parsedValue, err := elemParser.parse(v, elemType)
if err != nil {
return nil, err
}
result = reflect.Append(result, reflect.ValueOf(parsedValue))
}
return result.Interface(), nil
})
}
func ptrParser(elemParser paramParser) paramParser {
return parserFunc(func(value string, toType reflect.Type) (interface{}, error) {
parsedValue, err := elemParser.parse(value, toType.Elem())
if err != nil {
return nil, err
}
newValPtr := reflect.New(toType.Elem())
newVal := reflect.Indirect(newValPtr)
convertedVal, err := safeConvert(reflect.ValueOf(parsedValue), toType.Elem())
if err != nil {
return nil, err
}
newVal.Set(convertedVal)
return newValPtr.Interface(), nil
})
}

View File

@ -0,0 +1,84 @@
package param
import "testing"
import "reflect"
import "time"
type testDefinition struct {
strValue string
expectedValue interface{}
expectedParser paramParser
}
func Test_Parsers(t *testing.T) {
//ints
checkParser(testDefinition{"1", 1, intParser{}}, t)
checkParser(testDefinition{"-1", int64(-1), intParser{}}, t)
checkParser(testDefinition{"1", uint64(1), intParser{}}, t)
//floats
checkParser(testDefinition{"1.0", float32(1.0), floatParser{}}, t)
checkParser(testDefinition{"-1.0", float64(-1.0), floatParser{}}, t)
//strings
checkParser(testDefinition{"AB", "AB", stringParser{}}, t)
checkParser(testDefinition{"AB", []byte{65, 66}, stringParser{}}, t)
//bools
checkParser(testDefinition{"true", true, boolParser{}}, t)
checkParser(testDefinition{"0", false, boolParser{}}, t)
//timeParser
checkParser(testDefinition{"2017-05-30T13:54:53Z", time.Date(2017, 5, 30, 13, 54, 53, 0, time.UTC), timeParser{}}, t)
checkParser(testDefinition{"2017-05-30", time.Date(2017, 5, 30, 0, 0, 0, 0, time.UTC), timeParser{}}, t)
//json
checkParser(testDefinition{`{"X": 5, "Y":"Z"}`, struct {
X int
Y string
}{5, "Z"}, jsonParser{}}, t)
//slice in query is parsed as comma delimited
checkParser(testDefinition{`1,2`, []int{1, 2}, sliceParser(intParser{})}, t)
//slice in body is parsed as json
checkParser(testDefinition{`["a","b"]`, []string{"a", "b"}, jsonParser{}}, t, MethodParam{in: body})
//pointers
var someInt = 1
checkParser(testDefinition{`1`, &someInt, ptrParser(intParser{})}, t)
var someStruct = struct{ X int }{5}
checkParser(testDefinition{`{"X": 5}`, &someStruct, jsonParser{}}, t)
}
func checkParser(def testDefinition, t *testing.T, methodParam ...MethodParam) {
toType := reflect.TypeOf(def.expectedValue)
var mp MethodParam
if len(methodParam) == 0 {
mp = MethodParam{}
} else {
mp = methodParam[0]
}
parser := getParser(&mp, toType)
if reflect.TypeOf(parser) != reflect.TypeOf(def.expectedParser) {
t.Errorf("Invalid parser for value %v. Expected: %v, actual: %v", def.strValue, reflect.TypeOf(def.expectedParser).Name(), reflect.TypeOf(parser).Name())
return
}
result, err := parser.parse(def.strValue, toType)
if err != nil {
t.Errorf("Parsing error for value %v. Expected result: %v, error: %v", def.strValue, def.expectedValue, err)
return
}
convResult, err := safeConvert(reflect.ValueOf(result), toType)
if err != nil {
t.Errorf("Conversion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err)
return
}
if !reflect.DeepEqual(convResult.Interface(), def.expectedValue) {
t.Errorf("Parsing error for value %v. Expected result: %v, actual: %v", def.strValue, def.expectedValue, result)
}
}

12
context/renderer.go Normal file
View File

@ -0,0 +1,12 @@
package context
// Renderer defines an http response renderer
type Renderer interface {
Render(ctx *Context)
}
type rendererFunc func(ctx *Context)
func (f rendererFunc) Render(ctx *Context) {
f(ctx)
}

27
context/response.go Normal file
View File

@ -0,0 +1,27 @@
package context
import (
"strconv"
"net/http"
)
const (
//BadRequest indicates http error 400
BadRequest StatusCode = http.StatusBadRequest
//NotFound indicates http error 404
NotFound StatusCode = http.StatusNotFound
)
// StatusCode sets the http response status code
type StatusCode int
func (s StatusCode) Error() string {
return strconv.Itoa(int(s))
}
// Render sets the http status code
func (s StatusCode) Render(ctx *Context) {
ctx.Output.SetStatus(int(s))
}

View File

@ -28,16 +28,10 @@ import (
"strings" "strings"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/astaxie/beego/context/param"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
) )
//commonly used mime-types
const (
applicationJSON = "application/json"
applicationXML = "application/xml"
textXML = "text/xml"
)
var ( var (
// ErrAbort custom error when user stop request handler manually. // ErrAbort custom error when user stop request handler manually.
ErrAbort = errors.New("User stop run") ErrAbort = errors.New("User stop run")
@ -45,14 +39,49 @@ var (
GlobalControllerRouter = make(map[string][]ControllerComments) GlobalControllerRouter = make(map[string][]ControllerComments)
) )
// ControllerFilter store the filter for controller
type ControllerFilter struct {
Pattern string
Pos int
Filter FilterFunc
ReturnOnOutput bool
ResetParams bool
}
// ControllerFilterComments store the comment for controller level filter
type ControllerFilterComments struct {
Pattern string
Pos int
Filter string // NOQA
ReturnOnOutput bool
ResetParams bool
}
// ControllerImportComments store the import comment for controller needed
type ControllerImportComments struct {
ImportPath string
ImportAlias string
}
// ControllerComments store the comment for the controller method // ControllerComments store the comment for the controller method
type ControllerComments struct { type ControllerComments struct {
Method string Method string
Router string Router string
Filters []*ControllerFilter
ImportComments []*ControllerImportComments
FilterComments []*ControllerFilterComments
AllowHTTPMethods []string AllowHTTPMethods []string
Params []map[string]string Params []map[string]string
MethodParams []*param.MethodParam
} }
// ControllerCommentsSlice implements the sort interface
type ControllerCommentsSlice []ControllerComments
func (p ControllerCommentsSlice) Len() int { return len(p) }
func (p ControllerCommentsSlice) Less(i, j int) bool { return p[i].Router < p[j].Router }
func (p ControllerCommentsSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Controller defines some basic http request handler operations, such as // Controller defines some basic http request handler operations, such as
// http context, template and view, session and xsrf. // http context, template and view, session and xsrf.
type Controller struct { type Controller struct {
@ -263,9 +292,23 @@ func (c *Controller) viewPath() string {
// Redirect sends the redirection response to url with status code. // Redirect sends the redirection response to url with status code.
func (c *Controller) Redirect(url string, code int) { func (c *Controller) Redirect(url string, code int) {
logAccess(c.Ctx, nil, code)
c.Ctx.Redirect(code, url) c.Ctx.Redirect(code, url)
} }
// SetData set the data depending on the accepted
func (c *Controller) SetData(data interface{}) {
accept := c.Ctx.Input.Header("Accept")
switch accept {
case context.ApplicationYAML:
c.Data["yaml"] = data
case context.ApplicationXML, context.TextXML:
c.Data["xml"] = data
default:
c.Data["json"] = data
}
}
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string. // Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
func (c *Controller) Abort(code string) { func (c *Controller) Abort(code string) {
status, err := strconv.Atoi(code) status, err := strconv.Atoi(code)
@ -308,47 +351,35 @@ func (c *Controller) URLFor(endpoint string, values ...interface{}) string {
// ServeJSON sends a json response with encoding charset. // ServeJSON sends a json response with encoding charset.
func (c *Controller) ServeJSON(encoding ...bool) { func (c *Controller) ServeJSON(encoding ...bool) {
var ( var (
hasIndent = true hasIndent = BConfig.RunMode != PROD
hasEncoding = false hasEncoding = len(encoding) > 0 && encoding[0]
) )
if BConfig.RunMode == PROD {
hasIndent = false
}
if len(encoding) > 0 && encoding[0] == true {
hasEncoding = true
}
c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding) c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
} }
// ServeJSONP sends a jsonp response. // ServeJSONP sends a jsonp response.
func (c *Controller) ServeJSONP() { func (c *Controller) ServeJSONP() {
hasIndent := true hasIndent := BConfig.RunMode != PROD
if BConfig.RunMode == PROD {
hasIndent = false
}
c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent) c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent)
} }
// ServeXML sends xml response. // ServeXML sends xml response.
func (c *Controller) ServeXML() { func (c *Controller) ServeXML() {
hasIndent := true hasIndent := BConfig.RunMode != PROD
if BConfig.RunMode == PROD {
hasIndent = false
}
c.Ctx.Output.XML(c.Data["xml"], hasIndent) c.Ctx.Output.XML(c.Data["xml"], hasIndent)
} }
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header // ServeYAML sends yaml response.
func (c *Controller) ServeFormatted() { func (c *Controller) ServeYAML() {
accept := c.Ctx.Input.Header("Accept") c.Ctx.Output.YAML(c.Data["yaml"])
switch accept {
case applicationJSON:
c.ServeJSON()
case applicationXML, textXML:
c.ServeXML()
default:
c.ServeJSON()
} }
// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header
func (c *Controller) ServeFormatted(encoding ...bool) {
hasIndent := BConfig.RunMode != PROD
hasEncoding := len(encoding) > 0 && encoding[0]
c.Ctx.Output.ServeFormatted(c.Data, hasIndent, hasEncoding)
} }
// Input returns the input data map from POST or PUT request body and query string. // Input returns the input data map from POST or PUT request body and query string.

View File

@ -172,10 +172,10 @@ func TestAdditionalViewPaths(t *testing.T) {
t.Fatal("TestAdditionalViewPaths expected error") t.Fatal("TestAdditionalViewPaths expected error")
} }
}() }()
ctrl.RenderString(); ctrl.RenderString()
}() }()
ctrl.TplName = "file2.tpl" ctrl.TplName = "file2.tpl"
ctrl.ViewPath = dir2 ctrl.ViewPath = dir2
ctrl.RenderString(); ctrl.RenderString()
} }

View File

@ -93,11 +93,6 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
"BeegoVersion": VERSION, "BeegoVersion": VERSION,
"GoVersion": runtime.Version(), "GoVersion": runtime.Version(),
} }
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
t.Execute(ctx.ResponseWriter, data) t.Execute(ctx.ResponseWriter, data)
} }
@ -252,6 +247,30 @@ func forbidden(rw http.ResponseWriter, r *http.Request) {
) )
} }
// show 422 missing xsrf token
func missingxsrf(rw http.ResponseWriter, r *http.Request) {
responseError(rw, r,
422,
"<br>The page you have requested is forbidden."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>'_xsrf' argument missing from POST"+
"</ul>",
)
}
// show 417 invalid xsrf token
func invalidxsrf(rw http.ResponseWriter, r *http.Request) {
responseError(rw, r,
417,
"<br>The page you have requested is forbidden."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>expected XSRF not found"+
"</ul>",
)
}
// show 404 not found error. // show 404 not found error.
func notFound(rw http.ResponseWriter, r *http.Request) { func notFound(rw http.ResponseWriter, r *http.Request) {
responseError(rw, r, responseError(rw, r,
@ -342,7 +361,7 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) { func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{ data := M{
"Title": http.StatusText(errCode), "Title": http.StatusText(errCode),
"BeegoVersion": VERSION, "BeegoVersion": VERSION,
"Content": template.HTML(errContent), "Content": template.HTML(errContent),
@ -415,6 +434,9 @@ func exception(errCode string, ctx *context.Context) {
} }
func executeError(err *errorInfo, ctx *context.Context, code int) { func executeError(err *errorInfo, ctx *context.Context, code int) {
//make sure to log the error in the access log
logAccess(ctx, nil, code)
if err.errorType == errorTypeHandler { if err.errorType == errorTypeHandler {
ctx.ResponseWriter.WriteHeader(code) ctx.ResponseWriter.WriteHeader(code)
err.handler(ctx.ResponseWriter, ctx.Request) err.handler(ctx.ResponseWriter, ctx.Request)

View File

@ -52,7 +52,7 @@ func TestErrorCode_01(t *testing.T) {
if w.Code != code { if w.Code != code {
t.Fail() t.Fail()
} }
if !strings.Contains(string(w.Body.Bytes()), http.StatusText(code)) { if !strings.Contains(w.Body.String(), http.StatusText(code)) {
t.Fail() t.Fail()
} }
} }
@ -82,7 +82,7 @@ func TestErrorCode_03(t *testing.T) {
if w.Code != 200 { if w.Code != 200 {
t.Fail() t.Fail()
} }
if string(w.Body.Bytes()) != parseCodeError { if w.Body.String() != parseCodeError {
t.Fail() t.Fail()
} }
} }

View File

@ -48,7 +48,7 @@ func TestFlashHeader(t *testing.T) {
// match for the expected header // match for the expected header
res := strings.Contains(sc, "BEEGO_FLASH=%00notice%23BEEGOFLASH%23TestFlashString%00") res := strings.Contains(sc, "BEEGO_FLASH=%00notice%23BEEGOFLASH%23TestFlashString%00")
// validate the assertion // validate the assertion
if res != true { if !res {
t.Errorf("TestFlashHeader() unable to validate flash message") t.Errorf("TestFlashHeader() unable to validate flash message")
} }
} }

74
fs.go Normal file
View File

@ -0,0 +1,74 @@
package beego
import (
"net/http"
"os"
"path/filepath"
)
type FileSystem struct {
}
func (d FileSystem) Open(name string) (http.File, error) {
return os.Open(name)
}
// Walk walks the file tree rooted at root in filesystem, calling walkFn for each file or
// directory in the tree, including root. All errors that arise visiting files
// and directories are filtered by walkFn.
func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error {
f, err := fs.Open(root)
if err != nil {
return err
}
info, err := f.Stat()
if err != nil {
err = walkFn(root, nil, err)
} else {
err = walk(fs, root, info, walkFn)
}
if err == filepath.SkipDir {
return nil
}
return err
}
// walk recursively descends path, calling walkFn.
func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
var err error
if !info.IsDir() {
return walkFn(path, info, nil)
}
dir, err := fs.Open(path)
defer dir.Close()
if err != nil {
if err1 := walkFn(path, info, err); err1 != nil {
return err1
}
return err
}
dirs, err := dir.Readdir(-1)
err1 := walkFn(path, info, err)
// If err != nil, walk can't walk into this directory.
// err1 != nil means walkFn want walk to skip this directory or stop walking.
// Therefore, if one of err and err1 isn't nil, walk will return.
if err != nil || err1 != nil {
// The caller's behavior is controlled by the return value, which is decided
// by walkFn. walkFn may ignore err and return nil.
// If walkFn returns SkipDir, it will be handled by the caller.
// So walk should return whatever walkFn returns.
return err1
}
for _, fileInfo := range dirs {
filename := filepath.Join(path, fileInfo.Name())
if err = walk(fs, filename, fileInfo, walkFn); err != nil {
if !fileInfo.IsDir() || err != filepath.SkipDir {
return err
}
}
}
return nil
}

40
go.mod Normal file
View File

@ -0,0 +1,40 @@
module github.com/astaxie/beego
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
github.com/casbin/casbin v1.6.0
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
github.com/couchbase/go-couchbase v0.0.0-20181019154153-595f46701cbc
github.com/couchbase/gomemcached v0.0.0-20180723192129-20e69a1ee160 // indirect
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
github.com/elazarl/go-bindata-assetfs v0.0.0-20180223110309-38087fe4dafb
github.com/go-redis/redis v6.14.2+incompatible
github.com/go-sql-driver/mysql v1.4.0
github.com/gogo/protobuf v1.1.1
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/lib/pq v1.0.0
github.com/mattn/go-sqlite3 v1.10.0
github.com/onsi/gomega v1.4.2 // indirect
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pkg/errors v0.8.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
github.com/stretchr/testify v1.2.2 // indirect
github.com/syndtr/goleveldb v0.0.0-20181105012736-f9080354173f // indirect
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb
google.golang.org/appengine v1.1.0 // indirect
gopkg.in/yaml.v2 v2.2.1
)

94
go.sum Normal file
View File

@ -0,0 +1,94 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff h1:/kO0p2RTGLB8R5gub7ps0GmYpB2O8LXEoPq8tzFDCUI=
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.6.0 h1:uIhuV5I0ilXGUm3y+xJ8nG7VOnYDeZZQiNsFOTF2QmI=
github.com/casbin/casbin v1.6.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/couchbase/go-couchbase v0.0.0-20181019154153-595f46701cbc h1:Byzmalcea3rzOdgt4Ny3xrtXkd25zUMPFI5oeKksSbU=
github.com/couchbase/go-couchbase v0.0.0-20181019154153-595f46701cbc/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20180723192129-20e69a1ee160 h1:yaqs73s76owCkJbPZo8GKSosZoMjezdLDslJ8aaDk0w=
github.com/couchbase/gomemcached v0.0.0-20180723192129-20e69a1ee160/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v0.0.0-20180223110309-38087fe4dafb h1:T6FhFH6fLQPEu7n7PauDhb4mhpxhlfaL7a7MZEpIgDc=
github.com/elazarl/go-bindata-assetfs v0.0.0-20180223110309-38087fe4dafb/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 h1:p6IxqQMjab30l4lb9mmkIkkcE1yv6o0SKbPhW5pxqHI=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/syndtr/goleveldb v0.0.0-20181105012736-f9080354173f h1:EEVjSRihF8NIbfyCcErpSpNHEKrY3s8EAwqiPENZZn8=
github.com/syndtr/goleveldb v0.0.0-20181105012736-f9080354173f/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb h1:Ah9YqXLj6fEgeKqcmBuLCbAsrF3ScD7dJ/bYM0C6tXI=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -3,14 +3,17 @@ package grace
import ( import (
"errors" "errors"
"net" "net"
"sync"
) )
type graceConn struct { type graceConn struct {
net.Conn net.Conn
server *Server server *Server
m sync.Mutex
closed bool
} }
func (c graceConn) Close() (err error) { func (c *graceConn) Close() (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
switch x := r.(type) { switch x := r.(type) {
@ -23,6 +26,14 @@ func (c graceConn) Close() (err error) {
} }
} }
}() }()
c.m.Lock()
if c.closed {
c.m.Unlock()
return
}
c.server.wg.Done() c.server.wg.Done()
c.closed = true
c.m.Unlock()
return c.Conn.Close() return c.Conn.Close()
} }

View File

@ -21,7 +21,7 @@ func newGraceListener(l net.Listener, srv *Server) (el *graceListener) {
server: srv, server: srv,
} }
go func() { go func() {
_ = <-el.stop <-el.stop
el.stopped = true el.stopped = true
el.stop <- el.Listener.Close() el.stop <- el.Listener.Close()
}() }()
@ -37,7 +37,7 @@ func (gl *graceListener) Accept() (c net.Conn, err error) {
tc.SetKeepAlive(true) tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute) tc.SetKeepAlivePeriod(3 * time.Minute)
c = graceConn{ c = &graceConn{
Conn: tc, Conn: tc,
server: gl.server, server: gl.server,
} }

View File

@ -2,7 +2,9 @@ package grace
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -65,7 +67,7 @@ func (srv *Server) ListenAndServe() (err error) {
log.Println(err) log.Println(err)
return err return err
} }
err = process.Kill() err = process.Signal(syscall.SIGTERM)
if err != nil { if err != nil {
return err return err
} }
@ -114,6 +116,62 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
srv.tlsInnerListener = newGraceListener(l, srv) srv.tlsInnerListener = newGraceListener(l, srv)
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig) srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
if srv.isChild {
process, err := os.FindProcess(os.Getppid())
if err != nil {
log.Println(err)
return err
}
err = process.Signal(syscall.SIGTERM)
if err != nil {
return err
}
}
log.Println(os.Getpid(), srv.Addr)
return srv.Serve()
}
// ListenAndServeMutualTLS listens on the TCP network address srv.Addr and then calls
// Serve to handle requests on incoming mutual TLS connections.
func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string) (err error) {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
if srv.TLSConfig == nil {
srv.TLSConfig = &tls.Config{}
}
if srv.TLSConfig.NextProtos == nil {
srv.TLSConfig.NextProtos = []string{"http/1.1"}
}
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return
}
srv.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
pool := x509.NewCertPool()
data, err := ioutil.ReadFile(trustFile)
if err != nil {
log.Println(err)
return err
}
pool.AppendCertsFromPEM(data)
srv.TLSConfig.ClientCAs = pool
log.Println("Mutual HTTPS")
go srv.handleSignals()
l, err := srv.getListener(addr)
if err != nil {
log.Println(err)
return err
}
srv.tlsInnerListener = newGraceListener(l, srv)
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
if srv.isChild { if srv.isChild {
process, err := os.FindProcess(os.Getppid()) process, err := os.FindProcess(os.Getppid())
if err != nil { if err != nil {
@ -196,7 +254,6 @@ func (srv *Server) signalHooks(ppFlag int, sig os.Signal) {
for _, f := range srv.SignalHooks[ppFlag][sig] { for _, f := range srv.SignalHooks[ppFlag][sig] {
f() f()
} }
return
} }
// shutdown closes the listener so that no new connections are accepted. it also // shutdown closes the listener so that no new connections are accepted. it also
@ -292,7 +349,7 @@ func (srv *Server) fork() (err error) {
// RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal. // RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal.
func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) (err error) { func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) (err error) {
if ppFlag != PreSignal && ppFlag != PostSignal { if ppFlag != PreSignal && ppFlag != PostSignal {
err = fmt.Errorf("Invalid ppFlag argument. Must be either grace.PreSignal or grace.PostSignal.") err = fmt.Errorf("Invalid ppFlag argument. Must be either grace.PreSignal or grace.PostSignal")
return return
} }
for _, s := range hookableSignals { for _, s := range hookableSignals {
@ -301,6 +358,6 @@ func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) (err
return return
} }
} }
err = fmt.Errorf("Signal '%v' is not supported.", sig) err = fmt.Errorf("Signal '%v' is not supported", sig)
return return
} }

View File

@ -32,6 +32,8 @@ func registerDefaultErrorHandler() error {
"502": badGateway, "502": badGateway,
"503": serviceUnavailable, "503": serviceUnavailable,
"504": gatewayTimeout, "504": gatewayTimeout,
"417": invalidxsrf,
"422": missingxsrf,
} }
for e, h := range m { for e, h := range m {
if _, ok := ErrorMaps[e]; !ok { if _, ok := ErrorMaps[e]; !ok {
@ -55,9 +57,9 @@ func registerSession() error {
conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig) conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
conf.DisableHTTPOnly = BConfig.WebConfig.Session.SessionDisableHTTPOnly conf.DisableHTTPOnly = BConfig.WebConfig.Session.SessionDisableHTTPOnly
conf.Domain = BConfig.WebConfig.Session.SessionDomain conf.Domain = BConfig.WebConfig.Session.SessionDomain
conf.EnableSidInHttpHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
conf.SessionNameInHttpHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
conf.EnableSidInUrlQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
} else { } else {
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil { if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
return err return err

View File

@ -32,7 +32,7 @@ The default timeout is `60` seconds, function prototype:
SetTimeout(connectTimeout, readWriteTimeout time.Duration) SetTimeout(connectTimeout, readWriteTimeout time.Duration)
Exmaple: Example:
// GET // GET
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)

View File

@ -50,6 +50,7 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"gopkg.in/yaml.v2"
) )
var defaultSetting = BeegoHTTPSettings{ var defaultSetting = BeegoHTTPSettings{
@ -318,6 +319,34 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
return b return b
} }
// XMLBody adds request raw body encoding by XML.
func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
byts, err := xml.Marshal(obj)
if err != nil {
return b, err
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/xml")
}
return b, nil
}
// YAMLBody adds request raw body encoding by YAML.
func (b *BeegoHTTPRequest) YAMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
byts, err := yaml.Marshal(obj)
if err != nil {
return b, err
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/x+yaml")
}
return b, nil
}
// JSONBody adds request raw body encoding by JSON. // JSONBody adds request raw body encoding by JSON.
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil { if b.req.Body == nil && obj != nil {
@ -335,7 +364,7 @@ func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error)
func (b *BeegoHTTPRequest) buildURL(paramBody string) { func (b *BeegoHTTPRequest) buildURL(paramBody string) {
// build GET url with query string // build GET url with query string
if b.req.Method == "GET" && len(paramBody) > 0 { if b.req.Method == "GET" && len(paramBody) > 0 {
if strings.Index(b.url, "?") != -1 { if strings.Contains(b.url, "?") {
b.url += "&" + paramBody b.url += "&" + paramBody
} else { } else {
b.url = b.url + "?" + paramBody b.url = b.url + "?" + paramBody
@ -344,7 +373,7 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) {
} }
// build POST/PUT/PATCH url and body // build POST/PUT/PATCH url and body
if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH") && b.req.Body == nil { if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH" || b.req.Method == "DELETE") && b.req.Body == nil {
// with files // with files
if len(b.files) > 0 { if len(b.files) > 0 {
pr, pw := io.Pipe() pr, pw := io.Pipe()
@ -417,12 +446,12 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
} }
b.buildURL(paramBody) b.buildURL(paramBody)
url, err := url.Parse(b.url) urlParsed, err := url.Parse(b.url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
b.req.URL = url b.req.URL = urlParsed
trans := b.setting.Transport trans := b.setting.Transport
@ -432,7 +461,7 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
TLSClientConfig: b.setting.TLSClientConfig, TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy, Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: -1, MaxIdleConnsPerHost: 100,
} }
} else { } else {
// if b.transport is *http.Transport then set the settings. // if b.transport is *http.Transport then set the settings.
@ -520,9 +549,9 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
return nil, err return nil, err
} }
b.body, err = ioutil.ReadAll(reader) b.body, err = ioutil.ReadAll(reader)
} else { return b.body, err
b.body, err = ioutil.ReadAll(resp.Body)
} }
b.body, err = ioutil.ReadAll(resp.Body)
return b.body, err return b.body, err
} }
@ -567,6 +596,16 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
return xml.Unmarshal(data, v) return xml.Unmarshal(data, v)
} }
// ToYAML returns the map that marshals from the body bytes as yaml in response .
// it calls Response inner.
func (b *BeegoHTTPRequest) ToYAML(v interface{}) error {
data, err := b.Bytes()
if err != nil {
return err
}
return yaml.Unmarshal(data, v)
}
// Response executes request client gets response mannually. // Response executes request client gets response mannually.
func (b *BeegoHTTPRequest) Response() (*http.Response, error) { func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
return b.getResponse() return b.getResponse()

View File

@ -16,6 +16,8 @@ package httplib
import ( import (
"io/ioutil" "io/ioutil"
"net"
"net/http"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -102,6 +104,14 @@ func TestSimpleDelete(t *testing.T) {
t.Log(str) t.Log(str)
} }
func TestSimpleDeleteParam(t *testing.T) {
str, err := Delete("http://httpbin.org/delete").Param("key", "val").String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
}
func TestWithCookie(t *testing.T) { func TestWithCookie(t *testing.T) {
v := "smallfish" v := "smallfish"
str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String() str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String()
@ -153,7 +163,16 @@ func TestWithSetting(t *testing.T) {
var setting BeegoHTTPSettings var setting BeegoHTTPSettings
setting.EnableCookie = true setting.EnableCookie = true
setting.UserAgent = v setting.UserAgent = v
setting.Transport = nil setting.Transport = &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 50,
IdleConnTimeout: 90 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
setting.ReadWriteTimeout = 5 * time.Second setting.ReadWriteTimeout = 5 * time.Second
SetDefaultSetting(setting) SetDefaultSetting(setting)

View File

@ -16,48 +16,57 @@ As of now this logs support console, file,smtp and conn.
First you must import it First you must import it
```golang
import ( import (
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
) )
```
Then init a Log (example with console adapter) Then init a Log (example with console adapter)
log := NewLogger(10000) ```golang
log := logs.NewLogger(10000)
log.SetLogger("console", "") log.SetLogger("console", "")
```
> the first params stand for how many channel > the first params stand for how many channel
Use it like this: Use it like this:
```golang
log.Trace("trace") log.Trace("trace")
log.Info("info") log.Info("info")
log.Warn("warning") log.Warn("warning")
log.Debug("debug") log.Debug("debug")
log.Critical("critical") log.Critical("critical")
```
## File adapter ## File adapter
Configure file adapter like this: Configure file adapter like this:
```golang
log := NewLogger(10000) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test.log"}`) log.SetLogger("file", `{"filename":"test.log"}`)
```
## Conn adapter ## Conn adapter
Configure like this: Configure like this:
```golang
log := NewLogger(1000) log := NewLogger(1000)
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Info("info") log.Info("info")
```
## Smtp adapter ## Smtp adapter
Configure like this: Configure like this:
```golang
log := NewLogger(10000) log := NewLogger(10000)
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
log.Critical("sendmail critical") log.Critical("sendmail critical")
time.Sleep(time.Second * 30) time.Sleep(time.Second * 30)
```

83
logs/accesslog.go Normal file
View File

@ -0,0 +1,83 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"bytes"
"strings"
"encoding/json"
"fmt"
"time"
)
const (
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
apacheFormat = "APACHE_FORMAT"
jsonFormat = "JSON_FORMAT"
)
// AccessLogRecord struct for holding access log data.
type AccessLogRecord struct {
RemoteAddr string `json:"remote_addr"`
RequestTime time.Time `json:"request_time"`
RequestMethod string `json:"request_method"`
Request string `json:"request"`
ServerProtocol string `json:"server_protocol"`
Host string `json:"host"`
Status int `json:"status"`
BodyBytesSent int64 `json:"body_bytes_sent"`
ElapsedTime time.Duration `json:"elapsed_time"`
HTTPReferrer string `json:"http_referrer"`
HTTPUserAgent string `json:"http_user_agent"`
RemoteUser string `json:"remote_user"`
}
func (r *AccessLogRecord) json() ([]byte, error) {
buffer := &bytes.Buffer{}
encoder := json.NewEncoder(buffer)
disableEscapeHTML(encoder)
err := encoder.Encode(r)
return buffer.Bytes(), err
}
func disableEscapeHTML(i interface{}) {
if e, ok := i.(interface {
SetEscapeHTML(bool)
}); ok {
e.SetEscapeHTML(false)
}
}
// AccessLog - Format and print access log.
func AccessLog(r *AccessLogRecord, format string) {
var msg string
switch format {
case apacheFormat:
timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05")
msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent,
r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent)
case jsonFormat:
fallthrough
default:
jsonData, err := r.json()
if err != nil {
msg = fmt.Sprintf(`{"Error": "%s"}`, err)
} else {
msg = string(jsonData)
}
}
beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg))
}

View File

@ -2,19 +2,23 @@ package alils
import ( import (
"encoding/json" "encoding/json"
"github.com/astaxie/beego/logs"
"github.com/gogo/protobuf/proto"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/astaxie/beego/logs"
"github.com/gogo/protobuf/proto"
) )
const ( const (
// CacheSize set the flush size
CacheSize int = 64 CacheSize int = 64
// Delimiter define the topic delimiter
Delimiter string = "##" Delimiter string = "##"
) )
type AliLSConfig struct { // Config is the Config for Ali Log
type Config struct {
Project string `json:"project"` Project string `json:"project"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
KeyID string `json:"key_id"` KeyID string `json:"key_id"`
@ -34,18 +38,17 @@ type aliLSWriter struct {
withMap bool withMap bool
groupMap map[string]*LogGroup groupMap map[string]*LogGroup
lock *sync.Mutex lock *sync.Mutex
AliLSConfig Config
} }
// 创建提供Logger接口的日志服务 // NewAliLS create a new Logger
func NewAliLS() logs.Logger { func NewAliLS() logs.Logger {
alils := new(aliLSWriter) alils := new(aliLSWriter)
alils.Level = logs.LevelTrace alils.Level = logs.LevelTrace
return alils return alils
} }
// 读取配置 // Init parse config and init struct
// 初始化必要的数据结构
func (c *aliLSWriter) Init(jsonConfig string) (err error) { func (c *aliLSWriter) Init(jsonConfig string) (err error) {
json.Unmarshal([]byte(jsonConfig), c) json.Unmarshal([]byte(jsonConfig), c)
@ -54,28 +57,26 @@ func (c *aliLSWriter) Init(jsonConfig string) (err error) {
c.FlushWhen = CacheSize c.FlushWhen = CacheSize
} }
// 初始化Project
prj := &LogProject{ prj := &LogProject{
Name: c.Project, Name: c.Project,
Endpoint: c.Endpoint, Endpoint: c.Endpoint,
AccessKeyId: c.KeyID, AccessKeyID: c.KeyID,
AccessKeySecret: c.KeySecret, AccessKeySecret: c.KeySecret,
} }
// 获取logstore
c.store, err = prj.GetLogStore(c.LogStore) c.store, err = prj.GetLogStore(c.LogStore)
if err != nil { if err != nil {
return err return err
} }
// 创建默认Log Group // Create default Log Group
c.group = append(c.group, &LogGroup{ c.group = append(c.group, &LogGroup{
Topic: proto.String(""), Topic: proto.String(""),
Source: proto.String(c.Source), Source: proto.String(c.Source),
Logs: make([]*Log, 0, c.FlushWhen), Logs: make([]*Log, 0, c.FlushWhen),
}) })
// 创建其它Log Group // Create other Log Group
c.groupMap = make(map[string]*LogGroup) c.groupMap = make(map[string]*LogGroup)
for _, topic := range c.Topics { for _, topic := range c.Topics {
@ -113,7 +114,7 @@ func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error
var lg *LogGroup var lg *LogGroup
if c.withMap { if c.withMap {
// 解析出Topic并匹配LogGroup // TopicLogGroup
strs := strings.SplitN(msg, Delimiter, 2) strs := strings.SplitN(msg, Delimiter, 2)
if len(strs) == 2 { if len(strs) == 2 {
pos := strings.LastIndex(strs[0], " ") pos := strings.LastIndex(strs[0], " ")
@ -122,27 +123,24 @@ func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error
lg = c.groupMap[topic] lg = c.groupMap[topic]
} }
// 默认发到空Topic // send to empty Topic
if lg == nil { if lg == nil {
topic = ""
content = msg content = msg
lg = c.group[0] lg = c.group[0]
} }
} else { } else {
topic = ""
content = msg content = msg
lg = c.group[0] lg = c.group[0]
} }
// 生成日志 c1 := &LogContent{
c1 := &Log_Content{
Key: proto.String("msg"), Key: proto.String("msg"),
Value: proto.String(content), Value: proto.String(content),
} }
l := &Log{ l := &Log{
Time: proto.Uint32(uint32(when.Unix())), // 填写日志时间 Time: proto.Uint32(uint32(when.Unix())),
Contents: []*Log_Content{ Contents: []*LogContent{
c1, c1,
}, },
} }
@ -151,7 +149,6 @@ func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error
lg.Logs = append(lg.Logs, l) lg.Logs = append(lg.Logs, l)
c.lock.Unlock() c.lock.Unlock()
// 满足条件则Flush
if len(lg.Logs) >= c.FlushWhen { if len(lg.Logs) >= c.FlushWhen {
c.flush(lg) c.flush(lg)
} }
@ -162,7 +159,7 @@ func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error
// Flush implementing method. empty. // Flush implementing method. empty.
func (c *aliLSWriter) Flush() { func (c *aliLSWriter) Flush() {
// flush所有group // flush all group
for _, lg := range c.group { for _, lg := range c.group {
c.flush(lg) c.flush(lg)
} }
@ -176,9 +173,6 @@ func (c *aliLSWriter) flush(lg *LogGroup) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
// 把以上的LogGroup推送到SLS服务器
// SLS服务器会根据该logstore的shard个数自动进行负载均衡。
err := c.store.PutLogs(lg) err := c.store.PutLogs(lg)
if err != nil { if err != nil {
return return

View File

@ -1,30 +1,43 @@
package alils package alils
import "github.com/gogo/protobuf/proto" import (
import "fmt" "fmt"
import "math" "io"
"math"
// discarding unused import gogoproto "." "github.com/gogo/protobuf/proto"
github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
import github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto" )
import "io"
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal var _ = proto.Marshal
var _ = fmt.Errorf var _ = fmt.Errorf
var _ = math.Inf var _ = math.Inf
var (
// ErrInvalidLengthLog invalid proto
ErrInvalidLengthLog = fmt.Errorf("proto: negative length found during unmarshaling")
// ErrIntOverflowLog overflow
ErrIntOverflowLog = fmt.Errorf("proto: integer overflow")
)
// Log define the proto Log
type Log struct { type Log struct {
Time *uint32 `protobuf:"varint,1,req,name=Time" json:"Time,omitempty"` Time *uint32 `protobuf:"varint,1,req,name=Time" json:"Time,omitempty"`
Contents []*Log_Content `protobuf:"bytes,2,rep,name=Contents" json:"Contents,omitempty"` Contents []*LogContent `protobuf:"bytes,2,rep,name=Contents" json:"Contents,omitempty"`
XXX_unrecognized []byte `json:"-"` XXXUnrecognized []byte `json:"-"`
} }
// Reset the Log
func (m *Log) Reset() { *m = Log{} } func (m *Log) Reset() { *m = Log{} }
// String return the Compact Log
func (m *Log) String() string { return proto.CompactTextString(m) } func (m *Log) String() string { return proto.CompactTextString(m) }
// ProtoMessage not implemented
func (*Log) ProtoMessage() {} func (*Log) ProtoMessage() {}
// GetTime return the Log's Time
func (m *Log) GetTime() uint32 { func (m *Log) GetTime() uint32 {
if m != nil && m.Time != nil { if m != nil && m.Time != nil {
return *m.Time return *m.Time
@ -32,49 +45,65 @@ func (m *Log) GetTime() uint32 {
return 0 return 0
} }
func (m *Log) GetContents() []*Log_Content { // GetContents return the Log's Contents
func (m *Log) GetContents() []*LogContent {
if m != nil { if m != nil {
return m.Contents return m.Contents
} }
return nil return nil
} }
type Log_Content struct { // LogContent define the Log content struct
type LogContent struct {
Key *string `protobuf:"bytes,1,req,name=Key" json:"Key,omitempty"` Key *string `protobuf:"bytes,1,req,name=Key" json:"Key,omitempty"`
Value *string `protobuf:"bytes,2,req,name=Value" json:"Value,omitempty"` Value *string `protobuf:"bytes,2,req,name=Value" json:"Value,omitempty"`
XXX_unrecognized []byte `json:"-"` XXXUnrecognized []byte `json:"-"`
} }
func (m *Log_Content) Reset() { *m = Log_Content{} } // Reset LogContent
func (m *Log_Content) String() string { return proto.CompactTextString(m) } func (m *LogContent) Reset() { *m = LogContent{} }
func (*Log_Content) ProtoMessage() {}
func (m *Log_Content) GetKey() string { // String return the compact text
func (m *LogContent) String() string { return proto.CompactTextString(m) }
// ProtoMessage not implemented
func (*LogContent) ProtoMessage() {}
// GetKey return the Key
func (m *LogContent) GetKey() string {
if m != nil && m.Key != nil { if m != nil && m.Key != nil {
return *m.Key return *m.Key
} }
return "" return ""
} }
func (m *Log_Content) GetValue() string { // GetValue return the Value
func (m *LogContent) GetValue() string {
if m != nil && m.Value != nil { if m != nil && m.Value != nil {
return *m.Value return *m.Value
} }
return "" return ""
} }
// LogGroup define the logs struct
type LogGroup struct { type LogGroup struct {
Logs []*Log `protobuf:"bytes,1,rep,name=Logs" json:"Logs,omitempty"` Logs []*Log `protobuf:"bytes,1,rep,name=Logs" json:"Logs,omitempty"`
Reserved *string `protobuf:"bytes,2,opt,name=Reserved" json:"Reserved,omitempty"` Reserved *string `protobuf:"bytes,2,opt,name=Reserved" json:"Reserved,omitempty"`
Topic *string `protobuf:"bytes,3,opt,name=Topic" json:"Topic,omitempty"` Topic *string `protobuf:"bytes,3,opt,name=Topic" json:"Topic,omitempty"`
Source *string `protobuf:"bytes,4,opt,name=Source" json:"Source,omitempty"` Source *string `protobuf:"bytes,4,opt,name=Source" json:"Source,omitempty"`
XXX_unrecognized []byte `json:"-"` XXXUnrecognized []byte `json:"-"`
} }
// Reset LogGroup
func (m *LogGroup) Reset() { *m = LogGroup{} } func (m *LogGroup) Reset() { *m = LogGroup{} }
// String return the compact text
func (m *LogGroup) String() string { return proto.CompactTextString(m) } func (m *LogGroup) String() string { return proto.CompactTextString(m) }
// ProtoMessage not implemented
func (*LogGroup) ProtoMessage() {} func (*LogGroup) ProtoMessage() {}
// GetLogs return the loggroup logs
func (m *LogGroup) GetLogs() []*Log { func (m *LogGroup) GetLogs() []*Log {
if m != nil { if m != nil {
return m.Logs return m.Logs
@ -82,6 +111,7 @@ func (m *LogGroup) GetLogs() []*Log {
return nil return nil
} }
// GetReserved return Reserved
func (m *LogGroup) GetReserved() string { func (m *LogGroup) GetReserved() string {
if m != nil && m.Reserved != nil { if m != nil && m.Reserved != nil {
return *m.Reserved return *m.Reserved
@ -89,6 +119,7 @@ func (m *LogGroup) GetReserved() string {
return "" return ""
} }
// GetTopic return Topic
func (m *LogGroup) GetTopic() string { func (m *LogGroup) GetTopic() string {
if m != nil && m.Topic != nil { if m != nil && m.Topic != nil {
return *m.Topic return *m.Topic
@ -96,6 +127,7 @@ func (m *LogGroup) GetTopic() string {
return "" return ""
} }
// GetSource return Source
func (m *LogGroup) GetSource() string { func (m *LogGroup) GetSource() string {
if m != nil && m.Source != nil { if m != nil && m.Source != nil {
return *m.Source return *m.Source
@ -103,15 +135,22 @@ func (m *LogGroup) GetSource() string {
return "" return ""
} }
// LogGroupList define the LogGroups
type LogGroupList struct { type LogGroupList struct {
LogGroups []*LogGroup `protobuf:"bytes,1,rep,name=logGroups" json:"logGroups,omitempty"` LogGroups []*LogGroup `protobuf:"bytes,1,rep,name=logGroups" json:"logGroups,omitempty"`
XXX_unrecognized []byte `json:"-"` XXXUnrecognized []byte `json:"-"`
} }
// Reset LogGroupList
func (m *LogGroupList) Reset() { *m = LogGroupList{} } func (m *LogGroupList) Reset() { *m = LogGroupList{} }
// String return compact text
func (m *LogGroupList) String() string { return proto.CompactTextString(m) } func (m *LogGroupList) String() string { return proto.CompactTextString(m) }
// ProtoMessage not implemented
func (*LogGroupList) ProtoMessage() {} func (*LogGroupList) ProtoMessage() {}
// GetLogGroups return the LogGroups
func (m *LogGroupList) GetLogGroups() []*LogGroup { func (m *LogGroupList) GetLogGroups() []*LogGroup {
if m != nil { if m != nil {
return m.LogGroups return m.LogGroups
@ -119,6 +158,7 @@ func (m *LogGroupList) GetLogGroups() []*LogGroup {
return nil return nil
} }
// Marshal the logs to byte slice
func (m *Log) Marshal() (data []byte, err error) { func (m *Log) Marshal() (data []byte, err error) {
size := m.Size() size := m.Size()
data = make([]byte, size) data = make([]byte, size)
@ -129,6 +169,7 @@ func (m *Log) Marshal() (data []byte, err error) {
return data[:n], nil return data[:n], nil
} }
// MarshalTo data
func (m *Log) MarshalTo(data []byte) (int, error) { func (m *Log) MarshalTo(data []byte) (int, error) {
var i int var i int
_ = i _ = i
@ -136,11 +177,10 @@ func (m *Log) MarshalTo(data []byte) (int, error) {
_ = l _ = l
if m.Time == nil { if m.Time == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Time") return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Time")
} else { }
data[i] = 0x8 data[i] = 0x8
i++ i++
i = encodeVarintLog(data, i, uint64(*m.Time)) i = encodeVarintLog(data, i, uint64(*m.Time))
}
if len(m.Contents) > 0 { if len(m.Contents) > 0 {
for _, msg := range m.Contents { for _, msg := range m.Contents {
data[i] = 0x12 data[i] = 0x12
@ -153,13 +193,14 @@ func (m *Log) MarshalTo(data []byte) (int, error) {
i += n i += n
} }
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized) i += copy(data[i:], m.XXXUnrecognized)
} }
return i, nil return i, nil
} }
func (m *Log_Content) Marshal() (data []byte, err error) { // Marshal LogContent
func (m *LogContent) Marshal() (data []byte, err error) {
size := m.Size() size := m.Size()
data = make([]byte, size) data = make([]byte, size)
n, err := m.MarshalTo(data) n, err := m.MarshalTo(data)
@ -169,33 +210,34 @@ func (m *Log_Content) Marshal() (data []byte, err error) {
return data[:n], nil return data[:n], nil
} }
func (m *Log_Content) MarshalTo(data []byte) (int, error) { // MarshalTo logcontent to data
func (m *LogContent) MarshalTo(data []byte) (int, error) {
var i int var i int
_ = i _ = i
var l int var l int
_ = l _ = l
if m.Key == nil { if m.Key == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Key") return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Key")
} else { }
data[i] = 0xa data[i] = 0xa
i++ i++
i = encodeVarintLog(data, i, uint64(len(*m.Key))) i = encodeVarintLog(data, i, uint64(len(*m.Key)))
i += copy(data[i:], *m.Key) i += copy(data[i:], *m.Key)
}
if m.Value == nil { if m.Value == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Value") return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Value")
} else { }
data[i] = 0x12 data[i] = 0x12
i++ i++
i = encodeVarintLog(data, i, uint64(len(*m.Value))) i = encodeVarintLog(data, i, uint64(len(*m.Value)))
i += copy(data[i:], *m.Value) i += copy(data[i:], *m.Value)
} if m.XXXUnrecognized != nil {
if m.XXX_unrecognized != nil { i += copy(data[i:], m.XXXUnrecognized)
i += copy(data[i:], m.XXX_unrecognized)
} }
return i, nil return i, nil
} }
// Marshal LogGroup
func (m *LogGroup) Marshal() (data []byte, err error) { func (m *LogGroup) Marshal() (data []byte, err error) {
size := m.Size() size := m.Size()
data = make([]byte, size) data = make([]byte, size)
@ -206,6 +248,7 @@ func (m *LogGroup) Marshal() (data []byte, err error) {
return data[:n], nil return data[:n], nil
} }
// MarshalTo LogGroup to data
func (m *LogGroup) MarshalTo(data []byte) (int, error) { func (m *LogGroup) MarshalTo(data []byte) (int, error) {
var i int var i int
_ = i _ = i
@ -241,12 +284,13 @@ func (m *LogGroup) MarshalTo(data []byte) (int, error) {
i = encodeVarintLog(data, i, uint64(len(*m.Source))) i = encodeVarintLog(data, i, uint64(len(*m.Source)))
i += copy(data[i:], *m.Source) i += copy(data[i:], *m.Source)
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized) i += copy(data[i:], m.XXXUnrecognized)
} }
return i, nil return i, nil
} }
// Marshal LogGroupList
func (m *LogGroupList) Marshal() (data []byte, err error) { func (m *LogGroupList) Marshal() (data []byte, err error) {
size := m.Size() size := m.Size()
data = make([]byte, size) data = make([]byte, size)
@ -257,6 +301,7 @@ func (m *LogGroupList) Marshal() (data []byte, err error) {
return data[:n], nil return data[:n], nil
} }
// MarshalTo LogGroupList to data
func (m *LogGroupList) MarshalTo(data []byte) (int, error) { func (m *LogGroupList) MarshalTo(data []byte) (int, error) {
var i int var i int
_ = i _ = i
@ -274,8 +319,8 @@ func (m *LogGroupList) MarshalTo(data []byte) (int, error) {
i += n i += n
} }
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized) i += copy(data[i:], m.XXXUnrecognized)
} }
return i, nil return i, nil
} }
@ -307,6 +352,8 @@ func encodeVarintLog(data []byte, offset int, v uint64) int {
data[offset] = uint8(v) data[offset] = uint8(v)
return offset + 1 return offset + 1
} }
// Size return the log's size
func (m *Log) Size() (n int) { func (m *Log) Size() (n int) {
var l int var l int
_ = l _ = l
@ -319,13 +366,14 @@ func (m *Log) Size() (n int) {
n += 1 + l + sovLog(uint64(l)) n += 1 + l + sovLog(uint64(l))
} }
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
n += len(m.XXX_unrecognized) n += len(m.XXXUnrecognized)
} }
return n return n
} }
func (m *Log_Content) Size() (n int) { // Size return LogContent size based on Key and Value
func (m *LogContent) Size() (n int) {
var l int var l int
_ = l _ = l
if m.Key != nil { if m.Key != nil {
@ -336,12 +384,13 @@ func (m *Log_Content) Size() (n int) {
l = len(*m.Value) l = len(*m.Value)
n += 1 + l + sovLog(uint64(l)) n += 1 + l + sovLog(uint64(l))
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
n += len(m.XXX_unrecognized) n += len(m.XXXUnrecognized)
} }
return n return n
} }
// Size return LogGroup size based on Logs
func (m *LogGroup) Size() (n int) { func (m *LogGroup) Size() (n int) {
var l int var l int
_ = l _ = l
@ -363,12 +412,13 @@ func (m *LogGroup) Size() (n int) {
l = len(*m.Source) l = len(*m.Source)
n += 1 + l + sovLog(uint64(l)) n += 1 + l + sovLog(uint64(l))
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
n += len(m.XXX_unrecognized) n += len(m.XXXUnrecognized)
} }
return n return n
} }
// Size return LogGroupList size
func (m *LogGroupList) Size() (n int) { func (m *LogGroupList) Size() (n int) {
var l int var l int
_ = l _ = l
@ -378,8 +428,8 @@ func (m *LogGroupList) Size() (n int) {
n += 1 + l + sovLog(uint64(l)) n += 1 + l + sovLog(uint64(l))
} }
} }
if m.XXX_unrecognized != nil { if m.XXXUnrecognized != nil {
n += len(m.XXX_unrecognized) n += len(m.XXXUnrecognized)
} }
return n return n
} }
@ -395,8 +445,10 @@ func sovLog(x uint64) (n int) {
return n return n
} }
func sozLog(x uint64) (n int) { func sozLog(x uint64) (n int) {
return sovLog(uint64((x << 1) ^ uint64((int64(x) >> 63)))) return sovLog((x << 1) ^ (x >> 63))
} }
// Unmarshal data to log
func (m *Log) Unmarshal(data []byte) error { func (m *Log) Unmarshal(data []byte) error {
var hasFields [1]uint64 var hasFields [1]uint64
l := len(data) l := len(data)
@ -474,7 +526,7 @@ func (m *Log) Unmarshal(data []byte) error {
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
m.Contents = append(m.Contents, &Log_Content{}) m.Contents = append(m.Contents, &LogContent{})
if err := m.Contents[len(m.Contents)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { if err := m.Contents[len(m.Contents)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
return err return err
} }
@ -491,7 +543,7 @@ func (m *Log) Unmarshal(data []byte) error {
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...) m.XXXUnrecognized = append(m.XXXUnrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy iNdEx += skippy
} }
} }
@ -504,7 +556,9 @@ func (m *Log) Unmarshal(data []byte) error {
} }
return nil return nil
} }
func (m *Log_Content) Unmarshal(data []byte) error {
// Unmarshal data to LogContent
func (m *LogContent) Unmarshal(data []byte) error {
var hasFields [1]uint64 var hasFields [1]uint64
l := len(data) l := len(data)
iNdEx := 0 iNdEx := 0
@ -608,7 +662,7 @@ func (m *Log_Content) Unmarshal(data []byte) error {
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...) m.XXXUnrecognized = append(m.XXXUnrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy iNdEx += skippy
} }
} }
@ -624,6 +678,8 @@ func (m *Log_Content) Unmarshal(data []byte) error {
} }
return nil return nil
} }
// Unmarshal data to LogGroup
func (m *LogGroup) Unmarshal(data []byte) error { func (m *LogGroup) Unmarshal(data []byte) error {
l := len(data) l := len(data)
iNdEx := 0 iNdEx := 0
@ -786,7 +842,7 @@ func (m *LogGroup) Unmarshal(data []byte) error {
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...) m.XXXUnrecognized = append(m.XXXUnrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy iNdEx += skippy
} }
} }
@ -796,6 +852,8 @@ func (m *LogGroup) Unmarshal(data []byte) error {
} }
return nil return nil
} }
// Unmarshal data to LogGroupList
func (m *LogGroupList) Unmarshal(data []byte) error { func (m *LogGroupList) Unmarshal(data []byte) error {
l := len(data) l := len(data)
iNdEx := 0 iNdEx := 0
@ -868,7 +926,7 @@ func (m *LogGroupList) Unmarshal(data []byte) error {
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...) m.XXXUnrecognized = append(m.XXXUnrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy iNdEx += skippy
} }
} }
@ -878,6 +936,7 @@ func (m *LogGroupList) Unmarshal(data []byte) error {
} }
return nil return nil
} }
func skipLog(data []byte) (n int, err error) { func skipLog(data []byte) (n int, err error) {
l := len(data) l := len(data)
iNdEx := 0 iNdEx := 0
@ -940,7 +999,7 @@ func skipLog(data []byte) (n int, err error) {
case 3: case 3:
for { for {
var innerWire uint64 var innerWire uint64
var start int = iNdEx var start = iNdEx
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return 0, ErrIntOverflowLog return 0, ErrIntOverflowLog
@ -977,8 +1036,3 @@ func skipLog(data []byte) (n int, err error) {
} }
panic("unreachable") panic("unreachable")
} }
var (
ErrInvalidLengthLog = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowLog = fmt.Errorf("proto: integer overflow")
)

View File

@ -1,5 +1,6 @@
package alils package alils
// InputDetail define log detail
type InputDetail struct { type InputDetail struct {
LogType string `json:"logType"` LogType string `json:"logType"`
LogPath string `json:"logPath"` LogPath string `json:"logPath"`
@ -14,11 +15,13 @@ type InputDetail struct {
TopicFormat string `json:"topicFormat"` TopicFormat string `json:"topicFormat"`
} }
// OutputDetail define the output detail
type OutputDetail struct { type OutputDetail struct {
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
LogStoreName string `json:"logstoreName"` LogStoreName string `json:"logstoreName"`
} }
// LogConfig define Log Config
type LogConfig struct { type LogConfig struct {
Name string `json:"configName"` Name string `json:"configName"`
InputType string `json:"inputType"` InputType string `json:"inputType"`

View File

@ -1,5 +1,5 @@
/* /*
Package sls implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS). Package alils implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
For more description about SLS, please read this article: For more description about SLS, please read this article:
http://gitlab.alibaba-inc.com/sls/doc. http://gitlab.alibaba-inc.com/sls/doc.
@ -20,19 +20,20 @@ type errorMessage struct {
Message string `json:"errorMessage"` Message string `json:"errorMessage"`
} }
// LogProject Define the Ali Project detail
type LogProject struct { type LogProject struct {
Name string // Project name Name string // Project name
Endpoint string // IP or hostname of SLS endpoint Endpoint string // IP or hostname of SLS endpoint
AccessKeyId string AccessKeyID string
AccessKeySecret string AccessKeySecret string
} }
// NewLogProject creates a new SLS project. // NewLogProject creates a new SLS project.
func NewLogProject(name, endpoint, accessKeyId, accessKeySecret string) (p *LogProject, err error) { func NewLogProject(name, endpoint, AccessKeyID, accessKeySecret string) (p *LogProject, err error) {
p = &LogProject{ p = &LogProject{
Name: name, Name: name,
Endpoint: endpoint, Endpoint: endpoint,
AccessKeyId: accessKeyId, AccessKeyID: AccessKeyID,
AccessKeySecret: accessKeySecret, AccessKeySecret: accessKeySecret,
} }
return p, nil return p, nil

View File

@ -12,6 +12,7 @@ import (
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
) )
// LogStore Store the logs
type LogStore struct { type LogStore struct {
Name string `json:"logstoreName"` Name string `json:"logstoreName"`
TTL int TTL int
@ -23,6 +24,7 @@ type LogStore struct {
project *LogProject project *LogProject
} }
// Shard define the Log Shard
type Shard struct { type Shard struct {
ShardID int `json:"shardID"` ShardID int `json:"shardID"`
} }
@ -116,16 +118,16 @@ func (s *LogStore) PutLogs(lg *LogGroup) (err error) {
return return
} }
// GetCursor gets log cursor of one shard specified by shardId. // GetCursor gets log cursor of one shard specified by shardID.
// The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end". // The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end".
// For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore // For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore
func (s *LogStore) GetCursor(shardId int, from string) (cursor string, err error) { func (s *LogStore) GetCursor(shardID int, from string) (cursor string, err error) {
h := map[string]string{ h := map[string]string{
"x-sls-bodyrawsize": "0", "x-sls-bodyrawsize": "0",
} }
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v", uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v",
s.Name, shardId, from) s.Name, shardID, from)
r, err := request(s.project, "GET", uri, h, nil) r, err := request(s.project, "GET", uri, h, nil)
if err != nil { if err != nil {
@ -163,10 +165,10 @@ func (s *LogStore) GetCursor(shardId int, from string) (cursor string, err error
return return
} }
// GetLogsBytes gets logs binary data from shard specified by shardId according cursor. // GetLogsBytes gets logs binary data from shard specified by shardID according cursor.
// The logGroupMaxCount is the max number of logGroup could be returned. // The logGroupMaxCount is the max number of logGroup could be returned.
// The nextCursor is the next curosr can be used to read logs at next time. // The nextCursor is the next curosr can be used to read logs at next time.
func (s *LogStore) GetLogsBytes(shardId int, cursor string, func (s *LogStore) GetLogsBytes(shardID int, cursor string,
logGroupMaxCount int) (out []byte, nextCursor string, err error) { logGroupMaxCount int) (out []byte, nextCursor string, err error) {
h := map[string]string{ h := map[string]string{
@ -176,7 +178,7 @@ func (s *LogStore) GetLogsBytes(shardId int, cursor string,
} }
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v", uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v",
s.Name, shardId, cursor, logGroupMaxCount) s.Name, shardID, cursor, logGroupMaxCount)
r, err := request(s.project, "GET", uri, h, nil) r, err := request(s.project, "GET", uri, h, nil)
if err != nil { if err != nil {
@ -249,13 +251,13 @@ func LogsBytesDecode(data []byte) (gl *LogGroupList, err error) {
return return
} }
// GetLogs gets logs from shard specified by shardId according cursor. // GetLogs gets logs from shard specified by shardID according cursor.
// The logGroupMaxCount is the max number of logGroup could be returned. // The logGroupMaxCount is the max number of logGroup could be returned.
// The nextCursor is the next curosr can be used to read logs at next time. // The nextCursor is the next curosr can be used to read logs at next time.
func (s *LogStore) GetLogs(shardId int, cursor string, func (s *LogStore) GetLogs(shardID int, cursor string,
logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) { logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) {
out, nextCursor, err := s.GetLogsBytes(shardId, cursor, logGroupMaxCount) out, nextCursor, err := s.GetLogsBytes(shardID, cursor, logGroupMaxCount)
if err != nil { if err != nil {
return return
} }

View File

@ -8,18 +8,20 @@ import (
"net/http/httputil" "net/http/httputil"
) )
type MachinGroupAttribute struct { // MachineGroupAttribute define the Attribute
type MachineGroupAttribute struct {
ExternalName string `json:"externalName"` ExternalName string `json:"externalName"`
TopicName string `json:"groupTopic"` TopicName string `json:"groupTopic"`
} }
// MachineGroup define the machine Group
type MachineGroup struct { type MachineGroup struct {
Name string `json:"groupName"` Name string `json:"groupName"`
Type string `json:"groupType"` Type string `json:"groupType"`
MachineIdType string `json:"machineIdentifyType"` MachineIDType string `json:"machineIdentifyType"`
MachineIdList []string `json:"machineList"` MachineIDList []string `json:"machineList"`
Attribute MachinGroupAttribute `json:"groupAttribute"` Attribute MachineGroupAttribute `json:"groupAttribute"`
CreateTime uint32 CreateTime uint32
LastModifyTime uint32 LastModifyTime uint32
@ -27,12 +29,14 @@ type MachineGroup struct {
project *LogProject project *LogProject
} }
// Machine define the Machine
type Machine struct { type Machine struct {
IP string IP string
UniqueId string `json:"machine-uniqueid"` UniqueID string `json:"machine-uniqueid"`
UserdefinedId string `json:"userdefined-id"` UserdefinedID string `json:"userdefined-id"`
} }
// MachineList define the Machine List
type MachineList struct { type MachineList struct {
Total int Total int
Machines []*Machine Machines []*Machine

View File

@ -33,12 +33,12 @@ func request(project *LogProject, method, uri string, headers map[string]string,
} }
// Calc Authorization // Calc Authorization
// Authorization = "SLS <AccessKeyId>:<Signature>" // Authorization = "SLS <AccessKeyID>:<Signature>"
digest, err := signature(project, method, uri, headers) digest, err := signature(project, method, uri, headers)
if err != nil { if err != nil {
return return
} }
auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyId, digest) auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyID, digest)
headers["Authorization"] = auth headers["Authorization"] = auth
// Initialize http request // Initialize http request

View File

@ -76,7 +76,7 @@ func signature(project *LogProject, method, uri string,
var keys sort.StringSlice var keys sort.StringSlice
vals := u.Query() vals := u.Query()
for k, _ := range vals { for k := range vals {
keys = append(keys, k) keys = append(keys, k)
} }
@ -109,4 +109,3 @@ func signature(project *LogProject, method, uri string,
digest = base64.StdEncoding.EncodeToString(mac.Sum(nil)) digest = base64.StdEncoding.EncodeToString(mac.Sum(nil))
return return
} }

View File

@ -361,7 +361,7 @@ func isParameterChar(b byte) bool {
} }
func (cw *ansiColorWriter) Write(p []byte) (int, error) { func (cw *ansiColorWriter) Write(p []byte) (int, error) {
r, nw, first, last := 0, 0, 0, 0 var r, nw, first, last int
if cw.mode != DiscardNonColorEscSeq { if cw.mode != DiscardNonColorEscSeq {
cw.state = outsideCsiCode cw.state = outsideCsiCode
cw.resetBuffer() cw.resetBuffer()

View File

@ -41,7 +41,7 @@ var colors = []brush{
newBrush("1;33"), // Warning yellow newBrush("1;33"), // Warning yellow
newBrush("1;32"), // Notice green newBrush("1;32"), // Notice green
newBrush("1;34"), // Informational blue newBrush("1;34"), // Informational blue
newBrush("1;34"), // Debug blue newBrush("1;44"), // Debug Background blue
} }
// consoleWriter implements LoggerInterface and writes messages to terminal. // consoleWriter implements LoggerInterface and writes messages to terminal.

View File

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -40,6 +41,9 @@ type fileLogWriter struct {
MaxLines int `json:"maxlines"` MaxLines int `json:"maxlines"`
maxLinesCurLines int maxLinesCurLines int
MaxFiles int `json:"maxfiles"`
MaxFilesCurFiles int
// Rotate at size // Rotate at size
MaxSize int `json:"maxsize"` MaxSize int `json:"maxsize"`
maxSizeCurSize int maxSizeCurSize int
@ -50,12 +54,20 @@ type fileLogWriter struct {
dailyOpenDate int dailyOpenDate int
dailyOpenTime time.Time dailyOpenTime time.Time
// Rotate hourly
Hourly bool `json:"hourly"`
MaxHours int64 `json:"maxhours"`
hourlyOpenDate int
hourlyOpenTime time.Time
Rotate bool `json:"rotate"` Rotate bool `json:"rotate"`
Level int `json:"level"` Level int `json:"level"`
Perm string `json:"perm"` Perm string `json:"perm"`
RotatePerm string `json:"rotateperm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
} }
@ -64,9 +76,15 @@ func newFileWriter() Logger {
w := &fileLogWriter{ w := &fileLogWriter{
Daily: true, Daily: true,
MaxDays: 7, MaxDays: 7,
Hourly: false,
MaxHours: 168,
Rotate: true, Rotate: true,
RotatePerm: "0440",
Level: LevelTrace, Level: LevelTrace,
Perm: "0660", Perm: "0660",
MaxLines: 10000000,
MaxFiles: 999,
MaxSize: 1 << 28,
} }
return w return w
} }
@ -112,10 +130,16 @@ func (w *fileLogWriter) startLogger() error {
return w.initFd() return w.initFd()
} }
func (w *fileLogWriter) needRotate(size int, day int) bool { func (w *fileLogWriter) needRotateDaily(size int, day int) bool {
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
(w.Daily && day != w.dailyOpenDate) (w.Daily && day != w.dailyOpenDate)
}
func (w *fileLogWriter) needRotateHourly(size int, hour int) bool {
return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
(w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
(w.Hourly && hour != w.hourlyOpenDate)
} }
@ -124,14 +148,23 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level { if level > w.Level {
return nil return nil
} }
h, d := formatTimeHeader(when) hd, d, h := formatTimeHeader(when)
msg = string(h) + msg + "\n" msg = string(hd) + msg + "\n"
if w.Rotate { if w.Rotate {
w.RLock() w.RLock()
if w.needRotate(len(msg), d) { if w.needRotateHourly(len(msg), h) {
w.RUnlock() w.RUnlock()
w.Lock() w.Lock()
if w.needRotate(len(msg), d) { if w.needRotateHourly(len(msg), h) {
if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
} else if w.needRotateDaily(len(msg), d) {
w.RUnlock()
w.Lock()
if w.needRotateDaily(len(msg), d) {
if err := w.doRotate(when); err != nil { if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
} }
@ -158,6 +191,10 @@ func (w *fileLogWriter) createLogFile() (*os.File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
filepath := path.Dir(w.Filename)
os.MkdirAll(filepath, os.FileMode(perm))
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
if err == nil { if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
@ -170,16 +207,20 @@ func (w *fileLogWriter) initFd() error {
fd := w.fileWriter fd := w.fileWriter
fInfo, err := fd.Stat() fInfo, err := fd.Stat()
if err != nil { if err != nil {
return fmt.Errorf("get stat err: %s\n", err) return fmt.Errorf("get stat err: %s", err)
} }
w.maxSizeCurSize = int(fInfo.Size()) w.maxSizeCurSize = int(fInfo.Size())
w.dailyOpenTime = time.Now() w.dailyOpenTime = time.Now()
w.dailyOpenDate = w.dailyOpenTime.Day() w.dailyOpenDate = w.dailyOpenTime.Day()
w.hourlyOpenTime = time.Now()
w.hourlyOpenDate = w.hourlyOpenTime.Hour()
w.maxLinesCurLines = 0 w.maxLinesCurLines = 0
if w.Daily { if w.Hourly {
go w.hourlyRotate(w.hourlyOpenTime)
} else if w.Daily {
go w.dailyRotate(w.dailyOpenTime) go w.dailyRotate(w.dailyOpenTime)
} }
if fInfo.Size() > 0 { if fInfo.Size() > 0 && w.MaxLines > 0 {
count, err := w.lines() count, err := w.lines()
if err != nil { if err != nil {
return err return err
@ -193,16 +234,29 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) {
y, m, d := openTime.Add(24 * time.Hour).Date() y, m, d := openTime.Add(24 * time.Hour).Date()
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location()) nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100)) tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
select { <-tm.C
case <-tm.C:
w.Lock() w.Lock()
if w.needRotate(0, time.Now().Day()) { if w.needRotateDaily(0, time.Now().Day()) {
if err := w.doRotate(time.Now()); err != nil { if err := w.doRotate(time.Now()); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
} }
} }
w.Unlock() w.Unlock()
} }
func (w *fileLogWriter) hourlyRotate(openTime time.Time) {
y, m, d := openTime.Add(1 * time.Hour).Date()
h, _, _ := openTime.Add(1 * time.Hour).Clock()
nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100))
<-tm.C
w.Lock()
if w.needRotateHourly(0, time.Now().Hour()) {
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) { func (w *fileLogWriter) lines() (int, error) {
@ -237,31 +291,44 @@ func (w *fileLogWriter) lines() (int, error) {
func (w *fileLogWriter) doRotate(logTime time.Time) error { func (w *fileLogWriter) doRotate(logTime time.Time) error {
// file exists // file exists
// Find the next available number // Find the next available number
num := 1 num := w.MaxFilesCurFiles + 1
fName := "" fName := ""
format := ""
var openTime time.Time
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
if err != nil {
return err
}
_, err := os.Lstat(w.Filename) _, err = os.Lstat(w.Filename)
if err != nil { if err != nil {
//even if the file is not exist or other ,we should RESTART the logger //even if the file is not exist or other ,we should RESTART the logger
goto RESTART_LOGGER goto RESTART_LOGGER
} }
if w.Hourly {
format = "2006010215"
openTime = w.hourlyOpenTime
} else if w.Daily {
format = "2006-01-02"
openTime = w.dailyOpenTime
}
// only when one of them be setted, then the file would be splited
if w.MaxLines > 0 || w.MaxSize > 0 { if w.MaxLines > 0 || w.MaxSize > 0 {
for ; err == nil && num <= 999; num++ { for ; err == nil && num <= w.MaxFiles; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix) fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix)
_, err = os.Lstat(fName) _, err = os.Lstat(fName)
} }
} else { } else {
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix) fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, 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) _, err = os.Lstat(fName)
w.MaxFilesCurFiles = num
} }
}
// return error if the last file checked still existed // return error if the last file checked still existed
if err == nil { if err == nil {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename) return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename)
} }
// close fileWriter before rename // close fileWriter before rename
@ -270,21 +337,24 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
// Rename the file to its new found name // Rename the file to its new found name
// even if occurs error,we MUST guarantee to restart new logger // even if occurs error,we MUST guarantee to restart new logger
err = os.Rename(w.Filename, fName) err = os.Rename(w.Filename, fName)
err = os.Chmod(fName, os.FileMode(440)) if err != nil {
// re-start logger goto RESTART_LOGGER
}
err = os.Chmod(fName, os.FileMode(rotatePerm))
RESTART_LOGGER: RESTART_LOGGER:
startLoggerErr := w.startLogger() startLoggerErr := w.startLogger()
go w.deleteOldLog() go w.deleteOldLog()
if startLoggerErr != nil { if startLoggerErr != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr) return fmt.Errorf("Rotate StartLogger: %s", startLoggerErr)
} }
if err != nil { if err != nil {
return fmt.Errorf("Rotate: %s\n", err) return fmt.Errorf("Rotate: %s", err)
} }
return nil return nil
} }
func (w *fileLogWriter) deleteOldLog() { func (w *fileLogWriter) deleteOldLog() {
@ -299,12 +369,20 @@ func (w *fileLogWriter) deleteOldLog() {
if info == nil { if info == nil {
return return
} }
if w.Hourly {
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
} else if w.Daily {
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) { 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)) && if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) { strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path) os.Remove(path)
} }
}
} }
return return
}) })

View File

@ -112,7 +112,7 @@ func TestFile2(t *testing.T) {
os.Remove("test2.log") os.Remove("test2.log")
} }
func TestFileRotate_01(t *testing.T) { func TestFileDailyRotate_01(t *testing.T) {
log := NewLogger(10000) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`) log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
log.Debug("debug") log.Debug("debug")
@ -133,28 +133,28 @@ func TestFileRotate_01(t *testing.T) {
os.Remove("test3.log") os.Remove("test3.log")
} }
func TestFileRotate_02(t *testing.T) { func TestFileDailyRotate_02(t *testing.T) {
fn1 := "rotate_day.log" fn1 := "rotate_day.log"
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log" fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileRotate(t, fn1, fn2) testFileRotate(t, fn1, fn2, true, false)
} }
func TestFileRotate_03(t *testing.T) { func TestFileDailyRotate_03(t *testing.T) {
fn1 := "rotate_day.log" fn1 := "rotate_day.log"
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log" fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
os.Create(fn) os.Create(fn)
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log" fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileRotate(t, fn1, fn2) testFileRotate(t, fn1, fn2, true, false)
os.Remove(fn) os.Remove(fn)
} }
func TestFileRotate_04(t *testing.T) { func TestFileDailyRotate_04(t *testing.T) {
fn1 := "rotate_day.log" fn1 := "rotate_day.log"
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log" fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileDailyRotate(t, fn1, fn2) testFileDailyRotate(t, fn1, fn2)
} }
func TestFileRotate_05(t *testing.T) { func TestFileDailyRotate_05(t *testing.T) {
fn1 := "rotate_day.log" fn1 := "rotate_day.log"
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log" fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
os.Create(fn) os.Create(fn)
@ -162,23 +162,131 @@ func TestFileRotate_05(t *testing.T) {
testFileDailyRotate(t, fn1, fn2) testFileDailyRotate(t, fn1, fn2)
os.Remove(fn) os.Remove(fn)
} }
func TestFileDailyRotate_06(t *testing.T) { //test file mode
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
log.Debug("debug")
log.Info("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
s, _ := os.Lstat(rotateName)
if s.Mode() != 0440 {
os.Remove(rotateName)
os.Remove("test3.log")
t.Fatal("rotate file mode error")
}
os.Remove(rotateName)
os.Remove("test3.log")
}
func testFileRotate(t *testing.T, fn1, fn2 string) { func TestFileHourlyRotate_01(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
log.Debug("debug")
log.Info("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
b, err := exists(rotateName)
if !b || err != nil {
os.Remove("test3.log")
t.Fatal("rotate not generated")
}
os.Remove(rotateName)
os.Remove("test3.log")
}
func TestFileHourlyRotate_02(t *testing.T) {
fn1 := "rotate_hour.log"
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
testFileRotate(t, fn1, fn2, false, true)
}
func TestFileHourlyRotate_03(t *testing.T) {
fn1 := "rotate_hour.log"
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
os.Create(fn)
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
testFileRotate(t, fn1, fn2, false, true)
os.Remove(fn)
}
func TestFileHourlyRotate_04(t *testing.T) {
fn1 := "rotate_hour.log"
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
testFileHourlyRotate(t, fn1, fn2)
}
func TestFileHourlyRotate_05(t *testing.T) {
fn1 := "rotate_hour.log"
fn := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".log"
os.Create(fn)
fn2 := "rotate_hour." + time.Now().Add(-1*time.Hour).Format("2006010215") + ".001.log"
testFileHourlyRotate(t, fn1, fn2)
os.Remove(fn)
}
func TestFileHourlyRotate_06(t *testing.T) { //test file mode
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
log.Debug("debug")
log.Info("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006010215"), 1) + ".log"
s, _ := os.Lstat(rotateName)
if s.Mode() != 0440 {
os.Remove(rotateName)
os.Remove("test3.log")
t.Fatal("rotate file mode error")
}
os.Remove(rotateName)
os.Remove("test3.log")
}
func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
fw := &fileLogWriter{ fw := &fileLogWriter{
Daily: true, Daily: daily,
MaxDays: 7, MaxDays: 7,
Hourly: hourly,
MaxHours: 168,
Rotate: true, Rotate: true,
Level: LevelTrace, Level: LevelTrace,
Perm: "0660", Perm: "0660",
RotatePerm: "0440",
} }
if daily {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day() fw.dailyOpenDate = fw.dailyOpenTime.Day()
}
if hourly {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
}
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug) fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
for _, file := range []string{fn1, fn2} { for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file) _, err := os.Stat(file)
if err != nil { if err != nil {
t.Log(err)
t.FailNow() t.FailNow()
} }
os.Remove(file) os.Remove(file)
@ -193,6 +301,7 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
Rotate: true, Rotate: true,
Level: LevelTrace, Level: LevelTrace,
Perm: "0660", Perm: "0660",
RotatePerm: "0440",
} }
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1)) fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour) fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
@ -217,6 +326,37 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
fw.Destroy() fw.Destroy()
} }
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{
Hourly: true,
MaxHours: 168,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
RotatePerm: "0440",
}
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
fw.hourlyOpenDate = fw.hourlyOpenTime.Hour()
hour, _ := time.ParseInLocation("2006010215", time.Now().Format("2006010215"), fw.hourlyOpenTime.Location())
hour = hour.Add(-1 * time.Second)
fw.hourlyRotate(hour)
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) { func exists(path string) (bool, error) {
_, err := os.Stat(path) _, err := os.Stat(path)
if err == nil { if err == nil {

View File

@ -25,11 +25,7 @@ func newJLWriter() Logger {
// Init JLWriter with json config string // Init JLWriter with json config string
func (s *JLWriter) Init(jsonconfig string) error { func (s *JLWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s) return json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
return err
}
return nil
} }
// WriteMsg write message in smtp writer. // WriteMsg write message in smtp writer.
@ -65,12 +61,10 @@ func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
// Flush implementing method. empty. // Flush implementing method. empty.
func (s *JLWriter) Flush() { func (s *JLWriter) Flush() {
return
} }
// Destroy implementing method. empty. // Destroy implementing method. empty.
func (s *JLWriter) Destroy() { func (s *JLWriter) Destroy() {
return
} }
func init() { func init() {

View File

@ -116,6 +116,7 @@ type BeeLogger struct {
enableFuncCallDepth bool enableFuncCallDepth bool
loggerFuncCallDepth int loggerFuncCallDepth int
asynchronous bool asynchronous bool
prefix string
msgChanLen int64 msgChanLen int64
msgChan chan *logMsg msgChan chan *logMsg
signalChan chan string signalChan chan string
@ -267,6 +268,9 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
if len(v) > 0 { if len(v) > 0 {
msg = fmt.Sprintf(msg, v...) msg = fmt.Sprintf(msg, v...)
} }
msg = bl.prefix + " " + msg
when := time.Now() when := time.Now()
if bl.enableFuncCallDepth { if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
@ -275,7 +279,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
line = 0 line = 0
} }
_, filename := path.Split(file) _, filename := path.Split(file)
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg msg = "[" + filename + ":" + strconv.Itoa(line) + "] " + msg
} }
//set level info in front of filename info //set level info in front of filename info
@ -305,6 +309,11 @@ func (bl *BeeLogger) SetLevel(l int) {
bl.level = l bl.level = l
} }
// GetLevel Get Current log message level.
func (bl *BeeLogger) GetLevel() int {
return bl.level
}
// SetLogFuncCallDepth set log funcCallDepth // SetLogFuncCallDepth set log funcCallDepth
func (bl *BeeLogger) SetLogFuncCallDepth(d int) { func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
bl.loggerFuncCallDepth = d bl.loggerFuncCallDepth = d
@ -320,6 +329,11 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
bl.enableFuncCallDepth = b bl.enableFuncCallDepth = b
} }
// set prefix
func (bl *BeeLogger) SetPrefix(s string) {
bl.prefix = s
}
// start logger chan reading. // start logger chan reading.
// when chan is not empty, write logs. // when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() { func (bl *BeeLogger) startLogger() {
@ -492,9 +506,9 @@ func (bl *BeeLogger) flush() {
} }
// beeLogger references the used application logger. // beeLogger references the used application logger.
var beeLogger *BeeLogger = NewLogger() var beeLogger = NewLogger()
// GetLogger returns the default BeeLogger // GetBeeLogger returns the default BeeLogger
func GetBeeLogger() *BeeLogger { func GetBeeLogger() *BeeLogger {
return beeLogger return beeLogger
} }
@ -534,6 +548,7 @@ func Reset() {
beeLogger.Reset() beeLogger.Reset()
} }
// Async set the beelogger with Async mode and hold msglen messages
func Async(msgLen ...int64) *BeeLogger { func Async(msgLen ...int64) *BeeLogger {
return beeLogger.Async(msgLen...) return beeLogger.Async(msgLen...)
} }
@ -543,6 +558,11 @@ func SetLevel(l int) {
beeLogger.SetLevel(l) beeLogger.SetLevel(l)
} }
// SetPrefix sets the prefix
func SetPrefix(s string) {
beeLogger.SetPrefix(s)
}
// EnableFuncCallDepth enable log funcCallDepth // EnableFuncCallDepth enable log funcCallDepth
func EnableFuncCallDepth(b bool) { func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b beeLogger.enableFuncCallDepth = b
@ -561,11 +581,7 @@ func SetLogFuncCallDepth(d int) {
// SetLogger sets a new logger. // SetLogger sets a new logger.
func SetLogger(adapter string, config ...string) error { func SetLogger(adapter string, config ...string) error {
err := beeLogger.SetLogger(adapter, config...) return beeLogger.SetLogger(adapter, config...)
if err != nil {
return err
}
return nil
} }
// Emergency logs a message at emergency level. // Emergency logs a message at emergency level.

View File

@ -33,7 +33,7 @@ func newLogWriter(wr io.Writer) *logWriter {
func (lg *logWriter) println(when time.Time, msg string) { func (lg *logWriter) println(when time.Time, msg string) {
lg.Lock() lg.Lock()
h, _ := formatTimeHeader(when) h, _, _:= formatTimeHeader(when)
lg.writer.Write(append(append(h, msg...), '\n')) lg.writer.Write(append(append(h, msg...), '\n'))
lg.Unlock() lg.Unlock()
} }
@ -87,13 +87,15 @@ const (
mi2 = `012345678901234567890123456789012345678901234567890123456789` mi2 = `012345678901234567890123456789012345678901234567890123456789`
s1 = `000000000011111111112222222222333333333344444444445555555555` s1 = `000000000011111111112222222222333333333344444444445555555555`
s2 = `012345678901234567890123456789012345678901234567890123456789` s2 = `012345678901234567890123456789012345678901234567890123456789`
ns1 = `0123456789`
) )
func formatTimeHeader(when time.Time) ([]byte, int) { func formatTimeHeader(when time.Time) ([]byte, int, int) {
y, mo, d := when.Date() y, mo, d := when.Date()
h, mi, s := when.Clock() h, mi, s := when.Clock()
//len("2006/01/02 15:04:05 ")==20 ns := when.Nanosecond() / 1000000
var buf [20]byte //len("2006/01/02 15:04:05.123 ")==24
var buf [24]byte
buf[0] = y1[y/1000%10] buf[0] = y1[y/1000%10]
buf[1] = y2[y/100] buf[1] = y2[y/100]
@ -114,9 +116,14 @@ func formatTimeHeader(when time.Time) ([]byte, int) {
buf[16] = ':' buf[16] = ':'
buf[17] = s1[s] buf[17] = s1[s]
buf[18] = s2[s] buf[18] = s2[s]
buf[19] = ' ' buf[19] = '.'
buf[20] = ns1[ns/100]
buf[21] = ns1[ns%100/10]
buf[22] = ns1[ns%10]
return buf[0:], d buf[23] = ' '
return buf[0:], d, h
} }
var ( var (
@ -139,6 +146,11 @@ var (
reset = string([]byte{27, 91, 48, 109}) reset = string([]byte{27, 91, 48, 109})
) )
// ColorByStatus return color by http code
// 2xx return Green
// 3xx return White
// 4xx return Yellow
// 5xx return Red
func ColorByStatus(cond bool, code int) string { func ColorByStatus(cond bool, code int) string {
switch { switch {
case code >= 200 && code < 300: case code >= 200 && code < 300:
@ -152,6 +164,14 @@ func ColorByStatus(cond bool, code int) string {
} }
} }
// ColorByMethod return color by http code
// GET return Blue
// POST return Cyan
// PUT return Yellow
// DELETE return Red
// PATCH return Green
// HEAD return Magenta
// OPTIONS return WHITE
func ColorByMethod(cond bool, method string) string { func ColorByMethod(cond bool, method string) string {
switch method { switch method {
case "GET": case "GET":
@ -173,10 +193,10 @@ func ColorByMethod(cond bool, method string) string {
} }
} }
// Guard Mutex to guarantee atomicity of W32Debug(string) function // Guard Mutex to guarantee atomic of W32Debug(string) function
var mu sync.Mutex var mu sync.Mutex
// Helper method to output colored logs in Windows terminals // W32Debug Helper method to output colored logs in Windows terminals
func W32Debug(msg string) { func W32Debug(msg string) {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()

View File

@ -30,8 +30,8 @@ func TestFormatHeader_0(t *testing.T) {
if tm.Year() >= 2100 { if tm.Year() >= 2100 {
break break
} }
h, _ := formatTimeHeader(tm) h, _, _ := formatTimeHeader(tm)
if tm.Format("2006/01/02 15:04:05 ") != string(h) { if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
t.Log(tm) t.Log(tm)
t.FailNow() t.FailNow()
} }
@ -48,8 +48,8 @@ func TestFormatHeader_1(t *testing.T) {
if tm.Year() >= year+1 { if tm.Year() >= year+1 {
break break
} }
h, _ := formatTimeHeader(tm) h, _, _ := formatTimeHeader(tm)
if tm.Format("2006/01/02 15:04:05 ") != string(h) { if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
t.Log(tm) t.Log(tm)
t.FailNow() t.FailNow()
} }

View File

@ -67,7 +67,10 @@ func (f *multiFileLogWriter) Init(config string) error {
jsonMap["level"] = i jsonMap["level"] = i
bs, _ := json.Marshal(jsonMap) bs, _ := json.Marshal(jsonMap)
writer = newFileWriter().(*fileLogWriter) writer = newFileWriter().(*fileLogWriter)
writer.Init(string(bs)) err := writer.Init(string(bs))
if err != nil {
return err
}
f.writers[i] = writer f.writers[i] = writer
} }
} }

View File

@ -21,11 +21,7 @@ func newSLACKWriter() Logger {
// Init SLACKWriter with json config string // Init SLACKWriter with json config string
func (s *SLACKWriter) Init(jsonconfig string) error { func (s *SLACKWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s) return json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
return err
}
return nil
} }
// WriteMsg write message in smtp writer. // WriteMsg write message in smtp writer.
@ -53,12 +49,10 @@ func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error {
// Flush implementing method. empty. // Flush implementing method. empty.
func (s *SLACKWriter) Flush() { func (s *SLACKWriter) Flush() {
return
} }
// Destroy implementing method. empty. // Destroy implementing method. empty.
func (s *SLACKWriter) Destroy() { func (s *SLACKWriter) Destroy() {
return
} }
func init() { func init() {

View File

@ -52,11 +52,7 @@ func newSMTPWriter() Logger {
// "level":LevelError // "level":LevelError
// } // }
func (s *SMTPWriter) Init(jsonconfig string) error { func (s *SMTPWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s) return json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
return err
}
return nil
} }
func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth { func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
@ -106,7 +102,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
if err != nil { if err != nil {
return err return err
} }
_, err = w.Write([]byte(msgContent)) _, err = w.Write(msgContent)
if err != nil { if err != nil {
return err return err
} }
@ -116,12 +112,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
return err return err
} }
err = client.Quit() return client.Quit()
if err != nil {
return err
}
return nil
} }
// WriteMsg write message in smtp writer. // WriteMsg write message in smtp writer.
@ -147,12 +138,10 @@ func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
// Flush implementing method. empty. // Flush implementing method. empty.
func (s *SMTPWriter) Flush() { func (s *SMTPWriter) Flush() {
return
} }
// Destroy implementing method. empty. // Destroy implementing method. empty.
func (s *SMTPWriter) Destroy() { func (s *SMTPWriter) Destroy() {
return
} }
func init() { func init() {

View File

@ -14,40 +14,382 @@
package migration package migration
// Table store the tablename and Column import (
type Table struct { "fmt"
TableName string
"github.com/astaxie/beego"
)
// Index struct defines the structure of Index Columns
type Index struct {
Name string
}
// Unique struct defines a single unique key combination
type Unique struct {
Definition string
Columns []*Column Columns []*Column
} }
// Create return the create sql //Column struct defines a single column of a table
func (t *Table) Create() string {
return ""
}
// Drop return the drop sql
func (t *Table) Drop() string {
return ""
}
// Column define the columns name type and Default
type Column struct { type Column struct {
Name string Name string
Type string Inc string
Default interface{} Null string
Default string
Unsign string
DataType string
remove bool
Modify bool
} }
// Create return create sql with the provided tbname and columns // Foreign struct defines a single foreign relationship
func Create(tbname string, columns ...Column) string { type Foreign struct {
return "" ForeignTable string
ForeignColumn string
OnDelete string
OnUpdate string
Column
} }
// Drop return the drop sql with the provided tbname and columns // RenameColumn struct allows renaming of columns
func Drop(tbname string, columns ...Column) string { type RenameColumn struct {
return "" OldName string
OldNull string
OldDefault string
OldUnsign string
OldDataType string
NewName string
Column
} }
// TableDDL is still in think // CreateTable creates the table on system
func TableDDL(tbname string, columns ...Column) string { func (m *Migration) CreateTable(tablename, engine, charset string, p ...func()) {
return "" m.TableName = tablename
m.Engine = engine
m.Charset = charset
m.ModifyType = "create"
}
// AlterTable set the ModifyType to alter
func (m *Migration) AlterTable(tablename string) {
m.TableName = tablename
m.ModifyType = "alter"
}
// NewCol creates a new standard column and attaches it to m struct
func (m *Migration) NewCol(name string) *Column {
col := &Column{Name: name}
m.AddColumns(col)
return col
}
//PriCol creates a new primary column and attaches it to m struct
func (m *Migration) PriCol(name string) *Column {
col := &Column{Name: name}
m.AddColumns(col)
m.AddPrimary(col)
return col
}
//UniCol creates / appends columns to specified unique key and attaches it to m struct
func (m *Migration) UniCol(uni, name string) *Column {
col := &Column{Name: name}
m.AddColumns(col)
uniqueOriginal := &Unique{}
for _, unique := range m.Uniques {
if unique.Definition == uni {
unique.AddColumnsToUnique(col)
uniqueOriginal = unique
}
}
if uniqueOriginal.Definition == "" {
unique := &Unique{Definition: uni}
unique.AddColumnsToUnique(col)
m.AddUnique(unique)
}
return col
}
//ForeignCol creates a new foreign column and returns the instance of column
func (m *Migration) ForeignCol(colname, foreigncol, foreigntable string) (foreign *Foreign) {
foreign = &Foreign{ForeignColumn: foreigncol, ForeignTable: foreigntable}
foreign.Name = colname
m.AddForeign(foreign)
return foreign
}
//SetOnDelete sets the on delete of foreign
func (foreign *Foreign) SetOnDelete(del string) *Foreign {
foreign.OnDelete = "ON DELETE" + del
return foreign
}
//SetOnUpdate sets the on update of foreign
func (foreign *Foreign) SetOnUpdate(update string) *Foreign {
foreign.OnUpdate = "ON UPDATE" + update
return foreign
}
//Remove marks the columns to be removed.
//it allows reverse m to create the column.
func (c *Column) Remove() {
c.remove = true
}
//SetAuto enables auto_increment of column (can be used once)
func (c *Column) SetAuto(inc bool) *Column {
if inc {
c.Inc = "auto_increment"
}
return c
}
//SetNullable sets the column to be null
func (c *Column) SetNullable(null bool) *Column {
if null {
c.Null = ""
} else {
c.Null = "NOT NULL"
}
return c
}
//SetDefault sets the default value, prepend with "DEFAULT "
func (c *Column) SetDefault(def string) *Column {
c.Default = "DEFAULT " + def
return c
}
//SetUnsigned sets the column to be unsigned int
func (c *Column) SetUnsigned(unsign bool) *Column {
if unsign {
c.Unsign = "UNSIGNED"
}
return c
}
//SetDataType sets the dataType of the column
func (c *Column) SetDataType(dataType string) *Column {
c.DataType = dataType
return c
}
//SetOldNullable allows reverting to previous nullable on reverse ms
func (c *RenameColumn) SetOldNullable(null bool) *RenameColumn {
if null {
c.OldNull = ""
} else {
c.OldNull = "NOT NULL"
}
return c
}
//SetOldDefault allows reverting to previous default on reverse ms
func (c *RenameColumn) SetOldDefault(def string) *RenameColumn {
c.OldDefault = def
return c
}
//SetOldUnsigned allows reverting to previous unsgined on reverse ms
func (c *RenameColumn) SetOldUnsigned(unsign bool) *RenameColumn {
if unsign {
c.OldUnsign = "UNSIGNED"
}
return c
}
//SetOldDataType allows reverting to previous datatype on reverse ms
func (c *RenameColumn) SetOldDataType(dataType string) *RenameColumn {
c.OldDataType = dataType
return c
}
//SetPrimary adds the columns to the primary key (can only be used any number of times in only one m)
func (c *Column) SetPrimary(m *Migration) *Column {
m.Primary = append(m.Primary, c)
return c
}
//AddColumnsToUnique adds the columns to Unique Struct
func (unique *Unique) AddColumnsToUnique(columns ...*Column) *Unique {
unique.Columns = append(unique.Columns, columns...)
return unique
}
//AddColumns adds columns to m struct
func (m *Migration) AddColumns(columns ...*Column) *Migration {
m.Columns = append(m.Columns, columns...)
return m
}
//AddPrimary adds the column to primary in m struct
func (m *Migration) AddPrimary(primary *Column) *Migration {
m.Primary = append(m.Primary, primary)
return m
}
//AddUnique adds the column to unique in m struct
func (m *Migration) AddUnique(unique *Unique) *Migration {
m.Uniques = append(m.Uniques, unique)
return m
}
//AddForeign adds the column to foreign in m struct
func (m *Migration) AddForeign(foreign *Foreign) *Migration {
m.Foreigns = append(m.Foreigns, foreign)
return m
}
//AddIndex adds the column to index in m struct
func (m *Migration) AddIndex(index *Index) *Migration {
m.Indexes = append(m.Indexes, index)
return m
}
//RenameColumn allows renaming of columns
func (m *Migration) RenameColumn(from, to string) *RenameColumn {
rename := &RenameColumn{OldName: from, NewName: to}
m.Renames = append(m.Renames, rename)
return rename
}
//GetSQL returns the generated sql depending on ModifyType
func (m *Migration) GetSQL() (sql string) {
sql = ""
switch m.ModifyType {
case "create":
{
sql += fmt.Sprintf("CREATE TABLE `%s` (", m.TableName)
for index, column := range m.Columns {
sql += fmt.Sprintf("\n `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
if len(m.Columns) > index+1 {
sql += ","
}
}
if len(m.Primary) > 0 {
sql += fmt.Sprintf(",\n PRIMARY KEY( ")
}
for index, column := range m.Primary {
sql += fmt.Sprintf(" `%s`", column.Name)
if len(m.Primary) > index+1 {
sql += ","
}
}
if len(m.Primary) > 0 {
sql += fmt.Sprintf(")")
}
for _, unique := range m.Uniques {
sql += fmt.Sprintf(",\n UNIQUE KEY `%s`( ", unique.Definition)
for index, column := range unique.Columns {
sql += fmt.Sprintf(" `%s`", column.Name)
if len(unique.Columns) > index+1 {
sql += ","
}
}
sql += fmt.Sprintf(")")
}
for _, foreign := range m.Foreigns {
sql += fmt.Sprintf(",\n `%s` %s %s %s %s %s", foreign.Name, foreign.DataType, foreign.Unsign, foreign.Null, foreign.Inc, foreign.Default)
sql += fmt.Sprintf(",\n KEY `%s_%s_foreign`(`%s`),", m.TableName, foreign.Column.Name, foreign.Column.Name)
sql += fmt.Sprintf("\n CONSTRAINT `%s_%s_foreign` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`) %s %s", m.TableName, foreign.Column.Name, foreign.Column.Name, foreign.ForeignTable, foreign.ForeignColumn, foreign.OnDelete, foreign.OnUpdate)
}
sql += fmt.Sprintf(")ENGINE=%s DEFAULT CHARSET=%s;", m.Engine, m.Charset)
break
}
case "alter":
{
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
for index, column := range m.Columns {
if !column.remove {
beego.BeeLogger.Info("col")
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
} else {
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
}
if len(m.Columns) > index+1 {
sql += ","
}
}
for index, column := range m.Renames {
sql += fmt.Sprintf("CHANGE COLUMN `%s` `%s` %s %s %s %s %s", column.OldName, column.NewName, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
if len(m.Renames) > index+1 {
sql += ","
}
}
for index, foreign := range m.Foreigns {
sql += fmt.Sprintf("ADD `%s` %s %s %s %s %s", foreign.Name, foreign.DataType, foreign.Unsign, foreign.Null, foreign.Inc, foreign.Default)
sql += fmt.Sprintf(",\n ADD KEY `%s_%s_foreign`(`%s`)", m.TableName, foreign.Column.Name, foreign.Column.Name)
sql += fmt.Sprintf(",\n ADD CONSTRAINT `%s_%s_foreign` FOREIGN KEY (`%s`) REFERENCES `%s` (`%s`) %s %s", m.TableName, foreign.Column.Name, foreign.Column.Name, foreign.ForeignTable, foreign.ForeignColumn, foreign.OnDelete, foreign.OnUpdate)
if len(m.Foreigns) > index+1 {
sql += ","
}
}
sql += ";"
break
}
case "reverse":
{
sql += fmt.Sprintf("ALTER TABLE `%s`", m.TableName)
for index, column := range m.Columns {
if column.remove {
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
} else {
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
}
if len(m.Columns) > index+1 {
sql += ","
}
}
if len(m.Primary) > 0 {
sql += fmt.Sprintf("\n DROP PRIMARY KEY,")
}
for index, unique := range m.Uniques {
sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition)
if len(m.Uniques) > index+1 {
sql += ","
}
}
for index, column := range m.Renames {
sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault)
if len(m.Renames) > index+1 {
sql += ","
}
}
for _, foreign := range m.Foreigns {
sql += fmt.Sprintf("\n DROP KEY `%s_%s_foreign`", m.TableName, foreign.Column.Name)
sql += fmt.Sprintf(",\n DROP FOREIGN KEY `%s_%s_foreign`", m.TableName, foreign.Column.Name)
sql += fmt.Sprintf(",\n DROP COLUMN `%s`", foreign.Name)
}
sql += ";"
}
case "delete":
{
sql += fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", m.TableName)
}
}
return
} }

32
migration/doc.go Normal file
View File

@ -0,0 +1,32 @@
// Package migration enables you to generate migrations back and forth. It generates both migrations.
//
// //Creates a table
// m.CreateTable("tablename","InnoDB","utf8");
//
// //Alter a table
// m.AlterTable("tablename")
//
// Standard Column Methods
// * SetDataType
// * SetNullable
// * SetDefault
// * SetUnsigned (use only on integer types unless produces error)
//
// //Sets a primary column, multiple calls allowed, standard column methods available
// m.PriCol("id").SetAuto(true).SetNullable(false).SetDataType("INT(10)").SetUnsigned(true)
//
// //UniCol Can be used multiple times, allows standard Column methods. Use same "index" string to add to same index
// m.UniCol("index","column")
//
// //Standard Column Initialisation, can call .Remove() after NewCol("") on alter to remove
// m.NewCol("name").SetDataType("VARCHAR(255) COLLATE utf8_unicode_ci").SetNullable(false)
// m.NewCol("value").SetDataType("DOUBLE(8,2)").SetNullable(false)
//
// //Rename Columns , only use with Alter table, doesn't works with Create, prefix standard column methods with "Old" to
// //create a true reversible migration eg: SetOldDataType("DOUBLE(12,3)")
// m.RenameColumn("from","to")...
//
// //Foreign Columns, single columns are only supported, SetOnDelete & SetOnUpdate are available, call appropriately.
// //Supports standard column methods, automatic reverse.
// m.ForeignCol("local_col","foreign_col","foreign_table")
package migration

View File

@ -52,6 +52,26 @@ type Migrationer interface {
GetCreated() int64 GetCreated() int64
} }
//Migration defines the migrations by either SQL or DDL
type Migration struct {
sqls []string
Created string
TableName string
Engine string
Charset string
ModifyType string
Columns []*Column
Indexes []*Index
Primary []*Column
Uniques []*Unique
Foreigns []*Foreign
Renames []*RenameColumn
RemoveColumns []*Column
RemoveIndexes []*Index
RemoveUniques []*Unique
RemoveForeigns []*Foreign
}
var ( var (
migrationMap map[string]Migrationer migrationMap map[string]Migrationer
) )
@ -60,20 +80,34 @@ func init() {
migrationMap = make(map[string]Migrationer) migrationMap = make(map[string]Migrationer)
} }
// Migration the basic type which will implement the basic type
type Migration struct {
sqls []string
Created string
}
// Up implement in the Inheritance struct for upgrade // Up implement in the Inheritance struct for upgrade
func (m *Migration) Up() { func (m *Migration) Up() {
switch m.ModifyType {
case "reverse":
m.ModifyType = "alter"
case "delete":
m.ModifyType = "create"
}
m.sqls = append(m.sqls, m.GetSQL())
} }
// Down implement in the Inheritance struct for down // Down implement in the Inheritance struct for down
func (m *Migration) Down() { func (m *Migration) Down() {
switch m.ModifyType {
case "alter":
m.ModifyType = "reverse"
case "create":
m.ModifyType = "delete"
}
m.sqls = append(m.sqls, m.GetSQL())
}
//Migrate adds the SQL to the execution list
func (m *Migration) Migrate(migrationType string) {
m.ModifyType = migrationType
m.sqls = append(m.sqls, m.GetSQL())
} }
// SQL add sql want to execute // SQL add sql want to execute
@ -138,7 +172,7 @@ func Register(name string, m Migrationer) error {
return nil return nil
} }
// Upgrade upgrate the migration from lasttime // Upgrade upgrade the migration from lasttime
func Upgrade(lasttime int64) error { func Upgrade(lasttime int64) error {
sm := sortMap(migrationMap) sm := sortMap(migrationMap)
i := 0 i := 0

View File

@ -267,13 +267,12 @@ func addPrefix(t *Tree, prefix string) {
addPrefix(t.wildcard, prefix) addPrefix(t.wildcard, prefix)
} }
for _, l := range t.leaves { for _, l := range t.leaves {
if c, ok := l.runObject.(*controllerInfo); ok { if c, ok := l.runObject.(*ControllerInfo); ok {
if !strings.HasPrefix(c.pattern, prefix) { if !strings.HasPrefix(c.pattern, prefix) {
c.pattern = prefix + c.pattern c.pattern = prefix + c.pattern
} }
} }
} }
} }
// NSCond is Namespace Condition // NSCond is Namespace Condition
@ -284,16 +283,16 @@ func NSCond(cond namespaceCond) LinkNamespace {
} }
// NSBefore Namespace BeforeRouter filter // NSBefore Namespace BeforeRouter filter
func NSBefore(filiterList ...FilterFunc) LinkNamespace { func NSBefore(filterList ...FilterFunc) LinkNamespace {
return func(ns *Namespace) { return func(ns *Namespace) {
ns.Filter("before", filiterList...) ns.Filter("before", filterList...)
} }
} }
// NSAfter add Namespace FinishRouter filter // NSAfter add Namespace FinishRouter filter
func NSAfter(filiterList ...FilterFunc) LinkNamespace { func NSAfter(filterList ...FilterFunc) LinkNamespace {
return func(ns *Namespace) { return func(ns *Namespace) {
ns.Filter("after", filiterList...) ns.Filter("after", filterList...)
} }
} }

View File

@ -139,10 +139,7 @@ func TestNamespaceCond(t *testing.T) {
ns := NewNamespace("/v2") ns := NewNamespace("/v2")
ns.Cond(func(ctx *context.Context) bool { ns.Cond(func(ctx *context.Context) bool {
if ctx.Input.Domain() == "beego.me" { return ctx.Input.Domain() == "beego.me"
return true
}
return false
}). }).
AutoRouter(&TestController{}) AutoRouter(&TestController{})
AddNamespace(ns) AddNamespace(ns)

View File

@ -61,6 +61,9 @@ func init() {
// set default database // set default database
orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30)
// create table
orm.RunSyncdb("default", false, true)
} }
func main() { func main() {

View File

@ -150,7 +150,7 @@ func (d *commandSyncDb) Run() error {
} }
for _, fi := range mi.fields.fieldsDB { for _, fi := range mi.fields.fieldsDB {
if _, ok := columns[fi.column]; ok == false { if _, ok := columns[fi.column]; !ok {
fields = append(fields, fi) fields = append(fields, fi)
} }
} }
@ -175,7 +175,7 @@ func (d *commandSyncDb) Run() error {
} }
for _, idx := range indexes[mi.table] { for _, idx := range indexes[mi.table] {
if d.al.DbBaser.IndexExists(db, idx.Table, idx.Name) == false { if !d.al.DbBaser.IndexExists(db, idx.Table, idx.Name) {
if !d.noInfo { if !d.noInfo {
fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table) fmt.Printf("create index `%s` for table `%s`\n", idx.Name, idx.Table)
} }

View File

@ -51,12 +51,14 @@ checkColumn:
switch fieldType { switch fieldType {
case TypeBooleanField: case TypeBooleanField:
col = T["bool"] col = T["bool"]
case TypeCharField: case TypeVarCharField:
if al.Driver == DRPostgres && fi.toText { if al.Driver == DRPostgres && fi.toText {
col = T["string-text"] col = T["string-text"]
} else { } else {
col = fmt.Sprintf(T["string"], fieldSize) col = fmt.Sprintf(T["string"], fieldSize)
} }
case TypeCharField:
col = fmt.Sprintf(T["string-char"], fieldSize)
case TypeTextField: case TypeTextField:
col = T["string-text"] col = T["string-text"]
case TypeTimeField: case TypeTimeField:
@ -89,20 +91,20 @@ checkColumn:
col = T["float64"] col = T["float64"]
case TypeDecimalField: case TypeDecimalField:
s := T["float64-decimal"] s := T["float64-decimal"]
if strings.Index(s, "%d") == -1 { if !strings.Contains(s, "%d") {
col = s col = s
} else { } else {
col = fmt.Sprintf(s, fi.digits, fi.decimals) col = fmt.Sprintf(s, fi.digits, fi.decimals)
} }
case TypeJSONField: case TypeJSONField:
if al.Driver != DRPostgres { if al.Driver != DRPostgres {
fieldType = TypeCharField fieldType = TypeVarCharField
goto checkColumn goto checkColumn
} }
col = T["json"] col = T["json"]
case TypeJsonbField: case TypeJsonbField:
if al.Driver != DRPostgres { if al.Driver != DRPostgres {
fieldType = TypeCharField fieldType = TypeVarCharField
goto checkColumn goto checkColumn
} }
col = T["jsonb"] col = T["jsonb"]
@ -120,7 +122,7 @@ func getColumnAddQuery(al *alias, fi *fieldInfo) string {
Q := al.DbBaser.TableQuote() Q := al.DbBaser.TableQuote()
typ := getColumnTyp(al, fi) typ := getColumnTyp(al, fi)
if fi.null == false { if !fi.null {
typ += " " + "NOT NULL" typ += " " + "NOT NULL"
} }
@ -172,7 +174,7 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
} else { } else {
column += col column += col
if fi.null == false { if !fi.null {
column += " " + "NOT NULL" column += " " + "NOT NULL"
} }
@ -192,10 +194,14 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
} }
} }
if strings.Index(column, "%COL%") != -1 { if strings.Contains(column, "%COL%") {
column = strings.Replace(column, "%COL%", fi.column, -1) column = strings.Replace(column, "%COL%", fi.column, -1)
} }
if fi.description != "" {
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
}
columns = append(columns, column) columns = append(columns, column)
} }

View File

@ -87,7 +87,7 @@ func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string,
} else { } else {
panic(fmt.Errorf("wrong db field/column name `%s` for model `%s`", column, mi.fullName)) panic(fmt.Errorf("wrong db field/column name `%s` for model `%s`", column, mi.fullName))
} }
if fi.dbcol == false || fi.auto && skipAuto { if !fi.dbcol || fi.auto && skipAuto {
continue continue
} }
value, err := d.collectFieldValue(mi, fi, ind, insert, tz) value, err := d.collectFieldValue(mi, fi, ind, insert, tz)
@ -142,7 +142,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
} else { } else {
value = field.Bool() value = field.Bool()
} }
case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField: case TypeVarCharField, TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
if ns, ok := field.Interface().(sql.NullString); ok { if ns, ok := field.Interface().(sql.NullString); ok {
value = nil value = nil
if ns.Valid { if ns.Valid {
@ -224,7 +224,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
value = nil value = nil
} }
} }
if fi.null == false && value == nil { if !fi.null && value == nil {
return nil, fmt.Errorf("field `%s` cannot be NULL", fi.fullName) return nil, fmt.Errorf("field `%s` cannot be NULL", fi.fullName)
} }
} }
@ -271,7 +271,7 @@ func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string,
dbcols := make([]string, 0, len(mi.fields.dbcols)) dbcols := make([]string, 0, len(mi.fields.dbcols))
marks := make([]string, 0, len(mi.fields.dbcols)) marks := make([]string, 0, len(mi.fields.dbcols))
for _, fi := range mi.fields.fieldsDB { for _, fi := range mi.fields.fieldsDB {
if fi.auto == false { if !fi.auto {
dbcols = append(dbcols, fi.column) dbcols = append(dbcols, fi.column)
marks = append(marks, "?") marks = append(marks, "?")
} }
@ -326,7 +326,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
} else { } else {
// default use pk value as where condtion. // default use pk value as where condtion.
pkColumn, pkValue, ok := getExistPk(mi, ind) pkColumn, pkValue, ok := getExistPk(mi, ind)
if ok == false { if !ok {
return ErrMissPK return ErrMissPK
} }
whereCols = []string{pkColumn} whereCols = []string{pkColumn}
@ -507,10 +507,9 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
case DRPostgres: case DRPostgres:
if len(args) == 0 { if len(args) == 0 {
return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName) return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName)
} else { }
args0 = strings.ToLower(args[0]) args0 = strings.ToLower(args[0])
iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0) iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0)
}
default: default:
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName) return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
} }
@ -537,6 +536,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
updates := make([]string, len(names)) updates := make([]string, len(names))
var conflitValue interface{} var conflitValue interface{}
for i, v := range names { for i, v := range names {
// identifier in database may not be case-sensitive, so quote it
v = fmt.Sprintf("%s%s%s", Q, v, Q)
marks[i] = "?" marks[i] = "?"
valueStr := argsMap[strings.ToLower(v)] valueStr := argsMap[strings.ToLower(v)]
if v == args0 { if v == args0 {
@ -592,7 +593,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
row := q.QueryRow(query, values...) row := q.QueryRow(query, values...)
var id int64 var id int64
err = row.Scan(&id) err = row.Scan(&id)
if err.Error() == `pq: syntax error at or near "ON"` { if err != nil && err.Error() == `pq: syntax error at or near "ON"` {
err = fmt.Errorf("postgres version must 9.5 or higher") err = fmt.Errorf("postgres version must 9.5 or higher")
} }
return id, err return id, err
@ -601,7 +602,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
// execute update sql dbQuerier with given struct reflect.Value. // 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) { func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
pkName, pkValue, ok := getExistPk(mi, ind) pkName, pkValue, ok := getExistPk(mi, ind)
if ok == false { if !ok {
return 0, ErrMissPK return 0, ErrMissPK
} }
@ -654,7 +655,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
} else { } else {
// default use pk value as where condtion. // default use pk value as where condtion.
pkColumn, pkValue, ok := getExistPk(mi, ind) pkColumn, pkValue, ok := getExistPk(mi, ind)
if ok == false { if !ok {
return 0, ErrMissPK return 0, ErrMissPK
} }
whereCols = []string{pkColumn} whereCols = []string{pkColumn}
@ -699,7 +700,7 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
columns := make([]string, 0, len(params)) columns := make([]string, 0, len(params))
values := make([]interface{}, 0, len(params)) values := make([]interface{}, 0, len(params))
for col, val := range params { for col, val := range params {
if fi, ok := mi.fields.GetByAny(col); ok == false || fi.dbcol == false { if fi, ok := mi.fields.GetByAny(col); !ok || !fi.dbcol {
panic(fmt.Errorf("wrong field/column name `%s`", col)) panic(fmt.Errorf("wrong field/column name `%s`", col))
} else { } else {
columns = append(columns, fi.column) columns = append(columns, fi.column)
@ -834,7 +835,11 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
if err := rs.Scan(&ref); err != nil { if err := rs.Scan(&ref); err != nil {
return 0, err return 0, err
} }
args = append(args, reflect.ValueOf(ref).Interface()) pkValue, err := d.convertValueFromDB(mi.fields.pk, reflect.ValueOf(ref).Interface(), tz)
if err != nil {
return 0, err
}
args = append(args, pkValue)
cnt++ cnt++
} }
@ -923,13 +928,13 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
maps[fi.column] = true maps[fi.column] = true
} }
} else { } else {
panic(fmt.Errorf("wrong field/column name `%s`", col)) return 0, fmt.Errorf("wrong field/column name `%s`", col)
} }
} }
if hasRel { if hasRel {
for _, fi := range mi.fields.fieldsDB { for _, fi := range mi.fields.fieldsDB {
if fi.fieldType&IsRelField > 0 { if fi.fieldType&IsRelField > 0 {
if maps[fi.column] == false { if !maps[fi.column] {
tCols = append(tCols, fi.column) tCols = append(tCols, fi.column)
} }
} }
@ -966,6 +971,10 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
} }
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) 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)
if qs.forupdate {
query += " FOR UPDATE"
}
d.ins.ReplaceMarks(&query) d.ins.ReplaceMarks(&query)
var rs *sql.Rows var rs *sql.Rows
@ -987,7 +996,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
var cnt int64 var cnt int64
for rs.Next() { for rs.Next() {
if one && cnt == 0 || one == false { if one && cnt == 0 || !one {
if err := rs.Scan(refs...); err != nil { if err := rs.Scan(refs...); err != nil {
return 0, err return 0, err
} }
@ -1067,7 +1076,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
cnt++ cnt++
} }
if one == false { if !one {
if cnt > 0 { if cnt > 0 {
ind.Set(slice) ind.Set(slice)
} else { } else {
@ -1110,7 +1119,7 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
// generate sql with replacing operator string placeholders and replaced values. // generate sql with replacing operator string placeholders and replaced values.
func (d *dbBase) GenerateOperatorSQL(mi *modelInfo, fi *fieldInfo, operator string, args []interface{}, tz *time.Location) (string, []interface{}) { func (d *dbBase) GenerateOperatorSQL(mi *modelInfo, fi *fieldInfo, operator string, args []interface{}, tz *time.Location) (string, []interface{}) {
sql := "" var sql string
params := getFlatParams(fi, args, tz) params := getFlatParams(fi, args, tz)
if len(params) == 0 { if len(params) == 0 {
@ -1237,7 +1246,7 @@ setValue:
} }
value = b value = b
} }
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField: case fieldType == TypeVarCharField || fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
if str == nil { if str == nil {
value = ToStr(val) value = ToStr(val)
} else { } else {
@ -1357,7 +1366,7 @@ end:
func (d *dbBase) setFieldValue(fi *fieldInfo, value interface{}, field reflect.Value) (interface{}, error) { func (d *dbBase) setFieldValue(fi *fieldInfo, value interface{}, field reflect.Value) (interface{}, error) {
fieldType := fi.fieldType fieldType := fi.fieldType
isNative := fi.isFielder == false isNative := !fi.isFielder
setValue: setValue:
switch { switch {
@ -1383,7 +1392,7 @@ setValue:
field.SetBool(value.(bool)) field.SetBool(value.(bool))
} }
} }
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField: case fieldType == TypeVarCharField || fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
if isNative { if isNative {
if ns, ok := field.Interface().(sql.NullString); ok { if ns, ok := field.Interface().(sql.NullString); ok {
if value == nil { if value == nil {
@ -1533,7 +1542,7 @@ setValue:
} }
} }
if isNative == false { if !isNative {
fd := field.Addr().Interface().(Fielder) fd := field.Addr().Interface().(Fielder)
err := fd.SetRaw(value) err := fd.SetRaw(value)
if err != nil { if err != nil {
@ -1594,7 +1603,7 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
infos = make([]*fieldInfo, 0, len(exprs)) infos = make([]*fieldInfo, 0, len(exprs))
for _, ex := range exprs { for _, ex := range exprs {
index, name, fi, suc := tables.parseExprs(mi, strings.Split(ex, ExprSep)) index, name, fi, suc := tables.parseExprs(mi, strings.Split(ex, ExprSep))
if suc == false { if !suc {
panic(fmt.Errorf("unknown field/column name `%s`", ex)) panic(fmt.Errorf("unknown field/column name `%s`", ex))
} }
cols = append(cols, fmt.Sprintf("%s.%s%s%s %s%s%s", index, Q, fi.column, Q, Q, name, Q)) cols = append(cols, fmt.Sprintf("%s.%s%s%s %s%s%s", index, Q, fi.column, Q, Q, name, Q))
@ -1733,7 +1742,7 @@ func (d *dbBase) TableQuote() string {
return "`" return "`"
} }
// replace value placeholer in parametered sql string. // replace value placeholder in parametered sql string.
func (d *dbBase) ReplaceMarks(query *string) { func (d *dbBase) ReplaceMarks(query *string) {
// default use `?` as mark, do nothing // default use `?` as mark, do nothing
} }

View File

@ -60,6 +60,8 @@ var (
"sqlite3": DRSqlite, "sqlite3": DRSqlite,
"tidb": DRTiDB, "tidb": DRTiDB,
"oracle": DROracle, "oracle": DROracle,
"oci8": DROracle, // github.com/mattn/go-oci8
"ora": DROracle, //https://github.com/rana/ora
} }
dbBasers = map[DriverType]dbBaser{ dbBasers = map[DriverType]dbBaser{
DRMySQL: newdbBaseMysql(), DRMySQL: newdbBaseMysql(),
@ -117,7 +119,7 @@ type alias struct {
func detectTZ(al *alias) { func detectTZ(al *alias) {
// orm timezone system match database // orm timezone system match database
// default use Local // default use Local
al.TZ = time.Local al.TZ = DefaultTimeLoc
if al.DriverName == "sphinx" { if al.DriverName == "sphinx" {
return return
@ -134,7 +136,9 @@ func detectTZ(al *alias) {
} }
t, err := time.Parse("-07:00:00", tz) t, err := time.Parse("-07:00:00", tz)
if err == nil { if err == nil {
if t.Location().String() != "" {
al.TZ = t.Location() al.TZ = t.Location()
}
} else { } else {
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error()) DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
} }
@ -186,7 +190,7 @@ func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error()) return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
} }
if dataBaseCache.add(aliasName, al) == false { if !dataBaseCache.add(aliasName, al) {
return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName) return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
} }
@ -244,11 +248,11 @@ end:
// RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type. // RegisterDriver Register a database driver use specify driver name, this can be definition the driver is which database type.
func RegisterDriver(driverName string, typ DriverType) error { func RegisterDriver(driverName string, typ DriverType) error {
if t, ok := drivers[driverName]; ok == false { if t, ok := drivers[driverName]; !ok {
drivers[driverName] = typ drivers[driverName] = typ
} else { } else {
if t != typ { if t != typ {
return fmt.Errorf("driverName `%s` db driver already registered and is other type\n", driverName) return fmt.Errorf("driverName `%s` db driver already registered and is other type", driverName)
} }
} }
return nil return nil
@ -259,7 +263,7 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error {
if al, ok := dataBaseCache.get(aliasName); ok { if al, ok := dataBaseCache.get(aliasName); ok {
al.TZ = tz al.TZ = tz
} else { } else {
return fmt.Errorf("DataBase alias name `%s` not registered\n", aliasName) return fmt.Errorf("DataBase alias name `%s` not registered", aliasName)
} }
return nil return nil
} }
@ -294,5 +298,5 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
if ok { if ok {
return al.DB, nil return al.DB, nil
} }
return nil, fmt.Errorf("DataBase of alias name `%s` not found\n", name) return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
} }

View File

@ -46,6 +46,7 @@ var mysqlTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY", "pk": "NOT NULL PRIMARY KEY",
"bool": "bool", "bool": "bool",
"string": "varchar(%d)", "string": "varchar(%d)",
"string-char": "char(%d)",
"string-text": "longtext", "string-text": "longtext",
"time.Time-date": "date", "time.Time-date": "date",
"time.Time": "datetime", "time.Time": "datetime",
@ -103,8 +104,7 @@ func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool
// If no will insert // If no will insert
// Add "`" for mysql sql building // Add "`" for mysql sql building
func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) { func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
var iouStr string
iouStr := ""
argsMap := map[string]string{} argsMap := map[string]string{}
iouStr = "ON DUPLICATE KEY UPDATE" iouStr = "ON DUPLICATE KEY UPDATE"

View File

@ -34,6 +34,7 @@ var oracleTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY", "pk": "NOT NULL PRIMARY KEY",
"bool": "bool", "bool": "bool",
"string": "VARCHAR2(%d)", "string": "VARCHAR2(%d)",
"string-char": "CHAR(%d)",
"string-text": "VARCHAR2(%d)", "string-text": "VARCHAR2(%d)",
"time.Time-date": "DATE", "time.Time-date": "DATE",
"time.Time": "TIMESTAMP", "time.Time": "TIMESTAMP",
@ -94,3 +95,43 @@ func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool
row.Scan(&cnt) row.Scan(&cnt)
return cnt > 0 return cnt > 0
} }
// execute insert sql with given struct and given values.
// insert the given values, not the field values in struct.
func (d *dbBaseOracle) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
Q := d.ins.TableQuote()
marks := make([]string, len(names))
for i := range marks {
marks[i] = ":" + names[i]
}
sep := fmt.Sprintf("%s, %s", Q, Q)
qmarks := strings.Join(marks, ", ")
columns := strings.Join(names, sep)
multi := len(values) / len(names)
if isMulti {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s)", Q, mi.table, Q, Q, columns, Q, qmarks)
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)
return id, err
}

View File

@ -43,6 +43,7 @@ var postgresTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY", "pk": "NOT NULL PRIMARY KEY",
"bool": "bool", "bool": "bool",
"string": "varchar(%d)", "string": "varchar(%d)",
"string-char": "char(%d)",
"string-text": "text", "string-text": "text",
"time.Time-date": "date", "time.Time-date": "date",
"time.Time": "timestamp with time zone", "time.Time": "timestamp with time zone",

View File

@ -43,6 +43,7 @@ var sqliteTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY", "pk": "NOT NULL PRIMARY KEY",
"bool": "bool", "bool": "bool",
"string": "varchar(%d)", "string": "varchar(%d)",
"string-char": "character(%d)",
"string-text": "text", "string-text": "text",
"time.Time-date": "date", "time.Time-date": "date",
"time.Time": "datetime", "time.Time": "datetime",
@ -134,7 +135,7 @@ func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var tmp, index sql.NullString var tmp, index sql.NullString
rows.Scan(&tmp, &index, &tmp) rows.Scan(&tmp, &index, &tmp, &tmp, &tmp)
if name == index.String { if name == index.String {
return true return true
} }

View File

@ -63,7 +63,7 @@ func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool)
// add table info to collection. // add table info to collection.
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) { func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
name := strings.Join(names, ExprSep) name := strings.Join(names, ExprSep)
if _, ok := t.tablesM[name]; ok == false { if _, ok := t.tablesM[name]; !ok {
i := len(t.tables) + 1 i := len(t.tables) + 1
jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil} jt := &dbTable{i, fmt.Sprintf("T%d", i), name, names, false, inner, mi, fi, nil}
t.tablesM[name] = jt t.tablesM[name] = jt
@ -261,7 +261,7 @@ loopFor:
fiN, okN = mmi.fields.GetByAny(exprs[i+1]) fiN, okN = mmi.fields.GetByAny(exprs[i+1])
} }
if isRel && (fi.mi.isThrough == false || num != i) { if isRel && (!fi.mi.isThrough || num != i) {
if fi.null || t.skipEnd { if fi.null || t.skipEnd {
inner = false inner = false
} }
@ -364,7 +364,7 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe
} }
index, _, fi, suc := t.parseExprs(mi, exprs) index, _, fi, suc := t.parseExprs(mi, exprs)
if suc == false { if !suc {
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep))) panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
} }
@ -372,7 +372,13 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe
operator = "exact" operator = "exact"
} }
operSQL, args := t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz) var operSQL string
var args []interface{}
if p.isRaw {
operSQL = p.sql
} else {
operSQL, args = t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz)
}
leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q) leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q)
t.base.GenerateOperatorLeftCol(fi, operator, &leftCol) t.base.GenerateOperatorLeftCol(fi, operator, &leftCol)
@ -383,7 +389,7 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe
} }
} }
if sub == false && where != "" { if !sub && where != "" {
where = "WHERE " + where where = "WHERE " + where
} }
@ -403,7 +409,7 @@ func (t *dbTables) getGroupSQL(groups []string) (groupSQL string) {
exprs := strings.Split(group, ExprSep) exprs := strings.Split(group, ExprSep)
index, _, fi, suc := t.parseExprs(t.mi, exprs) index, _, fi, suc := t.parseExprs(t.mi, exprs)
if suc == false { if !suc {
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep))) panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
} }
@ -432,7 +438,7 @@ func (t *dbTables) getOrderSQL(orders []string) (orderSQL string) {
exprs := strings.Split(order, ExprSep) exprs := strings.Split(order, ExprSep)
index, _, fi, suc := t.parseExprs(t.mi, exprs) index, _, fi, suc := t.parseExprs(t.mi, exprs)
if suc == false { if !suc {
panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep))) panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
} }

View File

@ -52,7 +52,7 @@ func (mc *_modelCache) all() map[string]*modelInfo {
return m return m
} }
// get orderd model info // get ordered model info
func (mc *_modelCache) allOrdered() []*modelInfo { func (mc *_modelCache) allOrdered() []*modelInfo {
m := make([]*modelInfo, 0, len(mc.orders)) m := make([]*modelInfo, 0, len(mc.orders))
for _, table := range mc.orders { for _, table := range mc.orders {

View File

@ -75,7 +75,7 @@ func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
} }
if mi.fields.pk == nil { if mi.fields.pk == nil {
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field, default use 'id' if not set\n", name) fmt.Printf("<orm.RegisterModel> `%s` needs a primary key field, default is to use 'id' if not set\n", name)
os.Exit(2) os.Exit(2)
} }
@ -89,7 +89,7 @@ func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
modelCache.set(table, mi) modelCache.set(table, mi)
} }
// boostrap models // bootstrap models
func bootStrap() { func bootStrap() {
if modelCache.done { if modelCache.done {
return return
@ -128,7 +128,7 @@ func bootStrap() {
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
pn := fi.relThrough[:i] pn := fi.relThrough[:i]
rmi, ok := modelCache.getByFullName(fi.relThrough) rmi, ok := modelCache.getByFullName(fi.relThrough)
if ok == false || pn != rmi.pkg { if !ok || pn != rmi.pkg {
err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough) err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough)
goto end goto end
} }
@ -171,7 +171,7 @@ func bootStrap() {
break break
} }
} }
if inModel == false { if !inModel {
rmi := fi.relModelInfo rmi := fi.relModelInfo
ffi := new(fieldInfo) ffi := new(fieldInfo)
ffi.name = mi.name ffi.name = mi.name
@ -185,7 +185,7 @@ func bootStrap() {
} else { } else {
ffi.fieldType = RelReverseMany ffi.fieldType = RelReverseMany
} }
if rmi.fields.Add(ffi) == false { if !rmi.fields.Add(ffi) {
added := false added := false
for cnt := 0; cnt < 5; cnt++ { for cnt := 0; cnt < 5; cnt++ {
ffi.name = fmt.Sprintf("%s%d", mi.name, cnt) ffi.name = fmt.Sprintf("%s%d", mi.name, cnt)
@ -195,7 +195,7 @@ func bootStrap() {
break break
} }
} }
if added == false { if !added {
panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName)) panic(fmt.Errorf("cannot generate auto reverse field info `%s` to `%s`", fi.fullName, ffi.fullName))
} }
} }
@ -248,7 +248,7 @@ func bootStrap() {
break mForA break mForA
} }
} }
if found == false { if !found {
err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) err = fmt.Errorf("reverse field `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
goto end goto end
} }
@ -267,7 +267,7 @@ func bootStrap() {
break mForB break mForB
} }
} }
if found == false { if !found {
mForC: mForC:
for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] { for _, ffi := range fi.relModelInfo.fields.fieldsByType[RelManyToMany] {
conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough || conditions := fi.relThrough != "" && fi.relThrough == ffi.relThrough ||
@ -287,7 +287,7 @@ func bootStrap() {
} }
} }
} }
if found == false { if !found {
err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName) err = fmt.Errorf("reverse field for `%s` not found in model `%s`", fi.fullName, fi.relModelInfo.fullName)
goto end goto end
} }
@ -332,7 +332,7 @@ func RegisterModelWithSuffix(suffix string, models ...interface{}) {
} }
} }
// BootStrap bootrap models. // BootStrap bootstrap models.
// make all model parsed and can not add more models // make all model parsed and can not add more models
func BootStrap() { func BootStrap() {
if modelCache.done { if modelCache.done {

View File

@ -23,6 +23,7 @@ import (
// Define the Type enum // Define the Type enum
const ( const (
TypeBooleanField = 1 << iota TypeBooleanField = 1 << iota
TypeVarCharField
TypeCharField TypeCharField
TypeTextField TypeTextField
TypeTimeField TypeTimeField
@ -49,9 +50,9 @@ const (
// Define some logic enum // Define some logic enum
const ( const (
IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6 IsIntegerField = ^-TypePositiveBigIntegerField >> 6 << 7
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10 IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 10 << 11
IsRelField = ^-RelReverseMany >> 17 << 18 IsRelField = ^-RelReverseMany >> 18 << 19
IsFieldType = ^-RelReverseMany<<1 + 1 IsFieldType = ^-RelReverseMany<<1 + 1
) )
@ -85,7 +86,7 @@ func (e *BooleanField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Bool() v, err := StrTo(d).Bool()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err return err
@ -126,7 +127,7 @@ func (e *CharField) String() string {
// FieldType return the enum type // FieldType return the enum type
func (e *CharField) FieldType() int { func (e *CharField) FieldType() int {
return TypeCharField return TypeVarCharField
} }
// SetRaw set the interface to string // SetRaw set the interface to string
@ -190,7 +191,7 @@ func (e *TimeField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := timeParse(d, formatTime) v, err := timeParse(d, formatTime)
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err return err
@ -232,7 +233,7 @@ func (e *DateField) Set(d time.Time) {
*e = DateField(d) *e = DateField(d)
} }
// String convert datatime to string // String convert datetime to string
func (e *DateField) String() string { func (e *DateField) String() string {
return e.Value().String() return e.Value().String()
} }
@ -249,7 +250,7 @@ func (e *DateField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := timeParse(d, formatDate) v, err := timeParse(d, formatDate)
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err return err
@ -272,12 +273,12 @@ var _ Fielder = new(DateField)
// Takes the same extra arguments as DateField. // Takes the same extra arguments as DateField.
type DateTimeField time.Time type DateTimeField time.Time
// Value return the datatime value // Value return the datetime value
func (e DateTimeField) Value() time.Time { func (e DateTimeField) Value() time.Time {
return time.Time(e) return time.Time(e)
} }
// Set set the time.Time to datatime // Set set the time.Time to datetime
func (e *DateTimeField) Set(d time.Time) { func (e *DateTimeField) Set(d time.Time) {
*e = DateTimeField(d) *e = DateTimeField(d)
} }
@ -299,7 +300,7 @@ func (e *DateTimeField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := timeParse(d, formatDateTime) v, err := timeParse(d, formatDateTime)
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err return err
@ -309,12 +310,12 @@ func (e *DateTimeField) SetRaw(value interface{}) error {
return nil return nil
} }
// RawValue return the datatime value // RawValue return the datetime value
func (e *DateTimeField) RawValue() interface{} { func (e *DateTimeField) RawValue() interface{} {
return e.Value() return e.Value()
} }
// verify datatime implement fielder // verify datetime implement fielder
var _ Fielder = new(DateTimeField) var _ Fielder = new(DateTimeField)
// FloatField A floating-point number represented in go by a float32 value. // FloatField A floating-point number represented in go by a float32 value.
@ -349,9 +350,10 @@ func (e *FloatField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Float64() v, err := StrTo(d).Float64()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<FloatField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<FloatField.SetRaw> unknown value `%s`", value)
} }
@ -396,9 +398,10 @@ func (e *SmallIntegerField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Int16() v, err := StrTo(d).Int16()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<SmallIntegerField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<SmallIntegerField.SetRaw> unknown value `%s`", value)
} }
@ -443,9 +446,10 @@ func (e *IntegerField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Int32() v, err := StrTo(d).Int32()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<IntegerField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<IntegerField.SetRaw> unknown value `%s`", value)
} }
@ -490,9 +494,10 @@ func (e *BigIntegerField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Int64() v, err := StrTo(d).Int64()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<BigIntegerField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<BigIntegerField.SetRaw> unknown value `%s`", value)
} }
@ -537,9 +542,10 @@ func (e *PositiveSmallIntegerField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Uint16() v, err := StrTo(d).Uint16()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<PositiveSmallIntegerField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<PositiveSmallIntegerField.SetRaw> unknown value `%s`", value)
} }
@ -584,9 +590,10 @@ func (e *PositiveIntegerField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Uint32() v, err := StrTo(d).Uint32()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<PositiveIntegerField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<PositiveIntegerField.SetRaw> unknown value `%s`", value)
} }
@ -631,9 +638,10 @@ func (e *PositiveBigIntegerField) SetRaw(value interface{}) error {
e.Set(d) e.Set(d)
case string: case string:
v, err := StrTo(d).Uint64() v, err := StrTo(d).Uint64()
if err != nil { if err == nil {
e.Set(v) e.Set(v)
} }
return err
default: default:
return fmt.Errorf("<PositiveBigIntegerField.SetRaw> unknown value `%s`", value) return fmt.Errorf("<PositiveBigIntegerField.SetRaw> unknown value `%s`", value)
} }

View File

@ -47,7 +47,7 @@ func (f *fields) Add(fi *fieldInfo) (added bool) {
} else { } else {
return return
} }
if _, ok := f.fieldsByType[fi.fieldType]; ok == false { if _, ok := f.fieldsByType[fi.fieldType]; !ok {
f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0) f.fieldsByType[fi.fieldType] = make([]*fieldInfo, 0)
} }
f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi) f.fieldsByType[fi.fieldType] = append(f.fieldsByType[fi.fieldType], fi)
@ -136,6 +136,7 @@ type fieldInfo struct {
decimals int decimals int
isFielder bool // implement Fielder interface isFielder bool // implement Fielder interface
onDelete string onDelete string
description string
} }
// new field info // new field info
@ -244,8 +245,10 @@ checkType:
if err != nil { if err != nil {
goto end goto end
} }
if fieldType == TypeCharField { if fieldType == TypeVarCharField {
switch tags["type"] { switch tags["type"] {
case "char":
fieldType = TypeCharField
case "text": case "text":
fieldType = TypeTextField fieldType = TypeTextField
case "json": case "json":
@ -298,6 +301,7 @@ checkType:
fi.sf = sf fi.sf = sf
fi.fullName = mi.fullName + mName + "." + sf.Name fi.fullName = mi.fullName + mName + "." + sf.Name
fi.description = sf.Tag.Get("description")
fi.null = attrs["null"] fi.null = attrs["null"]
fi.index = attrs["index"] fi.index = attrs["index"]
fi.auto = attrs["auto"] fi.auto = attrs["auto"]
@ -334,12 +338,12 @@ checkType:
switch onDelete { switch onDelete {
case odCascade, odDoNothing: case odCascade, odDoNothing:
case odSetDefault: case odSetDefault:
if initial.Exist() == false { if !initial.Exist() {
err = errors.New("on_delete: set_default need set field a default value") err = errors.New("on_delete: set_default need set field a default value")
goto end goto end
} }
case odSetNULL: case odSetNULL:
if fi.null == false { if !fi.null {
err = errors.New("on_delete: set_null need set field null") err = errors.New("on_delete: set_null need set field null")
goto end goto end
} }
@ -357,7 +361,7 @@ checkType:
switch fieldType { switch fieldType {
case TypeBooleanField: case TypeBooleanField:
case TypeCharField, TypeJSONField, TypeJsonbField: case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
if size != "" { if size != "" {
v, e := StrTo(size).Int32() v, e := StrTo(size).Int32()
if e != nil { if e != nil {

View File

@ -75,10 +75,11 @@ func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int)
break break
} }
//record current field index //record current field index
fi.fieldIndex = append(index, i) fi.fieldIndex = append(fi.fieldIndex, index...)
fi.fieldIndex = append(fi.fieldIndex, i)
fi.mi = mi fi.mi = mi
fi.inModel = true fi.inModel = true
if mi.fields.Add(fi) == false { if !mi.fields.Add(fi) {
err = fmt.Errorf("duplicate column name: %s", fi.column) err = fmt.Errorf("duplicate column name: %s", fi.column)
break break
} }

View File

@ -49,7 +49,7 @@ func (e *SliceStringField) String() string {
} }
func (e *SliceStringField) FieldType() int { func (e *SliceStringField) FieldType() int {
return TypeCharField return TypeVarCharField
} }
func (e *SliceStringField) SetRaw(value interface{}) error { func (e *SliceStringField) SetRaw(value interface{}) error {
@ -433,11 +433,8 @@ var (
dDbBaser dbBaser dDbBaser dbBaser
) )
func init() { var (
Debug, _ = StrTo(DBARGS.Debug).Bool() helpinfo = `need driver and source!
if DBARGS.Driver == "" || DBARGS.Source == "" {
fmt.Println(`need driver and source!
Default DB Drivers. Default DB Drivers.
@ -479,7 +476,14 @@ export ORM_DRIVER=tidb
export ORM_SOURCE='memory://test/test' export ORM_SOURCE='memory://test/test'
go test -v github.com/astaxie/beego/orm go test -v github.com/astaxie/beego/orm
`) `
)
func init() {
Debug, _ = StrTo(DBARGS.Debug).Bool()
if DBARGS.Driver == "" || DBARGS.Source == "" {
fmt.Println(helpinfo)
os.Exit(2) os.Exit(2)
} }

View File

@ -149,7 +149,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
case reflect.TypeOf(new(bool)): case reflect.TypeOf(new(bool)):
ft = TypeBooleanField ft = TypeBooleanField
case reflect.TypeOf(new(string)): case reflect.TypeOf(new(string)):
ft = TypeCharField ft = TypeVarCharField
case reflect.TypeOf(new(time.Time)): case reflect.TypeOf(new(time.Time)):
ft = TypeDateTimeField ft = TypeDateTimeField
default: default:
@ -176,7 +176,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
case reflect.Bool: case reflect.Bool:
ft = TypeBooleanField ft = TypeBooleanField
case reflect.String: case reflect.String:
ft = TypeCharField ft = TypeVarCharField
default: default:
if elm.Interface() == nil { if elm.Interface() == nil {
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val)) panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
@ -189,7 +189,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
case sql.NullBool: case sql.NullBool:
ft = TypeBooleanField ft = TypeBooleanField
case sql.NullString: case sql.NullString:
ft = TypeCharField ft = TypeVarCharField
case time.Time: case time.Time:
ft = TypeDateTimeField ft = TypeDateTimeField
} }

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build go1.8
// Package orm provide ORM for MySQL/PostgreSQL/sqlite // Package orm provide ORM for MySQL/PostgreSQL/sqlite
// Simple Usage // Simple Usage
// //
@ -52,6 +54,7 @@
package orm package orm
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
@ -107,7 +110,7 @@ func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect
if mi, ok := modelCache.getByFullName(name); ok { if mi, ok := modelCache.getByFullName(name); ok {
return mi, ind return mi, ind
} }
panic(fmt.Errorf("<Ormer> table: `%s` not found, maybe not RegisterModel", name)) panic(fmt.Errorf("<Ormer> table: `%s` not found, make sure it was registered with `RegisterModel()`", name))
} }
// get field info from model info by given field name // get field info from model info by given field name
@ -122,21 +125,13 @@ func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
// read data to model // read data to model
func (o *orm) Read(md interface{}, cols ...string) error { func (o *orm) Read(md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true) mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
if err != nil {
return err
}
return nil
} }
// read data to model, like Read(), but use "SELECT FOR UPDATE" form // read data to model, like Read(), but use "SELECT FOR UPDATE" form
func (o *orm) ReadForUpdate(md interface{}, cols ...string) error { func (o *orm) ReadForUpdate(md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true) mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true) return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true)
if err != nil {
return err
}
return nil
} }
// Try to read a row from the database, or insert one if it doesn't exist // Try to read a row from the database, or insert one if it doesn't exist
@ -238,15 +233,11 @@ func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64
// cols set the columns those want to update. // cols set the columns those want to update.
func (o *orm) Update(md interface{}, cols ...string) (int64, error) { func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true) mi, ind := o.getMiInd(md, true)
num, err := o.alias.DbBaser.Update(o.db, mi, ind, o.alias.TZ, cols) return o.alias.DbBaser.Update(o.db, mi, ind, o.alias.TZ, cols)
if err != nil {
return num, err
}
return num, nil
} }
// delete model in database // delete model in database
// cols shows the delete conditions values read from. deafult is pk // cols shows the delete conditions values read from. default is pk
func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { func (o *orm) Delete(md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true) mi, ind := o.getMiInd(md, true)
num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols) num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols)
@ -361,7 +352,7 @@ func (o *orm) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo,
fi := o.getFieldInfo(mi, name) fi := o.getFieldInfo(mi, name)
_, _, exist := getExistPk(mi, ind) _, _, exist := getExistPk(mi, ind)
if exist == false { if !exist {
panic(ErrMissPK) panic(ErrMissPK)
} }
@ -432,7 +423,7 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
// table name can be string or struct. // table name can be string or struct.
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)), // e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
name := "" var name string
if table, ok := ptrStructOrTableName.(string); ok { if table, ok := ptrStructOrTableName.(string); ok {
name = snakeString(table) name = snakeString(table)
if mi, ok := modelCache.get(name); ok { if mi, ok := modelCache.get(name); ok {
@ -470,11 +461,15 @@ func (o *orm) Using(name string) error {
// begin transaction // begin transaction
func (o *orm) Begin() error { func (o *orm) Begin() error {
return o.BeginTx(context.Background(), nil)
}
func (o *orm) BeginTx(ctx context.Context, opts *sql.TxOptions) error {
if o.isTx { if o.isTx {
return ErrTxHasBegan return ErrTxHasBegan
} }
var tx *sql.Tx var tx *sql.Tx
tx, err := o.db.(txer).Begin() tx, err := o.db.(txer).BeginTx(ctx, opts)
if err != nil { if err != nil {
return err return err
} }
@ -489,7 +484,7 @@ func (o *orm) Begin() error {
// commit transaction // commit transaction
func (o *orm) Commit() error { func (o *orm) Commit() error {
if o.isTx == false { if !o.isTx {
return ErrTxDone return ErrTxDone
} }
err := o.db.(txEnder).Commit() err := o.db.(txEnder).Commit()
@ -504,7 +499,7 @@ func (o *orm) Commit() error {
// rollback transaction // rollback transaction
func (o *orm) Rollback() error { func (o *orm) Rollback() error {
if o.isTx == false { if !o.isTx {
return ErrTxDone return ErrTxDone
} }
err := o.db.(txEnder).Rollback() err := o.db.(txEnder).Rollback()
@ -553,6 +548,9 @@ func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) {
al.Name = aliasName al.Name = aliasName
al.DriverName = driverName al.DriverName = driverName
al.DB = db
detectTZ(al)
o := new(orm) o := new(orm)
o.alias = al o.alias = al

View File

@ -31,6 +31,8 @@ type condValue struct {
isOr bool isOr bool
isNot bool isNot bool
isCond bool isCond bool
isRaw bool
sql string
} }
// Condition struct. // Condition struct.
@ -45,6 +47,15 @@ func NewCondition() *Condition {
return c return c
} }
// Raw add raw sql to condition
func (c Condition) Raw(expr string, sql string) *Condition {
if len(sql) == 0 {
panic(fmt.Errorf("<Condition.Raw> sql cannot empty"))
}
c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), sql: sql, isRaw: true})
return &c
}
// And add expression to condition // And add expression to condition
func (c Condition) And(expr string, args ...interface{}) *Condition { func (c Condition) And(expr string, args ...interface{}) *Condition {
if expr == "" || len(args) == 0 { if expr == "" || len(args) == 0 {

View File

@ -15,6 +15,7 @@
package orm package orm
import ( import (
"context"
"database/sql" "database/sql"
"fmt" "fmt"
"io" "io"
@ -150,6 +151,13 @@ func (d *dbQueryLog) Begin() (*sql.Tx, error) {
return tx, err return tx, err
} }
func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
a := time.Now()
tx, err := d.db.(txer).BeginTx(ctx, opts)
debugLogQueies(d.alias, "db.BeginTx", "START TRANSACTION", a, err)
return tx, err
}
func (d *dbQueryLog) Commit() error { func (d *dbQueryLog) Commit() error {
a := time.Now() a := time.Now()
err := d.db.(txEnder).Commit() err := d.db.(txEnder).Commit()

Some files were not shown because too many files have changed in this diff Show More