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

288 Commits

Author SHA1 Message Date
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
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
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
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
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
83 changed files with 2478 additions and 560 deletions

View File

@ -1,9 +1,8 @@
language: go
go:
- 1.6.4
- 1.7.5
- 1.8.1
- "1.9.2"
- "1.10.3"
services:
- redis-server
- mysql
@ -11,7 +10,6 @@ services:
- memcached
env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
before_install:
- git clone git://github.com/ideawu/ssdb.git
@ -23,10 +21,11 @@ install:
- go get github.com/go-sql-driver/mysql
- go get github.com/mattn/go-sqlite3
- 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/couchbase/go-couchbase
- go get github.com/beego/goyaml2
- go get gopkg.in/yaml.v2
- go get github.com/belogik/goes
- go get github.com/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis
@ -34,11 +33,12 @@ install:
- go get github.com/cloudflare/golz4
- go get github.com/gogo/protobuf/proto
- go get github.com/Knetic/govaluate
- go get github.com/hsluoyz/casbin
- go get github.com/casbin/casbin
- 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:
- psql --version
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"

View File

@ -1,4 +1,5 @@
# 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 [![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)
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.

View File

@ -37,7 +37,7 @@ var beeAdminApp *adminApp
// 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.
// 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" {
// return false
// }
@ -50,7 +50,7 @@ var beeAdminApp *adminApp
// return true
// }
// beego.FilterMonitorFunc = MyFilterMonitor.
var FilterMonitorFunc func(string, string, time.Duration) bool
var FilterMonitorFunc func(string, string, time.Duration, string, int) bool
func init() {
beeAdminApp = &adminApp{
@ -62,7 +62,7 @@ func init() {
beeAdminApp.Route("/healthcheck", healthcheck)
beeAdminApp.Route("/task", taskStatus)
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.
@ -76,6 +76,18 @@ func adminIndex(rw http.ResponseWriter, r *http.Request) {
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
data := make(map[interface{}]interface{})
data["Content"] = toolbox.StatisticsMap.GetMap()
// do html escape before display path, avoid xss
if content, ok := (data["Content"]).(map[string]interface{}); 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)
}

View File

@ -67,6 +67,8 @@ func oldMap() map[string]interface{} {
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
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.Outputs"] = BConfig.Log.Outputs
return m

151
app.go
View File

@ -15,17 +15,22 @@
package beego
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/fcgi"
"os"
"path"
"strings"
"time"
"github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils"
"golang.org/x/crypto/acme/autocert"
)
var (
@ -51,8 +56,11 @@ func NewApp() *App {
return app
}
// MiddleWare function for http.Handler
type MiddleWare func(http.Handler) http.Handler
// Run beego application.
func (app *App) Run() {
func (app *App) Run(mws ...MiddleWare) {
addr := BConfig.Listen.HTTPAddr
if BConfig.Listen.HTTPPort != 0 {
@ -94,6 +102,12 @@ func (app *App) Run() {
}
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.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.ErrorLog = logs.GetLogger("HTTP")
@ -102,9 +116,9 @@ func (app *App) Run() {
if BConfig.Listen.Graceful {
httpsAddr := BConfig.Listen.HTTPSAddr
app.Server.Addr = httpsAddr
if BConfig.Listen.EnableHTTPS {
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
go func() {
time.Sleep(20 * time.Microsecond)
time.Sleep(1000 * time.Microsecond)
if BConfig.Listen.HTTPSPort != 0 {
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
app.Server.Addr = httpsAddr
@ -112,10 +126,27 @@ func (app *App) Run() {
server := grace.NewServer(httpsAddr, app.Handlers)
server.Server.ReadTimeout = app.Server.ReadTimeout
server.Server.WriteTimeout = app.Server.WriteTimeout
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
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 {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}
}()
}
@ -139,22 +170,44 @@ func (app *App) Run() {
}
// run normal mode
if BConfig.Listen.EnableHTTPS {
if BConfig.Listen.EnableHTTPS || BConfig.Listen.EnableMutualHTTPS {
go func() {
time.Sleep(20 * time.Microsecond)
time.Sleep(1000 * time.Microsecond)
if BConfig.Listen.HTTPSPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
} else if BConfig.Listen.EnableHTTP {
BeeLogger.Info("Start https server error, confict with http.Please reset https port")
BeeLogger.Info("Start https server error, conflict with http. Please reset https port")
return
}
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 {
logs.Critical("ListenAndServeTLS: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
}
if BConfig.Listen.EnableHTTP {
go func() {
@ -207,6 +260,84 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A
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
// usage:
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})

View File

@ -23,7 +23,7 @@ import (
const (
// VERSION represent beego web framework version.
VERSION = "1.8.3"
VERSION = "1.10.0"
// DEV is for develop
DEV = "dev"
@ -40,9 +40,9 @@ var (
// AddAPPStartHook is used to register the hookfunc
// The hookfuncs will run in beego.Run()
// such as sessionInit, middlerware start, buildtemplate, admin start
func AddAPPStartHook(hf hookfunc) {
hooks = append(hooks, hf)
// such as initiating session , starting middleware , building template, starting admin control and so on.
func AddAPPStartHook(hf ...hookfunc) {
hooks = append(hooks, hf...)
}
// Run beego application.
@ -62,19 +62,39 @@ func Run(params ...string) {
if len(strs) > 1 && strs[1] != "" {
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
}
BConfig.Listen.Domains = params
}
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() {
//init hooks
AddAPPStartHook(registerMime)
AddAPPStartHook(registerDefaultErrorHandler)
AddAPPStartHook(registerSession)
AddAPPStartHook(registerTemplate)
AddAPPStartHook(registerAdmin)
AddAPPStartHook(registerGzip)
AddAPPStartHook(
registerMime,
registerDefaultErrorHandler,
registerSession,
registerTemplate,
registerAdmin,
registerGzip,
)
for _, hk := range hooks {
if err := hk(); err != nil {

2
cache/README.md vendored
View File

@ -52,7 +52,7 @@ Configure like this:
## 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:

2
cache/cache.go vendored
View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// 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:
//
// import(

31
cache/memory.go vendored
View File

@ -217,26 +217,31 @@ func (bc *MemoryCache) vaccuum() {
if bc.items == nil {
return
}
for name := range bc.items {
bc.itemExpired(name)
if keys := bc.expiredKeys(); len(keys) != 0 {
bc.clearItems(keys)
}
}
}
// itemExpired returns true if an item is expired.
func (bc *MemoryCache) itemExpired(name string) bool {
// expiredKeys returns key list which are expired.
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()
defer bc.Unlock()
itm, ok := bc.items[name]
if !ok {
return true
for _, key := range keys {
delete(bc.items, key)
}
if itm.isExpire() {
delete(bc.items, name)
return true
}
return false
}
func init() {

82
cache/redis/redis.go vendored
View File

@ -14,9 +14,9 @@
// 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:
// import(
@ -32,10 +32,11 @@ package redis
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"time"
"github.com/garyburd/redigo/redis"
"github.com/gomodule/redigo/redis"
"github.com/astaxie/beego/cache"
)
@ -52,6 +53,7 @@ type Cache struct {
dbNum int
key string
password string
maxIdle int
}
// NewRedisCache create new redis cache with default collection name.
@ -59,14 +61,23 @@ func NewRedisCache() cache.Cache {
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) {
if len(args) < 1 {
return nil, errors.New("missing required arguments")
}
args[0] = rc.associate(args[0])
c := rc.p.Get()
defer c.Close()
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.
func (rc *Cache) Get(key string) interface{} {
if v, err := rc.do("GET", key); err == nil {
@ -77,57 +88,28 @@ func (rc *Cache) Get(key string) interface{} {
// GetMulti get cache from redis.
func (rc *Cache) GetMulti(keys []string) []interface{} {
size := len(keys)
var rv []interface{}
c := rc.p.Get()
defer c.Close()
var err error
var args []interface{}
for _, key := range keys {
err = c.Send("GET", key)
if err != nil {
goto ERROR
}
args = append(args, rc.associate(key))
}
if err = c.Flush(); err != nil {
goto ERROR
values, err := redis.Values(c.Do("MGET", args...))
if err != nil {
return nil
}
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
return values
}
// Put put cache to redis.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
var err error
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
}
_, err := rc.do("SETEX", key, int64(timeout/time.Second), val)
return err
}
// Delete delete cache in redis.
func (rc *Cache) Delete(key string) error {
var err error
if _, err = rc.do("DEL", key); err != nil {
return err
}
_, err = rc.do("HDEL", rc.key, key)
_, err := rc.do("DEL", key)
return err
}
@ -137,11 +119,6 @@ func (rc *Cache) IsExist(key string) bool {
if err != nil {
return false
}
if !v {
if _, err = rc.do("HDEL", rc.key, key); err != nil {
return false
}
}
return v
}
@ -159,16 +136,17 @@ func (rc *Cache) Decr(key string) error {
// ClearAll clean all cache in redis. delete this redis collection.
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 {
return err
}
for _, str := range cachedKeys {
if _, err = rc.do("DEL", str); err != nil {
if _, err = c.Do("DEL", str); err != nil {
return err
}
}
_, err = rc.do("DEL", rc.key)
return err
}
@ -192,10 +170,14 @@ func (rc *Cache) StartAndGC(config string) error {
if _, ok := cf["password"]; !ok {
cf["password"] = ""
}
if _, ok := cf["maxIdle"]; !ok {
cf["maxIdle"] = "3"
}
rc.key = cf["key"]
rc.conninfo = cf["conn"]
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
rc.password = cf["password"]
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
rc.connectInit()
@ -229,7 +211,7 @@ func (rc *Cache) connectInit() {
}
// initialize a new pool
rc.p = &redis.Pool{
MaxIdle: 3,
MaxIdle: rc.maxIdle,
IdleTimeout: 180 * time.Second,
Dial: dialFunc,
}

View File

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

View File

@ -49,22 +49,27 @@ type Config struct {
// Listen holds for http and https related config
type Listen struct {
Graceful bool // Graceful means use graceful module to start the server
ServerTimeOut int64
ListenTCP4 bool
EnableHTTP bool
HTTPAddr string
HTTPPort int
EnableHTTPS bool
HTTPSAddr string
HTTPSPort int
HTTPSCertFile string
HTTPSKeyFile string
EnableAdmin bool
AdminAddr string
AdminPort int
EnableFcgi bool
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
Graceful bool // Graceful means use graceful module to start the server
ServerTimeOut int64
ListenTCP4 bool
EnableHTTP bool
HTTPAddr string
HTTPPort int
AutoTLS bool
Domains []string
TLSCacheDir string
EnableHTTPS bool
EnableMutualHTTPS bool
HTTPSAddr string
HTTPSPort int
HTTPSCertFile string
HTTPSKeyFile string
TrustCaFile string
EnableAdmin bool
AdminAddr string
AdminPort int
EnableFcgi bool
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
}
// WebConfig holds web related config
@ -96,16 +101,18 @@ type SessionConfig struct {
SessionAutoSetCookie bool
SessionDomain string
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader string
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
}
// LogConfig holds Log related config
type LogConfig struct {
AccessLogs bool
FileLineNum bool
Outputs map[string]string // Store Adaptor : config
AccessLogs bool
EnableStaticLogs bool //log static files requests default: false
AccessLogsFormat string //access log format: JSON_FORMAT, APACHE_FORMAT or empty string
FileLineNum bool
Outputs map[string]string // Store Adaptor : config
}
var (
@ -134,9 +141,13 @@ func init() {
if err != nil {
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) {
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
appConfigPath = filepath.Join(AppPath, "conf", filename)
if !utils.FileExists(appConfigPath) {
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
return
@ -175,13 +186,18 @@ func recoverPanic(ctx *context.Context) {
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
showErr(err, ctx, stack)
}
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
}
}
func newBConfig() *Config {
return &Config{
AppName: "beego",
RunMode: DEV,
RunMode: PROD,
RouterCaseSensitive: true,
ServerName: "beegoServer:" + VERSION,
RecoverPanic: true,
@ -196,6 +212,9 @@ func newBConfig() *Config {
ServerTimeOut: 0,
ListenTCP4: false,
EnableHTTP: true,
AutoTLS: false,
Domains: []string{},
TLSCacheDir: ".",
HTTPAddr: "",
HTTPPort: 8080,
EnableHTTPS: false,
@ -233,15 +252,17 @@ func newBConfig() *Config {
SessionCookieLifeTime: 0, //set cookie default is the browser life
SessionAutoSetCookie: true,
SessionDomain: "",
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader: "Beegosessionid",
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
},
},
Log: LogConfig{
AccessLogs: false,
FileLineNum: true,
Outputs: map[string]string{"console": ""},
AccessLogs: false,
EnableStaticLogs: false,
AccessLogsFormat: "APACHE_FORMAT",
FileLineNum: true,
Outputs: map[string]string{"console": ""},
},
}
}

View File

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

View File

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

View File

@ -215,7 +215,7 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) {
}
// 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 {
v, err := c.Bool(key)
if err != nil {
@ -230,7 +230,7 @@ func (c *IniConfigContainer) Int(key string) (int, error) {
}
// 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 {
v, err := c.Int(key)
if err != nil {
@ -245,7 +245,7 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) {
}
// 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 {
v, err := c.Int64(key)
if err != nil {
@ -260,7 +260,7 @@ func (c *IniConfigContainer) Float(key string) (float64, error) {
}
// 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 {
v, err := c.Float(key)
if err != nil {
@ -275,7 +275,7 @@ func (c *IniConfigContainer) String(key string) string {
}
// 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 {
v := c.String(key)
if v == "" {
@ -295,7 +295,7 @@ func (c *IniConfigContainer) Strings(key string) []string {
}
// 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 {
v := c.Strings(key)
if v == nil {
@ -314,7 +314,7 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
// 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) {
// Write configuration file by filename.
f, err := os.Create(filename)

View File

@ -101,7 +101,7 @@ func (c *JSONConfigContainer) Int(key string) (int, error) {
}
// 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 {
if v, err := c.Int(key); err == nil {
return v
@ -122,7 +122,7 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) {
}
// 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 {
if v, err := c.Int64(key); err == nil {
return v
@ -143,7 +143,7 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) {
}
// 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 {
if v, err := c.Float(key); err == nil {
return v
@ -163,7 +163,7 @@ func (c *JSONConfigContainer) String(key string) string {
}
// 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 {
// TODO FIXME should not use "" to replace non existence
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.
// if err != nil return defaltval
// if err != nil return defaultval
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); v != nil {
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")
}
if !jsonconf.DefaultBool("unknow", true) {
if !jsonconf.DefaultBool("unknown", true) {
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.
// if err != nil return defaltval
// if err != nil return defaultval
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key)
if err != nil {
@ -117,7 +117,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
}
// 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 {
v, err := c.Int64(key)
if err != nil {
@ -133,7 +133,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
}
// 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 {
v, err := c.Float(key)
if err != nil {
@ -151,7 +151,7 @@ func (c *ConfigContainer) String(key string) string {
}
// 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 {
v := c.String(key)
if v == "" {
@ -170,7 +170,7 @@ func (c *ConfigContainer) Strings(key string) []string {
}
// 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 {
v := c.Strings(key)
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.
type ConfigContainer struct {
data map[string]interface{}
sync.Mutex
sync.RWMutex
}
// 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.
// if err != nil return defaltval
// if err != nil return defaultval
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key)
if err != nil {
@ -174,7 +174,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
}
// 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 {
v, err := c.Int64(key)
if err != nil {
@ -198,7 +198,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
}
// 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 {
v, err := c.Float(key)
if err != nil {
@ -218,7 +218,7 @@ func (c *ConfigContainer) String(key string) string {
}
// 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 {
v := c.String(key)
if v == "" {
@ -237,7 +237,7 @@ func (c *ConfigContainer) Strings(key string) []string {
}
// 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 {
v := c.Strings(key)
if v == nil {
@ -285,9 +285,25 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
if len(key) == 0 {
return nil, errors.New("key is empty")
}
c.RLock()
defer c.RUnlock()
if v, ok := c.data[key]; ok {
return v, nil
keys := strings.Split(key, ".")
tmpData := c.data
for _, k := range keys {
if v, ok := tmpData[k]; ok {
switch v.(type) {
case map[string]interface{}:
{
tmpData = v.(map[string]interface{})
}
default:
{
return v, nil
}
}
}
}
return nil, fmt.Errorf("not exist key %q", key)
}

View File

@ -75,7 +75,7 @@ func TestAssignConfig_02(t *testing.T) {
jcf := &config.JSONConfig{}
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} {
assignSingleConfig(i, ac)

View File

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

View File

@ -30,6 +30,7 @@ import (
"strconv"
"strings"
"time"
"gopkg.in/yaml.v2"
)
// BeegoOutput does work for sending response header.
@ -177,13 +178,13 @@ func jsonRenderer(value interface{}) Renderer {
func errorRenderer(err error) Renderer {
return rendererFunc(func(ctx *Context) {
ctx.Output.SetStatus(500)
ctx.WriteString(err.Error())
ctx.Output.Body([]byte(err.Error()))
})
}
// JSON writes json to response body.
// if coding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
// if encoding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
output.Header("Content-Type", "application/json; charset=utf-8")
var content []byte
var err error
@ -196,12 +197,26 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
if coding {
if encoding {
content = []byte(stringsToJSON(string(content)))
}
return output.Body(content)
}
// YAML writes yaml to response body.
func (output *BeegoOutput) YAML(data interface{}) error {
output.Header("Content-Type", "application/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.
func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/javascript; charset=utf-8")
@ -260,7 +275,7 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
} else {
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-Type", "application/octet-stream")
output.Header("Content-Transfer-Encoding", "binary")
@ -325,13 +340,13 @@ func (output *BeegoOutput) IsForbidden() bool {
}
// IsNotFound returns boolean of this request is not found.
// HTTP 404 means forbidden.
// HTTP 404 means not found.
func (output *BeegoOutput) IsNotFound() bool {
return output.Status == 404
}
// IsClientError returns boolean of this request client sends error data.
// HTTP 4xx means forbidden.
// HTTP 4xx means client error.
func (output *BeegoOutput) IsClientError() bool {
return output.Status >= 400 && output.Status < 500
}
@ -350,6 +365,11 @@ func stringsToJSON(str string) string {
jsons.WriteRune(r)
} else {
jsons.WriteString("\\u")
if rint < 0x100 {
jsons.WriteString("00")
} else if rint < 0x1000 {
jsons.WriteString("0")
}
jsons.WriteString(strconv.FormatInt(int64(rint), 16))
}
}

View File

@ -7,7 +7,7 @@ import (
// 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 ommited from the http request
// 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
}

View File

@ -75,7 +75,7 @@ func checkParser(def testDefinition, t *testing.T, methodParam ...MethodParam) {
}
convResult, err := safeConvert(reflect.ValueOf(result), toType)
if err != nil {
t.Errorf("Convertion error for %v. from value: %v, toType: %v, error: %v", def.strValue, result, toType, err)
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) {

View File

@ -36,6 +36,7 @@ import (
const (
applicationJSON = "application/json"
applicationXML = "application/xml"
applicationYAML = "application/x-yaml"
textXML = "text/xml"
)
@ -55,6 +56,13 @@ type ControllerComments struct {
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
// http context, template and view, session and xsrf.
type Controller struct {
@ -265,7 +273,22 @@ func (c *Controller) viewPath() string {
// Redirect sends the redirection response to url with status code.
func (c *Controller) Redirect(url string, code int) {
logAccess(c.Ctx, nil, code)
c.Ctx.Redirect(code, url)
panic(ErrAbort)
}
// Set the data depending on the accepted
func (c *Controller) SetData(data interface{}) {
accept := c.Ctx.Input.Header("Accept")
switch accept {
case applicationJSON:
c.Data["json"] = data
case applicationXML, 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.
@ -340,6 +363,11 @@ func (c *Controller) ServeXML() {
c.Ctx.Output.XML(c.Data["xml"], hasIndent)
}
// ServeXML sends xml response.
func (c *Controller) ServeYAML() {
c.Ctx.Output.YAML(c.Data["yaml"])
}
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
func (c *Controller) ServeFormatted() {
accept := c.Ctx.Input.Header("Accept")
@ -348,6 +376,8 @@ func (c *Controller) ServeFormatted() {
c.ServeJSON()
case applicationXML, textXML:
c.ServeXML()
case applicationYAML:
c.ServeYAML()
default:
c.ServeJSON()
}

View File

@ -28,7 +28,7 @@ import (
)
const (
errorTypeHandler = iota
errorTypeHandler = iota
errorTypeController
)
@ -93,11 +93,6 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
"BeegoVersion": 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)
}
@ -439,6 +434,9 @@ func exception(errCode string, ctx *context.Context) {
}
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 {
ctx.ResponseWriter.WriteHeader(code)
err.handler(ctx.ResponseWriter, ctx.Request)

View File

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

View File

@ -3,14 +3,17 @@ package grace
import (
"errors"
"net"
"sync"
)
type graceConn struct {
net.Conn
server *Server
m sync.Mutex
closed bool
}
func (c graceConn) Close() (err error) {
func (c *graceConn) Close() (err error) {
defer func() {
if r := recover(); r != nil {
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.closed = true
c.m.Unlock()
return c.Conn.Close()
}

View File

@ -37,7 +37,7 @@ func (gl *graceListener) Accept() (c net.Conn, err error) {
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
c = graceConn{
c = &graceConn{
Conn: tc,
server: gl.server,
}

View File

@ -2,7 +2,9 @@ package grace
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
@ -65,7 +67,7 @@ func (srv *Server) ListenAndServe() (err error) {
log.Println(err)
return err
}
err = process.Kill()
err = process.Signal(syscall.SIGTERM)
if err != nil {
return err
}
@ -114,6 +116,62 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
srv.tlsInnerListener = newGraceListener(l, srv)
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 {
process, err := os.FindProcess(os.Getppid())
if err != nil {

View File

@ -50,6 +50,7 @@ import (
"strings"
"sync"
"time"
"gopkg.in/yaml.v2"
)
var defaultSetting = BeegoHTTPSettings{
@ -318,6 +319,34 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
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.
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
@ -417,12 +446,12 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
}
b.buildURL(paramBody)
url, err := url.Parse(b.url)
urlParsed, err := url.Parse(b.url)
if err != nil {
return nil, err
}
b.req.URL = url
b.req.URL = urlParsed
trans := b.setting.Transport
@ -432,7 +461,7 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: -1,
MaxIdleConnsPerHost: 100,
}
} else {
// if b.transport is *http.Transport then set the settings.
@ -567,6 +596,16 @@ func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
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.
func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
return b.getResponse()

View File

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

@ -21,6 +21,7 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strconv"
"strings"
@ -40,6 +41,9 @@ type fileLogWriter struct {
MaxLines int `json:"maxlines"`
maxLinesCurLines int
MaxFiles int `json:"maxfiles"`
MaxFilesCurFiles int
// Rotate at size
MaxSize int `json:"maxsize"`
maxSizeCurSize int
@ -56,17 +60,23 @@ type fileLogWriter struct {
Perm string `json:"perm"`
RotatePerm string `json:"rotateperm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
}
// newFileWriter create a FileLogWriter returning as LoggerInterface.
func newFileWriter() Logger {
w := &fileLogWriter{
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
Daily: true,
MaxDays: 7,
Rotate: true,
RotatePerm: "0440",
Level: LevelTrace,
Perm: "0660",
MaxLines: 10000000,
MaxFiles: 999,
MaxSize: 1 << 28,
}
return w
}
@ -158,6 +168,10 @@ func (w *fileLogWriter) createLogFile() (*os.File, error) {
if err != nil {
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))
if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
@ -179,7 +193,7 @@ func (w *fileLogWriter) initFd() error {
if w.Daily {
go w.dailyRotate(w.dailyOpenTime)
}
if fInfo.Size() > 0 {
if fInfo.Size() > 0 && w.MaxLines > 0 {
count, err := w.lines()
if err != nil {
return err
@ -235,27 +249,29 @@ func (w *fileLogWriter) lines() (int, error) {
func (w *fileLogWriter) doRotate(logTime time.Time) error {
// file exists
// Find the next available number
num := 1
num := w.MaxFilesCurFiles + 1
fName := ""
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 {
//even if the file is not exist or other ,we should RESTART the logger
goto RESTART_LOGGER
}
// only when one of them be setted, then the file would be splited
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)
_, err = os.Lstat(fName)
}
} 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", w.dailyOpenTime.Format("2006-01-02"), 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)
}
w.MaxFilesCurFiles = num
}
// return error if the last file checked still existed
if err == nil {
@ -271,8 +287,9 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
if err != nil {
goto RESTART_LOGGER
}
err = os.Chmod(fName, os.FileMode(0440))
// re-start logger
err = os.Chmod(fName, os.FileMode(rotatePerm))
RESTART_LOGGER:
startLoggerErr := w.startLogger()
@ -300,7 +317,7 @@ func (w *fileLogWriter) deleteOldLog() {
return
}
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)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)

View File

@ -135,7 +135,7 @@ func TestFileRotate_01(t *testing.T) {
func TestFileRotate_02(t *testing.T) {
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)
}
@ -150,7 +150,7 @@ func TestFileRotate_03(t *testing.T) {
func TestFileRotate_04(t *testing.T) {
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)
}
@ -185,11 +185,12 @@ func TestFileRotate_06(t *testing.T) { //test file mode
}
func testFileRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
RotatePerm: "0440",
}
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
@ -199,6 +200,7 @@ func testFileRotate(t *testing.T, fn1, fn2 string) {
for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file)
if err != nil {
t.Log(err)
t.FailNow()
}
os.Remove(file)
@ -208,11 +210,12 @@ func testFileRotate(t *testing.T, fn1, fn2 string) {
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
RotatePerm: "0440",
}
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)

View File

@ -47,7 +47,7 @@ import (
// RFC5424 log message levels.
const (
LevelEmergency = iota
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
@ -116,6 +116,7 @@ type BeeLogger struct {
enableFuncCallDepth bool
loggerFuncCallDepth int
asynchronous bool
prefix string
msgChanLen int64
msgChan chan *logMsg
signalChan chan string
@ -247,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
}
// writeMsg will always add a '\n' character
if p[len(p)-1] == '\n' {
p = p[0 : len(p)-1]
p = p[0: len(p)-1]
}
// set levelLoggerImpl to ensure all log message will be write out
err = bl.writeMsg(levelLoggerImpl, string(p))
@ -267,6 +268,9 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
if len(v) > 0 {
msg = fmt.Sprintf(msg, v...)
}
msg = bl.prefix + " " + msg
when := time.Now()
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
@ -305,6 +309,11 @@ func (bl *BeeLogger) SetLevel(l int) {
bl.level = l
}
// GetLevel Get Current log message level.
func (bl *BeeLogger) GetLevel() int {
return bl.level
}
// SetLogFuncCallDepth set log funcCallDepth
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
bl.loggerFuncCallDepth = d
@ -320,6 +329,11 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
bl.enableFuncCallDepth = b
}
// set prefix
func (bl *BeeLogger) SetPrefix(s string) {
bl.prefix = s
}
// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
@ -544,6 +558,11 @@ func SetLevel(l int) {
beeLogger.SetLevel(l)
}
// SetPrefix sets the prefix
func SetPrefix(s string) {
beeLogger.SetPrefix(s)
}
// EnableFuncCallDepth enable log funcCallDepth
func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b

View File

@ -87,13 +87,15 @@ const (
mi2 = `012345678901234567890123456789012345678901234567890123456789`
s1 = `000000000011111111112222222222333333333344444444445555555555`
s2 = `012345678901234567890123456789012345678901234567890123456789`
ns1 = `0123456789`
)
func formatTimeHeader(when time.Time) ([]byte, int) {
y, mo, d := when.Date()
h, mi, s := when.Clock()
//len("2006/01/02 15:04:05 ")==20
var buf [20]byte
ns := when.Nanosecond() / 1000000
//len("2006/01/02 15:04:05.123 ")==24
var buf [24]byte
buf[0] = y1[y/1000%10]
buf[1] = y2[y/100]
@ -114,7 +116,12 @@ func formatTimeHeader(when time.Time) ([]byte, int) {
buf[16] = ':'
buf[17] = s1[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]
buf[23] = ' '
return buf[0:], d
}

View File

@ -31,7 +31,7 @@ func TestFormatHeader_0(t *testing.T) {
break
}
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.FailNow()
}
@ -49,7 +49,7 @@ func TestFormatHeader_1(t *testing.T) {
break
}
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.FailNow()
}

View File

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

View File

@ -14,40 +14,382 @@
package migration
// Table store the tablename and Column
type Table struct {
TableName string
Columns []*Column
import (
"fmt"
"github.com/astaxie/beego"
)
// Index struct defines the structure of Index Columns
type Index struct {
Name string
}
// Create return the create sql
func (t *Table) Create() string {
return ""
// Unique struct defines a single unique key combination
type Unique struct {
Definition string
Columns []*Column
}
// Drop return the drop sql
func (t *Table) Drop() string {
return ""
}
// Column define the columns name type and Default
//Column struct defines a single column of a table
type Column struct {
Name string
Type string
Default interface{}
Name string
Inc string
Null string
Default string
Unsign string
DataType string
remove bool
Modify bool
}
// Create return create sql with the provided tbname and columns
func Create(tbname string, columns ...Column) string {
return ""
// Foreign struct defines a single foreign relationship
type Foreign struct {
ForeignTable string
ForeignColumn string
OnDelete string
OnUpdate string
Column
}
// Drop return the drop sql with the provided tbname and columns
func Drop(tbname string, columns ...Column) string {
return ""
// RenameColumn struct allows renaming of columns
type RenameColumn struct {
OldName string
OldNull string
OldDefault string
OldUnsign string
OldDataType string
NewName string
Column
}
// TableDDL is still in think
func TableDDL(tbname string, columns ...Column) string {
return ""
// CreateTable creates the table on system
func (m *Migration) CreateTable(tablename, engine, charset string, p ...func()) {
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
}
//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 (
migrationMap map[string]Migrationer
)
@ -60,20 +80,34 @@ func init() {
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
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
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
@ -138,7 +172,7 @@ func Register(name string, m Migrationer) error {
return nil
}
// Upgrade upgrate the migration from lasttime
// Upgrade upgrade the migration from lasttime
func Upgrade(lasttime int64) error {
sm := sortMap(migrationMap)
i := 0

View File

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

View File

@ -51,12 +51,14 @@ checkColumn:
switch fieldType {
case TypeBooleanField:
col = T["bool"]
case TypeCharField:
case TypeVarCharField:
if al.Driver == DRPostgres && fi.toText {
col = T["string-text"]
} else {
col = fmt.Sprintf(T["string"], fieldSize)
}
case TypeCharField:
col = fmt.Sprintf(T["string-char"], fieldSize)
case TypeTextField:
col = T["string-text"]
case TypeTimeField:
@ -96,13 +98,13 @@ checkColumn:
}
case TypeJSONField:
if al.Driver != DRPostgres {
fieldType = TypeCharField
fieldType = TypeVarCharField
goto checkColumn
}
col = T["json"]
case TypeJsonbField:
if al.Driver != DRPostgres {
fieldType = TypeCharField
fieldType = TypeVarCharField
goto checkColumn
}
col = T["jsonb"]
@ -195,6 +197,10 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
if strings.Contains(column, "%COL%") {
column = strings.Replace(column, "%COL%", fi.column, -1)
}
if fi.description != "" {
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
}
columns = append(columns, column)
}

View File

@ -142,7 +142,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
} else {
value = field.Bool()
}
case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
case TypeVarCharField, TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
if ns, ok := field.Interface().(sql.NullString); ok {
value = nil
if ns.Valid {
@ -969,6 +969,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)
if qs.forupdate {
query += " FOR UPDATE"
}
d.ins.ReplaceMarks(&query)
var rs *sql.Rows
@ -1240,7 +1244,7 @@ setValue:
}
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 {
value = ToStr(val)
} else {
@ -1386,7 +1390,7 @@ setValue:
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 ns, ok := field.Interface().(sql.NullString); ok {
if value == nil {

View File

@ -119,7 +119,7 @@ type alias struct {
func detectTZ(al *alias) {
// orm timezone system match database
// default use Local
al.TZ = time.Local
al.TZ = DefaultTimeLoc
if al.DriverName == "sphinx" {
return
@ -136,7 +136,9 @@ func detectTZ(al *alias) {
}
t, err := time.Parse("-07:00:00", tz)
if err == nil {
al.TZ = t.Location()
if t.Location().String() != "" {
al.TZ = t.Location()
}
} else {
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
}

View File

@ -46,6 +46,7 @@ var mysqlTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY",
"bool": "bool",
"string": "varchar(%d)",
"string-char": "char(%d)",
"string-text": "longtext",
"time.Time-date": "date",
"time.Time": "datetime",

View File

@ -34,6 +34,7 @@ var oracleTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY",
"bool": "bool",
"string": "VARCHAR2(%d)",
"string-char": "CHAR(%d)",
"string-text": "VARCHAR2(%d)",
"time.Time-date": "DATE",
"time.Time": "TIMESTAMP",
@ -94,3 +95,43 @@ func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool
row.Scan(&cnt)
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",
"bool": "bool",
"string": "varchar(%d)",
"string-char": "char(%d)",
"string-text": "text",
"time.Time-date": "date",
"time.Time": "timestamp with time zone",

View File

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

View File

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

View File

@ -89,7 +89,7 @@ func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
modelCache.set(table, mi)
}
// boostrap models
// bootstrap models
func bootStrap() {
if modelCache.done {
return
@ -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
func BootStrap() {
if modelCache.done {

View File

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

View File

@ -136,6 +136,7 @@ type fieldInfo struct {
decimals int
isFielder bool // implement Fielder interface
onDelete string
description string
}
// new field info
@ -244,8 +245,10 @@ checkType:
if err != nil {
goto end
}
if fieldType == TypeCharField {
if fieldType == TypeVarCharField {
switch tags["type"] {
case "char":
fieldType = TypeCharField
case "text":
fieldType = TypeTextField
case "json":
@ -298,6 +301,7 @@ checkType:
fi.sf = sf
fi.fullName = mi.fullName + mName + "." + sf.Name
fi.description = sf.Tag.Get("description")
fi.null = attrs["null"]
fi.index = attrs["index"]
fi.auto = attrs["auto"]
@ -357,7 +361,7 @@ checkType:
switch fieldType {
case TypeBooleanField:
case TypeCharField, TypeJSONField, TypeJsonbField:
case TypeVarCharField, TypeCharField, TypeJSONField, TypeJsonbField:
if size != "" {
v, e := StrTo(size).Int32()
if e != nil {

View File

@ -75,7 +75,8 @@ func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int)
break
}
//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.inModel = true
if !mi.fields.Add(fi) {

View File

@ -49,7 +49,7 @@ func (e *SliceStringField) String() string {
}
func (e *SliceStringField) FieldType() int {
return TypeCharField
return TypeVarCharField
}
func (e *SliceStringField) SetRaw(value interface{}) error {
@ -433,53 +433,57 @@ var (
dDbBaser dbBaser
)
var (
helpinfo = `need driver and source!
Default DB Drivers.
driver: url
mysql: https://github.com/go-sql-driver/mysql
sqlite3: https://github.com/mattn/go-sqlite3
postgres: https://github.com/lib/pq
tidb: https://github.com/pingcap/tidb
usage:
go get -u github.com/astaxie/beego/orm
go get -u github.com/go-sql-driver/mysql
go get -u github.com/mattn/go-sqlite3
go get -u github.com/lib/pq
go get -u github.com/pingcap/tidb
#### MySQL
mysql -u root -e 'create database orm_test;'
export ORM_DRIVER=mysql
export ORM_SOURCE="root:@/orm_test?charset=utf8"
go test -v github.com/astaxie/beego/orm
#### Sqlite3
export ORM_DRIVER=sqlite3
export ORM_SOURCE='file:memory_test?mode=memory'
go test -v github.com/astaxie/beego/orm
#### PostgreSQL
psql -c 'create database orm_test;' -U postgres
export ORM_DRIVER=postgres
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
go test -v github.com/astaxie/beego/orm
#### TiDB
export ORM_DRIVER=tidb
export ORM_SOURCE='memory://test/test'
go test -v github.com/astaxie/beego/orm
`
)
func init() {
Debug, _ = StrTo(DBARGS.Debug).Bool()
if DBARGS.Driver == "" || DBARGS.Source == "" {
fmt.Println(`need driver and source!
Default DB Drivers.
driver: url
mysql: https://github.com/go-sql-driver/mysql
sqlite3: https://github.com/mattn/go-sqlite3
postgres: https://github.com/lib/pq
tidb: https://github.com/pingcap/tidb
usage:
go get -u github.com/astaxie/beego/orm
go get -u github.com/go-sql-driver/mysql
go get -u github.com/mattn/go-sqlite3
go get -u github.com/lib/pq
go get -u github.com/pingcap/tidb
#### MySQL
mysql -u root -e 'create database orm_test;'
export ORM_DRIVER=mysql
export ORM_SOURCE="root:@/orm_test?charset=utf8"
go test -v github.com/astaxie/beego/orm
#### Sqlite3
export ORM_DRIVER=sqlite3
export ORM_SOURCE='file:memory_test?mode=memory'
go test -v github.com/astaxie/beego/orm
#### PostgreSQL
psql -c 'create database orm_test;' -U postgres
export ORM_DRIVER=postgres
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
go test -v github.com/astaxie/beego/orm
#### TiDB
export ORM_DRIVER=tidb
export ORM_SOURCE='memory://test/test'
go test -v github.com/astaxie/beego/orm
`)
fmt.Println(helpinfo)
os.Exit(2)
}

View File

@ -149,7 +149,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
case reflect.TypeOf(new(bool)):
ft = TypeBooleanField
case reflect.TypeOf(new(string)):
ft = TypeCharField
ft = TypeVarCharField
case reflect.TypeOf(new(time.Time)):
ft = TypeDateTimeField
default:
@ -176,7 +176,7 @@ func getFieldType(val reflect.Value) (ft int, err error) {
case reflect.Bool:
ft = TypeBooleanField
case reflect.String:
ft = TypeCharField
ft = TypeVarCharField
default:
if elm.Interface() == nil {
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:
ft = TypeBooleanField
case sql.NullString:
ft = TypeCharField
ft = TypeVarCharField
case time.Time:
ft = TypeDateTimeField
}

View File

@ -55,16 +55,17 @@ func ColValue(opt operator, value interface{}) interface{} {
// real query struct
type querySet struct {
mi *modelInfo
cond *Condition
related []string
relDepth int
limit int64
offset int64
groups []string
orders []string
distinct bool
orm *orm
mi *modelInfo
cond *Condition
related []string
relDepth int
limit int64
offset int64
groups []string
orders []string
distinct bool
forupdate bool
orm *orm
}
var _ QuerySeter = new(querySet)
@ -127,6 +128,12 @@ func (o querySet) Distinct() QuerySeter {
return &o
}
// add FOR UPDATE to SELECT
func (o querySet) ForUpdate() QuerySeter {
o.forupdate = true
return &o
}
// set relation model to query together.
// it will query relation models and assign to parent model.
func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
@ -191,7 +198,11 @@ func (o *querySet) PrepareInsert() (Inserter, error) {
// query all data and map to containers.
// cols means the columns when querying.
func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
if num == 0 {
return 0, ErrNoRows
}
return num, err
}
// query one row data and map to containers.

View File

@ -1008,13 +1008,13 @@ func TestAll(t *testing.T) {
qs = dORM.QueryTable("user")
num, err = qs.Filter("user_name", "nothing").All(&users)
throwFailNow(t, err)
throwFailNow(t, AssertIs(err, ErrNoRows))
throwFailNow(t, AssertIs(num, 0))
var users3 []*User
qs = dORM.QueryTable("user")
num, err = qs.Filter("user_name", "nothing").All(&users3)
throwFailNow(t, err)
throwFailNow(t, AssertIs(err, ErrNoRows))
throwFailNow(t, AssertIs(num, 0))
throwFailNow(t, AssertIs(users3 == nil, false))
}

View File

@ -190,6 +190,10 @@ type QuerySeter interface {
// Distinct().
// All(&permissions)
Distinct() QuerySeter
// set FOR UPDATE to query.
// for example:
// o.QueryTable("user").Filter("uid", uid).ForUpdate().All(&users)
ForUpdate() QuerySeter
// return QuerySeter execution result number
// for example:
// num, err = qs.Filter("profile__age__gt", 28).Count()

View File

@ -114,20 +114,21 @@ type parsedParam struct {
func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
if f.Doc != nil {
parsedComment, err := parseComment(f.Doc.List)
parsedComments, err := parseComment(f.Doc.List)
if err != nil {
return err
}
if parsedComment.routerPath != "" {
key := pkgpath + ":" + controllerName
cc := ControllerComments{}
cc.Method = f.Name.String()
cc.Router = parsedComment.routerPath
cc.AllowHTTPMethods = parsedComment.methods
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
genInfoList[key] = append(genInfoList[key], cc)
for _, parsedComment := range parsedComments {
if parsedComment.routerPath != "" {
key := pkgpath + ":" + controllerName
cc := ControllerComments{}
cc.Method = f.Name.String()
cc.Router = parsedComment.routerPath
cc.AllowHTTPMethods = parsedComment.methods
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
genInfoList[key] = append(genInfoList[key], cc)
}
}
}
return nil
}
@ -135,15 +136,16 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.MethodParam {
result := make([]*param.MethodParam, 0, len(funcParams))
for _, fparam := range funcParams {
methodParam := buildMethodParam(fparam, pc)
result = append(result, methodParam)
for _, pName := range fparam.Names {
methodParam := buildMethodParam(fparam, pName.Name, pc)
result = append(result, methodParam)
}
}
return result
}
func buildMethodParam(fparam *ast.Field, pc *parsedComment) *param.MethodParam {
func buildMethodParam(fparam *ast.Field, name string, pc *parsedComment) *param.MethodParam {
options := []param.MethodParamOption{}
name := fparam.Names[0].Name
if cparam, ok := pc.params[name]; ok {
//Build param from comment info
name = cparam.name
@ -176,26 +178,13 @@ func paramInPath(name, route string) bool {
var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`)
func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) {
pc = &parsedComment{}
func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) {
pcs = []*parsedComment{}
params := map[string]parsedParam{}
for _, c := range lines {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
if strings.HasPrefix(t, "@router") {
matches := routeRegex.FindStringSubmatch(t)
if len(matches) == 3 {
pc.routerPath = matches[1]
methods := matches[2]
if methods == "" {
pc.methods = []string{"get"}
//pc.hasGet = true
} else {
pc.methods = strings.Split(methods, ",")
//pc.hasGet = strings.Contains(methods, "get")
}
} else {
return nil, errors.New("Router information is missing")
}
} else if strings.HasPrefix(t, "@Param") {
if strings.HasPrefix(t, "@Param") {
pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param")))
if len(pv) < 4 {
logs.Error("Invalid @Param format. Needs at least 4 parameters")
@ -216,17 +205,39 @@ func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) {
p.defValue = pv[3]
p.required, _ = strconv.ParseBool(pv[4])
}
if pc.params == nil {
pc.params = map[string]parsedParam{}
params[funcParamName] = p
}
}
for _, c := range lines {
var pc = &parsedComment{}
pc.params = params
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
if strings.HasPrefix(t, "@router") {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
matches := routeRegex.FindStringSubmatch(t)
if len(matches) == 3 {
pc.routerPath = matches[1]
methods := matches[2]
if methods == "" {
pc.methods = []string{"get"}
//pc.hasGet = true
} else {
pc.methods = strings.Split(methods, ",")
//pc.hasGet = strings.Contains(methods, "get")
}
pcs = append(pcs, pc)
} else {
return nil, errors.New("Router information is missing")
}
pc.params[funcParamName] = p
}
}
return
}
// direct copy from bee\g_docs.go
// analisys params return []string
// analysis params return []string
// @Param query form string true "The email for login"
// [query form string true "The email for login"]
func getparams(str string) []string {
@ -274,6 +285,7 @@ func genRouterCode(pkgRealpath string) {
sort.Strings(sortKey)
for _, k := range sortKey {
cList := genInfoList[k]
sort.Sort(ControllerCommentsSlice(cList))
for _, c := range cList {
allmethod := "nil"
if len(c.AllowHTTPMethods) > 0 {

View File

@ -17,7 +17,7 @@
// import(
// "github.com/astaxie/beego"
// "github.com/astaxie/beego/plugins/authz"
// "github.com/hsluoyz/casbin"
// "github.com/casbin/casbin"
// )
//
// func main(){
@ -42,7 +42,7 @@ package authz
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/hsluoyz/casbin"
"github.com/casbin/casbin"
"net/http"
)

View File

@ -18,7 +18,7 @@ import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/plugins/auth"
"github.com/hsluoyz/casbin"
"github.com/casbin/casbin"
"net/http"
"net/http/httptest"
"testing"

167
router.go
View File

@ -43,35 +43,35 @@ const (
)
const (
routerTypeBeego = iota
routerTypeBeego = iota
routerTypeRESTFul
routerTypeHandler
)
var (
// HTTPMETHOD list the supported http methods.
HTTPMETHOD = map[string]string{
"GET": "GET",
"POST": "POST",
"PUT": "PUT",
"DELETE": "DELETE",
"PATCH": "PATCH",
"OPTIONS": "OPTIONS",
"HEAD": "HEAD",
"TRACE": "TRACE",
"CONNECT": "CONNECT",
"MKCOL": "MKCOL",
"COPY": "COPY",
"MOVE": "MOVE",
"PROPFIND": "PROPFIND",
"PROPPATCH": "PROPPATCH",
"LOCK": "LOCK",
"UNLOCK": "UNLOCK",
HTTPMETHOD = map[string]bool{
"GET": true,
"POST": true,
"PUT": true,
"DELETE": true,
"PATCH": true,
"OPTIONS": true,
"HEAD": true,
"TRACE": true,
"CONNECT": true,
"MKCOL": true,
"COPY": true,
"MOVE": true,
"PROPFIND": true,
"PROPPATCH": true,
"LOCK": true,
"UNLOCK": true,
}
// these beego.Controller's methods shouldn't reflect to AutoRouter
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
"ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"ServeYAML", "ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
@ -117,6 +117,7 @@ type ControllerInfo struct {
handler http.Handler
runFunction FilterFunc
routerType int
initialize func() ControllerInterface
methodParams []*param.MethodParam
}
@ -169,7 +170,7 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt
}
comma := strings.Split(colon[0], ",")
for _, m := range comma {
if _, ok := HTTPMETHOD[strings.ToUpper(m)]; m == "*" || ok {
if m == "*" || HTTPMETHOD[strings.ToUpper(m)] {
if val := reflectVal.MethodByName(colon[1]); val.IsValid() {
methods[strings.ToUpper(m)] = colon[1]
} else {
@ -187,15 +188,39 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt
route.methods = methods
route.routerType = routerTypeBeego
route.controllerType = t
route.initialize = func() ControllerInterface {
vc := reflect.New(route.controllerType)
execController, ok := vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
elemVal := reflect.ValueOf(c).Elem()
elemType := reflect.TypeOf(c).Elem()
execElem := reflect.ValueOf(execController).Elem()
numOfFields := elemVal.NumField()
for i := 0; i < numOfFields; i++ {
fieldType := elemType.Field(i)
elemField := execElem.FieldByName(fieldType.Name)
if elemField.CanSet() {
fieldVal := elemVal.Field(i)
elemField.Set(fieldVal)
}
}
return execController
}
route.methodParams = methodParams
if len(methods) == 0 {
for _, m := range HTTPMETHOD {
for m := range HTTPMETHOD {
p.addToRouter(m, pattern, route)
}
} else {
for k := range methods {
if k == "*" {
for _, m := range HTTPMETHOD {
for m := range HTTPMETHOD {
p.addToRouter(m, pattern, route)
}
} else {
@ -337,7 +362,7 @@ func (p *ControllerRegister) Any(pattern string, f FilterFunc) {
// })
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
method = strings.ToUpper(method)
if _, ok := HTTPMETHOD[method]; method != "*" && !ok {
if method != "*" && !HTTPMETHOD[method] {
panic("not support http method: " + method)
}
route := &ControllerInfo{}
@ -346,7 +371,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
route.runFunction = f
methods := make(map[string]string)
if method == "*" {
for _, val := range HTTPMETHOD {
for val := range HTTPMETHOD {
methods[val] = val
}
} else {
@ -355,7 +380,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
route.methods = methods
for k := range methods {
if k == "*" {
for _, m := range HTTPMETHOD {
for m := range HTTPMETHOD {
p.addToRouter(m, pattern, route)
}
} else {
@ -375,7 +400,7 @@ func (p *ControllerRegister) Handler(pattern string, h http.Handler, options ...
pattern = path.Join(pattern, "?:all(.*)")
}
}
for _, m := range HTTPMETHOD {
for m := range HTTPMETHOD {
p.addToRouter(m, pattern, route)
}
}
@ -410,7 +435,7 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface)
patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name))
patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name)
route.pattern = pattern
for _, m := range HTTPMETHOD {
for m := range HTTPMETHOD {
p.addToRouter(m, pattern, route)
p.addToRouter(m, patternInit, route)
p.addToRouter(m, patternFix, route)
@ -511,7 +536,7 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
if c.routerType == routerTypeBeego &&
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllName) {
find := false
if _, ok := HTTPMETHOD[strings.ToUpper(methodName)]; ok {
if HTTPMETHOD[strings.ToUpper(methodName)] {
if len(c.methods) == 0 {
find = true
} else if m, ok := c.methods[strings.ToUpper(methodName)]; ok && m == strings.ToUpper(methodName) {
@ -659,7 +684,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
// filter wrong http method
if _, ok := HTTPMETHOD[r.Method]; !ok {
if !HTTPMETHOD[r.Method] {
http.Error(rw, "Method Not Allowed", 405)
goto Admin
}
@ -704,7 +729,6 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
// User can define RunController and RunMethod in filter
if context.Input.RunController != nil && context.Input.RunMethod != "" {
findRouter = true
isRunnable = true
runMethod = context.Input.RunMethod
runRouter = context.Input.RunController
} else {
@ -769,14 +793,20 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
// also defined runRouter & runMethod from filter
if !isRunnable {
//Invoke the request handler
vc := reflect.New(runRouter)
execController, ok := vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
var execController ControllerInterface
if routerInfo.initialize != nil {
execController = routerInfo.initialize()
} else {
vc := reflect.New(runRouter)
var ok bool
execController, ok = vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
}
//call the controller init function
execController.Init(context, runRouter.Name(), runMethod, vc.Interface())
execController.Init(context, runRouter.Name(), runMethod, execController)
//call prepare function
execController.Prepare()
@ -811,6 +841,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
execController.Options()
default:
if !execController.HandlerFunc(runMethod) {
vc := reflect.ValueOf(execController)
method := vc.MethodByName(runMethod)
in := param.ConvertParams(methodParams, method.Type(), context)
out := method.Call(in)
@ -846,10 +877,23 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
Admin:
//admin module record QPS
//admin module record QPS
statusCode := context.ResponseWriter.Status
if statusCode == 0 {
statusCode = 200
}
logAccess(context, &startTime, statusCode)
if BConfig.Listen.EnableAdmin {
timeDur := time.Since(startTime)
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur) {
pattern := ""
if routerInfo != nil {
pattern = routerInfo.pattern
}
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
if runRouter != nil {
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
} else {
@ -858,20 +902,13 @@ Admin:
}
}
if BConfig.RunMode == DEV || BConfig.Log.AccessLogs {
timeDur := time.Since(startTime)
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
var devInfo string
statusCode := context.ResponseWriter.Status
if statusCode == 0 {
statusCode = 200
}
timeDur := time.Since(startTime)
iswin := (runtime.GOOS == "windows")
statusColor := logs.ColorByStatus(iswin, statusCode)
methodColor := logs.ColorByMethod(iswin, r.Method)
resetColor := logs.ColorByMethod(iswin, "")
if findRouter {
if routerInfo != nil {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
@ -891,7 +928,6 @@ Admin:
logs.Debug(devInfo)
}
}
// Call WriteHeader if status code has been set changed
if context.Output.Status != 0 {
context.ResponseWriter.WriteHeader(context.Output.Status)
@ -907,7 +943,7 @@ func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, ex
context.RenderMethodResult(resultValue)
}
}
if !context.ResponseWriter.Started && context.Output.Status == 0 {
if !context.ResponseWriter.Started && len(results) > 0 && context.Output.Status == 0 {
context.Output.SetStatus(200)
}
}
@ -938,3 +974,38 @@ func toURL(params map[string]string) string {
}
return strings.TrimRight(u, "&")
}
func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
//Skip logging if AccessLogs config is false
if !BConfig.Log.AccessLogs {
return
}
//Skip logging static requests unless EnableStaticLogs config is true
if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) {
return
}
var (
requestTime time.Time
elapsedTime time.Duration
r = ctx.Request
)
if startTime != nil {
requestTime = *startTime
elapsedTime = time.Since(*startTime)
}
record := &logs.AccessLogRecord{
RemoteAddr: ctx.Input.IP(),
RequestTime: requestTime,
RequestMethod: r.Method,
Request: fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto),
ServerProtocol: r.Proto,
Host: r.Host,
Status: statusCode,
ElapsedTime: elapsedTime,
HTTPReferrer: r.Header.Get("Referer"),
HTTPUserAgent: r.Header.Get("User-Agent"),
RemoteUser: r.Header.Get("Remote-User"),
BodyBytesSent: 0, //@todo this one is missing!
}
logs.AccessLog(record, BConfig.Log.AccessLogsFormat)
}

View File

@ -695,3 +695,30 @@ func beegoResetParams(ctx *context.Context) {
func beegoHandleResetParams(ctx *context.Context) {
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
}
// YAML
type YAMLController struct {
Controller
}
func (jc *YAMLController) Prepare() {
jc.Data["yaml"] = "prepare"
jc.ServeYAML()
}
func (jc *YAMLController) Get() {
jc.Data["Username"] = "astaxie"
jc.Ctx.Output.Body([]byte("ok"))
}
func TestYAMLPrepare(t *testing.T) {
r, _ := http.NewRequest("GET", "/yaml/list", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Add("/yaml/list", &YAMLController{})
handler.ServeHTTP(w, r)
if strings.TrimSpace(w.Body.String()) != "prepare" {
t.Errorf(w.Body.String())
}
}

View File

@ -14,9 +14,9 @@
// Package redis for session 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:
// import(
@ -24,10 +24,10 @@
// "github.com/astaxie/beego/session"
// )
//
// func init() {
// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``)
// go globalSessions.GC()
// }
// func init() {
// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``)
// go globalSessions.GC()
// }
//
// more docs: http://beego.me/docs/module/session.md
package redis
@ -37,10 +37,11 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/astaxie/beego/session"
"github.com/garyburd/redigo/redis"
"github.com/gomodule/redigo/redis"
)
var redispder = &Provider{}
@ -118,8 +119,8 @@ type Provider struct {
}
// SessionInit init redis session
// savepath like redis server addr,pool size,password,dbnum
// e.g. 127.0.0.1:6379,100,astaxie,0
// savepath like redis server addr,pool size,password,dbnum,IdleTimeout second
// e.g. 127.0.0.1:6379,100,astaxie,0,30
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
@ -149,24 +150,39 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
} else {
rp.dbNum = 0
}
rp.poollist = redis.NewPool(func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rp.savePath)
if err != nil {
return nil, err
var idleTimeout time.Duration = 0
if len(configs) > 4 {
timeout, err := strconv.Atoi(configs[4])
if err == nil && timeout > 0 {
idleTimeout = time.Duration(timeout) * time.Second
}
if rp.password != "" {
if _, err = c.Do("AUTH", rp.password); err != nil {
c.Close()
}
rp.poollist = &redis.Pool{
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rp.savePath)
if err != nil {
return nil, err
}
}
_, err = c.Do("SELECT", rp.dbNum)
if err != nil {
c.Close()
return nil, err
}
return c, err
}, rp.poolsize)
if rp.password != "" {
if _, err = c.Do("AUTH", rp.password); err != nil {
c.Close()
return nil, err
}
}
// some redis proxy such as twemproxy is not support select command
if rp.dbNum > 0 {
_, err = c.Do("SELECT", rp.dbNum)
if err != nil {
c.Close()
return nil, err
}
}
return c, err
},
MaxIdle: rp.poolsize,
}
rp.poollist.IdleTimeout = idleTimeout
return rp.poollist.Get().Err()
}

View File

@ -0,0 +1,220 @@
// 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 redis for session provider
//
// depend on github.com/go-redis/redis
//
// go install github.com/go-redis/redis
//
// Usage:
// import(
// _ "github.com/astaxie/beego/session/redis_cluster"
// "github.com/astaxie/beego/session"
// )
//
// func init() {
// globalSessions, _ = session.NewManager("redis_cluster", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070;127.0.0.1:7071"}``)
// go globalSessions.GC()
// }
//
// more docs: http://beego.me/docs/module/session.md
package redis_cluster
import (
"net/http"
"strconv"
"strings"
"sync"
"github.com/astaxie/beego/session"
rediss "github.com/go-redis/redis"
"time"
)
var redispder = &Provider{}
// MaxPoolSize redis_cluster max pool size
var MaxPoolSize = 1000
// SessionStore redis_cluster session store
type SessionStore struct {
p *rediss.ClusterClient
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}
// Set value in redis_cluster session
func (rs *SessionStore) Set(key, value interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values[key] = value
return nil
}
// Get value in redis_cluster session
func (rs *SessionStore) Get(key interface{}) interface{} {
rs.lock.RLock()
defer rs.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v
}
return nil
}
// Delete value in redis_cluster session
func (rs *SessionStore) Delete(key interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
delete(rs.values, key)
return nil
}
// Flush clear all values in redis_cluster session
func (rs *SessionStore) Flush() error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
}
// SessionID get redis_cluster session id
func (rs *SessionStore) SessionID() string {
return rs.sid
}
// SessionRelease save session values to redis_cluster
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(rs.values)
if err != nil {
return
}
c := rs.p
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime) * time.Second)
}
// Provider redis_cluster session provider
type Provider struct {
maxlifetime int64
savePath string
poolsize int
password string
dbNum int
poollist *rediss.ClusterClient
}
// SessionInit init redis_cluster session
// savepath like redis server addr,pool size,password,dbnum
// e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
if len(configs) > 0 {
rp.savePath = configs[0]
}
if len(configs) > 1 {
poolsize, err := strconv.Atoi(configs[1])
if err != nil || poolsize < 0 {
rp.poolsize = MaxPoolSize
} else {
rp.poolsize = poolsize
}
} else {
rp.poolsize = MaxPoolSize
}
if len(configs) > 2 {
rp.password = configs[2]
}
if len(configs) > 3 {
dbnum, err := strconv.Atoi(configs[3])
if err != nil || dbnum < 0 {
rp.dbNum = 0
} else {
rp.dbNum = dbnum
}
} else {
rp.dbNum = 0
}
rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{
Addrs: strings.Split(rp.savePath, ";"),
Password: rp.password,
PoolSize: rp.poolsize,
})
return rp.poollist.Ping().Err()
}
// SessionRead read redis_cluster session by sid
func (rp *Provider) SessionRead(sid string) (session.Store, error) {
var kv map[interface{}]interface{}
kvs, err := rp.poollist.Get(sid).Result()
if err != nil && err != rediss.Nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
if kv, err = session.DecodeGob([]byte(kvs)); err != nil {
return nil, err
}
}
rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}
// SessionExist check redis_cluster session exist by sid
func (rp *Provider) SessionExist(sid string) bool {
c := rp.poollist
if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 {
return false
}
return true
}
// SessionRegenerate generate new sid for redis_cluster session
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
c := rp.poollist
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
// oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error
// the existed value will be 0
c.Set(sid, "", time.Duration(rp.maxlifetime) * time.Second)
} else {
c.Rename(oldsid, sid)
c.Expire(sid, time.Duration(rp.maxlifetime) * time.Second)
}
return rp.SessionRead(sid)
}
// SessionDestroy delete redis session by id
func (rp *Provider) SessionDestroy(sid string) error {
c := rp.poollist
c.Del(sid)
return nil
}
// SessionGC Impelment method, no used.
func (rp *Provider) SessionGC() {
}
// SessionAll return all activeSession
func (rp *Provider) SessionAll() int {
return 0
}
func init() {
session.Register("redis_cluster", redispder)
}

View File

@ -78,6 +78,8 @@ func (fs *FileSessionStore) SessionID() string {
// SessionRelease Write file session to local file with Gob string
func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
filepder.lock.Lock()
defer filepder.lock.Unlock()
b, err := EncodeGob(fs.values)
if err != nil {
SLogger.Println(err)
@ -164,7 +166,7 @@ func (fp *FileProvider) SessionRead(sid string) (Store, error) {
}
// SessionExist Check file session exist.
// it checkes the file named from sid exist or not.
// it checks the file named from sid exist or not.
func (fp *FileProvider) SessionExist(sid string) bool {
filepder.lock.Lock()
defer filepder.lock.Unlock()

View File

@ -149,7 +149,7 @@ func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime
// 2. Verify MAC. Value is "date|value|mac".
parts := bytes.SplitN(b, []byte("|"), 3)
if len(parts) != 3 {
return nil, errors.New("Decode: invalid value %v")
return nil, errors.New("Decode: invalid value format")
}
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)

View File

@ -74,7 +74,7 @@ func serverStaticRouter(ctx *context.Context) {
if enableCompress {
acceptEncoding = context.ParseEncoding(ctx.Request)
}
b, n, sch, err := openFile(filePath, fileInfo, acceptEncoding)
b, n, sch, reader, err := openFile(filePath, fileInfo, acceptEncoding)
if err != nil {
if BConfig.RunMode == DEV {
logs.Warn("Can't compress the file:", filePath, err)
@ -89,47 +89,53 @@ func serverStaticRouter(ctx *context.Context) {
ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10))
}
http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, sch)
http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, reader)
}
type serveContentHolder struct {
*bytes.Reader
data []byte
modTime time.Time
size int64
encoding string
}
type serveContentReader struct {
*bytes.Reader
}
var (
staticFileMap = make(map[string]*serveContentHolder)
mapLock sync.RWMutex
)
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) {
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) {
mapKey := acceptEncoding + ":" + filePath
mapLock.RLock()
mapFile := staticFileMap[mapKey]
mapLock.RUnlock()
if isOk(mapFile, fi) {
return mapFile.encoding != "", mapFile.encoding, mapFile, nil
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
}
mapLock.Lock()
defer mapLock.Unlock()
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) {
file, err := os.Open(filePath)
if err != nil {
return false, "", nil, err
return false, "", nil, nil, err
}
defer file.Close()
var bufferWriter bytes.Buffer
_, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file)
if err != nil {
return false, "", nil, err
return false, "", nil, nil, err
}
mapFile = &serveContentHolder{Reader: bytes.NewReader(bufferWriter.Bytes()), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
staticFileMap[mapKey] = mapFile
}
return mapFile.encoding != "", mapFile.encoding, mapFile, nil
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
}
func isOk(s *serveContentHolder, fi os.FileInfo) bool {

View File

@ -16,7 +16,7 @@ var licenseFile = filepath.Join(currentWorkDir, "LICENSE")
func testOpenFile(encoding string, content []byte, t *testing.T) {
fi, _ := os.Stat(licenseFile)
b, n, sch, err := openFile(licenseFile, fi, encoding)
b, n, sch, reader, err := openFile(licenseFile, fi, encoding)
if err != nil {
t.Log(err)
t.Fail()
@ -24,7 +24,7 @@ func testOpenFile(encoding string, content []byte, t *testing.T) {
t.Log("open static file encoding "+n, b)
assetOpenFileAndContent(sch, content, t)
assetOpenFileAndContent(sch, reader, content, t)
}
func TestOpenStaticFile_1(t *testing.T) {
file, _ := os.Open(licenseFile)
@ -53,13 +53,13 @@ func TestOpenStaticFileDeflate_1(t *testing.T) {
testOpenFile("deflate", content, t)
}
func assetOpenFileAndContent(sch *serveContentHolder, content []byte, t *testing.T) {
func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) {
t.Log(sch.size, len(content))
if sch.size != int64(len(content)) {
t.Log("static content file size not same")
t.Fail()
}
bs, _ := ioutil.ReadAll(sch)
bs, _ := ioutil.ReadAll(reader)
for i, v := range content {
if v != bs[i] {
t.Log("content not same")

View File

@ -121,6 +121,8 @@ type Schema struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
}
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
@ -130,7 +132,7 @@ type Propertie struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Example string `json:"example,omitempty" yaml:"example,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
@ -141,7 +143,7 @@ type Propertie struct {
// Response as they are returned from executing this operation.
type Response struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Description string `json:"description" yaml:"description"`
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
}

View File

@ -218,9 +218,10 @@ func BuildTemplate(dir string, files ...string) error {
}
if err != nil {
logs.Error("parse template err:", file, err)
} else {
beeTemplates[file] = t
templatesLock.Unlock()
return err
}
beeTemplates[file] = t
templatesLock.Unlock()
}
}

View File

@ -17,6 +17,7 @@ package beego
import (
"errors"
"fmt"
"html"
"html/template"
"net/url"
"reflect"
@ -84,24 +85,24 @@ func DateFormat(t time.Time, layout string) (datestring string) {
var datePatterns = []string{
// year
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
"y", "06", //A two digit representation of a year Examples: 99 or 03
"y", "06", //A two digit representation of a year Examples: 99 or 03
// month
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
"F", "January", // A full textual representation of a month, such as January or March January through December
// day
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
"j", "2", // Day of the month without leading zeros 1 to 31
"j", "2", // Day of the month without leading zeros 1 to 31
// week
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
// time
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
@ -207,14 +208,12 @@ func Htmlquote(text string) string {
'&lt;&#39;&amp;&quot;&gt;'
*/
text = strings.Replace(text, "&", "&amp;", -1) // Must be done first!
text = strings.Replace(text, "<", "&lt;", -1)
text = strings.Replace(text, ">", "&gt;", -1)
text = strings.Replace(text, "'", "&#39;", -1)
text = strings.Replace(text, "\"", "&quot;", -1)
text = strings.Replace(text, "“", "&ldquo;", -1)
text = strings.Replace(text, "”", "&rdquo;", -1)
text = strings.Replace(text, " ", "&nbsp;", -1)
text = html.EscapeString(text)
text = strings.NewReplacer(
``, "&ldquo;",
``, "&rdquo;",
` `, "&nbsp;",
).Replace(text)
return strings.TrimSpace(text)
}
@ -228,17 +227,7 @@ func Htmlunquote(text string) string {
'<\\'&">'
*/
// strings.Replace(s, old, new, n)
// 在s字符串中把old字符串替换为new字符串n表示替换的次数小于0表示全部替换
text = strings.Replace(text, "&nbsp;", " ", -1)
text = strings.Replace(text, "&rdquo;", "”", -1)
text = strings.Replace(text, "&ldquo;", "“", -1)
text = strings.Replace(text, "&quot;", "\"", -1)
text = strings.Replace(text, "&#39;", "'", -1)
text = strings.Replace(text, "&gt;", ">", -1)
text = strings.Replace(text, "&lt;", "<", -1)
text = strings.Replace(text, "&amp;", "&", -1) // Must be done last!
text = html.UnescapeString(text)
return strings.TrimSpace(text)
}

View File

@ -94,7 +94,7 @@ func TestCompareRelated(t *testing.T) {
}
func TestHtmlquote(t *testing.T) {
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&quot;&gt;`
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&#34;&gt;`
s := `<' ”“&">`
if Htmlquote(s) != h {
t.Error("should be equal")
@ -102,8 +102,8 @@ func TestHtmlquote(t *testing.T) {
}
func TestHtmlunquote(t *testing.T) {
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&quot;&gt;`
s := `<' ”“&">`
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&#34;&gt;`
s := `<' ”“&">`
if Htmlunquote(h) != s {
t.Error("should be equal")
}

View File

@ -28,7 +28,7 @@ var (
)
// Tree has three elements: FixRouter/wildcard/leaves
// fixRouter sotres Fixed Router
// fixRouter stores Fixed Router
// wildcard stores params
// leaves store the endpoint information
type Tree struct {

226
unregroute_test.go Normal file
View File

@ -0,0 +1,226 @@
// 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 beego
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
//
// The unregroute_test.go contains tests for the unregister route
// functionality, that allows overriding route paths in children project
// that embed parent routers.
//
const contentRootOriginal = "ok-original-root"
const contentLevel1Original = "ok-original-level1"
const contentLevel2Original = "ok-original-level2"
const contentRootReplacement = "ok-replacement-root"
const contentLevel1Replacement = "ok-replacement-level1"
const contentLevel2Replacement = "ok-replacement-level2"
// TestPreUnregController will supply content for the original routes,
// before unregistration
type TestPreUnregController struct {
Controller
}
func (tc *TestPreUnregController) GetFixedRoot() {
tc.Ctx.Output.Body([]byte(contentRootOriginal))
}
func (tc *TestPreUnregController) GetFixedLevel1() {
tc.Ctx.Output.Body([]byte(contentLevel1Original))
}
func (tc *TestPreUnregController) GetFixedLevel2() {
tc.Ctx.Output.Body([]byte(contentLevel2Original))
}
// TestPostUnregController will supply content for the overriding routes,
// after the original ones are unregistered.
type TestPostUnregController struct {
Controller
}
func (tc *TestPostUnregController) GetFixedRoot() {
tc.Ctx.Output.Body([]byte(contentRootReplacement))
}
func (tc *TestPostUnregController) GetFixedLevel1() {
tc.Ctx.Output.Body([]byte(contentLevel1Replacement))
}
func (tc *TestPostUnregController) GetFixedLevel2() {
tc.Ctx.Output.Body([]byte(contentLevel2Replacement))
}
// TestUnregisterFixedRouteRoot replaces just the root fixed route path.
// In this case, for a path like "/level1/level2" or "/level1", those actions
// should remain intact, and continue to serve the original content.
func TestUnregisterFixedRouteRoot(t *testing.T) {
var method = "GET"
handler := NewControllerRegister()
handler.Add("/", &TestPreUnregController{}, "get:GetFixedRoot")
handler.Add("/level1", &TestPreUnregController{}, "get:GetFixedLevel1")
handler.Add("/level1/level2", &TestPreUnregController{}, "get:GetFixedLevel2")
// Test original root
testHelperFnContentCheck(t, handler, "Test original root",
method, "/", contentRootOriginal)
// Test original level 1
testHelperFnContentCheck(t, handler, "Test original level 1",
method, "/level1", contentLevel1Original)
// Test original level 2
testHelperFnContentCheck(t, handler, "Test original level 2",
method, "/level1/level2", contentLevel2Original)
// Remove only the root path
findAndRemoveSingleTree(handler.routers[method])
// Replace the root path TestPreUnregController action with the action from
// TestPostUnregController
handler.Add("/", &TestPostUnregController{}, "get:GetFixedRoot")
// Test replacement root (expect change)
testHelperFnContentCheck(t, handler, "Test replacement root (expect change)", method, "/", contentRootReplacement)
// Test level 1 (expect no change from the original)
testHelperFnContentCheck(t, handler, "Test level 1 (expect no change from the original)", method, "/level1", contentLevel1Original)
// Test level 2 (expect no change from the original)
testHelperFnContentCheck(t, handler, "Test level 2 (expect no change from the original)", method, "/level1/level2", contentLevel2Original)
}
// TestUnregisterFixedRouteLevel1 replaces just the "/level1" fixed route path.
// In this case, for a path like "/level1/level2" or "/", those actions
// should remain intact, and continue to serve the original content.
func TestUnregisterFixedRouteLevel1(t *testing.T) {
var method = "GET"
handler := NewControllerRegister()
handler.Add("/", &TestPreUnregController{}, "get:GetFixedRoot")
handler.Add("/level1", &TestPreUnregController{}, "get:GetFixedLevel1")
handler.Add("/level1/level2", &TestPreUnregController{}, "get:GetFixedLevel2")
// Test original root
testHelperFnContentCheck(t, handler,
"TestUnregisterFixedRouteLevel1.Test original root",
method, "/", contentRootOriginal)
// Test original level 1
testHelperFnContentCheck(t, handler,
"TestUnregisterFixedRouteLevel1.Test original level 1",
method, "/level1", contentLevel1Original)
// Test original level 2
testHelperFnContentCheck(t, handler,
"TestUnregisterFixedRouteLevel1.Test original level 2",
method, "/level1/level2", contentLevel2Original)
// Remove only the level1 path
subPaths := splitPath("/level1")
if handler.routers[method].prefix == strings.Trim("/level1", "/ ") {
findAndRemoveSingleTree(handler.routers[method])
} else {
findAndRemoveTree(subPaths, handler.routers[method], method)
}
// Replace the "level1" path TestPreUnregController action with the action from
// TestPostUnregController
handler.Add("/level1", &TestPostUnregController{}, "get:GetFixedLevel1")
// Test replacement root (expect no change from the original)
testHelperFnContentCheck(t, handler, "Test replacement root (expect no change from the original)", method, "/", contentRootOriginal)
// Test level 1 (expect change)
testHelperFnContentCheck(t, handler, "Test level 1 (expect change)", method, "/level1", contentLevel1Replacement)
// Test level 2 (expect no change from the original)
testHelperFnContentCheck(t, handler, "Test level 2 (expect no change from the original)", method, "/level1/level2", contentLevel2Original)
}
// TestUnregisterFixedRouteLevel2 unregisters just the "/level1/level2" fixed
// route path. In this case, for a path like "/level1" or "/", those actions
// should remain intact, and continue to serve the original content.
func TestUnregisterFixedRouteLevel2(t *testing.T) {
var method = "GET"
handler := NewControllerRegister()
handler.Add("/", &TestPreUnregController{}, "get:GetFixedRoot")
handler.Add("/level1", &TestPreUnregController{}, "get:GetFixedLevel1")
handler.Add("/level1/level2", &TestPreUnregController{}, "get:GetFixedLevel2")
// Test original root
testHelperFnContentCheck(t, handler,
"TestUnregisterFixedRouteLevel1.Test original root",
method, "/", contentRootOriginal)
// Test original level 1
testHelperFnContentCheck(t, handler,
"TestUnregisterFixedRouteLevel1.Test original level 1",
method, "/level1", contentLevel1Original)
// Test original level 2
testHelperFnContentCheck(t, handler,
"TestUnregisterFixedRouteLevel1.Test original level 2",
method, "/level1/level2", contentLevel2Original)
// Remove only the level2 path
subPaths := splitPath("/level1/level2")
if handler.routers[method].prefix == strings.Trim("/level1/level2", "/ ") {
findAndRemoveSingleTree(handler.routers[method])
} else {
findAndRemoveTree(subPaths, handler.routers[method], method)
}
// Replace the "/level1/level2" path TestPreUnregController action with the action from
// TestPostUnregController
handler.Add("/level1/level2", &TestPostUnregController{}, "get:GetFixedLevel2")
// Test replacement root (expect no change from the original)
testHelperFnContentCheck(t, handler, "Test replacement root (expect no change from the original)", method, "/", contentRootOriginal)
// Test level 1 (expect no change from the original)
testHelperFnContentCheck(t, handler, "Test level 1 (expect no change from the original)", method, "/level1", contentLevel1Original)
// Test level 2 (expect change)
testHelperFnContentCheck(t, handler, "Test level 2 (expect change)", method, "/level1/level2", contentLevel2Replacement)
}
func testHelperFnContentCheck(t *testing.T, handler *ControllerRegister,
testName, method, path, expectedBodyContent string) {
r, err := http.NewRequest(method, path, nil)
if err != nil {
t.Errorf("httpRecorderBodyTest NewRequest error: %v", err)
return
}
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
body := w.Body.String()
if body != expectedBodyContent {
t.Errorf("%s: expected [%s], got [%s];", testName, expectedBodyContent, body)
}
}

View File

@ -31,6 +31,22 @@ func TestSet(t *testing.T) {
}
}
func TestReSet(t *testing.T) {
safeMap := NewBeeMap()
if ok := safeMap.Set("astaxie", 1); !ok {
t.Error("expected", true, "got", false)
}
// set diff value
if ok := safeMap.Set("astaxie", -1); !ok {
t.Error("expected", true, "got", false)
}
// set same value
if ok := safeMap.Set("astaxie", -1); ok {
t.Error("expected", false, "got", true)
}
}
func TestCheck(t *testing.T) {
if exists := safeMap.Check("astaxie"); !exists {
t.Error("expected", true, "got", false)
@ -50,6 +66,21 @@ func TestDelete(t *testing.T) {
}
}
func TestItems(t *testing.T) {
safeMap := NewBeeMap()
safeMap.Set("astaxie", "hello")
for k, v := range safeMap.Items() {
key := k.(string)
value := v.(string)
if key != "astaxie" {
t.Error("expected the key should be astaxie")
}
if value != "hello" {
t.Error("expected the value should be hello")
}
}
}
func TestCount(t *testing.T) {
if count := safeMap.Count(); count != 0 {
t.Error("expected count to be", 0, "got", count)

View File

@ -64,6 +64,9 @@ Struct Tag Use:
func main() {
valid := validation.Validation{}
// ignore empty field valid
// see CanSkipFuncs
// valid := validation.Validation{RequiredFirst:true}
u := user{Name: "test", Age: 40}
b, err := valid.Valid(u)
if err != nil {

View File

@ -25,6 +25,8 @@ import (
const (
// ValidTag struct tag
ValidTag = "valid"
wordsize = 32 << (^uint(0) >> 32 & 1)
)
var (
@ -43,6 +45,8 @@ var (
"Valid": true,
"NoMatch": true,
}
// ErrInt64On32 show 32 bit platform not support int64
ErrInt64On32 = fmt.Errorf("not support int64 on 32-bit platform")
)
func init() {
@ -249,16 +253,39 @@ func parseParam(t reflect.Type, s string) (i interface{}, err error) {
switch t.Kind() {
case reflect.Int:
i, err = strconv.Atoi(s)
case reflect.Int64:
if wordsize == 32 {
return nil, ErrInt64On32
}
i, err = strconv.ParseInt(s, 10, 64)
case reflect.Int32:
var v int64
v, err = strconv.ParseInt(s, 10, 32)
if err == nil {
i = int32(v)
}
case reflect.Int16:
var v int64
v, err = strconv.ParseInt(s, 10, 16)
if err == nil {
i = int16(v)
}
case reflect.Int8:
var v int64
v, err = strconv.ParseInt(s, 10, 8)
if err == nil {
i = int8(v)
}
case reflect.String:
i = s
case reflect.Ptr:
if t.Elem().String() != "regexp.Regexp" {
err = fmt.Errorf("does not support %s", t.Elem().String())
err = fmt.Errorf("not support %s", t.Elem().String())
return
}
i, err = regexp.Compile(s)
default:
err = fmt.Errorf("does not support %s", t.Kind().String())
err = fmt.Errorf("not support %s", t.Kind().String())
}
return
}

View File

@ -106,8 +106,13 @@ func (r *Result) Message(message string, args ...interface{}) *Result {
// A Validation context manages data validation and error messages.
type Validation struct {
// if this field set true, in struct tag valid
// if the struct field vale is empty
// it will skip those valid functions, see CanSkipFuncs
RequiredFirst bool
Errors []*Error
ErrorsMap map[string]*Error
ErrorsMap map[string][]*Error
}
// Clear Clean all ValidationError.
@ -124,7 +129,7 @@ func (v *Validation) HasErrors() bool {
// ErrorMap Return the errors mapped by key.
// If there are multiple validation errors associated with a single key, the
// first one "wins". (Typically the first validation will be the more basic).
func (v *Validation) ErrorMap() map[string]*Error {
func (v *Validation) ErrorMap() map[string][]*Error {
return v.ErrorsMap
}
@ -240,7 +245,21 @@ func (v *Validation) ZipCode(obj interface{}, key string) *Result {
}
func (v *Validation) apply(chk Validator, obj interface{}) *Result {
if chk.IsSatisfied(obj) {
if nil == obj {
if chk.IsSatisfied(obj) {
return &Result{Ok: true}
}
} else if reflect.TypeOf(obj).Kind() == reflect.Ptr {
if reflect.ValueOf(obj).IsNil() {
if chk.IsSatisfied(nil) {
return &Result{Ok: true}
}
} else {
if chk.IsSatisfied(reflect.ValueOf(obj).Elem().Interface()) {
return &Result{Ok: true}
}
}
} else if chk.IsSatisfied(obj) {
return &Result{Ok: true}
}
@ -273,14 +292,35 @@ func (v *Validation) apply(chk Validator, obj interface{}) *Result {
}
}
// AddError adds independent error message for the provided key
func (v *Validation) AddError(key, message string) {
Name := key
Field := ""
parts := strings.Split(key, ".")
if len(parts) == 2 {
Field = parts[0]
Name = parts[1]
}
err := &Error{
Message: message,
Key: key,
Name: Name,
Field: Field,
}
v.setError(err)
}
func (v *Validation) setError(err *Error) {
v.Errors = append(v.Errors, err)
if v.ErrorsMap == nil {
v.ErrorsMap = make(map[string]*Error)
v.ErrorsMap = make(map[string][]*Error)
}
if _, ok := v.ErrorsMap[err.Field]; !ok {
v.ErrorsMap[err.Field] = err
v.ErrorsMap[err.Field] = []*Error{}
}
v.ErrorsMap[err.Field] = append(v.ErrorsMap[err.Field], err)
}
// SetError Set error message for one field in ValidationError
@ -324,7 +364,30 @@ func (v *Validation) Valid(obj interface{}) (b bool, err error) {
if vfs, err = getValidFuncs(objT.Field(i)); err != nil {
return
}
var hasRequired bool
for _, vf := range vfs {
if vf.Name == "Required" {
hasRequired = true
}
currentField := objV.Field(i).Interface()
if objV.Field(i).Kind() == reflect.Ptr {
if objV.Field(i).IsNil() {
currentField = ""
} else {
currentField = objV.Field(i).Elem().Interface()
}
}
chk := Required{""}.IsSatisfied(currentField)
if !hasRequired && v.RequiredFirst && !chk {
if _, ok := CanSkipFuncs[vf.Name]; ok {
continue
}
}
if _, err = funcs.Call(vf.Name,
mergeParam(v, objV.Field(i).Interface(), vf.Params)...); err != nil {
return
@ -376,3 +439,9 @@ func (v *Validation) RecursiveValid(objc interface{}) (bool, error) {
}
return pass, err
}
func (v *Validation) CanSkipAlso(skipFunc string) {
if _, ok := CanSkipFuncs[skipFunc]; !ok {
CanSkipFuncs[skipFunc] = struct{}{}
}
}

View File

@ -391,3 +391,173 @@ func TestRecursiveValid(t *testing.T) {
t.Error("validation should not be passed")
}
}
func TestSkipValid(t *testing.T) {
type User struct {
ID int
Email string `valid:"Email"`
ReqEmail string `valid:"Required;Email"`
IP string `valid:"IP"`
ReqIP string `valid:"Required;IP"`
Mobile string `valid:"Mobile"`
ReqMobile string `valid:"Required;Mobile"`
Tel string `valid:"Tel"`
ReqTel string `valid:"Required;Tel"`
Phone string `valid:"Phone"`
ReqPhone string `valid:"Required;Phone"`
ZipCode string `valid:"ZipCode"`
ReqZipCode string `valid:"Required;ZipCode"`
}
u := User{
ReqEmail: "a@a.com",
ReqIP: "127.0.0.1",
ReqMobile: "18888888888",
ReqTel: "02088888888",
ReqPhone: "02088888888",
ReqZipCode: "510000",
}
valid := Validation{}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
}
func TestPointer(t *testing.T) {
type User struct {
ID int
Email *string `valid:"Email"`
ReqEmail *string `valid:"Required;Email"`
}
u := User{
ReqEmail: nil,
Email: nil,
}
valid := Validation{}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
validEmail := "a@a.com"
u = User{
ReqEmail: &validEmail,
Email: nil,
}
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
u = User{
ReqEmail: &validEmail,
Email: nil,
}
valid = Validation{}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
invalidEmail := "a@a"
u = User{
ReqEmail: &validEmail,
Email: &invalidEmail,
}
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
u = User{
ReqEmail: &validEmail,
Email: &invalidEmail,
}
valid = Validation{}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
}
func TestCanSkipAlso(t *testing.T) {
type User struct {
ID int
Email string `valid:"Email"`
ReqEmail string `valid:"Required;Email"`
MatchRange int `valid:"Range(10, 20)"`
}
u := User{
ReqEmail: "a@a.com",
Email: "",
MatchRange: 0,
}
valid := Validation{RequiredFirst: true}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
valid = Validation{RequiredFirst: true}
valid.CanSkipAlso("Range")
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
}

View File

@ -23,6 +23,16 @@ import (
"unicode/utf8"
)
// CanSkipFuncs will skip valid if RequiredFirst is true and the struct field's value is empty
var CanSkipFuncs = map[string]struct{}{
"Email": {},
"IP": {},
"Mobile": {},
"Tel": {},
"Phone": {},
"ZipCode": {},
}
// MessageTmpls store commond validate template
var MessageTmpls = map[string]string{
"Required": "Can not be empty",
@ -166,12 +176,28 @@ type Min struct {
}
// IsSatisfied judge whether obj is valid
// not support int64 on 32-bit platform
func (m Min) IsSatisfied(obj interface{}) bool {
num, ok := obj.(int)
if ok {
return num >= m.Min
var v int
switch obj.(type) {
case int64:
if wordsize == 32 {
return false
}
v = int(obj.(int64))
case int:
v = obj.(int)
case int32:
v = int(obj.(int32))
case int16:
v = int(obj.(int16))
case int8:
v = int(obj.(int8))
default:
return false
}
return false
return v >= m.Min
}
// DefaultMessage return the default min error message
@ -196,12 +222,28 @@ type Max struct {
}
// IsSatisfied judge whether obj is valid
// not support int64 on 32-bit platform
func (m Max) IsSatisfied(obj interface{}) bool {
num, ok := obj.(int)
if ok {
return num <= m.Max
var v int
switch obj.(type) {
case int64:
if wordsize == 32 {
return false
}
v = int(obj.(int64))
case int:
v = obj.(int)
case int32:
v = int(obj.(int32))
case int16:
v = int(obj.(int16))
case int8:
v = int(obj.(int8))
default:
return false
}
return false
return v <= m.Max
}
// DefaultMessage return the default max error message
@ -227,6 +269,7 @@ type Range struct {
}
// IsSatisfied judge whether obj is valid
// not support int64 on 32-bit platform
func (r Range) IsSatisfied(obj interface{}) bool {
return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj)
}