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

592 Commits

Author SHA1 Message Date
90999717dd Merge branch 'develop' 2016-12-05 23:14:26 +08:00
a20ccde90d beego 1.7.2 2016-12-05 22:58:29 +08:00
d548ddeb5b Merge pull request #2267 from centos-ren/develop
Fix :supervisor work dir
2016-12-05 22:57:38 +08:00
de99ac5da5 Merge pull request #2197 from OlegFX/master
policies implementation
2016-12-05 22:45:07 +08:00
e2d9d34c75 Merge pull request #2272 from szyhf/Fix#2263
Another Fix to #2263
2016-12-05 22:40:54 +08:00
bdb525831d Merge pull request #2293 from DusanKasan/develop
resolves #2291, introduces AndNotCond/OrNotCond to orm.Condition
2016-12-05 22:40:35 +08:00
22dba90ec4 Merge pull request #2305 from songtianyi/fixtypo-Getfiles
typo fix in comments, Getfiles-->GetFiles
2016-12-05 15:09:57 +08:00
7af6dad58e typo fix in comments, Getfiles-->GetFiles 2016-12-05 12:49:21 +08:00
5af26446ec Merge pull request #2299 from amrfaissal/fix-2294
Fix xml.GetSection() function which causes a panic
2016-11-30 14:25:14 +08:00
39d40ba8fa This fixes #2294 2016-11-29 14:55:57 +01:00
5bc3e30653 Added ToString method which converts values of any type to string 2016-11-29 14:55:56 +01:00
34e2b26b99 resolves #2291, introduces AndNotCond/OrNotCond to orm.Condition 2016-11-28 09:49:06 +01:00
24015e9ace Merge pull request #2285 from cat2neat/fix-bodyclose
Fix http body may not be closed
2016-11-20 19:48:09 +08:00
9ff88f0b35 Fix http body may not be closed 2016-11-20 05:16:52 +00:00
27c59a8017 Merge pull request #2278 from mgenov/err_fix
beego/session: return proper error when session is not found
2016-11-13 16:03:55 +08:00
a74ebaa1eb beego/session: return proper error when session is not found
Parent errs was returned instead of err which is returned from the last
statement.
2016-11-13 10:01:29 +02:00
b5c29d6143 Fix #2263
Update db_mysql.go instead of db.go, in order to avoid affect other database.
2016-11-08 13:39:31 +08:00
0a822209c8 Fix :supervisor work dir 2016-11-08 11:14:48 +08:00
3baac14095 Merge pull request #2257 from xiaoqiang0/develop
swagger: add 'Default' for Parameter
2016-11-07 22:48:06 +08:00
ef3655877a swagger: add 'Default' for Parameter 2016-11-03 22:59:23 +08:00
5a8c40710c Merge pull request #2228 from Hepri/develop
Rework getFlatParams for time.Time
2016-10-30 10:48:49 +08:00
b635af5a8c Merge pull request #2248 from smacker/RouterPattern-in-ctx
Add RouterPattern to context.Input
2016-10-30 10:48:15 +08:00
683e6856ef Add RouterPattern to context.Input
Right now beego adds this param only in dev mode, but I noticed that it's very useful to have in prod environment to.
My current use case - filter that sends logs in newrelic. Pattern there will help a lot to generate correct transaction name.
2016-10-28 10:44:16 +07:00
8beefc8bfd Fixed bug when all "time.Time" params in raw sql queries formatted as time 2016-10-17 21:51:31 +05:00
e430de3307 Merge pull request #2223 from tailnode/develop
修复windows上app.conf中include其他配置文件时找不到文件的BUG
2016-10-14 21:11:25 +08:00
2b442e842e fix path issue in windows 2016-10-14 16:52:03 +08:00
41633900da Merge pull request #2218 from WatchtowerSecurity/NameFix
Name fix
2016-10-13 21:24:11 +08:00
aaf6e775d6 Merge pull request #2216 from WatchtowerSecurity/httponlyfix
HTTPOnly Configurable
2016-10-13 21:19:28 +08:00
2f6fc3f62b Merge pull request #2213 from axyu/develop
fix log func call depth bug
2016-10-13 21:18:54 +08:00
84310b1652 Merge pull request #2205 from jirfag/master
add GetUint(8|16|32|64) to controller
2016-10-13 21:17:54 +08:00
5ceac1dd04 string convert int fail use math/big fix #756 2016-10-12 15:04:31 +08:00
51b31c6cb0 Changed the name to match. 2016-10-11 11:06:52 -05:00
5488a5bbd7 Forgot to fix it here 2016-10-11 11:06:22 -05:00
33f7f46670 Updated the name 2016-10-11 10:49:19 -05:00
3c05eafbc4 HTTP Only Configurable 2016-10-10 09:50:34 -05:00
dfa9e03980 fix log func call depth bug 2016-10-09 15:19:21 +08:00
53e996d4c3 Merge pull request #2211 from ihippik/patch-2
Default values
2016-10-08 17:52:07 +08:00
b151a9616e Default values 2016-10-08 11:28:47 +03:00
1effb6ce30 add GetUint(8|16|32|64) to controller 2016-10-02 07:42:06 +00:00
0be05eb47c policies implementation 2016-09-28 21:21:07 +03:00
1090ca0154 Merge pull request #2190 from szyhf/develop
fix to advice in #2187
2016-09-28 20:17:41 +08:00
a328584238 Merge pull request #2193 from LyricTian/develop
httplib add CheckRedirect
2016-09-28 20:16:48 +08:00
f19ad3fdd3 httplib add CheckRedirect 2016-09-28 18:04:51 +08:00
836be7ab9a fix to advice in #2187
Clear BConfig.Log.Outputs's default "console" while user has set "LogOutputs" in config file.
2016-09-28 16:20:41 +08:00
bba04dd864 Merge pull request #2184 from rahal/master
Emailer : Should use config Username only if no From is provided.
2016-09-28 07:56:12 +08:00
9499b3eb90 Emailer : Should use config Username only if no From is provided.
Should fix the case where Username is not an email.
2016-09-27 16:21:41 +01:00
9f6bbe3c51 add foundation link 2016-09-23 14:16:16 +08:00
2d87d4feaf Merge branch 'master' into develop 2016-09-22 23:18:45 +08:00
15a45ccc7b change to 1.7.1 2016-09-22 23:18:22 +08:00
e0c59fcf0b add more comments 2016-09-22 23:17:41 +08:00
083f697c59 Merge pull request #2173 from axyu/develop
fix a spelling mistake
2016-09-21 19:45:46 +08:00
a5a6546b91 fix a spelling mistake 2016-09-21 19:33:12 +08:00
9cafbf6a21 Merge pull request #2170 from ysqi/develop
Support load app config before test Beego
2016-09-19 20:55:20 +08:00
faba0d7273 Support load app config before test Beego 2016-09-19 18:38:56 +08:00
c3116d3601 Merge branch 'astaxie/develop' into develop 2016-09-19 18:16:47 +08:00
868e14b8ba fix #2017 2016-09-15 20:04:45 +08:00
421bf97b84 Support custome recover func fix #2004 2016-09-15 12:16:24 +08:00
c16507607c fix #2161 2016-09-15 11:11:34 +08:00
2b7dd85b92 access log add client request ip 2016-09-15 10:58:46 +08:00
a58115fed2 orm log delete repetition time 2016-09-15 10:54:21 +08:00
f9b5b0f551 Merge pull request #2162 from sergeylanzman/develop
improve Swagger
2016-09-15 08:56:27 +08:00
e53c147129 improve Swagger
1. Add yaml
2. Fix typo scurity => security
3. Make license optional
2016-09-15 00:15:02 +03:00
da0e6e790d Update swagger.go 2016-09-14 23:36:38 +03:00
58ffc6f5f8 fix #1877 2016-09-13 22:43:40 +08:00
d5fb74aa94 Merge pull request #2158 from simpleelegant/develop
Add support "SELECT FOR UPDATE" to orm. Resolve issue #2157
2016-09-12 21:48:14 +08:00
11247d41a7 Add support "SELECT FOR UPDATE" to orm. Resolve issue #2157 2016-09-12 20:07:30 +00:00
5b21c7cd71 fix #1802 2016-09-12 21:13:21 +08:00
dd0f05b1f1 fix the method color 2016-09-11 22:00:14 +08:00
a32241e7d3 fix #2142 2016-09-11 21:27:27 +08:00
0ef357ebd7 session:output error 2016-09-11 21:02:11 +08:00
32dd976620 Merge pull request #2146 from philchia/develop
Fix the typo
2016-09-08 17:42:10 +08:00
fcd8a2024e Add jianliao and slack log adapter 2016-09-08 17:21:11 +08:00
30661472c8 Fix the typo 2016-09-07 13:33:11 +08:00
6ced26660e Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-09-06 23:05:54 +08:00
7d6c45d4c9 add RegisterModelWithSuffix #2140 2016-09-06 23:05:41 +08:00
98740fddac Merge pull request #2138 from kbynd/patch-1
RequestURI captures the signature field as well.
2016-09-04 16:52:59 +08:00
6d3042f5e5 RequestURI captures the signature field as well.
This in turn results is failure of signature based validation. So what is need is only "/api/resource/action". which is given by ctx.Input.URL()
2016-09-04 11:36:17 +05:30
8b9d6eee1a Merge pull request #2132 from Zaaksam/master
beego.ParseForm() improvement
2016-09-02 16:23:14 +08:00
11ef5929aa update TestParseForm 2016-09-02 16:08:04 +08:00
c697b98006 enhancement 2016-09-01 23:28:34 +08:00
7c2e563879 beego.ParseForm() improvement 2016-09-01 15:04:57 +08:00
56aa224a6e simplfy the code 2016-08-31 22:47:31 +08:00
8c37a07adb optimize the ORM 2016-08-31 00:07:19 +08:00
161c061376 fix cache/memcache test 2016-08-30 23:24:30 +08:00
aa091cea42 improvement the error if use &&Struct 2016-08-30 22:02:11 +08:00
7df74c0a30 fix #1521 2016-08-30 21:24:56 +08:00
1e1e900278 fix #2126 2016-08-30 21:00:27 +08:00
fb2343567b fix #2125 2016-08-30 20:40:46 +08:00
3f67c62dd8 revert the snakeString 2016-08-30 00:15:02 +08:00
bb860d2752 Merge pull request #2124 from astaxie/revert-2071-issue_accept_encoding
Revert "route variables should not have underline"
2016-08-29 13:42:53 +08:00
f280dab880 Revert "route variables should not have underline" 2016-08-29 13:42:40 +08:00
7283aead42 Merge pull request #2116 from raphael10241024/self
fix#2039 & test
2016-08-24 23:15:53 +08:00
0ad4038d9f fix#2039 & test 2016-08-24 16:04:22 +08:00
227678c2ef Set SetLogFuncCallDepth default to 4 2016-08-23 23:48:47 +08:00
c611da2e3c Merge pull request #2112 from lday0321/master
log output format improvement
2016-08-23 23:18:10 +08:00
00e986cd3b log output format improvement
move log level info ahead to filename info, better readability
2016-08-23 16:16:57 +08:00
bed907653e Merge pull request #2102 from tnextday/develop
update swagger ParameterItems
2016-08-22 11:42:22 +08:00
7253ff2f8c update schema define 2016-08-20 15:03:41 +08:00
715c2c4312 Merge commit 'efbde1ee77517486eac03e814e01d724ddad18e6' into develop 2016-08-20 11:22:17 +08:00
9224cd3ef7 add ParameterItems in Parameter 2016-08-20 11:22:03 +08:00
efbde1ee77 add *Item into Schema 2016-08-19 23:52:19 +08:00
8b525b1aa5 fix #1656 2016-08-19 00:31:46 +08:00
86b3162aff fix #1695 update docs 2016-08-19 00:11:19 +08:00
3c016a0b2d Merge pull request #2100 from tnextday/develop
update swagger define
2016-08-18 23:29:06 +08:00
ffd748bf75 update swagger 2016-08-18 22:26:15 +08:00
1c54ff27c4 update swagger define 2016-08-18 21:24:54 +08:00
7760d24761 fix the typo 2016-08-17 23:52:34 +08:00
3672f96a9d fix the typo 2016-08-17 22:56:21 +08:00
68311b286e gofmt -s -w . 2016-08-17 22:49:30 +08:00
bed1d9bd27 update the performance 2016-08-17 22:33:36 +08:00
44a57e86dd fix #2090 2016-08-17 22:05:54 +08:00
3362f83662 change to v1.7.0 2016-08-16 23:54:52 +08:00
7813cb5783 Merge pull request #2096 from Maxgis/feature_session
session.NewManager second params shoube be a object not a string
2016-08-16 23:51:35 +08:00
ef0d3d80dc set session.managerconfig public 2016-08-13 21:07:27 +08:00
47b238728d Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-08-13 14:44:22 +08:00
1b4f30e11e update swagger 2016-08-13 14:43:56 +08:00
61c9387edd Merge pull request #2085 from danielscottt/reset-params
adds ability to reset params after a filter runs
2016-08-09 07:24:08 +08:00
957db1362f update swagger definistion 2016-08-08 16:45:11 +08:00
0e786fa4af adds ability to reset params after a filter runs
When a filter is run _after_ the router completes, it's input params,
such as `":splat"` will have been overwritten by the filter's router pass.
This commit adds the ability to tell the router to revert to the previous input
params after running a filter.
2016-08-07 07:44:30 -07:00
01c3812520 Merge pull request #2086 from Maxgis/issue_accept_encoding
Refactoring
2016-08-07 16:25:51 +08:00
ce6f19871c Refactoring 2016-08-06 21:13:58 +08:00
74778bb9d4 Merge pull request #1900 from redfoxli/patch-1
close socket when http client request over
2016-08-02 10:01:53 +08:00
7b542e612f Merge pull request #1964 from qAison/master
fix fk field null value
2016-08-02 10:00:24 +08:00
3ca68f9e30 Merge pull request #1976 from ysqi/develop
Fxied bug and Optimized code
2016-08-02 09:58:58 +08:00
1a52a34544 Merge pull request #2003 from GuyCheung/develop
add os.Chmod when create log file
2016-08-02 09:57:51 +08:00
86528c7b3c Merge pull request #2030 from saturn4er/feature/add_template_prefix_field
Add template prefix field to controller
2016-08-02 09:54:01 +08:00
ce6d673933 Merge pull request #2045 from Maxgis/master
avoid  error when the  callback function not exisit
2016-08-02 09:50:52 +08:00
c2aeab78aa Merge pull request #2053 from fudali113/develop
orm insert or update
2016-08-02 09:40:43 +08:00
0cad8207ec Merge pull request #2071 from Maxgis/issue_accept_encoding
route variables should not have underline
2016-08-01 14:28:25 +08:00
6f0a985755 update test 2016-07-30 23:14:29 +08:00
15c048d6ca update test 2016-07-30 22:07:35 +08:00
d5c339530a remove debug 2016-07-30 22:05:59 +08:00
b89bfe76d0 update route variables should not have underline 2016-07-30 21:57:23 +08:00
125030800b Merge pull request #1997 from amrfaissal/newfeature-enhanced-logs
Enhanced logging during DEV mode
2016-07-29 21:37:22 +08:00
e4fe54f6cd Merge pull request #2067 from Maxgis/issue_accept_encoding
reflection
2016-07-29 09:59:59 +08:00
d85293b7c0 Fixed bug and added more tests 2016-07-28 11:37:28 +02:00
5d1e60b468 reflection 2016-07-28 11:56:55 +08:00
2b867f8152 Removed external dependency 2016-07-27 17:42:28 +02:00
909afa9352 Merge pull request #2065 from Maxgis/issue_accept_encoding
remove not support encoding
2016-07-27 14:13:13 +08:00
d08e3d1b11 Merge pull request #2064 from Maxgis/feature_static
redirect path should add  query params
2016-07-27 12:55:21 +08:00
5485e1334f remove not support encoding 2016-07-27 09:31:53 +08:00
4b3ed53158 redirect should add query params 2016-07-26 19:54:10 +08:00
cacf6cde19 update db.go 2016-07-26 14:27:22 +08:00
4e10100575 update orm_test 2016-07-26 12:44:16 +08:00
f471ee9025 update orm_test 2016-07-26 12:19:06 +08:00
182a21172f Optimize the code logic 2016-07-26 11:15:59 +08:00
02330c6085 Merge pull request #2062 from Maxgis/feature_static
减少没有必要的代码以及静态前缀前面有/
2016-07-25 22:19:38 +08:00
e951c555e4 Merge branch 'astaxie-develop' into feature_static 2016-07-24 18:01:49 +08:00
6a4ebc67ac merge 2016-07-24 18:01:19 +08:00
33ef34b95d 减少没有必要的代码以及静态前缀前面有/ 2016-07-24 15:29:34 +08:00
bf17558d06 update “modification hardcode 2 2016-07-22 12:25:30 +08:00
e0e888ab8f update “modification hardcode 2016-07-22 12:10:37 +08:00
3583ad8cc0 update annotation 2016-07-21 15:49:55 +08:00
e2316c4b9e update 2016-07-20 17:28:26 +08:00
6d1b203bca update 2016-07-20 16:52:14 +08:00
50c5df32b1 update 2016-07-20 16:26:02 +08:00
530c32017c update 2016-07-20 15:33:30 +08:00
ec521ad166 update 2016-07-20 15:13:18 +08:00
4b8ecced83 orm insert or update 2016-07-20 14:37:05 +08:00
d11823548b Merge pull request #2050 from simpleton/bug/original_scheme
fix(context): retrieve scheme from X-Forwarded-Proto when it isn't none
2016-07-19 11:13:14 +08:00
ee26279311 fix(context): retrieve scheme from X-Forwarded-Proto when it isn't none 2016-07-19 00:36:51 +08:00
8099a81b7a avoid error when the callback function not exisit 2016-07-15 19:13:35 +08:00
0943ef9e74 Add TplPrefix to TplName also. 2016-07-14 11:48:49 +03:00
a2e63a3820 Merge pull request #1993 from NerdsvilleCEO/develop
Add meta fields with required flag on RenderForm
2016-07-14 10:47:21 +08:00
a08e937cf0 Merge pull request #2032 from ShevYan/make-go-vet-pass
use keyed fields to pass go vet
2016-07-14 10:38:05 +08:00
9ab5f6d808 use keyed fields to pass go vet 2016-07-06 12:53:47 +08:00
fee06a23bd Add template prefix field to controller 2016-07-03 14:44:11 +03:00
e0393b721c Change meta to required, and refactor a bit to cover edge cases 2016-06-30 10:32:53 -07:00
84b6bef7d0 Required field useful for not only input 2016-06-28 16:39:09 -07:00
8917fe44a9 Add test functions 2016-06-28 16:31:45 -07:00
21c7821692 Add test function 2016-06-28 16:24:32 -07:00
f9f92b4f61 Merge meta tag into parseFormField 2016-06-28 15:19:58 -07:00
c893b3472c added dep to .travis.yml 2016-06-24 15:51:59 +02:00
479dfdbd40 added support for Windows terminals 2016-06-24 15:38:18 +02:00
1f68e5a705 Merge pull request #2006 from victorpopkov/develop
Add support for time.Time pointer in struct types
2016-06-24 12:33:37 +08:00
415b9cf310 add support for time.Time pointer in struct types
Allow to use pointer *time.Time as a type in combination with orm tags in struct. This enables to treat them as "empty" in JSON marshaling/unmarshaling when using 'json:"null,omitempty'.
2016-06-22 16:57:05 +03:00
ed474b517d Merge pull request #1979 from ysqi/ormfix
ignore case of tag and fixed bug for columName
2016-06-22 12:00:37 +08:00
9572fdcf9a update error on type of w.Perm; change unit test perm value 2016-06-22 09:57:16 +08:00
cb3f240f44 add os.Chmod when create log file 2016-06-21 15:52:31 +08:00
844a3b0ffd removed unused import 2016-06-17 17:47:12 +02:00
448be6e58c added compatibility for go1.4 2016-06-17 17:39:32 +02:00
2bd743fcff Enhanced logging during DEV mode 2016-06-17 15:56:52 +02:00
0d3a806c23 Add meta fields with required flag 2016-06-15 17:17:50 -07:00
2c1cea08dd New func to find router info for context 2016-06-05 14:03:20 +08:00
3f016840db fixed test error 2016-06-04 10:41:55 +08:00
d528fafd43 ignore case of tag and fixed bug for columName 2016-06-03 22:06:43 +08:00
b7e3402995 Sport Error exeception for outside 2016-06-01 21:30:04 +08:00
b807362c39 Reset 2016-06-01 20:33:10 +08:00
d9b05e6b3f Print complete URL after running 2016-06-01 20:18:11 +08:00
e9f967102c Fixed parese ini file with empty space line 2016-06-01 19:58:35 +08:00
2e0bcf611c go fmt 2016-06-01 19:57:08 +08:00
2ebf3cd450 Merge branch 'astaxie/develop' into develop 2016-06-01 19:54:35 +08:00
1fe2226c11 Merge pull request #1968 from wy65701436/develop
modify the error log for registerModel.
2016-05-30 10:41:46 +08:00
d4d7621942 modify the error log for registerModel to tell user the default hard code PK is 'id'. 2016-05-27 02:03:58 -07:00
761b6c129c count func add support group by 2016-05-27 14:34:45 +08:00
8f0749ddee fix fk field null value 2016-05-27 13:53:28 +08:00
7b051e7ad1 Merge pull request #1940 from MachineShop-IOT/develop
More flexible support for template engines
2016-05-24 11:54:39 +08:00
24b8870637 move paren to new line 2016-05-23 21:43:18 -06:00
cef91db28e Merge pull request #1956 from gitchs/master
Ctx.Redirect patch
2016-05-23 13:50:59 +08:00
2165fb6e67 Merge pull request #1938 from wincss/develop
fix #1936
2016-05-22 21:29:32 +08:00
05e929ed8c Merge pull request #1948 from nullne/develop
fix bug with file permission in log module
2016-05-22 21:25:17 +08:00
f32392e956 net/http will do it better 2016-05-21 15:19:21 +08:00
e21b425957 Merge pull request #1953 from lcbluestorm/master
add ssdb session provider
2016-05-20 22:53:23 +08:00
e77a591a6c delete test file 2016-05-20 17:25:22 +08:00
4890dd708c clear code 2016-05-20 10:27:18 +08:00
6234b50111 test done 2016-05-19 21:00:04 +08:00
2c12383263 remove attribute perm to make it more brief 2016-05-17 10:29:05 +08:00
e50f4f5631 complete sess_ssdb.go 2016-05-15 12:15:40 +08:00
d679a4b865 fix bug with file permission in log module 2016-05-14 10:54:09 +08:00
153d76e200 More flexible support for template engines 2016-05-10 22:54:33 -06:00
0e4b9563ae add test file 2016-05-10 20:08:15 +08:00
27be1e7ca3 add ssdb-session interface 2016-05-10 19:56:45 +08:00
a4d4b8de77 fix #1936 2016-05-10 17:45:06 +08:00
dae2a36eb5 Merge pull request #1924 from ysqi/develop
implement some features
2016-05-06 13:58:29 +08:00
7ceff43db6 Fixed error in window os 2016-05-06 13:26:48 +08:00
85a8c5c4f4 Merge pull request #1927 from JessonChan/log_daily_rotate
file rotate by day
2016-05-06 12:55:21 +08:00
b28581a463 make daily rotate 2016-05-06 12:11:14 +08:00
fa8d94fa69 file rotate by day test 2016-05-06 12:09:00 +08:00
3c8ed9adfc Merge branch 'astaxie/develop' into develop
# Conflicts:
#	parser.go
2016-05-05 19:30:31 +08:00
8210fd12d1 Fixed router fileName error in window 2016-05-05 19:28:09 +08:00
77ff15ee33 Implement Error interface for validation error 2016-05-05 19:26:03 +08:00
4d8e1f93ff Merge pull request #1909 from albertma/master
Start timer task Multiple times
2016-04-29 11:34:52 +08:00
e607be6fa6 gofmt the code 2016-04-29 10:28:10 +08:00
73f499948a Merge pull request #1906 from Maxgis/issue_redundancy
remove redundancy code and add port confict tip
2016-04-28 09:59:47 +08:00
a7452388d9 Merge pull request #1912 from ysqi/issue02
Fixed some bug
2016-04-28 09:44:08 +08:00
2a2b433e19 go fmt 2016-04-27 23:57:36 +08:00
272271f588 Change comment router file info 2016-04-27 23:57:22 +08:00
fa7416452e Change set test mode way 2016-04-27 22:26:32 +08:00
245664010c Go Vet 2016-04-27 22:18:22 +08:00
9e17f518b8 Fixed print format error info 2016-04-27 22:17:53 +08:00
830985b90b QueryEscape Download File Name 2016-04-27 22:05:31 +08:00
a271d67ba4 1.fix blank issue 2016-04-26 18:13:52 +08:00
710c7c79d4 1.fix start task twice issue 2016-04-26 18:11:49 +08:00
5402c753fb remove redundancy code and add port confict tip 2016-04-26 10:16:53 +08:00
520a417cca Merge pull request #1874 from Maxgis/master
change limit 1000 to 1,reduce the amount the data
2016-04-26 09:56:44 +08:00
56dc9bf622 add return ErrMultiRows 2016-04-25 15:51:12 +08:00
b36afadbdc close socket when http client request over
To avoid a large number of  TIME_WAIT in server which http client talk with
2016-04-22 13:54:55 +08:00
e89f562396 Merge pull request #1897 from yuyongsheng/develop
add/get session id into/from http header, check the session name in http header
2016-04-21 13:04:28 +08:00
5aa085bf41 1. remove the invalid comments.
2. allow the user to config "Enable" store/get sessionId into/from http header
3. allow the user to config "Enable" get sessionId from Url query
4. when enable sessionId in http header, check the sessionName format as CanonicalMIMRHeaderKey, then panic if not.
2016-04-21 09:57:44 +08:00
70f3f6b8b1 Merge pull request #1883 from JessonChan/config_improve
Config improve
2016-04-20 13:19:47 +08:00
86e18bf6f9 Merge pull request #1882 from miraclesu/fix/orm_multi_insert
orm: fix multi insert panic
2016-04-20 13:06:47 +08:00
14159eaa7e Merge pull request #1891 from gitchs/develop
fix spell error
2016-04-20 09:49:11 +08:00
df27c96102 fix spell error 2016-04-18 19:37:38 +08:00
5ac254bf61 ut bug fixed 2016-04-18 19:18:46 +08:00
423f2dad35 list the config to map 2016-04-18 19:16:39 +08:00
203ab3eba8 ut fix 2016-04-18 18:41:40 +08:00
0c32255d14 config test 2016-04-18 17:45:39 +08:00
9ce6dc4cdf remove test ; because rows are returned in an unspecified order 2016-04-13 21:04:46 +08:00
903e21bef2 orm: add test case for InsertMulti 2016-04-13 20:22:27 +08:00
3a3f70027c orm: fix panic multi insert when slice lenght is 1 & the value is pointer 2016-04-13 20:14:02 +08:00
4cd2408248 log to Stderr 2016-04-13 19:41:07 +08:00
cb0c006cfa add session config 2016-04-13 19:37:55 +08:00
ce4fc7bfcd simplify the value assign 2016-04-13 17:51:54 +08:00
81c6c898cf remove orm one function thorw ErrMultiRows error 2016-04-13 10:36:12 +08:00
002dcaab23 Merge pull request #1880 from JessonChan/log_rotate_fix
Log rotate fix
2016-04-13 09:54:41 +08:00
abaa1bbcac file rotate file test 2016-04-13 09:05:16 +08:00
314a447d57 Merge pull request #1879 from miraclesu/feature/orm_text
orm: use `text` as postgres default type
2016-04-12 23:19:22 +08:00
9679f5e22a reduce the data transmission 2016-04-12 21:28:29 +08:00
5185816942 orm: use text as postgres default type 2016-04-12 21:19:43 +08:00
22617aeb13 file rotate name fixed 2016-04-12 15:05:35 +08:00
9c400778d3 Merge pull request #1863 from JessonChan/xsrf_fix
Xsrf fix
2016-04-12 14:26:20 +08:00
f6ad2cf848 Merge pull request #1875 from miraclesu/feature/orm_json
orm: add json & jsonb type support
2016-04-12 14:25:54 +08:00
7906b18d89 Merge pull request #1864 from yoki123/patch-2
Update README.md
2016-04-12 12:02:04 +08:00
99f1e6c8b5 orm: fix golint 2016-04-12 11:00:31 +08:00
e95bef1331 orm: add test case for json & jsonb type support 2016-04-12 11:00:31 +08:00
657744efb1 orm: add json & jsonb type support 2016-04-12 11:00:31 +08:00
6da765c465 Using a different PostgreSQL Version 2016-04-11 23:12:35 +08:00
1cac38b664 show postgres versionn 2016-04-11 21:19:10 +08:00
ba3a1f8457 update go vet 2016-04-11 13:15:14 +08:00
d7f41ccd0c update vet 2016-04-11 12:51:44 +08:00
881d010c01 upgrade swagger from 1.2 to 2.0 2016-04-11 11:56:43 +08:00
553078d956 change limit 1000 to 1,reduce the amount the data 2016-04-11 09:02:22 +05:30
528f273e58 Update README.md
fix url
2016-04-08 14:28:45 +08:00
53d680a493 rand func modify 2016-04-08 14:24:23 +08:00
ed0e6419f0 context xsrf test 2016-04-08 14:07:39 +08:00
a99c0d4025 context xsrf test 2016-04-08 14:04:25 +08:00
301dcfb626 context xsrf bug fixed 2016-04-08 14:04:10 +08:00
6362dc397a Merge pull request #1856 from b055/develop
added functionality for column type time
2016-04-06 09:25:46 +08:00
813f47fd41 gofmt test files 2016-04-05 15:38:42 +08:00
933ac0f369 Merge pull request #1858 from ysqi/issue
fix the issue #1850,the source of the problem is PR #1805
2016-04-05 09:06:14 +08:00
885d45db05 fix the issue #1850,the source of the problem is PR #1805 2016-04-04 21:32:43 +08:00
d49c7f96cb added functionality for column type time
updated the model_fields to cater for the time field type
2016-04-03 16:58:19 +02:00
ebdf4412b3 Merge pull request #1848 from JessonChan/template_fix
make template execution be expected
2016-04-01 21:03:39 +08:00
8ec6dd93cf make template execution be expected 2016-04-01 18:10:00 +08:00
fe4fa6a095 Merge pull request #1636 from ysqi/environmentVar
Support get environment variables in config
2016-04-01 13:50:38 +08:00
ccc84dd8eb Merge pull request #1762 from saturn4er/htmlEngines
Implemented possibility to add custom template engines
2016-03-30 16:43:39 +08:00
734bbb3337 remove some space 2016-03-30 15:48:38 +08:00
caa404cec1 Merge pull request #1840 from youngsterxyf/develop
To support `go run`
2016-03-30 15:46:47 +08:00
52fbab329d Merge pull request #1844 from mishudark/master
delete not used variable (status int) in output.go functions
2016-03-30 15:44:32 +08:00
8ce9f69b4d Merge pull request #1843 from JessonChan/beelog_bug_fix
Beelog bug fix
2016-03-30 15:44:20 +08:00
96a5d09ef0 add format header test 2016-03-30 15:13:01 +08:00
814b673e3c add a comment to the future bug 2016-03-30 14:51:46 +08:00
99436a75b1 format time header 5% faster than before 2016-03-30 14:31:28 +08:00
8e82ed319b beelog bug fixed 2016-03-30 14:31:16 +08:00
eae2147735 chore(output.go): delete not used variable (status int) in check status functions 2016-03-29 23:28:53 -06:00
e281b6e82a improve 2016-03-30 10:49:39 +08:00
adaa4ab929 Fix index out of range if there is no file extension 2016-03-29 17:15:43 +03:00
7e65338c87 Change key format
key format : ${ENV_PART||defaultValue} or  ${ENV_PART}
2016-03-29 21:47:33 +08:00
5bd7d8c43f Merge branch 'astaxie/develop' into environmentVar 2016-03-29 20:55:29 +08:00
561e7115f3 To support go run 2016-03-29 18:16:38 +08:00
220cf91180 Merge pull request #1837 from JessonChan/develop
add function of beego/logs
2016-03-29 11:37:09 +08:00
cbd6f31b66 Merge pull request #1839 from JessonChan/log_rotate_fix
file rotate bug
2016-03-29 11:35:44 +08:00
221306fff4 file rotate bug 2016-03-29 10:05:56 +08:00
f05bb2ecd3 add function of beego/logs 2016-03-28 15:18:51 +08:00
699de2ae75 Merge pull request #1826 from miraclesu/feature/orm_auto
orm: support insert a specified value to auto field
2016-03-28 12:04:31 +08:00
fb77464d69 golink: map range only key stlye 2016-03-27 15:07:51 +08:00
1794c52d65 orm: fix postgres sequence value 2016-03-27 15:06:57 +08:00
3ca44071e6 orm: insert specified values for insertMulti 2016-03-26 21:51:05 +08:00
e0a36fb61e Merge branch 'develop' into feature/orm_auto 2016-03-26 21:16:52 +08:00
85a9b05495 Merge pull request #1833 from youngsterxyf/develop
in `session` package, add some helpful tools to help subpackage logging information
2016-03-25 21:15:23 +08:00
70108131e6 Merge pull request #1832 from JessonChan/log_enhancement
Log enhancement
2016-03-25 21:12:06 +08:00
45f2390128 logger changed 2016-03-25 15:13:28 +08:00
826f81f479 remove from init method 2016-03-25 15:04:52 +08:00
4dbbae61e0 Merge pull request #1828 from miraclesu/fix/orm_read_or_create
orm: fix painc when pk is uint on ReadOrCreate
2016-03-25 14:44:41 +08:00
e59271662c fix bee fix 2016-03-25 13:25:29 +08:00
fa4a231cd4 duration change to second 2016-03-25 11:46:19 +08:00
850dc59b6e should remove when 2.0 is released 2016-03-25 11:13:39 +08:00
6d0fe8c4f4 go fmt cache file 2016-03-25 11:05:20 +08:00
2db8c753fd bee fix 2016-03-25 10:56:15 +08:00
56860d1fea not just export a variable 2016-03-25 10:48:59 +08:00
94bde3a777 change to logs 2016-03-25 10:31:48 +08:00
3300db832b in session package, add a helpful variable SLogger to help subpackage logging information 2016-03-24 22:43:57 +08:00
52a0b657b7 for better performance 2016-03-24 20:27:00 +08:00
f02ff0420d no need to call Sprintf when no args 2016-03-24 20:22:42 +08:00
3e2ffa545f orm: fix postgres returning id error 2016-03-24 20:03:45 +08:00
06299fa47b makes console as default logger 2016-03-24 19:32:29 +08:00
d8bed89c44 set console as default logger 2016-03-24 19:15:14 +08:00
0814eefa62 refactor writeMsg function 2016-03-24 18:21:52 +08:00
8344a60552 add to upper case 2016-03-24 17:49:39 +08:00
0fb4a8af24 function change 2016-03-24 17:46:52 +08:00
a6c1377f91 change to 0 logger 2016-03-24 17:43:45 +08:00
cdfd830f65 rename log format 2016-03-24 17:43:16 +08:00
98dfecfd8a change beego log function to logs function 2016-03-24 17:39:29 +08:00
03840f3fe8 give each of the adapter a neme 2016-03-24 17:38:26 +08:00
2e6a23743b refactor logs package 2016-03-24 17:37:56 +08:00
2362ca00b5 Merge pull request #1827 from ysqi/issue
Check file before download
2016-03-24 13:33:44 +08:00
b7d1afbf86 Remote empty line 2016-03-24 08:35:42 +08:00
eaf38bb096 orm: add test case for uint pk read or create 2016-03-23 21:59:09 +08:00
3be6688cd1 orm: fix painc when pk is uint on ReadOrCreate 2016-03-23 21:57:57 +08:00
1eab11ca90 fixed #1815 check file before download 2016-03-23 21:27:28 +08:00
c4276d31c5 Merge branch 'astaxie/develop' into issue 2016-03-23 21:26:55 +08:00
8f70df6c7b orm: add test case for insert specified value to auto field 2016-03-23 20:28:22 +08:00
1786b16e61 orm: support insert a specified value to auto field 2016-03-23 20:16:18 +08:00
9f18813c2b Merge branch 'master' into develop 2016-03-23 17:05:50 +08:00
88c5dfa6ea update issue template 2016-03-23 17:05:40 +08:00
0a86926522 Merge branch 'master' into develop 2016-03-23 16:56:53 +08:00
b78de2b440 add ISSUE_TEMPLATE 2016-03-23 16:56:40 +08:00
6c0979c314 Merge pull request #1805 from JessonChan/abort_panic_bug
Abort panic bug
2016-03-23 10:22:32 +08:00
5858607f49 go fmt error_test.go 2016-03-22 18:32:14 +08:00
1a401af23b copyright 2016-03-22 18:28:44 +08:00
b2098266a3 add error test 2016-03-22 18:27:29 +08:00
3ac90df5fa Merge pull request #1794 from youngsterxyf/issue1789
fix issue1789: when testing, load config explicitly and forcibly
2016-03-22 17:21:57 +08:00
9e9671d8cd Merge pull request #1799 from JessonChan/router_develop
Router Filter Improve
2016-03-22 17:15:28 +08:00
d3b54c46e3 Merge pull request #1808 from JessonChan/gzip_improve
Gzip improve
2016-03-22 17:13:20 +08:00
7bad3d1c67 change the compress leve to [0~9] 2016-03-22 16:47:11 +08:00
4db78f243e change the function args of init gzip method 2016-03-22 16:42:42 +08:00
ba7a809de8 Merge pull request #1810 from miraclesu/fix/orm_miss_pk
orm: fix miss pk when pk is negative
2016-03-22 10:09:35 +08:00
630f77bca3 update travis 2016-03-21 11:18:38 +08:00
f2ed27cc8f make sure works for travis 2016-03-21 11:10:57 +08:00
959b9a5a58 config index out of range bug fixed 2016-03-21 09:32:41 +08:00
142f4c9f42 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-03-21 09:15:47 +08:00
33ae75b251 golint check only works on Go 1.5 2016-03-21 08:50:56 +08:00
84ae930c64 orm: Add test case for integer pk 2016-03-18 21:58:11 +08:00
99c0b1e338 Merge pull request #1803 from JessonChan/log_develop
fix rotate  read-write lock race
2016-03-18 15:43:46 +08:00
4caf044be2 getMethodOnly assign fixed 2016-03-18 15:18:00 +08:00
a3d4218d9d orm: fix miss pk when pk is negative 2016-03-17 21:41:35 +08:00
9f21928a90 some typo fixed 2016-03-17 20:07:24 +08:00
0b401481ef go fmt the comment 2016-03-17 19:59:48 +08:00
57eace07a7 comment update 2016-03-17 19:52:09 +08:00
35e34261ab gzip method support 2016-03-17 19:40:29 +08:00
5a9bff2000 init gzip level 2016-03-17 19:09:38 +08:00
48147f50d8 add some gzip future 2016-03-17 19:09:21 +08:00
0859ec570c refactor of error response and fix err code bug 2016-03-17 09:46:34 +08:00
443d71397c write error to response 2016-03-17 09:35:14 +08:00
ec35bd0c28 orm log header flag 2016-03-16 18:04:27 +08:00
2b1316c738 data race bug fixed 2016-03-16 18:04:07 +08:00
94599013fc url to lower case 2016-03-16 07:53:36 +08:00
565c4a4d59 make the code run more fast 2016-03-15 18:50:18 +08:00
34615ee8fc add router filter enable flag 2016-03-15 18:37:54 +08:00
c51bc86d3f goto bug fixed 2016-03-15 16:51:21 +08:00
8660a54fac make router fast 2016-03-15 11:49:23 +08:00
1b04571c0b test the env use GOPATH not GOROOT 2016-03-14 19:22:00 +08:00
9c7d95b071 go vet 2016-03-14 19:21:09 +08:00
c8bbfb75f0 Merge pull request #1782 from JessonChan/some_wip
BeeLogger can be the writer of  http server's log
2016-03-14 14:47:29 +08:00
549a39c478 fix issue1789: when testing, load config explicitly and forcibly 2016-03-14 11:26:26 +08:00
e4066d820d Merge pull request #1790 from miraclesu/feature/orm_inline_struct
orm: inline struct relate test case
2016-03-14 10:35:00 +08:00
cc2b5f5b62 Merge pull request #1792 from youngsterxyf/issue1787
fix issue#1787: if `/m` is a static path mapping to a static dir,and we set `beego.BConfig.WebConfig.DirectoryIndex = true`, then it should redirect to `/m/`
2016-03-14 10:33:47 +08:00
c92c3fc8b5 make the BeegoLogger a adapter of http server ErrorLog 2016-03-14 10:21:07 +08:00
9aa2e5b575 fix issue#1787: The cause is that if the url is /m,and we set beego.BConfig.WebConfig.DirectoryIndex = true, then it should redirect to /m/ 2016-03-13 21:39:26 +08:00
dcfcb2789e orm: inline struct relate test case 2016-03-13 21:04:39 +08:00
d90195061f fix #1783 2016-03-13 11:16:19 +08:00
474bdd2e6b Merge pull request #1785 from KilledKenny/pathError
Fixed infinite loop in ini config adapter
2016-03-12 21:11:04 +08:00
1642cbd420 Merge branch 'astaxie/develop' into develop 2016-03-12 14:51:24 +08:00
b2a06c5fa0 Update config suport environment variable logic 2016-03-12 14:32:39 +08:00
cfef97175e change import sort 2016-03-12 12:34:54 +08:00
8b0957cf2e Fixed infinite loop in ini config adapter
If parseFile recived a directory it would go into a infinit loop
2016-03-12 00:20:19 +01:00
88816348b9 window console can't output colorful 2016-03-11 15:29:52 +08:00
66423f6935 Fix formatting with gofmt
Rename BeeTemplateEngines->beeTemplateEngines.  Create templateHandler function type
Fix var name in comment BeeTemplatePreprocessors -> beeTemplatePreprocessors
Rename TemplateI -> TemplateRenderer
2016-03-11 08:40:54 +02:00
9872041f12 timeDur is used only when need 2016-03-11 14:14:58 +08:00
83fe43f331 gofmt -s -w 2016-03-11 13:00:58 +08:00
40b41cc121 fix the misspell99 2016-03-11 12:14:18 +08:00
1aeb3d9051 release 1.6.1 2016-03-11 11:35:24 +08:00
778a5a11ac Merge pull request #1781 from JessonChan/develop
duplicate adapter logger  bug fixed
2016-03-11 11:19:07 +08:00
4801099675 duplicate adapter logger bug fixed 2016-03-11 10:12:17 +08:00
420cd507b2 update output information 2016-03-11 10:07:44 +08:00
22196d7841 add mis function NSHandler 2016-03-11 09:21:13 +08:00
571f9b4b65 Merge pull request #1780 from goodloop/develop
fix static pattern match for leaf
2016-03-11 09:07:53 +08:00
1f0a65f0a2 fix the orm test 2016-03-10 22:21:21 +08:00
90e7d252a7 fix static pattern match for leaf 2016-03-10 22:16:41 +08:00
31f7524dae fix the golint travis 2016-03-10 21:47:50 +08:00
3a12e238cc support oracle 2016-03-10 21:23:13 +08:00
86c7f1db9e Merge branch 'astaxie/develop' into environmentVar
# Conflicts:
#	config/fake.go
#	config/xml/xml_test.go
#	config/yaml/yaml_test.go
2016-03-10 19:57:16 +08:00
f45b271b96 Merge pull request #1723 from miraclesu/feature/orm_inline_struct
orm: inline struct support
2016-03-10 14:31:44 +08:00
589616b303 Merge pull request #1768 from aolu11/master
fix json extra newline
2016-03-09 21:11:20 +08:00
65b13eddad Merge pull request #1719 from JessonChan/err_ctrler
multiple response.WriteHeader calls
2016-03-09 19:18:09 +08:00
686d2e834e Merge pull request #1765 from saturn4er/fix_layout_rebuild_in_dev
Add layout rebuilding on each request in dev mode
2016-03-09 19:02:46 +08:00
adbae18e8c Fix formatting with gofmt 2016-03-09 10:47:09 +02:00
f21cff0166 some typo fixed 2016-03-09 16:00:52 +08:00
3dd9020249 Merge remote-tracking branch 'remotes/upstream/develop' into err_ctrler 2016-03-09 15:59:13 +08:00
9a2696d216 accept asta's idea see the talk
https://github.com/astaxie/beego/pull/1719
2016-03-09 15:56:18 +08:00
64e0858d44 orm: add inline struct test case 2016-03-08 22:24:38 +08:00
d86ab2ed31 Merge pull request #1721 from JessonChan/log_enhancement
Log enhancement
2016-03-08 21:35:31 +08:00
b2f071395b rename files to mulitfile 2016-03-08 18:44:39 +08:00
54b5120a64 rename files to mulitfile 2016-03-08 18:43:09 +08:00
5e2384e95a fix json extra newline 2016-03-08 17:04:14 +08:00
adb41eb299 Merge pull request #1761 from lcbluestorm/develop
add ssdb cache adapter
2016-03-08 15:47:45 +08:00
bd04be4470 move time to the top 2016-03-08 14:44:37 +08:00
ef59a0ed63 fix travis.yml 2016-03-08 14:03:33 +08:00
14be252d2a fix travis.yml 2016-03-08 14:01:33 +08:00
5698b5dc92 Merge pull request #1709 from mlgd/develop
Fix cookies in accordance with the "net / http" and Flash usage
2016-03-08 13:53:43 +08:00
0a0fc351e7 fix ssdb_test 2016-03-08 12:59:19 +08:00
662bea352f modify travis 2016-03-08 12:45:54 +08:00
2f18b9103b Merge pull request #1679 from ysqi/emptybodyfix
fix #1669 and return IO error
2016-03-08 09:48:17 +08:00
2c5ef8ccc8 Add layout rebuilding on each request in dev mode 2016-03-07 23:24:52 +02:00
1ddb1ce2fe add ssdb travis 2016-03-07 15:50:13 +08:00
9ee9f81861 Add functions passing to template engine callback 2016-03-07 09:37:47 +02:00
9ddc2f5474 fix panic err 2016-03-07 15:00:03 +08:00
e29f4b57a3 fix travis.yml 2016-03-07 14:53:36 +08:00
10ddb06782 Implemented possibility to add custom template engines 2016-03-07 08:45:49 +02:00
0caadb9b66 rename vars 2016-03-07 14:45:45 +08:00
22e3900403 add .travis.yml 2016-03-07 14:34:40 +08:00
b39830dff3 add .travis.yml 2016-03-07 10:31:56 +08:00
be23c42674 Merge branches 'master' and 'develop' of lcbluestorm.github.com:lcbluestorm/beego into develop 2016-03-07 10:19:55 +08:00
90344a7b8f fix conflicts 2016-03-06 21:25:43 +08:00
920862884d Merge branch 'astaxie/develop' into emptybodyfix
# Conflicts:
#	config.go
2016-03-06 21:19:04 +08:00
48ec7f736e fix GetMulti bug 2016-03-06 14:46:13 +08:00
292d8f2c00 add ssdb cache adapter 2016-03-06 13:17:16 +08:00
f6f34306ee Merge pull request #1740 from ysqi/configer
Fixed #1735 Return nil if config value does not exist or is empty
2016-03-05 22:05:43 +08:00
a40c0dd156 Merge pull request #1750 from JessonChan/staticfile_map_race
static file map race bug fixed
2016-03-05 20:43:00 +08:00
795092bdd2 Merge pull request #1751 from FlamingTree/develop
Update phone regexp
2016-03-05 20:41:52 +08:00
524446c857 Merge pull request #1752 from JessonChan/ab_lock_race
fix template  read-write lock race
2016-03-05 20:40:14 +08:00
f5adec31c6 improve the template reader function 2016-03-04 14:49:16 +08:00
6747c55a81 remove unused cache 2016-03-04 12:01:04 +08:00
1f46c1d231 add template read lock when dev mode 2016-03-04 12:00:43 +08:00
8bd1be8e29 Update validators.go
增加178号段
2016-03-04 11:16:47 +08:00
226e54e0d8 static file map race bug fixed 2016-03-04 10:54:54 +08:00
3379a2b7ed remove file bug fixed
remove file by filename and file suffix
2016-03-04 10:43:57 +08:00
19d921d3f5 Return nil not empty []string{}
Return nil if config value does not exist or is empty
2016-03-03 20:03:23 +08:00
4b99e41880 Merge pull request #1688 from ysqi/configIssue
fixed handle config issue
2016-03-03 09:50:14 +08:00
8ff74e71cb Fixed #1735 Return empty []string
Need return empty []string  if config value is empty.

split `“”` ==> []string{}, Not []string{“”}
2016-03-02 22:44:20 +08:00
2a148473e9 Merge remote-tracking branch 'remotes/upstream/develop' into log_enhancement 2016-03-02 13:34:37 +08:00
b30ce768f8 Merge remote-tracking branch 'remotes/upstream/develop' into err_ctrler 2016-03-02 13:34:00 +08:00
70e63570f5 Merge pull request #1731 from math345/develop
fix bug: session id undecoded when destroy and sesssion memory provider push wrong
2016-03-02 13:26:57 +08:00
36e3160904 add go1.6.0 and remove 1.3.3 2016-03-01 21:41:44 +08:00
ca3c57fbc6 add a line of comment 2016-03-01 17:13:50 +08:00
387dd6ec0e add a line of comment 2016-03-01 17:12:21 +08:00
26cc040f9a daily log name dot fixed 2016-03-01 17:00:24 +08:00
d81a768802 change couchbase to beego/go-couchbase 2016-03-01 16:54:37 +08:00
f0dcaa7f84 remove couchbase dependence 2016-03-01 16:43:56 +08:00
9da4d1d847 Merge branch 'develop' into log_enhancement 2016-03-01 13:59:27 +08:00
a144f117a3 remove comment 2016-03-01 13:39:36 +08:00
477de9a3f3 fix bug: session id undecoded when destroy and sesssion memory provider push wrong 2016-03-01 10:51:47 +08:00
ffbb45e567 Revert "ignore parse include config file error"
This reverts commit 891016a0a2.
2016-02-27 20:18:59 +08:00
67fbafb380 Merge pull request #1680 from ysqi/fix-router-error
fix #1595
2016-02-26 16:17:53 +08:00
6eaa5537f5 Merge pull request #1652 from JessonChan/develop
colorful console and go fmt
2016-02-26 16:10:11 +08:00
62cc987620 Merge pull request #1639 from youngsterxyf/logger-flush-close
try to fix the little bug when calling Close or Flush in async mode
2016-02-26 16:04:29 +08:00
f0a41f978f Merge pull request #1727 from JessonChan/templates_bug_fix
lock the templates map when goroutie update the map
2016-02-26 15:56:47 +08:00
3da28535fe lock the templates map when goroutie update the map 2016-02-25 17:37:28 +08:00
85f55fcb41 orm: inline struct support 2016-02-24 18:46:14 +08:00
8c37e76503 the net/http should set header first,the set http status code and then write the content 2016-02-24 14:14:16 +08:00
76d69b6e51 prevent auto detect of content-type
https://golang.org/src/net/http/server.go#L1031
2016-02-24 10:34:20 +08:00
20301bc212 multiple response.WriteHeader calls 2016-02-24 10:31:44 +08:00
9119f766d2 Fix cookies in accordance with the "net / http" and Flash usage
Fixed issue of Flash cookies that are deleted before being read
Max-age parameter conform to "net/http" Cookie
2016-02-22 13:35:54 +01:00
891016a0a2 ignore parse include config file error 2016-02-14 18:55:42 +08:00
d5f07d65bb panic parse config error 2016-02-14 18:54:40 +08:00
9411063574 fix #1595 2016-02-12 14:45:45 +08:00
23860e6807 go fmt 2016-02-12 11:36:59 +08:00
d35c50a8e0 return write body error 2016-02-12 11:36:25 +08:00
810f6db8d2 fix #1669 write empty body panic error 2016-02-12 11:27:59 +08:00
36f69a04a9 remove interfaceToStr function to package config 2016-02-04 20:15:37 +08:00
1f716dda3e add test files and bug fixed 2016-02-03 17:54:58 +08:00
f8c4b3aa4c add files logger to separate different logs 2016-02-03 17:11:53 +08:00
51b1095e73 add files logger 2016-02-03 16:32:59 +08:00
68cc53e92b when rotate by date ,there's no num after log file 2016-02-03 15:43:15 +08:00
6caa3ecd91 when rotate by date ,there's no num after log file 2016-02-03 15:31:59 +08:00
304a5ccea0 comment fix 2016-02-03 15:06:53 +08:00
9806a43783 make more fast 2016-02-03 15:03:37 +08:00
a1cb000701 remove log package 2016-02-03 14:42:38 +08:00
2b23764ee0 Merge pull request #1657 from lei-cao/slack
Added slack
2016-02-03 08:58:24 +08:00
2301633d42 Added slack 2016-02-02 23:34:32 +08:00
2efe7c4c89 merge multi commit 2016-02-02 17:12:47 +08:00
c71ac7431d Merge branch 'develop' into logger-flush-close 2016-02-02 17:11:41 +08:00
360220161b remove useless var , name style fixed 2016-02-02 16:52:53 +08:00
5dec3d127c colorful console only the terminal supports 2016-02-02 16:37:09 +08:00
16b01c362a Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-02-02 12:45:01 +08:00
441f795a1a Merge pull request #1651 from thanhtranjs/develop
Add GroupBy to QuerySeter
2016-02-02 12:44:49 +08:00
4906b600e3 fix the static url with / problem 2016-02-02 12:43:58 +08:00
bb50383aa9 Add GroupBy to QuerySeter 2016-02-02 11:28:43 +07:00
631b4d36f9 Merge pull request #1647 from youngsterxyf/fix-issue1641
fix issue #1641
2016-02-02 09:23:12 +08:00
da38cbfe41 fix issue #1641 2016-02-01 22:40:34 +08:00
09193213a0 add travis hooks 2016-01-31 23:28:56 +08:00
0382146c60 fix the go vet for go tip 2016-01-29 13:11:11 +08:00
1bf52e8922 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-01-29 10:26:40 +08:00
5cd1ed8106 add test for tip 2016-01-29 00:01:04 +08:00
bd79972d85 Merge pull request #1638 from pjoe/log_console_nocolor
Add noColor option for console logger
2016-01-28 22:39:02 +08:00
ecab397073 try to fix the little bug when calling Close or Flush in async mode 2016-01-28 21:53:44 +08:00
a043432398 Change option name from noColor to color
Still same default behavior (with color)
2016-01-28 07:50:07 +01:00
1222c87be3 optimization code 2016-01-28 14:49:44 +08:00
7f888e3d18 Add noColor option for console logger
- Also added simple test
2016-01-27 19:20:58 +01:00
85c0fcd335 Merge pull request #1637 from pjoe/input_param_fix
Fix Context.Input.SetParam not overwriting existing value
2016-01-27 23:01:10 +08:00
f769016126 Merge pull request #1632 from youngsterxyf/config-logic
Config logic
2016-01-27 22:25:00 +08:00
453d744db9 Fix Context.Input.SetParam not overwriting existing value
- Also added tests for Context.Input.Param handling
2016-01-27 14:58:50 +01:00
484ca3a643 fixed test code error 2016-01-27 21:13:11 +08:00
cd31c816cc Config support get environment variable
get environment variable if config item  has prefix "$ENV_" .
e.g.
```ini
[demo]
password = $ENV_MyPWD
```
2016-01-27 20:46:30 +08:00
31ef4ae507 rename AppConfigPath, AppConfigProvider to appConfigPath, appConfigProvider, make public to private 2016-01-27 16:43:01 +08:00
ccce566ba7 accept @astaxie suggestion: change the sequence adapterName and configPath 2016-01-27 16:19:10 +08:00
e357f6846b accept @ysqi suggestion 2016-01-27 13:30:34 +08:00
321bcc606a fix bug of test case 2016-01-27 12:26:37 +08:00
e549d0fd9c move some code piece 2016-01-27 12:13:26 +08:00
c59a029ce7 Merge branch 'develop' into config-logic 2016-01-27 10:58:38 +08:00
4ce094a29a Merge branch 'master' into develop 2016-01-27 10:14:34 +08:00
fbb98fbe1f Merge pull request #1631 from yunkai/issue1
Fix regression caused by commit ad65479
2016-01-27 10:13:30 +08:00
20efd5236e fix bug 2016-01-27 00:42:07 +08:00
330b3b1931 enhancement code 2016-01-27 00:17:56 +08:00
d9e6250d08 fix config logic 2016-01-27 00:10:21 +08:00
e3810b599d Fix regression caused by commit ad65479
Commit ad65479 will cause "Method Not Allow" in preflight response
when enable CORS plugin.

The root cause is that CORS plugin didn't generate http output after applied
commit ad65479, so the value of `ctx.ResponseWriter.Started` will be keep
`false`, and then later filter chains will be go on to run when CORS filter
finished.

This path will both fix "Method Not Allow" and the original bug
"multiple response.WriteHeader calls".

Signed-off-by: Yunkai Zhang <qiushu.zyk@taobao.com>
2016-01-26 23:27:26 +08:00
e1f9491aed Merge pull request #1608 from ysqi/iniSaveErrorFix
Fixed #1607
2016-01-26 21:46:31 +08:00
6aeff53d8c Merge pull request #1625 from miraclesu/fix/mail_from
Fix utils mail some bugs
2016-01-26 21:42:21 +08:00
bf870eb9a2 mv mime.QEncoding.Encode logic to mail
it is named qEncode
2016-01-26 20:50:03 +08:00
f26d360ec9 Fix vet fail 2016-01-26 14:54:36 +08:00
f73eaf6393 Merge pull request #1626 from JessonChan/develop
log file name bug fixed
2016-01-26 13:42:47 +08:00
e11d150e8b replace \t with space 2016-01-26 09:35:39 +08:00
f2567bc114 some typo fixed 2016-01-26 09:29:04 +08:00
b5a07c6ba8 log file name bug fixed
this bug happens when daily rotate. ex,when it is 2016-01-22 23:59:59 and need a rotate,the file name should named with 2016-01-22 but named with 2016-01-23(next day)
2016-01-26 09:20:49 +08:00
01ccc75d6b Merge pull request #1615 from ysqi/routerErrorFix
Fixed #1586
2016-01-26 00:31:01 +08:00
b19f9bf88c Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-01-26 00:27:44 +08:00
6cc3d4470a Merge pull request #1616 from coseyo/path_patch
fix path issue #1613
2016-01-26 00:27:34 +08:00
61e9dc74c9 make sure the memcache testing success 2016-01-26 00:27:02 +08:00
8611862fd7 Merge pull request #1609 from youngsterxyf/fix-issue1566
fix issue #1566
2016-01-26 00:07:43 +08:00
d1481ea659 move assignment to init 2016-01-25 23:00:09 +08:00
5930f27da7 Fix mail Chinese subject garbled bug 2016-01-25 22:55:40 +08:00
4de91f675d show from when Config from is empty 2016-01-25 22:29:45 +08:00
15e9ba19c0 fix the range only used in Go 1.4 fix #1623 2016-01-25 21:39:44 +08:00
f8004b69ad fix the go vet 2016-01-25 21:33:57 +08:00
3dac344ff6 fix the vet url 2016-01-25 21:20:10 +08:00
e7d4452af0 add golint and go test 2016-01-25 21:13:56 +08:00
7e3ad5bcb0 fix #1585 2016-01-25 21:08:29 +08:00
87650ce8bc make golint happy 2016-01-25 20:57:41 +08:00
fdce4af9c8 fix #1619 2016-01-25 20:53:52 +08:00
bcac4bb8e3 accept @JessonChan suggestion 2016-01-25 20:53:25 +08:00
0e17e2a3d2 accept @JessonChan suggestion 2016-01-25 20:20:53 +08:00
a80feb00b8 Fix utils mail from field can't including Chinese bug 2016-01-25 18:15:08 +08:00
cf055c9db2 Merge branch 'astaxie/develop' into iniSaveErrorFix
# Conflicts:
#	config/ini_test.go
2016-01-24 11:37:43 +08:00
3d7354b9d2 import reset 2016-01-24 11:10:04 +08:00
09d3d89c6f fix test error again 2016-01-24 00:47:37 +08:00
3031bdd176 fix test error 2016-01-24 00:40:03 +08:00
4c1cfc1386 fix path issue 2016-01-24 00:18:16 +08:00
57d522a96a Merge pull request #1606 from ysqi/configWork
Support Parse Bool with more diffrent values
2016-01-23 23:03:03 +08:00
fd7473466b Merge pull request #1581 from hbejgel/patch-2
Checks if index is greater than the length of the wildcards. #1580
2016-01-23 23:01:22 +08:00
007af6224e Fixed #1586 2016-01-23 19:13:19 +08:00
cbc7f43e88 fix issue #1601 2016-01-23 17:12:46 +08:00
ecf24640fd fix issue #1566 2016-01-23 16:56:54 +08:00
51ae45a799 Fixed #1607 2016-01-23 14:53:52 +08:00
be544f963e Support Parse Bool with more diffrent values
ParseBool returns the boolean value represented by the string.
It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on,
On,
 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
Any other value returns an error.
2016-01-23 11:02:40 +08:00
af346e871b Merge pull request #1603 from coseyo/remote_develop
fix conf load bug
2016-01-22 20:34:46 +08:00
cb5fd49612 fix conf load bug 2016-01-22 18:03:02 +08:00
0f85c82a21 Merge pull request #1594 from nemtsevp/patch-2
Exceptions in controller methods
2016-01-21 14:57:02 +08:00
93b04e8a3b Exceptions in controller methods
Exceptions in methods names should be changed according to controller.go
2016-01-21 09:44:17 +03:00
07937dea9a Merge pull request #1588 from Kavin-Cao/master
template.go 的beegoTplFuncMap注释有误
2016-01-20 14:18:35 +08:00
5757e6548e template.go 的urlfor Func注释有误
template.go 的urlfor Func注释有误
2016-01-19 10:14:16 +08:00
b48f251043 Merge remote-tracking branch 'refs/remotes/astaxie/master' 2016-01-19 09:43:43 +08:00
35e340b937 Checks if index is greater than the length of the wildcards. #1580 2016-01-18 21:35:14 -02:00
befeac5b61 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-01-18 23:30:12 +08:00
23bb36d35c fix the issue #1573 2016-01-18 23:29:56 +08:00
4b52a38183 Merge pull request #1575 from youngsterxyf/develop
simplify the implementation of splitPath in tree.go
2016-01-18 16:33:23 +08:00
30e5634bdb simplify the implementation of splitPath in tree.go 2016-01-18 16:13:31 +08:00
fa8f6e5a53 session destroy 2016-01-18 16:11:27 +08:00
6e987bfdaf Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-01-18 15:28:18 +08:00
918b9e510f fix the add tempfunc 2016-01-18 15:27:33 +08:00
12e5584c01 Merge pull request #1574 from youngsterxyf/develop
DRY
2016-01-18 15:24:53 +08:00
ac3b013de7 DRY 2016-01-18 15:17:42 +08:00
089242eda0 memcache test mulit return map has no sequence 2016-01-18 00:36:05 +08:00
127 changed files with 7063 additions and 2045 deletions

17
.github/ISSUE_TEMPLATE vendored Normal file
View File

@ -0,0 +1,17 @@
Please answer these questions before submitting your issue. Thanks!
1. What version of Go and beego are you using (`bee version`)?
2. What operating system and processor architecture are you using (`go env`)?
3. What did you do?
If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
4. What did you expect to see?
5. What did you see instead?

View File

@ -1,8 +1,9 @@
language: go
go:
- 1.5.1
- 1.6
- 1.5.3
- 1.4.3
services:
- redis-server
- mysql
@ -12,6 +13,11 @@ env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
before_install:
- git clone git://github.com/ideawu/ssdb.git
- cd ssdb
- make
- cd ..
install:
- go get github.com/lib/pq
- go get github.com/go-sql-driver/mysql
@ -19,12 +25,25 @@ install:
- go get github.com/bradfitz/gomemcache/memcache
- go get github.com/garyburd/redigo/redis
- go get github.com/beego/x2j
- go get github.com/couchbase/go-couchbase
- go get github.com/beego/goyaml2
- go get github.com/belogik/goes
- go get github.com/couchbase/go-couchbase
- go get github.com/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis
- go get github.com/ssdb/gossdb/ssdb
before_script:
- psql --version
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi"
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi"
- mkdir -p res/var
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
after_script:
-killall -w ssdb-server
- rm -rf ./res/var/*
script:
- go test -v ./...
addons:
postgresql: "9.4"

View File

@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/astaxie/beego.svg?branch=master)](https://travis-ci.org/astaxie/beego)
[![GoDoc](http://godoc.org/github.com/astaxie/beego?status.svg)](http://godoc.org/github.com/astaxie/beego)
[![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org)
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
@ -30,7 +31,7 @@ func main(){
```
######Congratulations!
You just built your first beego app.
Open your browser and visit `http://localhost:8000`.
Open your browser and visit `http://localhost:8080`.
Please see [Documentation](http://beego.me/docs) for more.
## Features
@ -53,6 +54,7 @@ Please see [Documentation](http://beego.me/docs) for more.
## Community
* [http://beego.me/community](http://beego.me/community)
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
## LICENSE

View File

@ -23,7 +23,10 @@ import (
"text/template"
"time"
"reflect"
"github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/toolbox"
"github.com/astaxie/beego/utils"
)
@ -90,57 +93,9 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
switch command {
case "conf":
m := make(map[string]interface{})
m["AppConfigPath"] = AppConfigPath
m["AppConfigProvider"] = AppConfigProvider
m["BConfig.AppName"] = BConfig.AppName
m["BConfig.RunMode"] = BConfig.RunMode
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
m["BConfig.ServerName"] = BConfig.ServerName
m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
m["BConfig.EnableGzip"] = BConfig.EnableGzip
m["BConfig.MaxMemory"] = BConfig.MaxMemory
m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
m["BConfig.WebConfig.XSRFKEY"] = BConfig.WebConfig.XSRFKey
m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
list("BConfig", BConfig, m)
m["AppConfigPath"] = appConfigPath
m["AppConfigProvider"] = appConfigProvider
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(configTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
@ -196,7 +151,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
BeforeExec: "Before Exec",
AfterExec: "After Exec",
FinishRouter: "Finish Router"} {
if bf, ok := BeeApp.Handlers.filters[k]; ok {
if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
filterType = fr
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
@ -223,6 +178,28 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
}
}
func list(root string, p interface{}, m map[string]interface{}) {
pt := reflect.TypeOf(p)
pv := reflect.ValueOf(p)
if pt.Kind() == reflect.Ptr {
pt = pt.Elem()
pv = pv.Elem()
}
for i := 0; i < pv.NumField(); i++ {
var key string
if root == "" {
key = pt.Field(i).Name
} else {
key = root + "." + pt.Field(i).Name
}
if pv.Field(i).Kind() == reflect.Struct {
list(key, pv.Field(i).Interface(), m)
} else {
m[key] = pv.Field(i).Interface()
}
}
}
func printTree(resultList *[][]string, t *Tree) {
for _, tr := range t.fixrouters {
printTree(resultList, tr)
@ -410,7 +387,7 @@ func (admin *adminApp) Run() {
for p, f := range admin.routers {
http.Handle(p, f)
}
BeeLogger.Info("Admin server Running on %s", addr)
logs.Info("Admin server Running on %s", addr)
var err error
if BConfig.Listen.Graceful {
@ -419,6 +396,6 @@ func (admin *adminApp) Run() {
err = http.ListenAndServe(addr, nil)
}
if err != nil {
BeeLogger.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
}
}

73
admin_test.go Normal file
View File

@ -0,0 +1,73 @@
package beego
import (
"fmt"
"testing"
)
func TestList_01(t *testing.T) {
m := make(map[string]interface{})
list("BConfig", BConfig, m)
t.Log(m)
om := oldMap()
for k, v := range om {
if fmt.Sprint(m[k]) != fmt.Sprint(v) {
t.Log(k, "old-key", v, "new-key", m[k])
t.FailNow()
}
}
}
func oldMap() map[string]interface{} {
m := make(map[string]interface{})
m["BConfig.AppName"] = BConfig.AppName
m["BConfig.RunMode"] = BConfig.RunMode
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
m["BConfig.ServerName"] = BConfig.ServerName
m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
m["BConfig.EnableGzip"] = BConfig.EnableGzip
m["BConfig.MaxMemory"] = BConfig.MaxMemory
m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
return m
}

View File

@ -78,13 +78,14 @@ var qpsTpl = `{{define "content"}}
{{range $i, $elem := .Content.Data}}
<tr>
{{range $elem}}
<td>
{{.}}
</td>
{{end}}
<td>{{index $elem 0}}</td>
<td>{{index $elem 1}}</td>
<td>{{index $elem 2}}</td>
<td data-order="{{index $elem 3}}">{{index $elem 4}}</td>
<td data-order="{{index $elem 5}}">{{index $elem 6}}</td>
<td data-order="{{index $elem 7}}">{{index $elem 8}}</td>
<td data-order="{{index $elem 9}}">{{index $elem 10}}</td>
</tr>
{{end}}
</tbody>

30
app.go
View File

@ -24,6 +24,7 @@ import (
"time"
"github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils"
)
@ -68,9 +69,9 @@ func (app *App) Run() {
if BConfig.Listen.EnableFcgi {
if BConfig.Listen.EnableStdIo {
if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O
BeeLogger.Info("Use FCGI via standard I/O")
logs.Info("Use FCGI via standard I/O")
} else {
BeeLogger.Critical("Cannot use FCGI via standard I/O", err)
logs.Critical("Cannot use FCGI via standard I/O", err)
}
return
}
@ -84,10 +85,10 @@ func (app *App) Run() {
l, err = net.Listen("tcp", addr)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
logs.Critical("Listen: ", err)
}
if err = fcgi.Serve(l, app.Handlers); err != nil {
BeeLogger.Critical("fcgi.Serve: ", err)
logs.Critical("fcgi.Serve: ", err)
}
return
}
@ -95,6 +96,7 @@ func (app *App) Run() {
app.Server.Handler = app.Handlers
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.ErrorLog = logs.GetLogger("HTTP")
// run graceful mode
if BConfig.Listen.Graceful {
@ -111,7 +113,7 @@ func (app *App) Run() {
server.Server.ReadTimeout = app.Server.ReadTimeout
server.Server.WriteTimeout = app.Server.WriteTimeout
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
BeeLogger.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
@ -126,7 +128,7 @@ func (app *App) Run() {
server.Network = "tcp4"
}
if err := server.ListenAndServe(); err != nil {
BeeLogger.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
@ -137,16 +139,18 @@ func (app *App) Run() {
}
// run normal mode
app.Server.Addr = addr
if BConfig.Listen.EnableHTTPS {
go func() {
time.Sleep(20 * time.Microsecond)
if BConfig.Listen.HTTPSPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
} else if BConfig.Listen.EnableHTTP {
BeeLogger.Info("Start https server error, confict with http.Please reset https port")
return
}
BeeLogger.Info("https server Running on %s", app.Server.Addr)
logs.Info("https server Running on https://%s", app.Server.Addr)
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
BeeLogger.Critical("ListenAndServeTLS: ", err)
logs.Critical("ListenAndServeTLS: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
@ -155,24 +159,24 @@ func (app *App) Run() {
if BConfig.Listen.EnableHTTP {
go func() {
app.Server.Addr = addr
BeeLogger.Info("http server Running on %s", app.Server.Addr)
logs.Info("http server Running on http://%s", app.Server.Addr)
if BConfig.Listen.ListenTCP4 {
ln, err := net.Listen("tcp4", app.Server.Addr)
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
if err = app.Server.Serve(ln); err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
} else {
if err := app.Server.ListenAndServe(); err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}

View File

@ -15,7 +15,6 @@
package beego
import (
"fmt"
"os"
"path/filepath"
"strconv"
@ -24,7 +23,7 @@ import (
const (
// VERSION represent beego web framework version.
VERSION = "1.6.0"
VERSION = "1.7.2"
// DEV is for develop
DEV = "dev"
@ -52,6 +51,7 @@ func AddAPPStartHook(hf hookfunc) {
// beego.Run(":8089")
// beego.Run("127.0.0.1:8089")
func Run(params ...string) {
initBeforeHTTPRun()
if len(params) > 0 && params[0] != "" {
@ -68,28 +68,13 @@ func Run(params ...string) {
}
func initBeforeHTTPRun() {
// if AppConfigPath is setted or conf/app.conf exist
err := ParseConfig()
if err != nil {
panic(err)
}
//init log
for adaptor, config := range BConfig.Log.Outputs {
err = BeeLogger.SetLogger(adaptor, config)
if err != nil {
fmt.Printf("%s with the config `%s` got err:%s\n", adaptor, config, err)
}
}
SetLogFuncCall(BConfig.Log.FileLineNum)
//init hooks
AddAPPStartHook(registerMime)
AddAPPStartHook(registerDefaultErrorHandler)
AddAPPStartHook(registerSession)
AddAPPStartHook(registerDocs)
AddAPPStartHook(registerTemplate)
AddAPPStartHook(registerAdmin)
AddAPPStartHook(registerGzip)
for _, hk := range hooks {
if err := hk(); err != nil {
@ -100,8 +85,16 @@ func initBeforeHTTPRun() {
// TestBeegoInit is for test package init
func TestBeegoInit(ap string) {
os.Setenv("BEEGO_RUNMODE", "test")
AppConfigPath = filepath.Join(ap, "conf", "app.conf")
path := filepath.Join(ap, "conf", "app.conf")
os.Chdir(ap)
InitBeegoBeforeTest(path)
}
// InitBeegoBeforeTest is for test package init
func InitBeegoBeforeTest(appConfigPath string) {
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
panic(err)
}
BConfig.RunMode = "test"
initBeforeHTTPRun()
}

View File

@ -33,12 +33,10 @@ import (
"encoding/json"
"errors"
"strings"
"github.com/bradfitz/gomemcache/memcache"
"time"
"github.com/astaxie/beego/cache"
"github.com/bradfitz/gomemcache/memcache"
)
// Cache Memcache adapter.
@ -60,7 +58,7 @@ func (rc *Cache) Get(key string) interface{} {
}
}
if item, err := rc.conn.Get(key); err == nil {
return string(item.Value)
return item.Value
}
return nil
}
@ -80,7 +78,7 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
mv, err := rc.conn.GetMulti(keys)
if err == nil {
for _, v := range mv {
rv = append(rv, string(v.Value))
rv = append(rv, v.Value)
}
return rv
}
@ -90,18 +88,21 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
return rv
}
// Put put value to memcache. only support string.
// Put put value to memcache.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
v, ok := val.(string)
if !ok {
return errors.New("val must string")
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
if v, ok := val.([]byte); ok {
item.Value = v
} else if str, ok := val.(string); ok {
item.Value = []byte(str)
} else {
return errors.New("val only support string and []byte")
}
item := memcache.Item{Key: key, Value: []byte(v), Expiration: int32(timeout / time.Second)}
return rc.conn.Set(&item)
}

View File

@ -17,10 +17,11 @@ package memcache
import (
_ "github.com/bradfitz/gomemcache/memcache"
"github.com/astaxie/beego/cache"
"strconv"
"testing"
"time"
"github.com/astaxie/beego/cache"
)
func TestMemcacheCache(t *testing.T) {
@ -36,7 +37,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("check err")
}
time.Sleep(10 * time.Second)
time.Sleep(11 * time.Second)
if bm.IsExist("astaxie") {
t.Error("check err")
@ -45,7 +46,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("set Error", err)
}
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 1 {
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
t.Error("get err")
}
@ -53,7 +54,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("Incr Error", err)
}
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 2 {
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
t.Error("get err")
}
@ -61,7 +62,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("Decr Error", err)
}
if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 1 {
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
@ -77,7 +78,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("check err")
}
if v := bm.Get("astaxie").(string); v != "author" {
if v := bm.Get("astaxie").([]byte); string(v) != "author" {
t.Error("get err")
}
@ -93,10 +94,10 @@ func TestMemcacheCache(t *testing.T) {
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
t.Error("GetMulti ERROR")
}

View File

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

240
cache/ssdb/ssdb.go vendored Normal file
View File

@ -0,0 +1,240 @@
package ssdb
import (
"encoding/json"
"errors"
"strconv"
"strings"
"time"
"github.com/ssdb/gossdb/ssdb"
"github.com/astaxie/beego/cache"
)
// Cache SSDB adapter
type Cache struct {
conn *ssdb.Client
conninfo []string
}
//NewSsdbCache create new ssdb adapter.
func NewSsdbCache() cache.Cache {
return &Cache{}
}
// Get get value from memcache.
func (rc *Cache) Get(key string) interface{} {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil
}
}
value, err := rc.conn.Get(key)
if err == nil {
return value
}
return nil
}
// GetMulti get value from memcache.
func (rc *Cache) GetMulti(keys []string) []interface{} {
size := len(keys)
var values []interface{}
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
for i := 0; i < size; i++ {
values = append(values, err)
}
return values
}
}
res, err := rc.conn.Do("multi_get", keys)
resSize := len(res)
if err == nil {
for i := 1; i < resSize; i += 2 {
values = append(values, string(res[i+1]))
}
return values
}
for i := 0; i < size; i++ {
values = append(values, err)
}
return values
}
// DelMulti get value from memcache.
func (rc *Cache) DelMulti(keys []string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("multi_del", keys)
if err != nil {
return err
}
return nil
}
// Put put value to memcache. only support string.
func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
v, ok := value.(string)
if !ok {
return errors.New("value must string")
}
var resp []string
var err error
ttl := int(timeout / time.Second)
if ttl < 0 {
resp, err = rc.conn.Do("set", key, v)
} else {
resp, err = rc.conn.Do("setx", key, v, ttl)
}
if err != nil {
return err
}
if len(resp) == 2 && resp[0] == "ok" {
return nil
}
return errors.New("bad response")
}
// Delete delete value in memcache.
func (rc *Cache) Delete(key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Del(key)
if err != nil {
return err
}
return nil
}
// Incr increase counter.
func (rc *Cache) Incr(key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("incr", key, 1)
return err
}
// Decr decrease counter.
func (rc *Cache) Decr(key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Do("incr", key, -1)
return err
}
// IsExist check value exists in memcache.
func (rc *Cache) IsExist(key string) bool {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return false
}
}
resp, err := rc.conn.Do("exists", key)
if err != nil {
return false
}
if resp[1] == "1" {
return true
}
return false
}
// ClearAll clear all cached in memcache.
func (rc *Cache) ClearAll() error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
keyStart, keyEnd, limit := "", "", 50
resp, err := rc.Scan(keyStart, keyEnd, limit)
for err == nil {
size := len(resp)
if size == 1 {
return nil
}
keys := []string{}
for i := 1; i < size; i += 2 {
keys = append(keys, string(resp[i]))
}
_, e := rc.conn.Do("multi_del", keys)
if e != nil {
return e
}
keyStart = resp[size-2]
resp, err = rc.Scan(keyStart, keyEnd, limit)
}
return err
}
// Scan key all cached in ssdb.
func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return nil, err
}
}
resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
if err != nil {
return nil, err
}
return resp, nil
}
// StartAndGC start memcache adapter.
// config string is like {"conn":"connection info"}.
// if connecting error, return.
func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
}
rc.conninfo = strings.Split(cf["conn"], ";")
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return nil
}
// connect to memcache and keep the connection.
func (rc *Cache) connectInit() error {
conninfoArray := strings.Split(rc.conninfo[0], ":")
host := conninfoArray[0]
port, e := strconv.Atoi(conninfoArray[1])
if e != nil {
return e
}
var err error
rc.conn, err = ssdb.Connect(host, port)
if err != nil {
return err
}
return nil
}
func init() {
cache.Register("ssdb", NewSsdbCache)
}

104
cache/ssdb/ssdb_test.go vendored Normal file
View File

@ -0,0 +1,104 @@
package ssdb
import (
"strconv"
"testing"
"time"
"github.com/astaxie/beego/cache"
)
func TestSsdbcacheCache(t *testing.T) {
ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`)
if err != nil {
t.Error("init err")
}
// test put and exist
if ssdb.IsExist("ssdb") {
t.Error("check err")
}
timeoutDuration := 10 * time.Second
//timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !ssdb.IsExist("ssdb") {
t.Error("check err")
}
// Get test done
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if v := ssdb.Get("ssdb"); v != "ssdb" {
t.Error("get Error")
}
//inc/dec test done
if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if err = ssdb.Incr("ssdb"); err != nil {
t.Error("incr Error", err)
}
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
t.Error("get err")
}
if err = ssdb.Decr("ssdb"); err != nil {
t.Error("decr error")
}
// test del
if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
t.Error("get err")
}
if err := ssdb.Delete("ssdb"); err == nil {
if ssdb.IsExist("ssdb") {
t.Error("delete err")
}
}
//test string
if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil {
t.Error("set Error", err)
}
if !ssdb.IsExist("ssdb") {
t.Error("check err")
}
if v := ssdb.Get("ssdb").(string); v != "ssdb" {
t.Error("get err")
}
//test GetMulti done
if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil {
t.Error("set Error", err)
}
if !ssdb.IsExist("ssdb1") {
t.Error("check err")
}
vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
if len(vv) != 2 {
t.Error("getmulti error")
}
if vv[0].(string) != "ssdb" {
t.Error("getmulti error")
}
if vv[1].(string) != "ssdb1" {
t.Error("getmulti error")
}
// test clear all done
if err = ssdb.ClearAll(); err != nil {
t.Error("clear all err")
}
if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") {
t.Error("check err")
}
}

307
config.go
View File

@ -15,12 +15,16 @@
package beego
import (
"html/template"
"fmt"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"github.com/astaxie/beego/config"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/session"
"github.com/astaxie/beego/utils"
)
@ -32,6 +36,7 @@ type Config struct {
RouterCaseSensitive bool
ServerName string
RecoverPanic bool
RecoverFunc func(*context.Context)
CopyRequestBody bool
EnableGzip bool
MaxMemory int64
@ -81,14 +86,18 @@ type WebConfig struct {
// SessionConfig holds session related config
type SessionConfig struct {
SessionOn bool
SessionProvider string
SessionName string
SessionGCMaxLifetime int64
SessionProviderConfig string
SessionCookieLifeTime int
SessionAutoSetCookie bool
SessionDomain string
SessionOn bool
SessionProvider string
SessionName string
SessionGCMaxLifetime int64
SessionProviderConfig string
SessionCookieLifeTime int
SessionAutoSetCookie bool
SessionDomain string
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader string
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
}
// LogConfig holds Log related config
@ -103,23 +112,82 @@ var (
BConfig *Config
// AppConfig is the instance of Config, store the config information from file
AppConfig *beegoAppConfig
// AppConfigPath is the path to the config files
AppConfigPath string
// AppConfigProvider is the provider for the config, default is ini
AppConfigProvider = "ini"
// TemplateCache stores template caching
TemplateCache map[string]*template.Template
// AppPath is the absolute path to the app
AppPath string
// GlobalSessions is the instance for the session manager
GlobalSessions *session.Manager
// appConfigPath is the path to the config files
appConfigPath string
// appConfigProvider is the provider for the config, default is ini
appConfigProvider = "ini"
)
func init() {
BConfig = &Config{
BConfig = newBConfig()
var err error
if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
panic(err)
}
workPath, err := os.Getwd()
if err != nil {
panic(err)
}
appConfigPath = filepath.Join(workPath, "conf", "app.conf")
if !utils.FileExists(appConfigPath) {
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if !utils.FileExists(appConfigPath) {
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
return
}
}
if err = parseConfig(appConfigPath); err != nil {
panic(err)
}
if err = os.Chdir(AppPath); err != nil {
panic(err)
}
}
func recoverPanic(ctx *context.Context) {
if err := recover(); err != nil {
if err == ErrAbort {
return
}
if !BConfig.RecoverPanic {
panic(err)
}
if BConfig.EnableErrorsShow {
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
exception(fmt.Sprint(err), ctx)
return
}
}
var stack string
logs.Critical("the request url is ", ctx.Input.URL())
logs.Critical("Handler crashed with error", err)
for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
logs.Critical(fmt.Sprintf("%s:%d", file, line))
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
}
if BConfig.RunMode == DEV {
showErr(err, ctx, stack)
}
}
}
func newBConfig() *Config {
return &Config{
AppName: "beego",
RunMode: DEV,
RouterCaseSensitive: true,
ServerName: "beegoServer:" + VERSION,
RecoverPanic: true,
RecoverFunc: recoverPanic,
CopyRequestBody: false,
EnableGzip: false,
MaxMemory: 1 << 26, //64MB
@ -157,14 +225,18 @@ func init() {
XSRFKey: "beegoxsrf",
XSRFExpire: 0,
Session: SessionConfig{
SessionOn: false,
SessionProvider: "memory",
SessionName: "beegosessionID",
SessionGCMaxLifetime: 3600,
SessionProviderConfig: "",
SessionCookieLifeTime: 0, //set cookie default is the brower life
SessionAutoSetCookie: true,
SessionDomain: "",
SessionOn: false,
SessionProvider: "memory",
SessionName: "beegosessionID",
SessionGCMaxLifetime: 3600,
SessionProviderConfig: "",
SessionDisableHTTPOnly: false,
SessionCookieLifeTime: 0, //set cookie default is the browser life
SessionAutoSetCookie: true,
SessionDomain: "",
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader: "Beegosessionid",
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
},
},
Log: LogConfig{
@ -173,90 +245,42 @@ func init() {
Outputs: map[string]string{"console": ""},
},
}
ParseConfig()
}
// ParseConfig parsed default config file.
// now only support ini, next will support json.
func ParseConfig() (err error) {
if AppConfigPath == "" {
if utils.FileExists(filepath.Join("conf", "app.conf")) {
AppConfigPath = filepath.Join("conf", "app.conf")
} else {
AppConfig = &beegoAppConfig{config.NewFakeConfig()}
return
}
}
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
func parseConfig(appConfigPath string) (err error) {
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
if err != nil {
return err
}
// set the runmode first
return assignConfig(AppConfig)
}
func assignConfig(ac config.Configer) error {
// set the run mode first
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
BConfig.RunMode = envRunMode
} else if runmode := AppConfig.String("RunMode"); runmode != "" {
BConfig.RunMode = runmode
} else if runMode := ac.String("RunMode"); runMode != "" {
BConfig.RunMode = runMode
}
BConfig.AppName = AppConfig.DefaultString("AppName", BConfig.AppName)
BConfig.RecoverPanic = AppConfig.DefaultBool("RecoverPanic", BConfig.RecoverPanic)
BConfig.RouterCaseSensitive = AppConfig.DefaultBool("RouterCaseSensitive", BConfig.RouterCaseSensitive)
BConfig.ServerName = AppConfig.DefaultString("ServerName", BConfig.ServerName)
BConfig.EnableGzip = AppConfig.DefaultBool("EnableGzip", BConfig.EnableGzip)
BConfig.EnableErrorsShow = AppConfig.DefaultBool("EnableErrorsShow", BConfig.EnableErrorsShow)
BConfig.CopyRequestBody = AppConfig.DefaultBool("CopyRequestBody", BConfig.CopyRequestBody)
BConfig.MaxMemory = AppConfig.DefaultInt64("MaxMemory", BConfig.MaxMemory)
BConfig.Listen.Graceful = AppConfig.DefaultBool("Graceful", BConfig.Listen.Graceful)
BConfig.Listen.HTTPAddr = AppConfig.String("HTTPAddr")
BConfig.Listen.HTTPPort = AppConfig.DefaultInt("HTTPPort", BConfig.Listen.HTTPPort)
BConfig.Listen.ListenTCP4 = AppConfig.DefaultBool("ListenTCP4", BConfig.Listen.ListenTCP4)
BConfig.Listen.EnableHTTP = AppConfig.DefaultBool("EnableHTTP", BConfig.Listen.EnableHTTP)
BConfig.Listen.EnableHTTPS = AppConfig.DefaultBool("EnableHTTPS", BConfig.Listen.EnableHTTPS)
BConfig.Listen.HTTPSAddr = AppConfig.DefaultString("HTTPSAddr", BConfig.Listen.HTTPSAddr)
BConfig.Listen.HTTPSPort = AppConfig.DefaultInt("HTTPSPort", BConfig.Listen.HTTPSPort)
BConfig.Listen.HTTPSCertFile = AppConfig.DefaultString("HTTPSCertFile", BConfig.Listen.HTTPSCertFile)
BConfig.Listen.HTTPSKeyFile = AppConfig.DefaultString("HTTPSKeyFile", BConfig.Listen.HTTPSKeyFile)
BConfig.Listen.EnableAdmin = AppConfig.DefaultBool("EnableAdmin", BConfig.Listen.EnableAdmin)
BConfig.Listen.AdminAddr = AppConfig.DefaultString("AdminAddr", BConfig.Listen.AdminAddr)
BConfig.Listen.AdminPort = AppConfig.DefaultInt("AdminPort", BConfig.Listen.AdminPort)
BConfig.Listen.EnableFcgi = AppConfig.DefaultBool("EnableFcgi", BConfig.Listen.EnableFcgi)
BConfig.Listen.EnableStdIo = AppConfig.DefaultBool("EnableStdIo", BConfig.Listen.EnableStdIo)
BConfig.Listen.ServerTimeOut = AppConfig.DefaultInt64("ServerTimeOut", BConfig.Listen.ServerTimeOut)
BConfig.WebConfig.AutoRender = AppConfig.DefaultBool("AutoRender", BConfig.WebConfig.AutoRender)
BConfig.WebConfig.ViewsPath = AppConfig.DefaultString("ViewsPath", BConfig.WebConfig.ViewsPath)
BConfig.WebConfig.DirectoryIndex = AppConfig.DefaultBool("DirectoryIndex", BConfig.WebConfig.DirectoryIndex)
BConfig.WebConfig.FlashName = AppConfig.DefaultString("FlashName", BConfig.WebConfig.FlashName)
BConfig.WebConfig.FlashSeparator = AppConfig.DefaultString("FlashSeparator", BConfig.WebConfig.FlashSeparator)
BConfig.WebConfig.EnableDocs = AppConfig.DefaultBool("EnableDocs", BConfig.WebConfig.EnableDocs)
BConfig.WebConfig.XSRFKey = AppConfig.DefaultString("XSRFKEY", BConfig.WebConfig.XSRFKey)
BConfig.WebConfig.EnableXSRF = AppConfig.DefaultBool("EnableXSRF", BConfig.WebConfig.EnableXSRF)
BConfig.WebConfig.XSRFExpire = AppConfig.DefaultInt("XSRFExpire", BConfig.WebConfig.XSRFExpire)
BConfig.WebConfig.TemplateLeft = AppConfig.DefaultString("TemplateLeft", BConfig.WebConfig.TemplateLeft)
BConfig.WebConfig.TemplateRight = AppConfig.DefaultString("TemplateRight", BConfig.WebConfig.TemplateRight)
BConfig.WebConfig.Session.SessionOn = AppConfig.DefaultBool("SessionOn", BConfig.WebConfig.Session.SessionOn)
BConfig.WebConfig.Session.SessionProvider = AppConfig.DefaultString("SessionProvider", BConfig.WebConfig.Session.SessionProvider)
BConfig.WebConfig.Session.SessionName = AppConfig.DefaultString("SessionName", BConfig.WebConfig.Session.SessionName)
BConfig.WebConfig.Session.SessionProviderConfig = AppConfig.DefaultString("SessionProviderConfig", BConfig.WebConfig.Session.SessionProviderConfig)
BConfig.WebConfig.Session.SessionGCMaxLifetime = AppConfig.DefaultInt64("SessionGCMaxLifetime", BConfig.WebConfig.Session.SessionGCMaxLifetime)
BConfig.WebConfig.Session.SessionCookieLifeTime = AppConfig.DefaultInt("SessionCookieLifeTime", BConfig.WebConfig.Session.SessionCookieLifeTime)
BConfig.WebConfig.Session.SessionAutoSetCookie = AppConfig.DefaultBool("SessionAutoSetCookie", BConfig.WebConfig.Session.SessionAutoSetCookie)
BConfig.WebConfig.Session.SessionDomain = AppConfig.DefaultString("SessionDomain", BConfig.WebConfig.Session.SessionDomain)
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
assignSingleConfig(i, ac)
}
if sd := AppConfig.String("StaticDir"); sd != "" {
for k := range BConfig.WebConfig.StaticDir {
delete(BConfig.WebConfig.StaticDir, k)
}
if sd := ac.String("StaticDir"); sd != "" {
BConfig.WebConfig.StaticDir = map[string]string{}
sds := strings.Fields(sd)
for _, v := range sds {
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
BConfig.WebConfig.StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1]
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1]
} else {
BConfig.WebConfig.StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0]
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0]
}
}
}
if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" {
extensions := strings.Split(sgz, ",")
fileExts := []string{}
for _, ext := range extensions {
@ -273,15 +297,90 @@ func ParseConfig() (err error) {
BConfig.WebConfig.StaticExtensionsToGzip = fileExts
}
}
if lo := ac.String("LogOutputs"); lo != "" {
// if lo is not nil or empty
// means user has set his own LogOutputs
// clear the default setting to BConfig.Log.Outputs
BConfig.Log.Outputs = make(map[string]string)
los := strings.Split(lo, ";")
for _, v := range los {
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1]
} else {
continue
}
}
}
//init log
logs.Reset()
for adaptor, config := range BConfig.Log.Outputs {
err := logs.SetLogger(adaptor, config)
if err != nil {
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error()))
}
}
logs.SetLogFuncCall(BConfig.Log.FileLineNum)
return nil
}
func assignSingleConfig(p interface{}, ac config.Configer) {
pt := reflect.TypeOf(p)
if pt.Kind() != reflect.Ptr {
return
}
pt = pt.Elem()
if pt.Kind() != reflect.Struct {
return
}
pv := reflect.ValueOf(p).Elem()
for i := 0; i < pt.NumField(); i++ {
pf := pv.Field(i)
if !pf.CanSet() {
continue
}
name := pt.Field(i).Name
switch pf.Kind() {
case reflect.String:
pf.SetString(ac.DefaultString(name, pf.String()))
case reflect.Int, reflect.Int64:
pf.SetInt(int64(ac.DefaultInt64(name, pf.Int())))
case reflect.Bool:
pf.SetBool(ac.DefaultBool(name, pf.Bool()))
case reflect.Struct:
default:
//do nothing here
}
}
}
// LoadAppConfig allow developer to apply a config file
func LoadAppConfig(adapterName, configPath string) error {
absConfigPath, err := filepath.Abs(configPath)
if err != nil {
return err
}
if !utils.FileExists(absConfigPath) {
return fmt.Errorf("the target config file: %s don't exist", configPath)
}
appConfigPath = absConfigPath
appConfigProvider = adapterName
return parseConfig(appConfigPath)
}
type beegoAppConfig struct {
innerConfig config.Configer
}
func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, error) {
ac, err := config.NewConfig(AppConfigProvider, AppConfigPath)
func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
ac, err := config.NewConfig(appConfigProvider, appConfigPath)
if err != nil {
return nil, err
}
@ -303,7 +402,7 @@ func (b *beegoAppConfig) String(key string) string {
}
func (b *beegoAppConfig) Strings(key string) []string {
if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); v[0] != "" {
if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 {
return v
}
return b.innerConfig.Strings(key)
@ -337,46 +436,46 @@ func (b *beegoAppConfig) Float(key string) (float64, error) {
return b.innerConfig.Float(key)
}
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string {
func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
if v := b.String(key); v != "" {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string {
func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
if v := b.Strings(key); len(v) != 0 {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int {
func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
if v, err := b.Int(key); err == nil {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 {
func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
if v, err := b.Int64(key); err == nil {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool {
func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
if v, err := b.Bool(key); err == nil {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 {
func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
if v, err := b.Float(key); err == nil {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {

View File

@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package config is used to parse config
// Package config is used to parse config.
// Usage:
// import(
// "github.com/astaxie/beego/config"
// )
// import "github.com/astaxie/beego/config"
//Examples.
//
// cnf, err := config.NewConfig("ini", "config.conf")
//
@ -38,12 +37,14 @@
// cnf.DIY(key string) (interface{}, error)
// cnf.GetSection(section string) (map[string]string, error)
// cnf.SaveConfigFile(filename string) error
//
// more docs http://beego.me/docs/module/config.md
//More docs http://beego.me/docs/module/config.md
package config
import (
"fmt"
"os"
"reflect"
"time"
)
// Configer defines how to get and set value from configuration raw data.
@ -106,3 +107,136 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
}
return adapter.ParseData(data)
}
// ExpandValueEnvForMap convert all string value with environment variable.
func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} {
for k, v := range m {
switch value := v.(type) {
case string:
m[k] = ExpandValueEnv(value)
case map[string]interface{}:
m[k] = ExpandValueEnvForMap(value)
case map[string]string:
for k2, v2 := range value {
value[k2] = ExpandValueEnv(v2)
}
m[k] = value
}
}
return m
}
// ExpandValueEnv returns value of convert with environment variable.
//
// Return environment variable if value start with "${" and end with "}".
// Return default value if environment variable is empty or not exist.
//
// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue".
// Examples:
// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable.
// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/".
// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie".
func ExpandValueEnv(value string) (realValue string) {
realValue = value
vLen := len(value)
// 3 = ${}
if vLen < 3 {
return
}
// Need start with "${" and end with "}", then return.
if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
return
}
key := ""
defalutV := ""
// value start with "${"
for i := 2; i < vLen; i++ {
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
key = value[2:i]
defalutV = value[i+2 : vLen-1] // other string is default value.
break
} else if value[i] == '}' {
key = value[2:i]
break
}
}
realValue = os.Getenv(key)
if realValue == "" {
realValue = defalutV
}
return
}
// ParseBool returns the boolean value represented by the string.
//
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
// Any other value returns an error.
func ParseBool(val interface{}) (value bool, err error) {
if val != nil {
switch v := val.(type) {
case bool:
return v, nil
case string:
switch v {
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
return true, nil
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
return false, nil
}
case int8, int32, int64:
strV := fmt.Sprintf("%s", v)
if strV == "1" {
return true, nil
} else if strV == "0" {
return false, nil
}
case float64:
if v == 1 {
return true, nil
} else if v == 0 {
return false, nil
}
}
return false, fmt.Errorf("parsing %q: invalid syntax", val)
}
return false, fmt.Errorf("parsing <nil>: invalid syntax")
}
// ToString converts values of any type to string.
func ToString(x interface{}) string {
switch y := x.(type) {
// Handle dates with special logic
// This needs to come above the fmt.Stringer
// test since time.Time's have a .String()
// method
case time.Time:
return y.Format("A Monday")
// Handle type string
case string:
return y
// Handle type with .String() method
case fmt.Stringer:
return y.String()
// Handle type with .Error() method
case error:
return y.Error()
}
// Handle named string type
if v := reflect.ValueOf(x); v.Kind() == reflect.String {
return v.String()
}
// Fallback to fmt package for anything else like numeric types
return fmt.Sprint(x)
}

55
config/config_test.go Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2016 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"os"
"testing"
)
func TestExpandValueEnv(t *testing.T) {
testCases := []struct {
item string
want string
}{
{"", ""},
{"$", "$"},
{"{", "{"},
{"{}", "{}"},
{"${}", ""},
{"${|}", ""},
{"${}", ""},
{"${{}}", ""},
{"${{||}}", "}"},
{"${pwd||}", ""},
{"${pwd||}", ""},
{"${pwd||}", ""},
{"${pwd||}}", "}"},
{"${pwd||{{||}}}", "{{||}}"},
{"${GOPATH}", os.Getenv("GOPATH")},
{"${GOPATH||}", os.Getenv("GOPATH")},
{"${GOPATH||root}", os.Getenv("GOPATH")},
{"${GOPATH_NOT||root}", "root"},
{"${GOPATH_NOT||||root}", "||root"},
}
for _, c := range testCases {
if got := ExpandValueEnv(c.item); got != c.want {
t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got)
}
}
}

View File

@ -38,7 +38,7 @@ func (c *fakeConfigContainer) String(key string) string {
}
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
v := c.getData(key)
v := c.String(key)
if v == "" {
return defaultval
}
@ -46,12 +46,16 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin
}
func (c *fakeConfigContainer) Strings(key string) []string {
return strings.Split(c.getData(key), ";")
v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
}
func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v
@ -82,7 +86,7 @@ func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
}
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.getData(key))
return ParseBool(c.getData(key))
}
func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {

View File

@ -23,11 +23,11 @@ import (
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unicode"
)
var (
@ -83,11 +83,14 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
if err == io.EOF {
break
}
//It might be a good idea to throw a error on all unknonw errors?
if _, ok := err.(*os.PathError); ok {
return nil, err
}
line = bytes.TrimSpace(line)
if bytes.Equal(line, bEmpty) {
continue
}
line = bytes.TrimSpace(line)
var bComment []byte
switch {
case bytes.HasPrefix(line, bNumComment):
@ -97,9 +100,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
}
if bComment != nil {
line = bytes.TrimLeft(line, string(bComment))
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// Need append to a new line if multi-line comments.
if comment.Len() > 0 {
comment.WriteByte('\n')
}
comment.Write(line)
comment.WriteByte('\n')
continue
}
@ -128,8 +133,8 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
includefiles := strings.Fields(key)
if includefiles[0] == "include" && len(includefiles) == 2 {
otherfile := strings.Trim(includefiles[1], "\"")
if !path.IsAbs(otherfile) {
otherfile = path.Join(path.Dir(name), otherfile)
if !filepath.IsAbs(otherfile) {
otherfile = filepath.Join(filepath.Dir(name), otherfile)
}
i, err := ini.parseFile(otherfile)
if err != nil {
@ -161,7 +166,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
val = bytes.Trim(val, `"`)
}
cfg.data[section][key] = string(val)
cfg.data[section][key] = ExpandValueEnv(string(val))
if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String()
comment.Reset()
@ -194,7 +199,7 @@ type IniConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.getdata(key))
return ParseBool(c.getdata(key))
}
// DefaultBool returns the boolean value for a given key.
@ -268,15 +273,20 @@ func (c *IniConfigContainer) DefaultString(key string, defaultval string) string
}
// Strings returns the []string value for a given key.
// Return nil if config value does not exist or is empty.
func (c *IniConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v
@ -290,7 +300,9 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
return nil, errors.New("not exist setction")
}
// SaveConfigFile save the config into file
// SaveConfigFile save the config into file.
//
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
@ -299,14 +311,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
}
defer f.Close()
// Get section or key comments. Fixed #1607
getCommentStr := func(section, key string) string {
comment, ok := "", false
if len(key) == 0 {
comment, ok = c.sectionComment[section]
} else {
comment, ok = c.keyComment[section+"."+key]
}
if ok {
// Empty comment
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
return string(bNumComment)
}
prefix := string(bNumComment)
// Add the line head character "#"
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
}
return ""
}
buf := bytes.NewBuffer(nil)
// Save default section at first place
if dt, ok := c.data[defaultSection]; ok {
for key, val := range dt {
if key != " " {
// Write key comments.
if v, ok := c.keyComment[key]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
if v := getCommentStr(defaultSection, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
@ -327,8 +360,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
for section, dt := range c.data {
if section != defaultSection {
// Write section comments.
if v, ok := c.sectionComment[section]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
if v := getCommentStr(section, ""); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
@ -341,8 +374,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
for key, val := range dt {
if key != " " {
// Write key comments.
if v, ok := c.keyComment[key]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
if v := getCommentStr(section, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}

View File

@ -15,11 +15,17 @@
package config
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
)
var inicontext = `
func TestIni(t *testing.T) {
var (
inicontext = `
;comment one
#comment two
appname = beeapi
@ -29,14 +35,52 @@ PI = 3.1415976
runmode = "dev"
autorender = false
copyrequestbody = true
session= on
cookieon= off
newreg = OFF
needlogin = ON
enableSession = Y
enableCookie = N
flag = 1
path1 = ${GOPATH}
path2 = ${GOPATH||/home/go}
[demo]
key1="asta"
key2 = "xie"
CaseInsensitive = true
peers = one;two;three
password = ${GOPATH}
`
func TestIni(t *testing.T) {
keyValue = map[string]interface{}{
"appname": "beeapi",
"httpport": 8080,
"mysqlport": int64(3600),
"pi": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"session": true,
"cookieon": false,
"newreg": false,
"needlogin": true,
"enableSession": true,
"enableCookie": false,
"flag": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"demo::key1": "asta",
"demo::key2": "xie",
"demo::CaseInsensitive": true,
"demo::peers": []string{"one", "two", "three"},
"demo::password": os.Getenv("GOPATH"),
"null": "",
"demo2::key1": "",
"error": "",
"emptystrings": []string{},
}
)
f, err := os.Create("testini.conf")
if err != nil {
t.Fatal(err)
@ -52,31 +96,31 @@ func TestIni(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if iniconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
if port, err := iniconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port)
t.Fatal(err)
}
if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port)
t.Fatal(err)
}
if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 {
t.Error(pi)
t.Fatal(err)
}
if iniconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v, err := iniconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
}
if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true {
t.Error(v)
t.Fatal(err)
for k, v := range keyValue {
var err error
var value interface{}
switch v.(type) {
case int:
value, err = iniconf.Int(k)
case int64:
value, err = iniconf.Int64(k)
case float64:
value, err = iniconf.Float(k)
case bool:
value, err = iniconf.Bool(k)
case []string:
value = iniconf.Strings(k)
case string:
value = iniconf.String(k)
default:
value, err = iniconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fail,err %s", k, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = iniconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
@ -84,20 +128,63 @@ func TestIni(t *testing.T) {
if iniconf.String("name") != "astaxie" {
t.Fatal("get name error")
}
if iniconf.String("demo::key1") != "asta" {
t.Fatal("get demo.key1 error")
}
if iniconf.String("demo::key2") != "xie" {
t.Fatal("get demo.key2 error")
}
if v, err := iniconf.Bool("demo::caseinsensitive"); err != nil || v != true {
t.Fatal("get demo.caseinsensitive error")
}
if data := iniconf.Strings("demo::peers"); len(data) != 3 {
t.Fatal("get strings error", data)
} else if data[0] != "one" {
t.Fatal("get first params error not equat to one")
}
}
func TestIniSave(t *testing.T) {
const (
inicontext = `
app = app
;comment one
#comment two
# comment three
appname = beeapi
httpport = 8080
# DB Info
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
name = mysql
`
saveResult = `
app=app
#comment one
#comment two
# comment three
appname=beeapi
httpport=8080
# DB Info
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
name=mysql
`
)
cfg, err := NewConfigData("ini", []byte(inicontext))
if err != nil {
t.Fatal(err)
}
name := "newIniConfig.ini"
if err := cfg.SaveConfigFile(name); err != nil {
t.Fatal(err)
}
defer os.Remove(name)
if data, err := ioutil.ReadFile(name); err != nil {
t.Fatal(err)
} else {
cfgData := string(data)
datas := strings.Split(saveResult, "\n")
for _, line := range datas {
if strings.Contains(cfgData, line+"\n") == false {
t.Fatalf("different after save ini config file. need contains %q", line)
}
}
}
}

View File

@ -17,6 +17,7 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
@ -56,6 +57,9 @@ func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
}
x.data["rootArray"] = wrappingArray
}
x.data = ExpandValueEnvForMap(x.data)
return x, nil
}
@ -70,12 +74,9 @@ type JSONConfigContainer struct {
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
val := c.getData(key)
if val != nil {
if v, ok := val.(bool); ok {
return v, nil
}
return false, errors.New("not bool value")
return ParseBool(val)
}
return false, errors.New("not exist key:" + key)
return false, fmt.Errorf("not exist key: %q", key)
}
// DefaultBool return the bool value if has no error
@ -175,7 +176,7 @@ func (c *JSONConfigContainer) DefaultString(key string, defaultval string) strin
func (c *JSONConfigContainer) Strings(key string) []string {
stringVal := c.String(key)
if stringVal == "" {
return []string{}
return nil
}
return strings.Split(c.String(key), ";")
}
@ -183,7 +184,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) > 0 {
if v := c.Strings(key); v != nil {
return v
}
return defaultval

View File

@ -15,34 +15,14 @@
package config
import (
"fmt"
"os"
"testing"
)
var jsoncontext = `{
"appname": "beeapi",
"testnames": "foo;bar",
"httpport": 8080,
"mysqlport": 3600,
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"database": {
"host": "host",
"port": "port",
"database": "database",
"username": "username",
"password": "password",
"conns":{
"maxconnection":12,
"autoconnect":true,
"connectioninfo":"info"
}
}
}`
func TestJsonStartsWithArray(t *testing.T) {
var jsoncontextwitharray = `[
const jsoncontextwitharray = `[
{
"url": "user",
"serviceAPI": "http://www.test.com/user"
@ -52,8 +32,6 @@ var jsoncontextwitharray = `[
"serviceAPI": "http://www.test.com/employee"
}
]`
func TestJsonStartsWithArray(t *testing.T) {
f, err := os.Create("testjsonWithArray.conf")
if err != nil {
t.Fatal(err)
@ -90,6 +68,70 @@ func TestJsonStartsWithArray(t *testing.T) {
}
func TestJson(t *testing.T) {
var (
jsoncontext = `{
"appname": "beeapi",
"testnames": "foo;bar",
"httpport": 8080,
"mysqlport": 3600,
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"session": "on",
"cookieon": "off",
"newreg": "OFF",
"needlogin": "ON",
"enableSession": "Y",
"enableCookie": "N",
"flag": 1,
"path1": "${GOPATH}",
"path2": "${GOPATH||/home/go}",
"database": {
"host": "host",
"port": "port",
"database": "database",
"username": "username",
"password": "${GOPATH}",
"conns":{
"maxconnection":12,
"autoconnect":true,
"connectioninfo":"info",
"root": "${GOPATH}"
}
}
}`
keyValue = map[string]interface{}{
"appname": "beeapi",
"testnames": []string{"foo", "bar"},
"httpport": 8080,
"mysqlport": int64(3600),
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"session": true,
"cookieon": false,
"newreg": false,
"needlogin": true,
"enableSession": true,
"enableCookie": false,
"flag": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"database::host": "host",
"database::port": "port",
"database::database": "database",
"database::password": os.Getenv("GOPATH"),
"database::conns::maxconnection": 12,
"database::conns::autoconnect": true,
"database::conns::connectioninfo": "info",
"database::conns::root": os.Getenv("GOPATH"),
"unknown": "",
}
)
f, err := os.Create("testjson.conf")
if err != nil {
t.Fatal(err)
@ -105,37 +147,32 @@ func TestJson(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if jsonconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port)
t.Fatal(err)
}
if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port)
t.Fatal(err)
}
if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 {
t.Error(pi)
t.Fatal(err)
}
if jsonconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v := jsonconf.Strings("unknown"); len(v) > 0 {
t.Fatal("unknown strings, the length should be 0")
}
if v := jsonconf.Strings("testnames"); len(v) != 2 {
t.Fatal("testnames length should be 2")
}
if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
}
if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true {
t.Error(v)
t.Fatal(err)
for k, v := range keyValue {
var err error
var value interface{}
switch v.(type) {
case int:
value, err = jsonconf.Int(k)
case int64:
value, err = jsonconf.Int64(k)
case float64:
value, err = jsonconf.Float(k)
case bool:
value, err = jsonconf.Bool(k)
case []string:
value = jsonconf.Strings(k)
case string:
value = jsonconf.String(k)
default:
value, err = jsonconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = jsonconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
@ -143,15 +180,7 @@ func TestJson(t *testing.T) {
if jsonconf.String("name") != "astaxie" {
t.Fatal("get name error")
}
if jsonconf.String("database::host") != "host" {
t.Fatal("get database::host error")
}
if jsonconf.String("database::conns::connectioninfo") != "info" {
t.Fatal("get database::conns::connectioninfo error")
}
if maxconnection, err := jsonconf.Int("database::conns::maxconnection"); err != nil || maxconnection != 12 {
t.Fatal("get database::conns::maxconnection error")
}
if db, err := jsonconf.DIY("database"); err != nil {
t.Fatal(err)
} else if m, ok := db.(map[string]interface{}); !ok {

View File

@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package xml for config provider
// Package xml for config provider.
//
// depend on github.com/beego/x2j
// depend on github.com/beego/x2j.
//
// go install github.com/beego/x2j
// go install github.com/beego/x2j.
//
// Usage:
// import(
// _ "github.com/astaxie/beego/config/xml"
// "github.com/astaxie/beego/config"
// )
// import(
// _ "github.com/astaxie/beego/config/xml"
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("xml", "config.xml")
//
// more docs http://beego.me/docs/module/config.md
//More docs http://beego.me/docs/module/config.md
package xml
import (
@ -69,7 +69,7 @@ func (xc *Config) Parse(filename string) (config.Configer, error) {
return nil, err
}
x.data = d["config"].(map[string]interface{})
x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
return x, nil
}
@ -92,7 +92,10 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.data[key].(string))
if v := c.data[key]; v != nil {
return config.ParseBool(v)
}
return false, fmt.Errorf("not exist key: %q", key)
}
// DefaultBool return the bool value if has no error
@ -171,14 +174,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
// Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v
@ -186,10 +193,14 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok {
return v.(map[string]string), nil
if v, ok := c.data[section].(map[string]interface{}); ok {
mapstr := make(map[string]string)
for k, val := range v {
mapstr[k] = config.ToString(val)
}
return mapstr, nil
}
return nil, errors.New("not exist setction")
return nil, fmt.Errorf("section '%s' not found", section)
}
// SaveConfigFile save the config into file

View File

@ -15,14 +15,18 @@
package xml
import (
"fmt"
"os"
"testing"
"github.com/astaxie/beego/config"
)
//xml parse should incluce in <config></config> tags
var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
func TestXML(t *testing.T) {
var (
//xml parse should incluce in <config></config> tags
xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
<config>
<appname>beeapi</appname>
<httpport>8080</httpport>
@ -31,10 +35,29 @@ var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
<runmode>dev</runmode>
<autorender>false</autorender>
<copyrequestbody>true</copyrequestbody>
<path1>${GOPATH}</path1>
<path2>${GOPATH||/home/go}</path2>
<mysection>
<id>1</id>
<name>MySection</name>
</mysection>
</config>
`
keyValue = map[string]interface{}{
"appname": "beeapi",
"httpport": 8080,
"mysqlport": int64(3600),
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"error": "",
"emptystrings": []string{},
}
)
func TestXML(t *testing.T) {
f, err := os.Create("testxml.conf")
if err != nil {
t.Fatal(err)
@ -46,36 +69,53 @@ func TestXML(t *testing.T) {
}
f.Close()
defer os.Remove("testxml.conf")
xmlconf, err := config.NewConfig("xml", "testxml.conf")
if err != nil {
t.Fatal(err)
}
if xmlconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port)
var xmlsection map[string]string
xmlsection, err = xmlconf.GetSection("mysection")
if err != nil {
t.Fatal(err)
}
if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port)
t.Fatal(err)
if len(xmlsection) == 0 {
t.Error("section should not be empty")
}
if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 {
t.Error(pi)
t.Fatal(err)
}
if xmlconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v, err := xmlconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
}
if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true {
t.Error(v)
t.Fatal(err)
for k, v := range keyValue {
var (
value interface{}
err error
)
switch v.(type) {
case int:
value, err = xmlconf.Int(k)
case int64:
value, err = xmlconf.Int64(k)
case float64:
value, err = xmlconf.Float(k)
case bool:
value, err = xmlconf.Bool(k)
case []string:
value = xmlconf.Strings(k)
case string:
value = xmlconf.String(k)
default:
value, err = xmlconf.DIY(k)
}
if err != nil {
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Errorf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = xmlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}

View File

@ -19,14 +19,14 @@
// go install github.com/beego/goyaml2
//
// Usage:
// import(
// import(
// _ "github.com/astaxie/beego/config/yaml"
// "github.com/astaxie/beego/config"
// )
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("yaml", "config.yaml")
//
// more docs http://beego.me/docs/module/config.md
//More docs http://beego.me/docs/module/config.md
package yaml
import (
@ -110,6 +110,7 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
log.Println("Not a Map? >> ", string(buf), data)
cnf = nil
}
cnf = config.ExpandValueEnvForMap(cnf)
return
}
@ -121,10 +122,11 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key].(bool); ok {
return v, nil
v, err := c.getData(key)
if err != nil {
return false, err
}
return false, errors.New("not bool value")
return config.ParseBool(v)
}
// DefaultBool return the bool value if has no error
@ -139,8 +141,12 @@ func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
// Int returns the integer value for a given key.
func (c *ConfigContainer) Int(key string) (int, error) {
if v, ok := c.data[key].(int64); ok {
return int(v), nil
if v, err := c.getData(key); err != nil {
return 0, err
} else if vv, ok := v.(int); ok {
return vv, nil
} else if vv, ok := v.(int64); ok {
return int(vv), nil
}
return 0, errors.New("not int value")
}
@ -157,8 +163,10 @@ func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
// Int64 returns the int64 value for a given key.
func (c *ConfigContainer) Int64(key string) (int64, error) {
if v, ok := c.data[key].(int64); ok {
return v, nil
if v, err := c.getData(key); err != nil {
return 0, err
} else if vv, ok := v.(int64); ok {
return vv, nil
}
return 0, errors.New("not bool value")
}
@ -175,8 +183,14 @@ func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
// Float returns the float value for a given key.
func (c *ConfigContainer) Float(key string) (float64, error) {
if v, ok := c.data[key].(float64); ok {
return v, nil
if v, err := c.getData(key); err != nil {
return 0.0, err
} else if vv, ok := v.(float64); ok {
return vv, nil
} else if vv, ok := v.(int); ok {
return float64(vv), nil
} else if vv, ok := v.(int64); ok {
return float64(vv), nil
}
return 0.0, errors.New("not float64 value")
}
@ -193,8 +207,10 @@ func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
// String returns the string value for a given key.
func (c *ConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok {
return v
if v, err := c.getData(key); err == nil {
if vv, ok := v.(string); ok {
return vv
}
}
return ""
}
@ -211,14 +227,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
// Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v
@ -226,8 +246,8 @@ func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []stri
// GetSection returns map for the given section
func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
v, ok := c.data[section]
if ok {
if v, ok := c.data[section]; ok {
return v.(map[string]string), nil
}
return nil, errors.New("not exist setction")
@ -255,10 +275,19 @@ func (c *ConfigContainer) Set(key, val string) error {
// DIY returns the raw value by a given key.
func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
return c.getData(key)
}
func (c *ConfigContainer) getData(key string) (interface{}, error) {
if len(key) == 0 {
return nil, errors.New("key is empty")
}
if v, ok := c.data[key]; ok {
return v, nil
}
return nil, errors.New("not exist key")
return nil, fmt.Errorf("not exist key %q", key)
}
func init() {

View File

@ -15,13 +15,17 @@
package yaml
import (
"fmt"
"os"
"testing"
"github.com/astaxie/beego/config"
)
var yamlcontext = `
func TestYaml(t *testing.T) {
var (
yamlcontext = `
"appname": beeapi
"httpport": 8080
"mysqlport": 3600
@ -29,9 +33,27 @@ var yamlcontext = `
"runmode": dev
"autorender": false
"copyrequestbody": true
"PATH": GOPATH
"path1": ${GOPATH}
"path2": ${GOPATH||/home/go}
"empty": ""
`
func TestYaml(t *testing.T) {
keyValue = map[string]interface{}{
"appname": "beeapi",
"httpport": 8080,
"mysqlport": int64(3600),
"PI": 3.1415976,
"runmode": "dev",
"autorender": false,
"copyrequestbody": true,
"PATH": "GOPATH",
"path1": os.Getenv("GOPATH"),
"path2": os.Getenv("GOPATH"),
"error": "",
"emptystrings": []string{},
}
)
f, err := os.Create("testyaml.conf")
if err != nil {
t.Fatal(err)
@ -47,36 +69,47 @@ func TestYaml(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if yamlconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port)
t.Fatal(err)
}
if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port)
t.Fatal(err)
}
if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 {
t.Error(pi)
t.Fatal(err)
}
if yamlconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v, err := yamlconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
}
if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true {
t.Error(v)
t.Fatal(err)
for k, v := range keyValue {
var (
value interface{}
err error
)
switch v.(type) {
case int:
value, err = yamlconf.Int(k)
case int64:
value, err = yamlconf.Int64(k)
case float64:
value, err = yamlconf.Float(k)
case bool:
value, err = yamlconf.Bool(k)
case []string:
value = yamlconf.Strings(k)
case string:
value = yamlconf.String(k)
default:
value, err = yamlconf.DIY(k)
}
if err != nil {
t.Errorf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Errorf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = yamlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
}
if yamlconf.String("name") != "astaxie" {
t.Fatal("get name error")
}
}

View File

@ -15,7 +15,11 @@
package beego
import (
"encoding/json"
"reflect"
"testing"
"github.com/astaxie/beego/config"
)
func TestDefaults(t *testing.T) {
@ -27,3 +31,108 @@ func TestDefaults(t *testing.T) {
t.Errorf("FlashName was not set to default.")
}
}
func TestAssignConfig_01(t *testing.T) {
_BConfig := &Config{}
_BConfig.AppName = "beego_test"
jcf := &config.JSONConfig{}
ac, _ := jcf.ParseData([]byte(`{"AppName":"beego_json"}`))
assignSingleConfig(_BConfig, ac)
if _BConfig.AppName != "beego_json" {
t.Log(_BConfig)
t.FailNow()
}
}
func TestAssignConfig_02(t *testing.T) {
_BConfig := &Config{}
bs, _ := json.Marshal(newBConfig())
jsonMap := map[string]interface{}{}
json.Unmarshal(bs, &jsonMap)
configMap := map[string]interface{}{}
for k, v := range jsonMap {
if reflect.TypeOf(v).Kind() == reflect.Map {
for k1, v1 := range v.(map[string]interface{}) {
if reflect.TypeOf(v1).Kind() == reflect.Map {
for k2, v2 := range v1.(map[string]interface{}) {
configMap[k2] = v2
}
} else {
configMap[k1] = v1
}
}
} else {
configMap[k] = v
}
}
configMap["MaxMemory"] = 1024
configMap["Graceful"] = true
configMap["XSRFExpire"] = 32
configMap["SessionProviderConfig"] = "file"
configMap["FileLineNum"] = true
jcf := &config.JSONConfig{}
bs, _ = json.Marshal(configMap)
ac, _ := jcf.ParseData([]byte(bs))
for _, i := range []interface{}{_BConfig, &_BConfig.Listen, &_BConfig.WebConfig, &_BConfig.Log, &_BConfig.WebConfig.Session} {
assignSingleConfig(i, ac)
}
if _BConfig.MaxMemory != 1024 {
t.Log(_BConfig.MaxMemory)
t.FailNow()
}
if !_BConfig.Listen.Graceful {
t.Log(_BConfig.Listen.Graceful)
t.FailNow()
}
if _BConfig.WebConfig.XSRFExpire != 32 {
t.Log(_BConfig.WebConfig.XSRFExpire)
t.FailNow()
}
if _BConfig.WebConfig.Session.SessionProviderConfig != "file" {
t.Log(_BConfig.WebConfig.Session.SessionProviderConfig)
t.FailNow()
}
if !_BConfig.Log.FileLineNum {
t.Log(_BConfig.Log.FileLineNum)
t.FailNow()
}
}
func TestAssignConfig_03(t *testing.T) {
jcf := &config.JSONConfig{}
ac, _ := jcf.ParseData([]byte(`{"AppName":"beego"}`))
ac.Set("AppName", "test_app")
ac.Set("RunMode", "online")
ac.Set("StaticDir", "download:down download2:down2")
ac.Set("StaticExtensionsToGzip", ".css,.js,.html,.jpg,.png")
assignConfig(ac)
t.Logf("%#v", BConfig)
if BConfig.AppName != "test_app" {
t.FailNow()
}
if BConfig.RunMode != "online" {
t.FailNow()
}
if BConfig.WebConfig.StaticDir["/download"] != "down" {
t.FailNow()
}
if BConfig.WebConfig.StaticDir["/download2"] != "down2" {
t.FailNow()
}
if len(BConfig.WebConfig.StaticExtensionsToGzip) != 5 {
t.FailNow()
}
}

View File

@ -27,6 +27,33 @@ import (
"sync"
)
var (
//Default size==20B same as nginx
defaultGzipMinLength = 20
//Content will only be compressed if content length is either unknown or greater than gzipMinLength.
gzipMinLength = defaultGzipMinLength
//The compression level used for deflate compression. (0-9).
gzipCompressLevel int
//List of HTTP methods to compress. If not set, only GET requests are compressed.
includedMethods map[string]bool
getMethodOnly bool
)
func InitGzip(minLength, compressLevel int, methods []string) {
if minLength >= 0 {
gzipMinLength = minLength
}
gzipCompressLevel = compressLevel
if gzipCompressLevel < flate.NoCompression || gzipCompressLevel > flate.BestCompression {
gzipCompressLevel = flate.BestSpeed
}
getMethodOnly = (len(methods) == 0) || (len(methods) == 1 && strings.ToUpper(methods[0]) == "GET")
includedMethods = make(map[string]bool, len(methods))
for _, v := range methods {
includedMethods[strings.ToUpper(v)] = true
}
}
type resetWriter interface {
io.Writer
Reset(w io.Writer)
@ -41,20 +68,20 @@ func (n nopResetWriter) Reset(w io.Writer) {
}
type acceptEncoder struct {
name string
levelEncode func(int) resetWriter
bestSpeedPool *sync.Pool
bestCompressionPool *sync.Pool
name string
levelEncode func(int) resetWriter
customCompressLevelPool *sync.Pool
bestCompressionPool *sync.Pool
}
func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter {
if ac.bestSpeedPool == nil || ac.bestCompressionPool == nil {
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
return nopResetWriter{wr}
}
var rwr resetWriter
switch level {
case flate.BestSpeed:
rwr = ac.bestSpeedPool.Get().(resetWriter)
rwr = ac.customCompressLevelPool.Get().(resetWriter)
case flate.BestCompression:
rwr = ac.bestCompressionPool.Get().(resetWriter)
default:
@ -65,13 +92,18 @@ func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter {
}
func (ac acceptEncoder) put(wr resetWriter, level int) {
if ac.bestSpeedPool == nil || ac.bestCompressionPool == nil {
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
return
}
wr.Reset(nil)
//notice
//compressionLevel==BestCompression DOES NOT MATTER
//sync.Pool will not memory leak
switch level {
case flate.BestSpeed:
ac.bestSpeedPool.Put(wr)
case gzipCompressLevel:
ac.customCompressLevelPool.Put(wr)
case flate.BestCompression:
ac.bestCompressionPool.Put(wr)
}
@ -79,28 +111,22 @@ func (ac acceptEncoder) put(wr resetWriter, level int) {
var (
noneCompressEncoder = acceptEncoder{"", nil, nil, nil}
gzipCompressEncoder = acceptEncoder{"gzip",
func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr },
&sync.Pool{
New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestSpeed); return wr },
},
&sync.Pool{
New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr },
},
gzipCompressEncoder = acceptEncoder{
name: "gzip",
levelEncode: func(level int) resetWriter { wr, _ := gzip.NewWriterLevel(nil, level); return wr },
customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, gzipCompressLevel); return wr }},
bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := gzip.NewWriterLevel(nil, flate.BestCompression); return wr }},
}
//according to the sec :http://tools.ietf.org/html/rfc2616#section-3.5 ,the deflate compress in http is zlib indeed
//deflate
//The "zlib" format defined in RFC 1950 [31] in combination with
//the "deflate" compression mechanism described in RFC 1951 [29].
deflateCompressEncoder = acceptEncoder{"deflate",
func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr },
&sync.Pool{
New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestSpeed); return wr },
},
&sync.Pool{
New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr },
},
deflateCompressEncoder = acceptEncoder{
name: "deflate",
levelEncode: func(level int) resetWriter { wr, _ := zlib.NewWriterLevel(nil, level); return wr },
customCompressLevelPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, gzipCompressLevel); return wr }},
bestCompressionPool: &sync.Pool{New: func() interface{} { wr, _ := zlib.NewWriterLevel(nil, flate.BestCompression); return wr }},
}
)
@ -120,7 +146,11 @@ func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string,
// WriteBody reads writes content to writer by the specific encoding(gzip/deflate)
func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) {
return writeLevel(encoding, writer, bytes.NewReader(content), flate.BestSpeed)
if encoding == "" || len(content) < gzipMinLength {
_, err := writer.Write(content)
return false, "", err
}
return writeLevel(encoding, writer, bytes.NewReader(content), gzipCompressLevel)
}
// writeLevel reads from reader,writes to writer by specific encoding and compress level
@ -156,7 +186,10 @@ func ParseEncoding(r *http.Request) string {
if r == nil {
return ""
}
return parseEncoding(r)
if (getMethodOnly && r.Method == "GET") || includedMethods[r.Method] {
return parseEncoding(r)
}
return ""
}
type q struct {
@ -176,9 +209,13 @@ func parseEncoding(r *http.Request) string {
continue
}
vs := strings.Split(v, ";")
var cf acceptEncoder
var ok bool
if cf, ok = encoderMap[vs[0]]; !ok {
continue
}
if len(vs) == 1 {
lastQ = q{vs[0], 1}
break
return cf.name
}
if len(vs) == 2 {
f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64)
@ -186,12 +223,9 @@ func parseEncoding(r *http.Request) string {
continue
}
if f > lastQ.value {
lastQ = q{vs[0], f}
lastQ = q{cf.name, f}
}
}
}
if cf, ok := encoderMap[lastQ.name]; ok {
return cf.name
}
return ""
return lastQ.name
}

View File

@ -41,4 +41,19 @@ func Test_ExtractEncoding(t *testing.T) {
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"*"}}}) != "gzip" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x,gzip,deflate"}}}) != "gzip" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip,x,deflate"}}}) != "gzip" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x,deflate"}}}) != "deflate" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x"}}}) != "" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x;q=0.8"}}}) != "gzip" {
t.Fail()
}
}

View File

@ -59,21 +59,24 @@ type Context struct {
// Reset init Context, BeegoInput and BeegoOutput
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
ctx.Request = r
ctx.ResponseWriter = &Response{rw, false, 0}
if ctx.ResponseWriter == nil {
ctx.ResponseWriter = &Response{}
}
ctx.ResponseWriter.reset(rw)
ctx.Input.Reset(ctx)
ctx.Output.Reset(ctx)
ctx._xsrfToken = ""
}
// Redirect does redirection to localurl with http header status code.
// It sends http response header directly.
func (ctx *Context) Redirect(status int, localurl string) {
ctx.Output.Header("Location", localurl)
ctx.ResponseWriter.WriteHeader(status)
http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status)
}
// Abort stops this request.
// if beego.ErrorMaps exists, panic body.
func (ctx *Context) Abort(status int, body string) {
ctx.Output.SetStatus(status)
panic(body)
}
@ -176,25 +179,35 @@ type Response struct {
Status int
}
func (r *Response) reset(rw http.ResponseWriter) {
r.ResponseWriter = rw
r.Status = 0
r.Started = false
}
// Write writes the data to the connection as part of an HTTP reply,
// and sets `started` to true.
// started means the response has sent out.
func (w *Response) Write(p []byte) (int, error) {
w.Started = true
return w.ResponseWriter.Write(p)
func (r *Response) Write(p []byte) (int, error) {
r.Started = true
return r.ResponseWriter.Write(p)
}
// WriteHeader sends an HTTP response header with status code,
// and sets `started` to true.
func (w *Response) WriteHeader(code int) {
w.Status = code
w.Started = true
w.ResponseWriter.WriteHeader(code)
func (r *Response) WriteHeader(code int) {
if r.Status > 0 {
//prevent multiple response.WriteHeader calls
return
}
r.Status = code
r.Started = true
r.ResponseWriter.WriteHeader(code)
}
// Hijack hijacker for http
func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := w.ResponseWriter.(http.Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := r.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, errors.New("webserver doesn't support hijacking")
}
@ -202,15 +215,15 @@ func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
}
// Flush http.Flusher
func (w *Response) Flush() {
if f, ok := w.ResponseWriter.(http.Flusher); ok {
func (r *Response) Flush() {
if f, ok := r.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}
// CloseNotify http.CloseNotifier
func (w *Response) CloseNotify() <-chan bool {
if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok {
func (r *Response) CloseNotify() <-chan bool {
if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok {
return cn.CloseNotify()
}
return nil

47
context/context_test.go Normal file
View File

@ -0,0 +1,47 @@
// Copyright 2016 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package context
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestXsrfReset_01(t *testing.T) {
r := &http.Request{}
c := NewContext()
c.Request = r
c.ResponseWriter = &Response{}
c.ResponseWriter.reset(httptest.NewRecorder())
c.Output.Reset(c)
c.Input.Reset(c)
c.XSRFToken("key", 16)
if c._xsrfToken == "" {
t.FailNow()
}
token := c._xsrfToken
c.Reset(&Response{ResponseWriter: httptest.NewRecorder()}, r)
if c._xsrfToken != "" {
t.FailNow()
}
c.XSRFToken("key", 16)
if c._xsrfToken == "" {
t.FailNow()
}
if token == c._xsrfToken {
t.FailNow()
}
}

View File

@ -40,12 +40,14 @@ var (
// BeegoInput operates the http request header, data, cookie and body.
// it also contains router params and current session.
type BeegoInput struct {
Context *Context
CruSession session.Store
pnames []string
pvalues []string
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
RequestBody []byte
Context *Context
CruSession session.Store
pnames []string
pvalues []string
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
RequestBody []byte
RunMethod string
RunController reflect.Type
}
// NewInput return BeegoInput generated by Context.
@ -89,6 +91,9 @@ func (input *BeegoInput) Site() string {
// Scheme returns request scheme as "http" or "https".
func (input *BeegoInput) Scheme() string {
if scheme := input.Header("X-Forwarded-Proto"); scheme != "" {
return scheme
}
if input.Context.Request.URL.Scheme != "" {
return input.Context.Request.URL.Scheme
}
@ -287,10 +292,25 @@ func (input *BeegoInput) Params() map[string]string {
// SetParam will set the param with key and value
func (input *BeegoInput) SetParam(key, val string) {
// check if already exists
for i, v := range input.pnames {
if v == key && i <= len(input.pvalues) {
input.pvalues[i] = val
return
}
}
input.pvalues = append(input.pvalues, val)
input.pnames = append(input.pnames, key)
}
// ResetParams clears any of the input's Params
// This function is used to clear parameters so they may be reset between filter
// passes.
func (input *BeegoInput) ResetParams() {
input.pnames = input.pnames[:0]
input.pvalues = input.pvalues[:0]
}
// Query returns input data item string by a given string.
func (input *BeegoInput) Query(key string) string {
if val := input.Param(key); val != "" {
@ -319,13 +339,16 @@ func (input *BeegoInput) Cookie(key string) string {
}
// Session returns current session item value by a given key.
// if non-existed, return empty string.
// if non-existed, return nil.
func (input *BeegoInput) Session(key interface{}) interface{} {
return input.CruSession.Get(key)
}
// CopyBody returns the raw request body data as bytes.
func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
if input.Context.Request.Body == nil {
return []byte{}
}
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
requestbody, _ := ioutil.ReadAll(safe)
input.Context.Request.Body.Close()
@ -569,12 +592,15 @@ func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.
result := reflect.New(typ).Elem()
fieldValues := make(map[string]reflect.Value)
for reqKey, val := range *params {
if !strings.HasPrefix(reqKey, key+".") {
var fieldName string
if strings.HasPrefix(reqKey, key+".") {
fieldName = reqKey[len(key)+1:]
} else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' {
fieldName = reqKey[len(key)+1 : len(reqKey)-1]
} else {
continue
}
fieldName := reqKey[len(key)+1:]
if _, ok := fieldValues[fieldName]; !ok {
// Time to bind this field. Get it and make sure we can set it.
fieldValue := result.FieldByName(fieldName)

View File

@ -18,6 +18,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
)
@ -74,6 +75,24 @@ func TestParse(t *testing.T) {
fmt.Println(user)
}
func TestParse2(t *testing.T) {
r, _ := http.NewRequest("GET", "/?user[0][Username]=Raph&user[1].Username=Leo&user[0].Password=123456&user[1][Password]=654321", nil)
beegoInput := NewInput()
beegoInput.Context = NewContext()
beegoInput.Context.Reset(httptest.NewRecorder(), r)
beegoInput.ParseFormOrMulitForm(1 << 20)
type User struct {
Username string
Password string
}
var users []User
err := beegoInput.Bind(&users, "user")
fmt.Println(users)
if err != nil || users[0].Username != "Raph" || users[0].Password != "123456" || users[1].Username != "Leo" || users[1].Password != "654321" {
t.Fatal("users info wrong")
}
}
func TestSubDomain(t *testing.T) {
r, _ := http.NewRequest("GET", "http://www.example.com/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil)
beegoInput := NewInput()
@ -99,7 +118,7 @@ func TestSubDomain(t *testing.T) {
/* TODO Fix this
r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil)
beegoInput.Request = r
beegoInput.Context.Request = r
if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
}
@ -117,3 +136,56 @@ func TestSubDomain(t *testing.T) {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
}
}
func TestParams(t *testing.T) {
inp := NewInput()
inp.SetParam("p1", "val1_ver1")
inp.SetParam("p2", "val2_ver1")
inp.SetParam("p3", "val3_ver1")
if l := inp.ParamsLen(); l != 3 {
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
}
if val := inp.Param("p1"); val != "val1_ver1" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver1")
}
if val := inp.Param("p3"); val != "val3_ver1" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val3_ver1")
}
vals := inp.Params()
expected := map[string]string{
"p1": "val1_ver1",
"p2": "val2_ver1",
"p3": "val3_ver1",
}
if !reflect.DeepEqual(vals, expected) {
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
}
// overwriting existing params
inp.SetParam("p1", "val1_ver2")
inp.SetParam("p2", "val2_ver2")
expected = map[string]string{
"p1": "val1_ver2",
"p2": "val2_ver2",
"p3": "val3_ver1",
}
vals = inp.Params()
if !reflect.DeepEqual(vals, expected) {
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
}
if l := inp.ParamsLen(); l != 3 {
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
}
if val := inp.Param("p1"); val != "val1_ver2" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
}
if val := inp.Param("p2"); val != "val2_ver2" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
}
}

View File

@ -24,6 +24,8 @@ import (
"io"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
@ -57,7 +59,7 @@ func (output *BeegoOutput) Header(key, val string) {
// Body sets response body content.
// if EnableGzip, compress content string.
// it sends out response body directly.
func (output *BeegoOutput) Body(content []byte) {
func (output *BeegoOutput) Body(content []byte) error {
var encoding string
var buf = &bytes.Buffer{}
if output.EnableGzip {
@ -73,9 +75,11 @@ func (output *BeegoOutput) Body(content []byte) {
if output.Status != 0 {
output.Context.ResponseWriter.WriteHeader(output.Status)
output.Status = 0
} else {
output.Context.ResponseWriter.Started = true
}
io.Copy(output.Context.ResponseWriter, buf)
return nil
}
// Cookie sets cookie value via given key.
@ -97,9 +101,10 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
maxAge = v
}
if maxAge > 0 {
switch {
case maxAge > 0:
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge)
} else {
case maxAge < 0:
fmt.Fprintf(&b, "; Max-Age=0")
}
}
@ -141,18 +146,12 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
}
// default false. for session cookie default true
httponly := false
if len(others) > 4 {
if v, ok := others[4].(bool); ok && v {
// HttpOnly = true
httponly = true
fmt.Fprintf(&b, "; HttpOnly")
}
}
if httponly {
fmt.Fprintf(&b, "; HttpOnly")
}
output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
}
@ -186,8 +185,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
if coding {
content = []byte(stringsToJSON(string(content)))
}
output.Body(content)
return nil
return output.Body(content)
}
// JSONP writes jsonp to response body.
@ -208,12 +206,12 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
if callback == "" {
return errors.New(`"callback" parameter required`)
}
callbackContent := bytes.NewBufferString(" " + template.JSEscapeString(callback))
callback = template.JSEscapeString(callback)
callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback)
callbackContent.WriteString("(")
callbackContent.Write(content)
callbackContent.WriteString(");\r\n")
output.Body(callbackContent.Bytes())
return nil
return output.Body(callbackContent.Bytes())
}
// XML writes xml string to response body.
@ -230,20 +228,27 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err
}
output.Body(content)
return nil
return output.Body(content)
}
// Download forces response for download file.
// it prepares the download response header automatically.
func (output *BeegoOutput) Download(file string, filename ...string) {
// check get file error, file not found or other error.
if _, err := os.Stat(file); err != nil {
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
return
}
var fName string
if len(filename) > 0 && filename[0] != "" {
fName = filename[0]
} else {
fName = filepath.Base(file)
}
output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName))
output.Header("Content-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream")
if len(filename) > 0 && filename[0] != "" {
output.Header("Content-Disposition", "attachment; filename="+filename[0])
} else {
output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file))
}
output.Header("Content-Transfer-Encoding", "binary")
output.Header("Expires", "0")
output.Header("Cache-Control", "must-revalidate")
@ -271,55 +276,55 @@ func (output *BeegoOutput) SetStatus(status int) {
// IsCachable returns boolean of this request is cached.
// HTTP 304 means cached.
func (output *BeegoOutput) IsCachable(status int) bool {
func (output *BeegoOutput) IsCachable() bool {
return output.Status >= 200 && output.Status < 300 || output.Status == 304
}
// IsEmpty returns boolean of this request is empty.
// HTTP 201204 and 304 means empty.
func (output *BeegoOutput) IsEmpty(status int) bool {
func (output *BeegoOutput) IsEmpty() bool {
return output.Status == 201 || output.Status == 204 || output.Status == 304
}
// IsOk returns boolean of this request runs well.
// HTTP 200 means ok.
func (output *BeegoOutput) IsOk(status int) bool {
func (output *BeegoOutput) IsOk() bool {
return output.Status == 200
}
// IsSuccessful returns boolean of this request runs successfully.
// HTTP 2xx means ok.
func (output *BeegoOutput) IsSuccessful(status int) bool {
func (output *BeegoOutput) IsSuccessful() bool {
return output.Status >= 200 && output.Status < 300
}
// IsRedirect returns boolean of this request is redirection header.
// HTTP 301,302,307 means redirection.
func (output *BeegoOutput) IsRedirect(status int) bool {
func (output *BeegoOutput) IsRedirect() bool {
return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
}
// IsForbidden returns boolean of this request is forbidden.
// HTTP 403 means forbidden.
func (output *BeegoOutput) IsForbidden(status int) bool {
func (output *BeegoOutput) IsForbidden() bool {
return output.Status == 403
}
// IsNotFound returns boolean of this request is not found.
// HTTP 404 means forbidden.
func (output *BeegoOutput) IsNotFound(status int) bool {
func (output *BeegoOutput) IsNotFound() bool {
return output.Status == 404
}
// IsClientError returns boolean of this request client sends error data.
// HTTP 4xx means forbidden.
func (output *BeegoOutput) IsClientError(status int) bool {
func (output *BeegoOutput) IsClientError() bool {
return output.Status >= 400 && output.Status < 500
}
// IsServerError returns boolean of this server handler errors.
// HTTP 5xx means server internal error.
func (output *BeegoOutput) IsServerError(status int) bool {
func (output *BeegoOutput) IsServerError() bool {
return output.Status >= 500 && output.Status < 600
}

View File

@ -71,6 +71,7 @@ type Controller struct {
TplName string
Layout string
LayoutSections map[string]string // the key is the section name and the value is the template name
TplPrefix string
TplExt string
EnableRender bool
@ -185,8 +186,7 @@ func (c *Controller) Render() error {
return err
}
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
c.Ctx.Output.Body(rb)
return nil
return c.Ctx.Output.Body(rb)
}
// RenderString returns the rendered template string. Do not send out response.
@ -197,33 +197,9 @@ func (c *Controller) RenderString() (string, error) {
// RenderBytes returns the bytes of rendered template string. Do not send out response.
func (c *Controller) RenderBytes() ([]byte, error) {
//if the controller has set layout, then first get the tplname's content set the content to the layout
var buf bytes.Buffer
if c.Layout != "" {
if c.TplName == "" {
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
}
if BConfig.RunMode == DEV {
buildFiles := []string{c.TplName}
if c.LayoutSections != nil {
for _, sectionTpl := range c.LayoutSections {
if sectionTpl == "" {
continue
}
buildFiles = append(buildFiles, sectionTpl)
}
}
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
}
if _, ok := BeeTemplates[c.TplName]; !ok {
panic("can't find templatefile in the path:" + c.TplName)
}
err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
buf, err := c.renderTemplate()
//if the controller has set layout, then first get the tplName's content set the content to the layout
if err == nil && c.Layout != "" {
c.Data["LayoutContent"] = template.HTML(buf.String())
if c.LayoutSections != nil {
@ -232,11 +208,9 @@ func (c *Controller) RenderBytes() ([]byte, error) {
c.Data[sectionName] = ""
continue
}
buf.Reset()
err = BeeTemplates[sectionTpl].ExecuteTemplate(&buf, sectionTpl, c.Data)
err = ExecuteTemplate(&buf, sectionTpl, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
c.Data[sectionName] = template.HTML(buf.String())
@ -244,30 +218,35 @@ func (c *Controller) RenderBytes() ([]byte, error) {
}
buf.Reset()
err = BeeTemplates[c.Layout].ExecuteTemplate(&buf, c.Layout, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
return buf.Bytes(), nil
ExecuteTemplate(&buf, c.Layout, c.Data)
}
return buf.Bytes(), err
}
func (c *Controller) renderTemplate() (bytes.Buffer, error) {
var buf bytes.Buffer
if c.TplName == "" {
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
}
if c.TplPrefix != "" {
c.TplName = c.TplPrefix + c.TplName
}
if BConfig.RunMode == DEV {
BuildTemplate(BConfig.WebConfig.ViewsPath, c.TplName)
buildFiles := []string{c.TplName}
if c.Layout != "" {
buildFiles = append(buildFiles, c.Layout)
if c.LayoutSections != nil {
for _, sectionTpl := range c.LayoutSections {
if sectionTpl == "" {
continue
}
buildFiles = append(buildFiles, sectionTpl)
}
}
}
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
}
if _, ok := BeeTemplates[c.TplName]; !ok {
panic("can't find templatefile in the path:" + c.TplName)
}
buf.Reset()
err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
return buf.Bytes(), nil
return buf, ExecuteTemplate(&buf, c.TplName, c.Data)
}
// Redirect sends the redirection response to url with status code.
@ -286,12 +265,13 @@ func (c *Controller) Abort(code string) {
// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body.
func (c *Controller) CustomAbort(status int, body string) {
c.Ctx.ResponseWriter.WriteHeader(status)
// first panic from ErrorMaps, is is user defined error functions.
// first panic from ErrorMaps, it is user defined error functions.
if _, ok := ErrorMaps[body]; ok {
c.Ctx.Output.Status = status
panic(body)
}
// last panic user string
c.Ctx.ResponseWriter.WriteHeader(status)
c.Ctx.ResponseWriter.Write([]byte(body))
panic(ErrAbort)
}
@ -419,6 +399,16 @@ func (c *Controller) GetInt8(key string, def ...int8) (int8, error) {
return int8(i64), err
}
// GetUint8 return input as an uint8 or the default value while it's present and input is blank
func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) {
strv := c.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return def[0], nil
}
u64, err := strconv.ParseUint(strv, 10, 8)
return uint8(u64), err
}
// GetInt16 returns input as an int16 or the default value while it's present and input is blank
func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
strv := c.Ctx.Input.Query(key)
@ -429,6 +419,16 @@ func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
return int16(i64), err
}
// GetUint16 returns input as an uint16 or the default value while it's present and input is blank
func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) {
strv := c.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return def[0], nil
}
u64, err := strconv.ParseUint(strv, 10, 16)
return uint16(u64), err
}
// GetInt32 returns input as an int32 or the default value while it's present and input is blank
func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
strv := c.Ctx.Input.Query(key)
@ -439,6 +439,16 @@ func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
return int32(i64), err
}
// GetUint32 returns input as an uint32 or the default value while it's present and input is blank
func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) {
strv := c.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return def[0], nil
}
u64, err := strconv.ParseUint(strv, 10, 32)
return uint32(u64), err
}
// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
strv := c.Ctx.Input.Query(key)
@ -448,6 +458,15 @@ func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
return strconv.ParseInt(strv, 10, 64)
}
// GetUint64 returns input value as uint64 or the default value while it's present and input is blank.
func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) {
strv := c.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return def[0], nil
}
return strconv.ParseUint(strv, 10, 64)
}
// GetBool returns input value as bool or the default value while it's present and input is blank.
func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
strv := c.Ctx.Input.Query(key)
@ -473,7 +492,7 @@ func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader,
}
// GetFiles return multi-upload files
// files, err:=c.Getfiles("myfiles")
// files, err:=c.GetFiles("myfiles")
// if err != nil {
// http.Error(w, err.Error(), http.StatusNoContent)
// return
@ -568,6 +587,7 @@ func (c *Controller) SessionRegenerateID() {
// DestroySession cleans session data and session cookie.
func (c *Controller) DestroySession() {
c.Ctx.Input.CruSession.Flush()
c.Ctx.Input.CruSession = nil
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
}

View File

@ -15,6 +15,8 @@
package beego
import (
"math"
"strconv"
"testing"
"github.com/astaxie/beego/context"
@ -75,3 +77,47 @@ func TestGetInt64(t *testing.T) {
t.Errorf("TestGeetInt64 expect 40,get %T,%v", val, val)
}
}
func TestGetUint8(t *testing.T) {
i := context.NewInput()
i.SetParam("age", strconv.FormatUint(math.MaxUint8, 10))
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetUint8("age")
if val != math.MaxUint8 {
t.Errorf("TestGetUint8 expect %v,get %T,%v", math.MaxUint8, val, val)
}
}
func TestGetUint16(t *testing.T) {
i := context.NewInput()
i.SetParam("age", strconv.FormatUint(math.MaxUint16, 10))
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetUint16("age")
if val != math.MaxUint16 {
t.Errorf("TestGetUint16 expect %v,get %T,%v", math.MaxUint16, val, val)
}
}
func TestGetUint32(t *testing.T) {
i := context.NewInput()
i.SetParam("age", strconv.FormatUint(math.MaxUint32, 10))
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetUint32("age")
if val != math.MaxUint32 {
t.Errorf("TestGetUint32 expect %v,get %T,%v", math.MaxUint32, val, val)
}
}
func TestGetUint64(t *testing.T) {
i := context.NewInput()
i.SetParam("age", strconv.FormatUint(math.MaxUint64, 10))
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetUint64("age")
if val != math.MaxUint64 {
t.Errorf("TestGetUint64 expect %v,get %T,%v", uint64(math.MaxUint64), val, val)
}
}

225
error.go
View File

@ -93,7 +93,11 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
"BeegoVersion": VERSION,
"GoVersion": runtime.Version(),
}
ctx.ResponseWriter.WriteHeader(500)
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
t.Execute(ctx.ResponseWriter, data)
}
@ -210,159 +214,139 @@ var ErrorMaps = make(map[string]*errorInfo, 10)
// show 401 unauthorized error.
func unauthorized(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(401),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested can't be authorized." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The credentials you supplied are incorrect" +
"<br>There are errors in the website address" +
"</ul>")
t.Execute(rw, data)
responseError(rw, r,
401,
"<br>The page you have requested can't be authorized."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>The credentials you supplied are incorrect"+
"<br>There are errors in the website address"+
"</ul>",
)
}
// show 402 Payment Required
func paymentRequired(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(402),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested Payment Required." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The credentials you supplied are incorrect" +
"<br>There are errors in the website address" +
"</ul>")
t.Execute(rw, data)
responseError(rw, r,
402,
"<br>The page you have requested Payment Required."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>The credentials you supplied are incorrect"+
"<br>There are errors in the website address"+
"</ul>",
)
}
// show 403 forbidden error.
func forbidden(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(403),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested is forbidden." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>Your address may be blocked" +
"<br>The site may be disabled" +
"<br>You need to log in" +
"</ul>")
t.Execute(rw, data)
responseError(rw, r,
403,
"<br>The page you have requested is forbidden."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>Your address may be blocked"+
"<br>The site may be disabled"+
"<br>You need to log in"+
"</ul>",
)
}
// show 404 notfound error.
// show 404 not found error.
func notFound(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(404),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested has flown the coop." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The page has moved" +
"<br>The page no longer exists" +
"<br>You were looking for your puppy and got lost" +
"<br>You like 404 pages" +
"</ul>")
t.Execute(rw, data)
responseError(rw, r,
404,
"<br>The page you have requested has flown the coop."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>The page has moved"+
"<br>The page no longer exists"+
"<br>You were looking for your puppy and got lost"+
"<br>You like 404 pages"+
"</ul>",
)
}
// show 405 Method Not Allowed
func methodNotAllowed(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(405),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The method you have requested Not Allowed." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI" +
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource." +
"</ul>")
t.Execute(rw, data)
responseError(rw, r,
405,
"<br>The method you have requested Not Allowed."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI"+
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource."+
"</ul>",
)
}
// show 500 internal server error.
func internalServerError(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(500),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
"<br><br><ul>" +
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
t.Execute(rw, data)
responseError(rw, r,
500,
"<br>The page you have requested is down right now."+
"<br><br><ul>"+
"<br>Please try again later and report the error to the website administrator"+
"<br></ul>",
)
}
// show 501 Not Implemented.
func notImplemented(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(504),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested is Not Implemented." +
"<br><br><ul>" +
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
t.Execute(rw, data)
responseError(rw, r,
501,
"<br>The page you have requested is Not Implemented."+
"<br><br><ul>"+
"<br>Please try again later and report the error to the website administrator"+
"<br></ul>",
)
}
// show 502 Bad Gateway.
func badGateway(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(502),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
"<br><br><ul>" +
"<br>The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request." +
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
t.Execute(rw, data)
responseError(rw, r,
502,
"<br>The page you have requested is down right now."+
"<br><br><ul>"+
"<br>The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."+
"<br>Please try again later and report the error to the website administrator"+
"<br></ul>",
)
}
// show 503 service unavailable error.
func serviceUnavailable(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(503),
"BeegoVersion": VERSION,
}
data["Content"] = template.HTML("<br>The page you have requested is unavailable." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br><br>The page is overloaded" +
"<br>Please try again later." +
"</ul>")
t.Execute(rw, data)
responseError(rw, r,
503,
"<br>The page you have requested is unavailable."+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br><br>The page is overloaded"+
"<br>Please try again later."+
"</ul>",
)
}
// show 504 Gateway Timeout.
func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
responseError(rw, r,
504,
"<br>The page you have requested is unavailable"+
"<br>Perhaps you are here because:"+
"<br><br><ul>"+
"<br><br>The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI."+
"<br>Please try again later."+
"</ul>",
)
}
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := map[string]interface{}{
"Title": http.StatusText(504),
"Title": http.StatusText(errCode),
"BeegoVersion": VERSION,
"Content": template.HTML(errContent),
}
data["Content"] = template.HTML("<br>The page you have requested is unavailable." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br><br>The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI." +
"<br>Please try again later." +
"</ul>")
t.Execute(rw, data)
}
@ -400,6 +384,11 @@ func ErrorController(c ControllerInterface) *App {
return BeeApp
}
// Exception Write HttpStatus with errCode and Exec error handler if exist.
func Exception(errCode uint64, ctx *context.Context) {
exception(strconv.FormatUint(errCode, 10), ctx)
}
// show error string as simple text message.
// if error string is empty, show 503 or 500 error as default.
func exception(errCode string, ctx *context.Context) {
@ -408,7 +397,10 @@ func exception(errCode string, ctx *context.Context) {
if err == nil {
return v
}
return 503
if ctx.Output.Status == 0 {
return 503
}
return ctx.Output.Status
}
for _, ec := range []string{errCode, "503", "500"} {
@ -424,6 +416,7 @@ func exception(errCode string, ctx *context.Context) {
func executeError(err *errorInfo, ctx *context.Context, code int) {
if err.errorType == errorTypeHandler {
ctx.ResponseWriter.WriteHeader(code)
err.handler(ctx.ResponseWriter, ctx.Request)
return
}

88
error_test.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2016 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
)
type errorTestController struct {
Controller
}
const parseCodeError = "parse code error"
func (ec *errorTestController) Get() {
errorCode, err := ec.GetInt("code")
if err != nil {
ec.Abort(parseCodeError)
}
if errorCode != 0 {
ec.CustomAbort(errorCode, ec.GetString("code"))
}
ec.Abort("404")
}
func TestErrorCode_01(t *testing.T) {
registerDefaultErrorHandler()
for k := range ErrorMaps {
r, _ := http.NewRequest("GET", "/error?code="+k, nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Add("/error", &errorTestController{})
handler.ServeHTTP(w, r)
code, _ := strconv.Atoi(k)
if w.Code != code {
t.Fail()
}
if !strings.Contains(string(w.Body.Bytes()), http.StatusText(code)) {
t.Fail()
}
}
}
func TestErrorCode_02(t *testing.T) {
registerDefaultErrorHandler()
r, _ := http.NewRequest("GET", "/error?code=0", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Add("/error", &errorTestController{})
handler.ServeHTTP(w, r)
if w.Code != 404 {
t.Fail()
}
}
func TestErrorCode_03(t *testing.T) {
registerDefaultErrorHandler()
r, _ := http.NewRequest("GET", "/error?code=panic", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Add("/error", &errorTestController{})
handler.ServeHTTP(w, r)
if w.Code != 200 {
t.Fail()
}
if string(w.Body.Bytes()) != parseCodeError {
t.Fail()
}
}

View File

@ -27,6 +27,7 @@ type FilterRouter struct {
tree *Tree
pattern string
returnOnOutput bool
resetParams bool
}
// ValidRouter checks if the current request is matched by this filter.

View File

@ -20,14 +20,8 @@ import (
"testing"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
)
func init() {
BeeLogger = logs.NewLogger(10000)
BeeLogger.SetLogger("console", "")
}
var FilterUser = func(ctx *context.Context) {
ctx.Output.Body([]byte("i am " + ctx.Input.Param(":last") + ctx.Input.Param(":first")))
}

View File

@ -90,16 +90,15 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
addr = ":https"
}
config := &tls.Config{}
if srv.TLSConfig != nil {
*config = *srv.TLSConfig
if srv.TLSConfig == nil {
srv.TLSConfig = &tls.Config{}
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
if srv.TLSConfig.NextProtos == nil {
srv.TLSConfig.NextProtos = []string{"http/1.1"}
}
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return
}
@ -113,7 +112,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
}
srv.tlsInnerListener = newGraceListener(l, srv)
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, config)
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
if srv.isChild {
process, err := os.FindProcess(os.Getppid())

View File

@ -6,6 +6,8 @@ import (
"net/http"
"path/filepath"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/session"
)
@ -43,23 +45,25 @@ func registerSession() error {
if BConfig.WebConfig.Session.SessionOn {
var err error
sessionConfig := AppConfig.String("sessionConfig")
conf := new(session.ManagerConfig)
if sessionConfig == "" {
conf := map[string]interface{}{
"cookieName": BConfig.WebConfig.Session.SessionName,
"gclifetime": BConfig.WebConfig.Session.SessionGCMaxLifetime,
"providerConfig": filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig),
"secure": BConfig.Listen.EnableHTTPS,
"enableSetCookie": BConfig.WebConfig.Session.SessionAutoSetCookie,
"domain": BConfig.WebConfig.Session.SessionDomain,
"cookieLifeTime": BConfig.WebConfig.Session.SessionCookieLifeTime,
}
confBytes, err := json.Marshal(conf)
if err != nil {
conf.CookieName = BConfig.WebConfig.Session.SessionName
conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie
conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime
conf.Secure = BConfig.Listen.EnableHTTPS
conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime
conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig)
conf.DisableHTTPOnly = BConfig.WebConfig.Session.SessionDisableHTTPOnly
conf.Domain = BConfig.WebConfig.Session.SessionDomain
conf.EnableSidInHttpHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
conf.SessionNameInHttpHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
conf.EnableSidInUrlQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
} else {
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
return err
}
sessionConfig = string(confBytes)
}
if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, sessionConfig); err != nil {
if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, conf); err != nil {
return err
}
go GlobalSessions.GC()
@ -68,21 +72,11 @@ func registerSession() error {
}
func registerTemplate() error {
if BConfig.WebConfig.AutoRender {
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
if BConfig.RunMode == DEV {
Warn(err)
}
return err
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
if BConfig.RunMode == DEV {
logs.Warn(err)
}
}
return nil
}
func registerDocs() error {
if BConfig.WebConfig.EnableDocs {
Get("/docs", serverDocs)
Get("/docs/*", serverDocs)
return err
}
return nil
}
@ -93,3 +87,14 @@ func registerAdmin() error {
}
return nil
}
func registerGzip() error {
if BConfig.EnableGzip {
context.InitGzip(
AppConfig.DefaultInt("gzipMinLength", -1),
AppConfig.DefaultInt("gzipCompressLevel", -1),
AppConfig.DefaultStrings("includedMethods", []string{"GET"}),
)
}
return nil
}

View File

@ -136,6 +136,7 @@ type BeegoHTTPSettings struct {
TLSClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper
CheckRedirect func(req *http.Request, via []*http.Request) error
EnableCookie bool
Gzip bool
DumpBody bool
@ -265,6 +266,15 @@ func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error))
return b
}
// SetCheckRedirect specifies the policy for handling redirects.
//
// If CheckRedirect is nil, the Client uses its default policy,
// which is to stop after 10 consecutive requests.
func (b *BeegoHTTPRequest) SetCheckRedirect(redirect func(req *http.Request, via []*http.Request) error) *BeegoHTTPRequest {
b.setting.CheckRedirect = redirect
return b
}
// Param adds query param in to request.
// params build query string as ?key1=value1&key2=value2...
func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest {
@ -301,13 +311,12 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
// JSONBody adds request raw body encoding by JSON.
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
if err := enc.Encode(obj); err != nil {
byts, err := json.Marshal(obj)
if err != nil {
return b, err
}
b.req.Body = ioutil.NopCloser(buf)
b.req.ContentLength = int64(buf.Len())
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/json")
}
return b, nil
@ -410,9 +419,10 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: -1,
}
} else {
// if b.transport is *http.Transport then set the settings.
@ -446,6 +456,10 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) {
b.req.Header.Set("User-Agent", b.setting.UserAgent)
}
if b.setting.CheckRedirect != nil {
client.CheckRedirect = b.setting.CheckRedirect
}
if b.setting.ShowDebug {
dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
if err != nil {

35
log.go
View File

@ -33,82 +33,77 @@ const (
)
// BeeLogger references the used application logger.
var BeeLogger = logs.NewLogger(100)
var BeeLogger = logs.GetBeeLogger()
// SetLevel sets the global log level used by the simple logger.
func SetLevel(l int) {
BeeLogger.SetLevel(l)
logs.SetLevel(l)
}
// SetLogFuncCall set the CallDepth, default is 3
func SetLogFuncCall(b bool) {
BeeLogger.EnableFuncCallDepth(b)
BeeLogger.SetLogFuncCallDepth(3)
logs.SetLogFuncCall(b)
}
// SetLogger sets a new logger.
func SetLogger(adaptername string, config string) error {
err := BeeLogger.SetLogger(adaptername, config)
if err != nil {
return err
}
return nil
return logs.SetLogger(adaptername, config)
}
// Emergency logs a message at emergency level.
func Emergency(v ...interface{}) {
BeeLogger.Emergency(generateFmtStr(len(v)), v...)
logs.Emergency(generateFmtStr(len(v)), v...)
}
// Alert logs a message at alert level.
func Alert(v ...interface{}) {
BeeLogger.Alert(generateFmtStr(len(v)), v...)
logs.Alert(generateFmtStr(len(v)), v...)
}
// Critical logs a message at critical level.
func Critical(v ...interface{}) {
BeeLogger.Critical(generateFmtStr(len(v)), v...)
logs.Critical(generateFmtStr(len(v)), v...)
}
// Error logs a message at error level.
func Error(v ...interface{}) {
BeeLogger.Error(generateFmtStr(len(v)), v...)
logs.Error(generateFmtStr(len(v)), v...)
}
// Warning logs a message at warning level.
func Warning(v ...interface{}) {
BeeLogger.Warning(generateFmtStr(len(v)), v...)
logs.Warning(generateFmtStr(len(v)), v...)
}
// Warn compatibility alias for Warning()
func Warn(v ...interface{}) {
BeeLogger.Warn(generateFmtStr(len(v)), v...)
logs.Warn(generateFmtStr(len(v)), v...)
}
// Notice logs a message at notice level.
func Notice(v ...interface{}) {
BeeLogger.Notice(generateFmtStr(len(v)), v...)
logs.Notice(generateFmtStr(len(v)), v...)
}
// Informational logs a message at info level.
func Informational(v ...interface{}) {
BeeLogger.Informational(generateFmtStr(len(v)), v...)
logs.Informational(generateFmtStr(len(v)), v...)
}
// Info compatibility alias for Warning()
func Info(v ...interface{}) {
BeeLogger.Info(generateFmtStr(len(v)), v...)
logs.Info(generateFmtStr(len(v)), v...)
}
// Debug logs a message at debug level.
func Debug(v ...interface{}) {
BeeLogger.Debug(generateFmtStr(len(v)), v...)
logs.Debug(generateFmtStr(len(v)), v...)
}
// Trace logs a message at trace level.
// compatibility alias for Warning()
func Trace(v ...interface{}) {
BeeLogger.Trace(generateFmtStr(len(v)), v...)
logs.Trace(generateFmtStr(len(v)), v...)
}
func generateFmtStr(n int) string {

View File

@ -12,28 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
// +build !windows
import (
"github.com/astaxie/beego/context"
)
package logs
// GlobalDocAPI store the swagger api documents
var GlobalDocAPI = make(map[string]interface{})
import "io"
func serverDocs(ctx *context.Context) {
var obj interface{}
if splat := ctx.Input.Param(":splat"); splat == "" {
obj = GlobalDocAPI["Root"]
} else {
if v, ok := GlobalDocAPI[splat]; ok {
obj = v
}
}
if obj != nil {
ctx.Output.Header("Access-Control-Allow-Origin", "*")
ctx.Output.JSON(obj, false, false)
return
}
ctx.Output.SetStatus(404)
type ansiColorWriter struct {
w io.Writer
mode outputMode
}
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
return cw.w.Write(p)
}

428
logs/color_windows.go Normal file
View File

@ -0,0 +1,428 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package logs
import (
"bytes"
"io"
"strings"
"syscall"
"unsafe"
)
type (
csiState int
parseResult int
)
const (
outsideCsiCode csiState = iota
firstCsiCode
secondCsiCode
)
const (
noConsole parseResult = iota
changedColor
unknown
)
type ansiColorWriter struct {
w io.Writer
mode outputMode
state csiState
paramStartBuf bytes.Buffer
paramBuf bytes.Buffer
}
const (
firstCsiChar byte = '\x1b'
secondeCsiChar byte = '['
separatorChar byte = ';'
sgrCode byte = 'm'
)
const (
foregroundBlue = uint16(0x0001)
foregroundGreen = uint16(0x0002)
foregroundRed = uint16(0x0004)
foregroundIntensity = uint16(0x0008)
backgroundBlue = uint16(0x0010)
backgroundGreen = uint16(0x0020)
backgroundRed = uint16(0x0040)
backgroundIntensity = uint16(0x0080)
underscore = uint16(0x8000)
foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity
backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity
)
const (
ansiReset = "0"
ansiIntensityOn = "1"
ansiIntensityOff = "21"
ansiUnderlineOn = "4"
ansiUnderlineOff = "24"
ansiBlinkOn = "5"
ansiBlinkOff = "25"
ansiForegroundBlack = "30"
ansiForegroundRed = "31"
ansiForegroundGreen = "32"
ansiForegroundYellow = "33"
ansiForegroundBlue = "34"
ansiForegroundMagenta = "35"
ansiForegroundCyan = "36"
ansiForegroundWhite = "37"
ansiForegroundDefault = "39"
ansiBackgroundBlack = "40"
ansiBackgroundRed = "41"
ansiBackgroundGreen = "42"
ansiBackgroundYellow = "43"
ansiBackgroundBlue = "44"
ansiBackgroundMagenta = "45"
ansiBackgroundCyan = "46"
ansiBackgroundWhite = "47"
ansiBackgroundDefault = "49"
ansiLightForegroundGray = "90"
ansiLightForegroundRed = "91"
ansiLightForegroundGreen = "92"
ansiLightForegroundYellow = "93"
ansiLightForegroundBlue = "94"
ansiLightForegroundMagenta = "95"
ansiLightForegroundCyan = "96"
ansiLightForegroundWhite = "97"
ansiLightBackgroundGray = "100"
ansiLightBackgroundRed = "101"
ansiLightBackgroundGreen = "102"
ansiLightBackgroundYellow = "103"
ansiLightBackgroundBlue = "104"
ansiLightBackgroundMagenta = "105"
ansiLightBackgroundCyan = "106"
ansiLightBackgroundWhite = "107"
)
type drawType int
const (
foreground drawType = iota
background
)
type winColor struct {
code uint16
drawType drawType
}
var colorMap = map[string]winColor{
ansiForegroundBlack: {0, foreground},
ansiForegroundRed: {foregroundRed, foreground},
ansiForegroundGreen: {foregroundGreen, foreground},
ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground},
ansiForegroundBlue: {foregroundBlue, foreground},
ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground},
ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground},
ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
ansiBackgroundBlack: {0, background},
ansiBackgroundRed: {backgroundRed, background},
ansiBackgroundGreen: {backgroundGreen, background},
ansiBackgroundYellow: {backgroundRed | backgroundGreen, background},
ansiBackgroundBlue: {backgroundBlue, background},
ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background},
ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background},
ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background},
ansiBackgroundDefault: {0, background},
ansiLightForegroundGray: {foregroundIntensity, foreground},
ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground},
ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground},
ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground},
ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground},
ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground},
ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground},
ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground},
ansiLightBackgroundGray: {backgroundIntensity, background},
ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background},
ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background},
ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background},
ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background},
ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background},
ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background},
ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background},
}
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
defaultAttr *textAttributes
)
func init() {
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
if screenInfo != nil {
colorMap[ansiForegroundDefault] = winColor{
screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue),
foreground,
}
colorMap[ansiBackgroundDefault] = winColor{
screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue),
background,
}
defaultAttr = convertTextAttr(screenInfo.WAttributes)
}
}
type coord struct {
X, Y int16
}
type smallRect struct {
Left, Top, Right, Bottom int16
}
type consoleScreenBufferInfo struct {
DwSize coord
DwCursorPosition coord
WAttributes uint16
SrWindow smallRect
DwMaximumWindowSize coord
}
func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo {
var csbi consoleScreenBufferInfo
ret, _, _ := procGetConsoleScreenBufferInfo.Call(
hConsoleOutput,
uintptr(unsafe.Pointer(&csbi)))
if ret == 0 {
return nil
}
return &csbi
}
func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool {
ret, _, _ := procSetConsoleTextAttribute.Call(
hConsoleOutput,
uintptr(wAttributes))
return ret != 0
}
type textAttributes struct {
foregroundColor uint16
backgroundColor uint16
foregroundIntensity uint16
backgroundIntensity uint16
underscore uint16
otherAttributes uint16
}
func convertTextAttr(winAttr uint16) *textAttributes {
fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue)
bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue)
fgIntensity := winAttr & foregroundIntensity
bgIntensity := winAttr & backgroundIntensity
underline := winAttr & underscore
otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore)
return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes}
}
func convertWinAttr(textAttr *textAttributes) uint16 {
var winAttr uint16
winAttr |= textAttr.foregroundColor
winAttr |= textAttr.backgroundColor
winAttr |= textAttr.foregroundIntensity
winAttr |= textAttr.backgroundIntensity
winAttr |= textAttr.underscore
winAttr |= textAttr.otherAttributes
return winAttr
}
func changeColor(param []byte) parseResult {
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
if screenInfo == nil {
return noConsole
}
winAttr := convertTextAttr(screenInfo.WAttributes)
strParam := string(param)
if len(strParam) <= 0 {
strParam = "0"
}
csiParam := strings.Split(strParam, string(separatorChar))
for _, p := range csiParam {
c, ok := colorMap[p]
switch {
case !ok:
switch p {
case ansiReset:
winAttr.foregroundColor = defaultAttr.foregroundColor
winAttr.backgroundColor = defaultAttr.backgroundColor
winAttr.foregroundIntensity = defaultAttr.foregroundIntensity
winAttr.backgroundIntensity = defaultAttr.backgroundIntensity
winAttr.underscore = 0
winAttr.otherAttributes = 0
case ansiIntensityOn:
winAttr.foregroundIntensity = foregroundIntensity
case ansiIntensityOff:
winAttr.foregroundIntensity = 0
case ansiUnderlineOn:
winAttr.underscore = underscore
case ansiUnderlineOff:
winAttr.underscore = 0
case ansiBlinkOn:
winAttr.backgroundIntensity = backgroundIntensity
case ansiBlinkOff:
winAttr.backgroundIntensity = 0
default:
// unknown code
}
case c.drawType == foreground:
winAttr.foregroundColor = c.code
case c.drawType == background:
winAttr.backgroundColor = c.code
}
}
winTextAttribute := convertWinAttr(winAttr)
setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute)
return changedColor
}
func parseEscapeSequence(command byte, param []byte) parseResult {
if defaultAttr == nil {
return noConsole
}
switch command {
case sgrCode:
return changeColor(param)
default:
return unknown
}
}
func (cw *ansiColorWriter) flushBuffer() (int, error) {
return cw.flushTo(cw.w)
}
func (cw *ansiColorWriter) resetBuffer() (int, error) {
return cw.flushTo(nil)
}
func (cw *ansiColorWriter) flushTo(w io.Writer) (int, error) {
var n1, n2 int
var err error
startBytes := cw.paramStartBuf.Bytes()
cw.paramStartBuf.Reset()
if w != nil {
n1, err = cw.w.Write(startBytes)
if err != nil {
return n1, err
}
} else {
n1 = len(startBytes)
}
paramBytes := cw.paramBuf.Bytes()
cw.paramBuf.Reset()
if w != nil {
n2, err = cw.w.Write(paramBytes)
if err != nil {
return n1 + n2, err
}
} else {
n2 = len(paramBytes)
}
return n1 + n2, nil
}
func isParameterChar(b byte) bool {
return ('0' <= b && b <= '9') || b == separatorChar
}
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
r, nw, first, last := 0, 0, 0, 0
if cw.mode != DiscardNonColorEscSeq {
cw.state = outsideCsiCode
cw.resetBuffer()
}
var err error
for i, ch := range p {
switch cw.state {
case outsideCsiCode:
if ch == firstCsiChar {
cw.paramStartBuf.WriteByte(ch)
cw.state = firstCsiCode
}
case firstCsiCode:
switch ch {
case firstCsiChar:
cw.paramStartBuf.WriteByte(ch)
break
case secondeCsiChar:
cw.paramStartBuf.WriteByte(ch)
cw.state = secondCsiCode
last = i - 1
default:
cw.resetBuffer()
cw.state = outsideCsiCode
}
case secondCsiCode:
if isParameterChar(ch) {
cw.paramBuf.WriteByte(ch)
} else {
nw, err = cw.w.Write(p[first:last])
r += nw
if err != nil {
return r, err
}
first = i + 1
result := parseEscapeSequence(ch, cw.paramBuf.Bytes())
if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) {
cw.paramBuf.WriteByte(ch)
nw, err := cw.flushBuffer()
if err != nil {
return r, err
}
r += nw
} else {
n, _ := cw.resetBuffer()
// Add one more to the size of the buffer for the last ch
r += n + 1
}
cw.state = outsideCsiCode
}
default:
cw.state = outsideCsiCode
}
}
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
nw, err = cw.w.Write(p[first:])
r += nw
}
return r, err
}

294
logs/color_windows_test.go Normal file
View File

@ -0,0 +1,294 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build windows
package logs
import (
"bytes"
"fmt"
"syscall"
"testing"
)
var GetConsoleScreenBufferInfo = getConsoleScreenBufferInfo
func ChangeColor(color uint16) {
setConsoleTextAttribute(uintptr(syscall.Stdout), color)
}
func ResetColor() {
ChangeColor(uint16(0x0007))
}
func TestWritePlanText(t *testing.T) {
inner := bytes.NewBufferString("")
w := NewAnsiColorWriter(inner)
expected := "plain text"
fmt.Fprintf(w, expected)
actual := inner.String()
if actual != expected {
t.Errorf("Get %q, want %q", actual, expected)
}
}
func TestWriteParseText(t *testing.T) {
inner := bytes.NewBufferString("")
w := NewAnsiColorWriter(inner)
inputTail := "\x1b[0mtail text"
expectedTail := "tail text"
fmt.Fprintf(w, inputTail)
actualTail := inner.String()
inner.Reset()
if actualTail != expectedTail {
t.Errorf("Get %q, want %q", actualTail, expectedTail)
}
inputHead := "head text\x1b[0m"
expectedHead := "head text"
fmt.Fprintf(w, inputHead)
actualHead := inner.String()
inner.Reset()
if actualHead != expectedHead {
t.Errorf("Get %q, want %q", actualHead, expectedHead)
}
inputBothEnds := "both ends \x1b[0m text"
expectedBothEnds := "both ends text"
fmt.Fprintf(w, inputBothEnds)
actualBothEnds := inner.String()
inner.Reset()
if actualBothEnds != expectedBothEnds {
t.Errorf("Get %q, want %q", actualBothEnds, expectedBothEnds)
}
inputManyEsc := "\x1b\x1b\x1b\x1b[0m many esc"
expectedManyEsc := "\x1b\x1b\x1b many esc"
fmt.Fprintf(w, inputManyEsc)
actualManyEsc := inner.String()
inner.Reset()
if actualManyEsc != expectedManyEsc {
t.Errorf("Get %q, want %q", actualManyEsc, expectedManyEsc)
}
expectedSplit := "split text"
for _, ch := range "split \x1b[0m text" {
fmt.Fprintf(w, string(ch))
}
actualSplit := inner.String()
inner.Reset()
if actualSplit != expectedSplit {
t.Errorf("Get %q, want %q", actualSplit, expectedSplit)
}
}
type screenNotFoundError struct {
error
}
func writeAnsiColor(expectedText, colorCode string) (actualText string, actualAttributes uint16, err error) {
inner := bytes.NewBufferString("")
w := NewAnsiColorWriter(inner)
fmt.Fprintf(w, "\x1b[%sm%s", colorCode, expectedText)
actualText = inner.String()
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
if screenInfo != nil {
actualAttributes = screenInfo.WAttributes
} else {
err = &screenNotFoundError{}
}
return
}
type testParam struct {
text string
attributes uint16
ansiColor string
}
func TestWriteAnsiColorText(t *testing.T) {
screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout))
if screenInfo == nil {
t.Fatal("Could not get ConsoleScreenBufferInfo")
}
defer ChangeColor(screenInfo.WAttributes)
defaultFgColor := screenInfo.WAttributes & uint16(0x0007)
defaultBgColor := screenInfo.WAttributes & uint16(0x0070)
defaultFgIntensity := screenInfo.WAttributes & uint16(0x0008)
defaultBgIntensity := screenInfo.WAttributes & uint16(0x0080)
fgParam := []testParam{
{"foreground black ", uint16(0x0000 | 0x0000), "30"},
{"foreground red ", uint16(0x0004 | 0x0000), "31"},
{"foreground green ", uint16(0x0002 | 0x0000), "32"},
{"foreground yellow ", uint16(0x0006 | 0x0000), "33"},
{"foreground blue ", uint16(0x0001 | 0x0000), "34"},
{"foreground magenta", uint16(0x0005 | 0x0000), "35"},
{"foreground cyan ", uint16(0x0003 | 0x0000), "36"},
{"foreground white ", uint16(0x0007 | 0x0000), "37"},
{"foreground default", defaultFgColor | 0x0000, "39"},
{"foreground light gray ", uint16(0x0000 | 0x0008 | 0x0000), "90"},
{"foreground light red ", uint16(0x0004 | 0x0008 | 0x0000), "91"},
{"foreground light green ", uint16(0x0002 | 0x0008 | 0x0000), "92"},
{"foreground light yellow ", uint16(0x0006 | 0x0008 | 0x0000), "93"},
{"foreground light blue ", uint16(0x0001 | 0x0008 | 0x0000), "94"},
{"foreground light magenta", uint16(0x0005 | 0x0008 | 0x0000), "95"},
{"foreground light cyan ", uint16(0x0003 | 0x0008 | 0x0000), "96"},
{"foreground light white ", uint16(0x0007 | 0x0008 | 0x0000), "97"},
}
bgParam := []testParam{
{"background black ", uint16(0x0007 | 0x0000), "40"},
{"background red ", uint16(0x0007 | 0x0040), "41"},
{"background green ", uint16(0x0007 | 0x0020), "42"},
{"background yellow ", uint16(0x0007 | 0x0060), "43"},
{"background blue ", uint16(0x0007 | 0x0010), "44"},
{"background magenta", uint16(0x0007 | 0x0050), "45"},
{"background cyan ", uint16(0x0007 | 0x0030), "46"},
{"background white ", uint16(0x0007 | 0x0070), "47"},
{"background default", uint16(0x0007) | defaultBgColor, "49"},
{"background light gray ", uint16(0x0007 | 0x0000 | 0x0080), "100"},
{"background light red ", uint16(0x0007 | 0x0040 | 0x0080), "101"},
{"background light green ", uint16(0x0007 | 0x0020 | 0x0080), "102"},
{"background light yellow ", uint16(0x0007 | 0x0060 | 0x0080), "103"},
{"background light blue ", uint16(0x0007 | 0x0010 | 0x0080), "104"},
{"background light magenta", uint16(0x0007 | 0x0050 | 0x0080), "105"},
{"background light cyan ", uint16(0x0007 | 0x0030 | 0x0080), "106"},
{"background light white ", uint16(0x0007 | 0x0070 | 0x0080), "107"},
}
resetParam := []testParam{
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, "0"},
{"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, ""},
}
boldParam := []testParam{
{"bold on", uint16(0x0007 | 0x0008), "1"},
{"bold off", uint16(0x0007), "21"},
}
underscoreParam := []testParam{
{"underscore on", uint16(0x0007 | 0x8000), "4"},
{"underscore off", uint16(0x0007), "24"},
}
blinkParam := []testParam{
{"blink on", uint16(0x0007 | 0x0080), "5"},
{"blink off", uint16(0x0007), "25"},
}
mixedParam := []testParam{
{"both black, bold, underline, blink", uint16(0x0000 | 0x0000 | 0x0008 | 0x8000 | 0x0080), "30;40;1;4;5"},
{"both red, bold, underline, blink", uint16(0x0004 | 0x0040 | 0x0008 | 0x8000 | 0x0080), "31;41;1;4;5"},
{"both green, bold, underline, blink", uint16(0x0002 | 0x0020 | 0x0008 | 0x8000 | 0x0080), "32;42;1;4;5"},
{"both yellow, bold, underline, blink", uint16(0x0006 | 0x0060 | 0x0008 | 0x8000 | 0x0080), "33;43;1;4;5"},
{"both blue, bold, underline, blink", uint16(0x0001 | 0x0010 | 0x0008 | 0x8000 | 0x0080), "34;44;1;4;5"},
{"both magenta, bold, underline, blink", uint16(0x0005 | 0x0050 | 0x0008 | 0x8000 | 0x0080), "35;45;1;4;5"},
{"both cyan, bold, underline, blink", uint16(0x0003 | 0x0030 | 0x0008 | 0x8000 | 0x0080), "36;46;1;4;5"},
{"both white, bold, underline, blink", uint16(0x0007 | 0x0070 | 0x0008 | 0x8000 | 0x0080), "37;47;1;4;5"},
{"both default, bold, underline, blink", uint16(defaultFgColor | defaultBgColor | 0x0008 | 0x8000 | 0x0080), "39;49;1;4;5"},
}
assertTextAttribute := func(expectedText string, expectedAttributes uint16, ansiColor string) {
actualText, actualAttributes, err := writeAnsiColor(expectedText, ansiColor)
if actualText != expectedText {
t.Errorf("Get %q, want %q", actualText, expectedText)
}
if err != nil {
t.Fatal("Could not get ConsoleScreenBufferInfo")
}
if actualAttributes != expectedAttributes {
t.Errorf("Text: %q, Get 0x%04x, want 0x%04x", expectedText, actualAttributes, expectedAttributes)
}
}
for _, v := range fgParam {
ResetColor()
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
for _, v := range bgParam {
ChangeColor(uint16(0x0070 | 0x0007))
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
for _, v := range resetParam {
ChangeColor(uint16(0x0000 | 0x0070 | 0x0008))
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
ResetColor()
for _, v := range boldParam {
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
ResetColor()
for _, v := range underscoreParam {
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
ResetColor()
for _, v := range blinkParam {
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
for _, v := range mixedParam {
ResetColor()
assertTextAttribute(v.text, v.attributes, v.ansiColor)
}
}
func TestIgnoreUnknownSequences(t *testing.T) {
inner := bytes.NewBufferString("")
w := NewModeAnsiColorWriter(inner, OutputNonColorEscSeq)
inputText := "\x1b[=decpath mode"
expectedTail := inputText
fmt.Fprintf(w, inputText)
actualTail := inner.String()
inner.Reset()
if actualTail != expectedTail {
t.Errorf("Get %q, want %q", actualTail, expectedTail)
}
inputText = "\x1b[=tailing esc and bracket\x1b["
expectedTail = inputText
fmt.Fprintf(w, inputText)
actualTail = inner.String()
inner.Reset()
if actualTail != expectedTail {
t.Errorf("Get %q, want %q", actualTail, expectedTail)
}
inputText = "\x1b[?tailing esc\x1b"
expectedTail = inputText
fmt.Fprintf(w, inputText)
actualTail = inner.String()
inner.Reset()
if actualTail != expectedTail {
t.Errorf("Get %q, want %q", actualTail, expectedTail)
}
inputText = "\x1b[1h;3punended color code invalid\x1b3"
expectedTail = inputText
fmt.Fprintf(w, inputText)
actualTail = inner.String()
inner.Reset()
if actualTail != expectedTail {
t.Errorf("Get %q, want %q", actualTail, expectedTail)
}
}

View File

@ -17,14 +17,14 @@ package logs
import (
"encoding/json"
"io"
"log"
"net"
"time"
)
// connWriter implements LoggerInterface.
// it writes messages in keep-live tcp connection.
type connWriter struct {
lg *log.Logger
lg *logWriter
innerWriter io.WriteCloser
ReconnectOnMsg bool `json:"reconnectOnMsg"`
Reconnect bool `json:"reconnect"`
@ -42,17 +42,17 @@ func NewConn() Logger {
// Init init connection writer with json config.
// json config only need key "level".
func (c *connWriter) Init(jsonconfig string) error {
return json.Unmarshal([]byte(jsonconfig), c)
func (c *connWriter) Init(jsonConfig string) error {
return json.Unmarshal([]byte(jsonConfig), c)
}
// WriteMsg write message in connection.
// if connection is down, try to re-connect.
func (c *connWriter) WriteMsg(msg string, level int) error {
func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > c.Level {
return nil
}
if c.neddedConnectOnMsg() {
if c.needToConnectOnMsg() {
err := c.connect()
if err != nil {
return err
@ -62,7 +62,8 @@ func (c *connWriter) WriteMsg(msg string, level int) error {
if c.ReconnectOnMsg {
defer c.innerWriter.Close()
}
c.lg.Println(msg)
c.lg.println(when, msg)
return nil
}
@ -94,11 +95,11 @@ func (c *connWriter) connect() error {
}
c.innerWriter = conn
c.lg = log.New(conn, "", log.Ldate|log.Ltime)
c.lg = newLogWriter(conn)
return nil
}
func (c *connWriter) neddedConnectOnMsg() bool {
func (c *connWriter) needToConnectOnMsg() bool {
if c.Reconnect {
c.Reconnect = false
return true
@ -112,5 +113,5 @@ func (c *connWriter) neddedConnectOnMsg() bool {
}
func init() {
Register("conn", NewConn)
Register(AdapterConn, NewConn)
}

View File

@ -16,9 +16,9 @@ package logs
import (
"encoding/json"
"log"
"os"
"runtime"
"time"
)
// brush is a color join function
@ -34,51 +34,55 @@ func newBrush(color string) brush {
}
var colors = []brush{
newBrush("1;37"), // Emergency white
newBrush("1;36"), // Alert cyan
newBrush("1;35"), // Critical magenta
newBrush("1;31"), // Error red
newBrush("1;33"), // Warning yellow
newBrush("1;32"), // Notice green
newBrush("1;34"), // Informational blue
newBrush("1;34"), // Debug blue
newBrush("1;37"), // Emergency white
newBrush("1;36"), // Alert cyan
newBrush("1;35"), // Critical magenta
newBrush("1;31"), // Error red
newBrush("1;33"), // Warning yellow
newBrush("1;32"), // Notice green
newBrush("1;34"), // Informational blue
newBrush("1;34"), // Debug blue
}
// consoleWriter implements LoggerInterface and writes messages to terminal.
type consoleWriter struct {
lg *log.Logger
Level int `json:"level"`
lg *logWriter
Level int `json:"level"`
Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color
}
// NewConsole create ConsoleWriter returning as LoggerInterface.
func NewConsole() Logger {
cw := &consoleWriter{
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
Level: LevelDebug,
lg: newLogWriter(os.Stdout),
Level: LevelDebug,
Colorful: runtime.GOOS != "windows",
}
return cw
}
// Init init console logger.
// jsonconfig like '{"level":LevelTrace}'.
func (c *consoleWriter) Init(jsonconfig string) error {
if len(jsonconfig) == 0 {
// jsonConfig like '{"level":LevelTrace}'.
func (c *consoleWriter) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
return nil
}
return json.Unmarshal([]byte(jsonconfig), c)
err := json.Unmarshal([]byte(jsonConfig), c)
if runtime.GOOS == "windows" {
c.Colorful = false
}
return err
}
// WriteMsg write message in console.
func (c *consoleWriter) WriteMsg(msg string, level int) error {
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > c.Level {
return nil
}
if goos := runtime.GOOS; goos == "windows" {
c.lg.Println(msg)
return nil
if c.Colorful {
msg = colors[level](msg)
}
c.lg.Println(colors[level](msg))
c.lg.println(when, msg)
return nil
}
@ -93,5 +97,5 @@ func (c *consoleWriter) Flush() {
}
func init() {
Register("console", NewConsole)
Register(AdapterConsole, NewConsole)
}

View File

@ -42,3 +42,10 @@ func TestConsole(t *testing.T) {
log2.SetLogger("console", `{"level":3}`)
testConsoleCalls(log2)
}
// Test console without color
func TestConsoleNoColor(t *testing.T) {
log := NewLogger(100)
log.SetLogger("console", `{"color":false}`)
testConsoleCalls(log)
}

View File

@ -48,16 +48,16 @@ func (el *esLogger) Init(jsonconfig string) error {
}
// WriteMsg will write the msg and level into es
func (el *esLogger) WriteMsg(msg string, level int) error {
func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
if level > el.Level {
return nil
}
t := time.Now()
vals := make(map[string]interface{})
vals["@timestamp"] = t.Format(time.RFC3339)
vals["@timestamp"] = when.Format(time.RFC3339)
vals["@msg"] = msg
d := goes.Document{
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
Type: "logs",
Fields: vals,
}
@ -76,5 +76,5 @@ func (el *esLogger) Flush() {
}
func init() {
logs.Register("es", NewES)
logs.Register(logs.AdapterEs, NewES)
}

View File

@ -22,6 +22,7 @@ import (
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
@ -30,7 +31,7 @@ import (
// fileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency.
type fileLogWriter struct {
sync.Mutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
// The opened file
Filename string `json:"filename"`
fileWriter *os.File
@ -47,25 +48,25 @@ type fileLogWriter struct {
Daily bool `json:"daily"`
MaxDays int64 `json:"maxdays"`
dailyOpenDate int
dailyOpenTime time.Time
Rotate bool `json:"rotate"`
Level int `json:"level"`
Perm os.FileMode `json:"perm"`
Perm string `json:"perm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
}
// NewFileWriter create a FileLogWriter returning as LoggerInterface.
// newFileWriter create a FileLogWriter returning as LoggerInterface.
func newFileWriter() Logger {
w := &fileLogWriter{
Filename: "",
MaxLines: 1000000,
MaxSize: 1 << 28, //256 MB
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: 0660,
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
}
return w
}
@ -75,11 +76,11 @@ func newFileWriter() Logger {
// {
// "filename":"logs/beego.log",
// "maxLines":10000,
// "maxsize":1<<30,
// "maxsize":1024,
// "daily":true,
// "maxDays":15,
// "rotate":true,
// "perm":0600
// "perm":"0600"
// }
func (w *fileLogWriter) Init(jsonConfig string) error {
err := json.Unmarshal([]byte(jsonConfig), w)
@ -89,6 +90,11 @@ func (w *fileLogWriter) Init(jsonConfig string) error {
if len(w.Filename) == 0 {
return errors.New("jsonconfig must have filename")
}
w.suffix = filepath.Ext(w.Filename)
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
if w.suffix == "" {
w.suffix = ".log"
}
err = w.startLogger()
return err
}
@ -114,61 +120,25 @@ func (w *fileLogWriter) needRotate(size int, day int) bool {
}
// WriteMsg write logger message into file.
func (w *fileLogWriter) WriteMsg(msg string, level int) error {
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level {
return nil
}
//2016/01/12 21:34:33
now := time.Now()
y, mo, d := now.Date()
h, mi, s := now.Clock()
//len(2006/01/02 15:03:04)==19
var buf [20]byte
t := 3
for y >= 10 {
p := y / 10
buf[t] = byte('0' + y - p*10)
y = p
t--
}
buf[0] = byte('0' + y)
buf[4] = '/'
if mo > 9 {
buf[5] = '1'
buf[6] = byte('0' + mo - 9)
} else {
buf[5] = '0'
buf[6] = byte('0' + mo)
}
buf[7] = '/'
t = d / 10
buf[8] = byte('0' + t)
buf[9] = byte('0' + d - t*10)
buf[10] = ' '
t = h / 10
buf[11] = byte('0' + t)
buf[12] = byte('0' + h - t*10)
buf[13] = ':'
t = mi / 10
buf[14] = byte('0' + t)
buf[15] = byte('0' + mi - t*10)
buf[16] = ':'
t = s / 10
buf[17] = byte('0' + t)
buf[18] = byte('0' + s - t*10)
buf[19] = ' '
msg = string(buf[0:]) + msg + "\n"
h, d := formatTimeHeader(when)
msg = string(h) + msg + "\n"
if w.Rotate {
w.RLock()
if w.needRotate(len(msg), d) {
w.RUnlock()
w.Lock()
if w.needRotate(len(msg), d) {
if err := w.doRotate(); err != nil {
if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
} else {
w.RUnlock()
}
}
@ -184,7 +154,15 @@ func (w *fileLogWriter) WriteMsg(msg string, level int) error {
func (w *fileLogWriter) createLogFile() (*os.File, error) {
// Open the log file
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, w.Perm)
perm, err := strconv.ParseInt(w.Perm, 8, 64)
if err != nil {
return nil, err
}
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm))
if err == nil {
// Make sure file perm is user set perm cause of `os.OpenFile` will obey umask
os.Chmod(w.Filename, os.FileMode(perm))
}
return fd, err
}
@ -195,8 +173,12 @@ func (w *fileLogWriter) initFd() error {
return fmt.Errorf("get stat err: %s\n", err)
}
w.maxSizeCurSize = int(fInfo.Size())
w.dailyOpenDate = time.Now().Day()
w.dailyOpenTime = time.Now()
w.dailyOpenDate = w.dailyOpenTime.Day()
w.maxLinesCurLines = 0
if w.Daily {
go w.dailyRotate(w.dailyOpenTime)
}
if fInfo.Size() > 0 {
count, err := w.lines()
if err != nil {
@ -207,6 +189,22 @@ func (w *fileLogWriter) initFd() error {
return nil
}
func (w *fileLogWriter) dailyRotate(openTime time.Time) {
y, m, d := openTime.Add(24 * time.Hour).Date()
nextDay := time.Date(y, m, d, 0, 0, 0, 0, openTime.Location())
tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100))
select {
case <-tm.C:
w.Lock()
if w.needRotate(0, time.Now().Day()) {
if err := w.doRotate(time.Now()); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
}
}
func (w *fileLogWriter) lines() (int, error) {
fd, err := os.Open(w.Filename)
if err != nil {
@ -235,24 +233,31 @@ func (w *fileLogWriter) lines() (int, error) {
}
// DoRotate means it need to write file in new file.
// new file name like xx.2013-01-01.2.log
func (w *fileLogWriter) doRotate() error {
_, err := os.Lstat(w.Filename)
if err != nil {
return err
}
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
func (w *fileLogWriter) doRotate(logTime time.Time) error {
// file exists
// Find the next available number
num := 1
fName := ""
suffix := filepath.Ext(w.Filename)
filenameOnly := strings.TrimSuffix(w.Filename, suffix)
if suffix == "" {
suffix = ".log"
_, 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
}
for ; err == nil && num <= 999; num++ {
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", time.Now().Format("2006-01-02"), num, suffix)
if w.MaxLines > 0 || w.MaxSize > 0 {
for ; err == nil && num <= 999; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
}
} else {
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix)
_, err = os.Lstat(fName)
for ; err == nil && num <= 999; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
}
}
// return error if the last file checked still existed
if err == nil {
@ -264,16 +269,18 @@ func (w *fileLogWriter) doRotate() error {
// Rename the file to its new found name
// even if occurs error,we MUST guarantee to restart new logger
renameErr := os.Rename(w.Filename, fName)
err = os.Rename(w.Filename, fName)
// re-start logger
RESTART_LOGGER:
startLoggerErr := w.startLogger()
go w.deleteOldLog()
if startLoggerErr != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
}
if renameErr != nil {
return fmt.Errorf("Rotate: %s\n", renameErr)
if err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
return nil
@ -288,8 +295,13 @@ func (w *fileLogWriter) deleteOldLog() {
}
}()
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
if info == nil {
return
}
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
@ -310,5 +322,5 @@ func (w *fileLogWriter) Flush() {
}
func init() {
Register("file", newFileWriter)
Register(AdapterFile, newFileWriter)
}

View File

@ -17,12 +17,35 @@ package logs
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"strconv"
"testing"
"time"
)
func TestFilePerm(t *testing.T) {
log := NewLogger(10000)
// use 0666 as test perm cause the default umask is 022
log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`)
log.Debug("debug")
log.Informational("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
file, err := os.Stat("test.log")
if err != nil {
t.Fatal(err)
}
if file.Mode() != 0666 {
t.Fatal("unexpected log file permission")
}
os.Remove("test.log")
}
func TestFile1(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test.log"}`)
@ -89,7 +112,7 @@ func TestFile2(t *testing.T) {
os.Remove("test2.log")
}
func TestFileRotate(t *testing.T) {
func TestFileRotate_01(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
log.Debug("debug")
@ -110,6 +133,90 @@ func TestFileRotate(t *testing.T) {
os.Remove("test3.log")
}
func TestFileRotate_02(t *testing.T) {
fn1 := "rotate_day.log"
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
testFileRotate(t, fn1, fn2)
}
func TestFileRotate_03(t *testing.T) {
fn1 := "rotate_day.log"
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
os.Create(fn)
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileRotate(t, fn1, fn2)
os.Remove(fn)
}
func TestFileRotate_04(t *testing.T) {
fn1 := "rotate_day.log"
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
testFileDailyRotate(t, fn1, fn2)
}
func TestFileRotate_05(t *testing.T) {
fn1 := "rotate_day.log"
fn := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log"
os.Create(fn)
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileDailyRotate(t, fn1, fn2)
os.Remove(fn)
}
func testFileRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
}
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day()
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file)
if err != nil {
t.FailNow()
}
os.Remove(file)
}
fw.Destroy()
}
func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{
Daily: true,
MaxDays: 7,
Rotate: true,
Level: LevelTrace,
Perm: "0660",
}
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day()
today, _ := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), fw.dailyOpenTime.Location())
today = today.Add(-1 * time.Second)
fw.dailyRotate(today)
for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file)
if err != nil {
t.FailNow()
}
content, err := ioutil.ReadFile(file)
if err != nil {
t.FailNow()
}
if len(content) > 0 {
t.FailNow()
}
os.Remove(file)
}
fw.Destroy()
}
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {

78
logs/jianliao.go Normal file
View File

@ -0,0 +1,78 @@
package logs
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook
type JLWriter struct {
AuthorName string `json:"authorname"`
Title string `json:"title"`
WebhookURL string `json:"webhookurl"`
RedirectURL string `json:"redirecturl,omitempty"`
ImageURL string `json:"imageurl,omitempty"`
Level int `json:"level"`
}
// newJLWriter create jiaoliao writer.
func newJLWriter() Logger {
return &JLWriter{Level: LevelTrace}
}
// Init JLWriter with json config string
func (s *JLWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
return err
}
return nil
}
// WriteMsg write message in smtp writer.
// it will send an email with subject and only this message.
func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > s.Level {
return nil
}
text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg)
form := url.Values{}
form.Add("authorName", s.AuthorName)
form.Add("title", s.Title)
form.Add("text", text)
if s.RedirectURL != "" {
form.Add("redirectUrl", s.RedirectURL)
}
if s.ImageURL != "" {
form.Add("imageUrl", s.ImageURL)
}
resp, err := http.PostForm(s.WebhookURL, form)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
}
return nil
}
// Flush implementing method. empty.
func (s *JLWriter) Flush() {
return
}
// Destroy implementing method. empty.
func (s *JLWriter) Destroy() {
return
}
func init() {
Register(AdapterJianLiao, newJLWriter)
}

View File

@ -35,11 +35,14 @@ package logs
import (
"fmt"
"log"
"os"
"path"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
// RFC5424 log message levels.
@ -54,31 +57,46 @@ const (
LevelDebug
)
// Legacy loglevel constants to ensure backwards compatibility.
//
// Deprecated: will be removed in 1.5.0.
// levelLogLogger is defined to implement log.Logger
// the real log level will be LevelEmergency
const levelLoggerImpl = -1
// Name for adapter with beego official support
const (
AdapterConsole = "console"
AdapterFile = "file"
AdapterMultiFile = "multifile"
AdapterMail = "smtp"
AdapterConn = "conn"
AdapterEs = "es"
AdapterJianLiao = "jianliao"
AdapterSlack = "slack"
)
// Legacy log level constants to ensure backwards compatibility.
const (
LevelInfo = LevelInformational
LevelTrace = LevelDebug
LevelWarn = LevelWarning
)
type loggerType func() Logger
type newLoggerFunc func() Logger
// Logger defines the behavior of a log provider.
type Logger interface {
Init(config string) error
WriteMsg(msg string, level int) error
WriteMsg(when time.Time, msg string, level int) error
Destroy()
Flush()
}
var adapters = make(map[string]loggerType)
var adapters = make(map[string]newLoggerFunc)
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
// Register makes a log provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, log loggerType) {
func Register(name string, log newLoggerFunc) {
if log == nil {
panic("logs: Register provide is nil")
}
@ -93,13 +111,19 @@ func Register(name string, log loggerType) {
type BeeLogger struct {
lock sync.Mutex
level int
init bool
enableFuncCallDepth bool
loggerFuncCallDepth int
asynchronous bool
msgChanLen int64
msgChan chan *logMsg
signalChan chan string
wg sync.WaitGroup
outputs []*nameLogger
}
const defaultAsyncMsgLen = 1e3
type nameLogger struct {
Logger
name string
@ -108,6 +132,7 @@ type nameLogger struct {
type logMsg struct {
level int
msg string
when time.Time
}
var logMsgPool *sync.Pool
@ -115,45 +140,78 @@ var logMsgPool *sync.Pool
// NewLogger returns a new BeeLogger.
// channelLen means the number of messages in chan(used where asynchronous is true).
// if the buffering chan is full, logger adapters write to file or other way.
func NewLogger(channelLen int64) *BeeLogger {
func NewLogger(channelLens ...int64) *BeeLogger {
bl := new(BeeLogger)
bl.level = LevelDebug
bl.loggerFuncCallDepth = 2
bl.msgChan = make(chan *logMsg, channelLen)
bl.msgChanLen = append(channelLens, 0)[0]
if bl.msgChanLen <= 0 {
bl.msgChanLen = defaultAsyncMsgLen
}
bl.signalChan = make(chan string, 1)
bl.setLogger(AdapterConsole)
return bl
}
// Async set the log to asynchronous and start the goroutine
func (bl *BeeLogger) Async() *BeeLogger {
func (bl *BeeLogger) Async(msgLen ...int64) *BeeLogger {
bl.lock.Lock()
defer bl.lock.Unlock()
if bl.asynchronous {
return bl
}
bl.asynchronous = true
if len(msgLen) > 0 && msgLen[0] > 0 {
bl.msgChanLen = msgLen[0]
}
bl.msgChan = make(chan *logMsg, bl.msgChanLen)
logMsgPool = &sync.Pool{
New: func() interface{} {
return &logMsg{}
},
}
bl.wg.Add(1)
go bl.startLogger()
return bl
}
// SetLogger provides a given logger adapter into BeeLogger with config string.
// config need to be correct JSON as string: {"interval":360}.
func (bl *BeeLogger) SetLogger(adapterName string, config string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
if log, ok := adapters[adapterName]; ok {
lg := log()
err := lg.Init(config)
if err != nil {
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
return err
func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
config := append(configs, "{}")[0]
for _, l := range bl.outputs {
if l.name == adapterName {
return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
}
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
} else {
}
log, ok := adapters[adapterName]
if !ok {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
}
lg := log()
err := lg.Init(config)
if err != nil {
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
return err
}
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
return nil
}
// SetLogger provides a given logger adapter into BeeLogger with config string.
// config need to be correct JSON as string: {"interval":360}.
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
if !bl.init {
bl.outputs = []*nameLogger{}
bl.init = true
}
return bl.setLogger(adapterName, configs...)
}
// DelLogger remove a logger adapter in BeeLogger.
func (bl *BeeLogger) DelLogger(adapterName string) error {
bl.lock.Lock()
@ -173,16 +231,42 @@ func (bl *BeeLogger) DelLogger(adapterName string) error {
return nil
}
func (bl *BeeLogger) writeToLoggers(msg string, level int) {
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
for _, l := range bl.outputs {
err := l.WriteMsg(msg, level)
err := l.WriteMsg(when, msg, level)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
}
}
}
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
func (bl *BeeLogger) Write(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
// writeMsg will always add a '\n' character
if p[len(p)-1] == '\n' {
p = p[0 : len(p)-1]
}
// set levelLoggerImpl to ensure all log message will be write out
err = bl.writeMsg(levelLoggerImpl, string(p))
if err == nil {
return len(p), err
}
return 0, err
}
func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error {
if !bl.init {
bl.lock.Lock()
bl.setLogger(AdapterConsole)
bl.lock.Unlock()
}
if len(v) > 0 {
msg = fmt.Sprintf(msg, v...)
}
when := time.Now()
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if !ok {
@ -190,15 +274,25 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
line = 0
}
_, filename := path.Split(file)
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "]" + msg
msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg
}
//set level info in front of filename info
if logLevel == levelLoggerImpl {
// set to emergency to ensure all log will be print out correctly
logLevel = LevelEmergency
} else {
msg = levelPrefix[logLevel] + msg
}
if bl.asynchronous {
lm := logMsgPool.Get().(*logMsg)
lm.level = logLevel
lm.msg = msg
lm.when = when
bl.msgChan <- lm
} else {
bl.writeToLoggers(msg, logLevel)
bl.writeToLoggers(when, msg, logLevel)
}
return nil
}
@ -228,11 +322,26 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
gameOver := false
for {
select {
case bm := <-bl.msgChan:
bl.writeToLoggers(bm.msg, bm.level)
bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm)
case sg := <-bl.signalChan:
// Now should only send "flush" or "close" to bl.signalChan
bl.flush()
if sg == "close" {
for _, l := range bl.outputs {
l.Destroy()
}
bl.outputs = nil
gameOver = true
}
bl.wg.Done()
}
if gameOver {
break
}
}
}
@ -242,8 +351,7 @@ func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
if LevelEmergency > bl.level {
return
}
msg := fmt.Sprintf("[M] "+format, v...)
bl.writeMsg(LevelEmergency, msg)
bl.writeMsg(LevelEmergency, format, v...)
}
// Alert Log ALERT level message.
@ -251,8 +359,7 @@ func (bl *BeeLogger) Alert(format string, v ...interface{}) {
if LevelAlert > bl.level {
return
}
msg := fmt.Sprintf("[A] "+format, v...)
bl.writeMsg(LevelAlert, msg)
bl.writeMsg(LevelAlert, format, v...)
}
// Critical Log CRITICAL level message.
@ -260,8 +367,7 @@ func (bl *BeeLogger) Critical(format string, v ...interface{}) {
if LevelCritical > bl.level {
return
}
msg := fmt.Sprintf("[C] "+format, v...)
bl.writeMsg(LevelCritical, msg)
bl.writeMsg(LevelCritical, format, v...)
}
// Error Log ERROR level message.
@ -269,17 +375,15 @@ func (bl *BeeLogger) Error(format string, v ...interface{}) {
if LevelError > bl.level {
return
}
msg := fmt.Sprintf("[E] "+format, v...)
bl.writeMsg(LevelError, msg)
bl.writeMsg(LevelError, format, v...)
}
// Warning Log WARNING level message.
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
if LevelWarning > bl.level {
if LevelWarn > bl.level {
return
}
msg := fmt.Sprintf("[W] "+format, v...)
bl.writeMsg(LevelWarning, msg)
bl.writeMsg(LevelWarn, format, v...)
}
// Notice Log NOTICE level message.
@ -287,17 +391,15 @@ func (bl *BeeLogger) Notice(format string, v ...interface{}) {
if LevelNotice > bl.level {
return
}
msg := fmt.Sprintf("[N] "+format, v...)
bl.writeMsg(LevelNotice, msg)
bl.writeMsg(LevelNotice, format, v...)
}
// Informational Log INFORMATIONAL level message.
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
if LevelInformational > bl.level {
if LevelInfo > bl.level {
return
}
msg := fmt.Sprintf("[I] "+format, v...)
bl.writeMsg(LevelInformational, msg)
bl.writeMsg(LevelInfo, format, v...)
}
// Debug Log DEBUG level message.
@ -305,28 +407,25 @@ func (bl *BeeLogger) Debug(format string, v ...interface{}) {
if LevelDebug > bl.level {
return
}
msg := fmt.Sprintf("[D] "+format, v...)
bl.writeMsg(LevelDebug, msg)
bl.writeMsg(LevelDebug, format, v...)
}
// Warn Log WARN level message.
// compatibility alias for Warning()
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
if LevelWarning > bl.level {
if LevelWarn > bl.level {
return
}
msg := fmt.Sprintf("[W] "+format, v...)
bl.writeMsg(LevelWarning, msg)
bl.writeMsg(LevelWarn, format, v...)
}
// Info Log INFO level message.
// compatibility alias for Informational()
func (bl *BeeLogger) Info(format string, v ...interface{}) {
if LevelInformational > bl.level {
if LevelInfo > bl.level {
return
}
msg := fmt.Sprintf("[I] "+format, v...)
bl.writeMsg(LevelInformational, msg)
bl.writeMsg(LevelInfo, format, v...)
}
// Trace Log TRACE level message.
@ -335,30 +434,215 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) {
if LevelDebug > bl.level {
return
}
msg := fmt.Sprintf("[D] "+format, v...)
bl.writeMsg(LevelDebug, msg)
bl.writeMsg(LevelDebug, format, v...)
}
// Flush flush all chan data.
func (bl *BeeLogger) Flush() {
if bl.asynchronous {
bl.signalChan <- "flush"
bl.wg.Wait()
bl.wg.Add(1)
return
}
bl.flush()
}
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
func (bl *BeeLogger) Close() {
if bl.asynchronous {
bl.signalChan <- "close"
bl.wg.Wait()
close(bl.msgChan)
} else {
bl.flush()
for _, l := range bl.outputs {
l.Destroy()
}
bl.outputs = nil
}
close(bl.signalChan)
}
// Reset close all outputs, and set bl.outputs to nil
func (bl *BeeLogger) Reset() {
bl.Flush()
for _, l := range bl.outputs {
l.Destroy()
}
bl.outputs = nil
}
func (bl *BeeLogger) flush() {
if bl.asynchronous {
for {
if len(bl.msgChan) > 0 {
bm := <-bl.msgChan
bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm)
continue
}
break
}
}
for _, l := range bl.outputs {
l.Flush()
}
}
// Close close logger, flush all chan data and destroy all adapters in BeeLogger.
func (bl *BeeLogger) Close() {
for {
if len(bl.msgChan) > 0 {
bm := <-bl.msgChan
bl.writeToLoggers(bm.msg, bm.level)
logMsgPool.Put(bm)
continue
}
break
}
for _, l := range bl.outputs {
l.Flush()
l.Destroy()
}
// beeLogger references the used application logger.
var beeLogger *BeeLogger = NewLogger()
// GetLogger returns the default BeeLogger
func GetBeeLogger() *BeeLogger {
return beeLogger
}
var beeLoggerMap = struct {
sync.RWMutex
logs map[string]*log.Logger
}{
logs: map[string]*log.Logger{},
}
// GetLogger returns the default BeeLogger
func GetLogger(prefixes ...string) *log.Logger {
prefix := append(prefixes, "")[0]
if prefix != "" {
prefix = fmt.Sprintf(`[%s] `, strings.ToUpper(prefix))
}
beeLoggerMap.RLock()
l, ok := beeLoggerMap.logs[prefix]
if ok {
beeLoggerMap.RUnlock()
return l
}
beeLoggerMap.RUnlock()
beeLoggerMap.Lock()
defer beeLoggerMap.Unlock()
l, ok = beeLoggerMap.logs[prefix]
if !ok {
l = log.New(beeLogger, prefix, 0)
beeLoggerMap.logs[prefix] = l
}
return l
}
// Reset will remove all the adapter
func Reset() {
beeLogger.Reset()
}
func Async(msgLen ...int64) *BeeLogger {
return beeLogger.Async(msgLen...)
}
// SetLevel sets the global log level used by the simple logger.
func SetLevel(l int) {
beeLogger.SetLevel(l)
}
// EnableFuncCallDepth enable log funcCallDepth
func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b
}
// SetLogFuncCall set the CallDepth, default is 4
func SetLogFuncCall(b bool) {
beeLogger.EnableFuncCallDepth(b)
beeLogger.SetLogFuncCallDepth(4)
}
// SetLogFuncCallDepth set log funcCallDepth
func SetLogFuncCallDepth(d int) {
beeLogger.loggerFuncCallDepth = d
}
// SetLogger sets a new logger.
func SetLogger(adapter string, config ...string) error {
err := beeLogger.SetLogger(adapter, config...)
if err != nil {
return err
}
return nil
}
// Emergency logs a message at emergency level.
func Emergency(f interface{}, v ...interface{}) {
beeLogger.Emergency(formatLog(f, v...))
}
// Alert logs a message at alert level.
func Alert(f interface{}, v ...interface{}) {
beeLogger.Alert(formatLog(f, v...))
}
// Critical logs a message at critical level.
func Critical(f interface{}, v ...interface{}) {
beeLogger.Critical(formatLog(f, v...))
}
// Error logs a message at error level.
func Error(f interface{}, v ...interface{}) {
beeLogger.Error(formatLog(f, v...))
}
// Warning logs a message at warning level.
func Warning(f interface{}, v ...interface{}) {
beeLogger.Warn(formatLog(f, v...))
}
// Warn compatibility alias for Warning()
func Warn(f interface{}, v ...interface{}) {
beeLogger.Warn(formatLog(f, v...))
}
// Notice logs a message at notice level.
func Notice(f interface{}, v ...interface{}) {
beeLogger.Notice(formatLog(f, v...))
}
// Informational logs a message at info level.
func Informational(f interface{}, v ...interface{}) {
beeLogger.Info(formatLog(f, v...))
}
// Info compatibility alias for Warning()
func Info(f interface{}, v ...interface{}) {
beeLogger.Info(formatLog(f, v...))
}
// Debug logs a message at debug level.
func Debug(f interface{}, v ...interface{}) {
beeLogger.Debug(formatLog(f, v...))
}
// Trace logs a message at trace level.
// compatibility alias for Warning()
func Trace(f interface{}, v ...interface{}) {
beeLogger.Trace(formatLog(f, v...))
}
func formatLog(f interface{}, v ...interface{}) string {
var msg string
switch f.(type) {
case string:
msg = f.(string)
if len(v) == 0 {
return msg
}
if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
//format string
} else {
//do not contain format char
msg += strings.Repeat(" %v", len(v))
}
default:
msg = fmt.Sprint(f)
if len(v) == 0 {
return msg
}
msg += strings.Repeat(" %v", len(v))
}
return fmt.Sprintf(msg, v...)
}

188
logs/logger.go Normal file
View File

@ -0,0 +1,188 @@
// 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 (
"fmt"
"io"
"os"
"sync"
"time"
)
type logWriter struct {
sync.Mutex
writer io.Writer
}
func newLogWriter(wr io.Writer) *logWriter {
return &logWriter{writer: wr}
}
func (lg *logWriter) println(when time.Time, msg string) {
lg.Lock()
h, _ := formatTimeHeader(when)
lg.writer.Write(append(append(h, msg...), '\n'))
lg.Unlock()
}
type outputMode int
// DiscardNonColorEscSeq supports the divided color escape sequence.
// But non-color escape sequence is not output.
// Please use the OutputNonColorEscSeq If you want to output a non-color
// escape sequences such as ncurses. However, it does not support the divided
// color escape sequence.
const (
_ outputMode = iota
DiscardNonColorEscSeq
OutputNonColorEscSeq
)
// NewAnsiColorWriter creates and initializes a new ansiColorWriter
// using io.Writer w as its initial contents.
// In the console of Windows, which change the foreground and background
// colors of the text by the escape sequence.
// In the console of other systems, which writes to w all text.
func NewAnsiColorWriter(w io.Writer) io.Writer {
return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq)
}
// NewModeAnsiColorWriter create and initializes a new ansiColorWriter
// by specifying the outputMode.
func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer {
if _, ok := w.(*ansiColorWriter); !ok {
return &ansiColorWriter{
w: w,
mode: mode,
}
}
return w
}
const (
y1 = `0123456789`
y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999`
y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789`
mo1 = `000000000111`
mo2 = `123456789012`
d1 = `0000000001111111111222222222233`
d2 = `1234567890123456789012345678901`
h1 = `000000000011111111112222`
h2 = `012345678901234567890123`
mi1 = `000000000011111111112222222222333333333344444444445555555555`
mi2 = `012345678901234567890123456789012345678901234567890123456789`
s1 = `000000000011111111112222222222333333333344444444445555555555`
s2 = `012345678901234567890123456789012345678901234567890123456789`
)
func formatTimeHeader(when time.Time) ([]byte, int) {
y, mo, d := when.Date()
h, mi, s := when.Clock()
//len("2006/01/02 15:04:05 ")==20
var buf [20]byte
buf[0] = y1[y/1000%10]
buf[1] = y2[y/100]
buf[2] = y3[y-y/100*100]
buf[3] = y4[y-y/100*100]
buf[4] = '/'
buf[5] = mo1[mo-1]
buf[6] = mo2[mo-1]
buf[7] = '/'
buf[8] = d1[d-1]
buf[9] = d2[d-1]
buf[10] = ' '
buf[11] = h1[h]
buf[12] = h2[h]
buf[13] = ':'
buf[14] = mi1[mi]
buf[15] = mi2[mi]
buf[16] = ':'
buf[17] = s1[s]
buf[18] = s2[s]
buf[19] = ' '
return buf[0:], d
}
var (
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109})
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
w32Green = string([]byte{27, 91, 52, 50, 109})
w32White = string([]byte{27, 91, 52, 55, 109})
w32Yellow = string([]byte{27, 91, 52, 51, 109})
w32Red = string([]byte{27, 91, 52, 49, 109})
w32Blue = string([]byte{27, 91, 52, 52, 109})
w32Magenta = string([]byte{27, 91, 52, 53, 109})
w32Cyan = string([]byte{27, 91, 52, 54, 109})
reset = string([]byte{27, 91, 48, 109})
)
func ColorByStatus(cond bool, code int) string {
switch {
case code >= 200 && code < 300:
return map[bool]string{true: green, false: w32Green}[cond]
case code >= 300 && code < 400:
return map[bool]string{true: white, false: w32White}[cond]
case code >= 400 && code < 500:
return map[bool]string{true: yellow, false: w32Yellow}[cond]
default:
return map[bool]string{true: red, false: w32Red}[cond]
}
}
func ColorByMethod(cond bool, method string) string {
switch method {
case "GET":
return map[bool]string{true: blue, false: w32Blue}[cond]
case "POST":
return map[bool]string{true: cyan, false: w32Cyan}[cond]
case "PUT":
return map[bool]string{true: yellow, false: w32Yellow}[cond]
case "DELETE":
return map[bool]string{true: red, false: w32Red}[cond]
case "PATCH":
return map[bool]string{true: green, false: w32Green}[cond]
case "HEAD":
return map[bool]string{true: magenta, false: w32Magenta}[cond]
case "OPTIONS":
return map[bool]string{true: white, false: w32White}[cond]
default:
return reset
}
}
// Guard Mutex to guarantee atomicity of W32Debug(string) function
var mu sync.Mutex
// Helper method to output colored logs in Windows terminals
func W32Debug(msg string) {
mu.Lock()
defer mu.Unlock()
current := time.Now()
w := NewAnsiColorWriter(os.Stdout)
fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg)
}

75
logs/logger_test.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2016 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"bytes"
"testing"
"time"
)
func TestFormatHeader_0(t *testing.T) {
tm := time.Now()
if tm.Year() >= 2100 {
t.FailNow()
}
dur := time.Second
for {
if tm.Year() >= 2100 {
break
}
h, _ := formatTimeHeader(tm)
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
t.Log(tm)
t.FailNow()
}
tm = tm.Add(dur)
dur *= 2
}
}
func TestFormatHeader_1(t *testing.T) {
tm := time.Now()
year := tm.Year()
dur := time.Second
for {
if tm.Year() >= year+1 {
break
}
h, _ := formatTimeHeader(tm)
if tm.Format("2006/01/02 15:04:05 ") != string(h) {
t.Log(tm)
t.FailNow()
}
tm = tm.Add(dur)
}
}
func TestNewAnsiColor1(t *testing.T) {
inner := bytes.NewBufferString("")
w := NewAnsiColorWriter(inner)
if w == inner {
t.Errorf("Get %#v, want %#v", w, inner)
}
}
func TestNewAnsiColor2(t *testing.T) {
inner := bytes.NewBufferString("")
w1 := NewAnsiColorWriter(inner)
w2 := NewAnsiColorWriter(w1)
if w1 != w2 {
t.Errorf("Get %#v, want %#v", w1, w2)
}
}

116
logs/multifile.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"encoding/json"
"time"
)
// A filesLogWriter manages several fileLogWriter
// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file
// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log
// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log
// the rotate attribute also acts like fileLogWriter
type multiFileLogWriter struct {
writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter
fullLogWriter *fileLogWriter
Separate []string `json:"separate"`
}
var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"}
// Init file logger with json config.
// jsonConfig like:
// {
// "filename":"logs/beego.log",
// "maxLines":0,
// "maxsize":0,
// "daily":true,
// "maxDays":15,
// "rotate":true,
// "perm":0600,
// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"],
// }
func (f *multiFileLogWriter) Init(config string) error {
writer := newFileWriter().(*fileLogWriter)
err := writer.Init(config)
if err != nil {
return err
}
f.fullLogWriter = writer
f.writers[LevelDebug+1] = writer
//unmarshal "separate" field to f.Separate
json.Unmarshal([]byte(config), f)
jsonMap := map[string]interface{}{}
json.Unmarshal([]byte(config), &jsonMap)
for i := LevelEmergency; i < LevelDebug+1; i++ {
for _, v := range f.Separate {
if v == levelNames[i] {
jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix
jsonMap["level"] = i
bs, _ := json.Marshal(jsonMap)
writer = newFileWriter().(*fileLogWriter)
writer.Init(string(bs))
f.writers[i] = writer
}
}
}
return nil
}
func (f *multiFileLogWriter) Destroy() {
for i := 0; i < len(f.writers); i++ {
if f.writers[i] != nil {
f.writers[i].Destroy()
}
}
}
func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if f.fullLogWriter != nil {
f.fullLogWriter.WriteMsg(when, msg, level)
}
for i := 0; i < len(f.writers)-1; i++ {
if f.writers[i] != nil {
if level == f.writers[i].Level {
f.writers[i].WriteMsg(when, msg, level)
}
}
}
return nil
}
func (f *multiFileLogWriter) Flush() {
for i := 0; i < len(f.writers); i++ {
if f.writers[i] != nil {
f.writers[i].Flush()
}
}
}
// newFilesWriter create a FileLogWriter returning as LoggerInterface.
func newFilesWriter() Logger {
return &multiFileLogWriter{}
}
func init() {
Register(AdapterMultiFile, newFilesWriter)
}

78
logs/multifile_test.go Normal file
View File

@ -0,0 +1,78 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"bufio"
"os"
"strconv"
"strings"
"testing"
)
func TestFiles_1(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`)
log.Debug("debug")
log.Informational("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
fns := []string{""}
fns = append(fns, levelNames[0:]...)
name := "test"
suffix := ".log"
for _, fn := range fns {
file := name + suffix
if fn != "" {
file = name + "." + fn + suffix
}
f, err := os.Open(file)
if err != nil {
t.Fatal(err)
}
b := bufio.NewReader(f)
lineNum := 0
lastLine := ""
for {
line, _, err := b.ReadLine()
if err != nil {
break
}
if len(line) > 0 {
lastLine = string(line)
lineNum++
}
}
var expected = 1
if fn == "" {
expected = LevelDebug + 1
}
if lineNum != expected {
t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines")
}
if lineNum == 1 {
if !strings.Contains(lastLine, fn) {
t.Fatal(file + " " + lastLine + " not contains the log msg " + fn)
}
}
os.Remove(file)
}
}

66
logs/slack.go Normal file
View File

@ -0,0 +1,66 @@
package logs
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook
type SLACKWriter struct {
WebhookURL string `json:"webhookurl"`
Level int `json:"level"`
}
// newSLACKWriter create jiaoliao writer.
func newSLACKWriter() Logger {
return &SLACKWriter{Level: LevelTrace}
}
// Init SLACKWriter with json config string
func (s *SLACKWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
return err
}
return nil
}
// WriteMsg write message in smtp writer.
// it will send an email with subject and only this message.
func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > s.Level {
return nil
}
text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg)
form := url.Values{}
form.Add("payload", text)
resp, err := http.PostForm(s.WebhookURL, form)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode)
}
return nil
}
// Flush implementing method. empty.
func (s *SLACKWriter) Flush() {
return
}
// Destroy implementing method. empty.
func (s *SLACKWriter) Destroy() {
return
}
func init() {
Register(AdapterSlack, newSLACKWriter)
}

View File

@ -126,7 +126,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
// WriteMsg write message in smtp writer.
// it will send an email with subject and only this message.
func (s *SMTPWriter) WriteMsg(msg string, level int) error {
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > s.Level {
return nil
}
@ -140,7 +140,7 @@ func (s *SMTPWriter) WriteMsg(msg string, level int) error {
// and send the email all in one step.
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
}
@ -156,5 +156,5 @@ func (s *SMTPWriter) Destroy() {
}
func init() {
Register("smtp", newSMTPWriter)
Register(AdapterMail, newSMTPWriter)
}

View File

@ -33,7 +33,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/orm"
)
@ -90,7 +90,7 @@ func (m *Migration) Reset() {
func (m *Migration) Exec(name, status string) error {
o := orm.NewOrm()
for _, s := range m.sqls {
beego.Info("exec sql:", s)
logs.Info("exec sql:", s)
r := o.Raw(s)
_, err := r.Exec()
if err != nil {
@ -144,20 +144,20 @@ func Upgrade(lasttime int64) error {
i := 0
for _, v := range sm {
if v.created > lasttime {
beego.Info("start upgrade", v.name)
logs.Info("start upgrade", v.name)
v.m.Reset()
v.m.Up()
err := v.m.Exec(v.name, "up")
if err != nil {
beego.Error("execute error:", err)
logs.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
beego.Info("end upgrade:", v.name)
logs.Info("end upgrade:", v.name)
i++
}
}
beego.Info("total success upgrade:", i, " migration")
logs.Info("total success upgrade:", i, " migration")
time.Sleep(2 * time.Second)
return nil
}
@ -165,20 +165,20 @@ func Upgrade(lasttime int64) error {
// Rollback rollback the migration by the name
func Rollback(name string) error {
if v, ok := migrationMap[name]; ok {
beego.Info("start rollback")
logs.Info("start rollback")
v.Reset()
v.Down()
err := v.Exec(name, "down")
if err != nil {
beego.Error("execute error:", err)
logs.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
beego.Info("end rollback")
logs.Info("end rollback")
time.Sleep(2 * time.Second)
return nil
}
beego.Error("not exist the migrationMap name:" + name)
logs.Error("not exist the migrationMap name:" + name)
time.Sleep(2 * time.Second)
return errors.New("not exist the migrationMap name:" + name)
}
@ -191,23 +191,23 @@ func Reset() error {
for j := len(sm) - 1; j >= 0; j-- {
v := sm[j]
if isRollBack(v.name) {
beego.Info("skip the", v.name)
logs.Info("skip the", v.name)
time.Sleep(1 * time.Second)
continue
}
beego.Info("start reset:", v.name)
logs.Info("start reset:", v.name)
v.m.Reset()
v.m.Down()
err := v.m.Exec(v.name, "down")
if err != nil {
beego.Error("execute error:", err)
logs.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
i++
beego.Info("end reset:", v.name)
logs.Info("end reset:", v.name)
}
beego.Info("total success reset:", i, " migration")
logs.Info("total success reset:", i, " migration")
time.Sleep(2 * time.Second)
return nil
}
@ -216,7 +216,7 @@ func Reset() error {
func Refresh() error {
err := Reset()
if err != nil {
beego.Error("execute error:", err)
logs.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
@ -265,7 +265,7 @@ func isRollBack(name string) bool {
var maps []orm.Params
num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps)
if err != nil {
beego.Info("get name has error", err)
logs.Info("get name has error", err)
return false
}
if num <= 0 {

View File

@ -339,7 +339,7 @@ var mimemaps = map[string]string{
".pvu": "paleovu/x-pv",
".pwz": "application/vndms-powerpoint",
".py": "text/x-scriptphyton",
".pyc": "applicaiton/x-bytecodepython",
".pyc": "application/x-bytecodepython",
".qcp": "audio/vndqcelp",
".qd3": "x-world/x-3dmf",
".qd3d": "x-world/x-3dmf",

View File

@ -44,7 +44,7 @@ func NewNamespace(prefix string, params ...LinkNamespace) *Namespace {
return ns
}
// Cond set condtion function
// Cond set condition function
// if cond return true can run this namespace, else can't
// usage:
// ns.Cond(func (ctx *context.Context) bool{
@ -60,7 +60,7 @@ func (n *Namespace) Cond(cond namespaceCond) *Namespace {
exception("405", ctx)
}
}
if v, ok := n.handlers.filters[BeforeRouter]; ok {
if v := n.handlers.filters[BeforeRouter]; len(v) > 0 {
mr := new(FilterRouter)
mr.tree = NewTree()
mr.pattern = "*"
@ -388,3 +388,10 @@ func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace {
ns.Namespace(n)
}
}
// NSHandler add handler
func NSHandler(rootpath string, h http.Handler) LinkNamespace {
return func(ns *Namespace) {
ns.Handler(rootpath, h)
}
}

View File

@ -61,8 +61,8 @@ func TestNamespaceNest(t *testing.T) {
ns.Namespace(
NewNamespace("/admin").
Get("/order", func(ctx *context.Context) {
ctx.Output.Body([]byte("order"))
}),
ctx.Output.Body([]byte("order"))
}),
)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
@ -79,8 +79,8 @@ func TestNamespaceNestParam(t *testing.T) {
ns.Namespace(
NewNamespace("/admin").
Get("/order/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
}),
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
}),
)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
@ -124,8 +124,8 @@ func TestNamespaceFilter(t *testing.T) {
ctx.Output.Body([]byte("this is Filter"))
}).
Get("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "this is Filter" {

View File

@ -52,9 +52,15 @@ checkColumn:
case TypeBooleanField:
col = T["bool"]
case TypeCharField:
col = fmt.Sprintf(T["string"], fieldSize)
if al.Driver == DRPostgres && fi.toText {
col = T["string-text"]
} else {
col = fmt.Sprintf(T["string"], fieldSize)
}
case TypeTextField:
col = T["string-text"]
case TypeTimeField:
col = T["time.Time-clock"]
case TypeDateField:
col = T["time.Time-date"]
case TypeDateTimeField:
@ -88,6 +94,18 @@ checkColumn:
} else {
col = fmt.Sprintf(s, fi.digits, fi.decimals)
}
case TypeJSONField:
if al.Driver != DRPostgres {
fieldType = TypeCharField
goto checkColumn
}
col = T["json"]
case TypeJsonbField:
if al.Driver != DRPostgres {
fieldType = TypeCharField
goto checkColumn
}
col = T["jsonb"]
case RelForeignKey, RelOneToOne:
fieldType = fi.relModelInfo.fields.pk.fieldType
fieldSize = fi.relModelInfo.fields.pk.size
@ -264,7 +282,7 @@ func getColumnDefault(fi *fieldInfo) string {
// These defaults will be useful if there no config value orm:"default" and NOT NULL is on
switch fi.fieldType {
case TypeDateField, TypeDateTimeField, TypeTextField:
case TypeTimeField, TypeDateField, TypeDateTimeField, TypeTextField:
return v
case TypeBitField, TypeSmallIntegerField, TypeIntegerField,
@ -276,6 +294,8 @@ func getColumnDefault(fi *fieldInfo) string {
case TypeBooleanField:
t = " DEFAULT %s "
d = "FALSE"
case TypeJSONField, TypeJsonbField:
d = "{}"
}
if fi.colDefault {

299
orm/db.go
View File

@ -24,6 +24,7 @@ import (
)
const (
formatTime = "15:04:05"
formatDate = "2006-01-02"
formatDateTime = "2006-01-02 15:04:05"
)
@ -71,12 +72,12 @@ type dbBase struct {
var _ dbBaser = new(dbBase)
// get struct columns values as interface slice.
func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string, skipAuto bool, insert bool, names *[]string, tz *time.Location) (values []interface{}, err error) {
var columns []string
if names != nil {
columns = *names
func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string, skipAuto bool, insert bool, names *[]string, tz *time.Location) (values []interface{}, autoFields []string, err error) {
if names == nil {
ns := make([]string, 0, len(cols))
names = &ns
}
values = make([]interface{}, 0, len(cols))
for _, column := range cols {
var fi *fieldInfo
@ -90,18 +91,24 @@ func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string,
}
value, err := d.collectFieldValue(mi, fi, ind, insert, tz)
if err != nil {
return nil, err
return nil, nil, err
}
if names != nil {
columns = append(columns, column)
// ignore empty value auto field
if insert && fi.auto {
if fi.fieldType&IsPositiveIntegerField > 0 {
if vu, ok := value.(uint64); !ok || vu == 0 {
continue
}
} else {
if vu, ok := value.(int64); !ok || vu == 0 {
continue
}
}
autoFields = append(autoFields, fi.column)
}
values = append(values, value)
}
if names != nil {
*names = columns
*names, values = append(*names, column), append(values, value)
}
return
@ -113,7 +120,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
if fi.pk {
_, value, _ = getExistPk(mi, ind)
} else {
field := ind.Field(fi.fieldIndex)
field := ind.FieldByIndex(fi.fieldIndex)
if fi.isFielder {
f := field.Addr().Interface().(Fielder)
value = f.RawValue()
@ -134,7 +141,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
} else {
value = field.Bool()
}
case TypeCharField, TypeTextField:
case TypeCharField, TypeTextField, TypeJSONField, TypeJsonbField:
if ns, ok := field.Interface().(sql.NullString); ok {
value = nil
if ns.Valid {
@ -169,7 +176,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
value = field.Float()
}
}
case TypeDateField, TypeDateTimeField:
case TypeTimeField, TypeDateField, TypeDateTimeField:
value = field.Interface()
if t, ok := value.(time.Time); ok {
d.ins.TimeToDB(&t, tz)
@ -181,7 +188,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
}
default:
switch {
case fi.fieldType&IsPostiveIntegerField > 0:
case fi.fieldType&IsPositiveIntegerField > 0:
if field.Kind() == reflect.Ptr {
if field.IsNil() {
value = nil
@ -223,7 +230,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
}
}
switch fi.fieldType {
case TypeDateField, TypeDateTimeField:
case TypeTimeField, TypeDateField, TypeDateTimeField:
if fi.autoNow || fi.autoNowAdd && insert {
if insert {
if t, ok := value.(time.Time); ok && !t.IsZero() {
@ -236,10 +243,21 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
if fi.isFielder {
f := field.Addr().Interface().(Fielder)
f.SetRaw(tnow.In(DefaultTimeLoc))
} else if field.Kind() == reflect.Ptr {
v := tnow.In(DefaultTimeLoc)
field.Set(reflect.ValueOf(&v))
} else {
field.Set(reflect.ValueOf(tnow.In(DefaultTimeLoc)))
}
}
case TypeJSONField, TypeJsonbField:
if s, ok := value.(string); (ok && len(s) == 0) || value == nil {
if fi.colDefault && fi.initial.Exist() {
value = fi.initial.String()
} else {
value = nil
}
}
}
}
return value, nil
@ -273,7 +291,7 @@ func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string,
// insert struct with prepared statement and given struct reflect value.
func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
if err != nil {
return 0, err
}
@ -292,7 +310,7 @@ func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value,
}
// query sql ,read records and persist in dbBaser.
func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) error {
func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
var whereCols []string
var args []interface{}
@ -300,7 +318,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
if len(cols) > 0 {
var err error
whereCols = make([]string, 0, len(cols))
args, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
args, _, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
if err != nil {
return err
}
@ -323,7 +341,12 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
sep = fmt.Sprintf("%s = ? AND %s", Q, Q)
wheres := strings.Join(whereCols, sep)
query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ?", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q)
forUpdate := ""
if isForUpdate {
forUpdate = "FOR UPDATE"
}
query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ? %s", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q, forUpdate)
refs := make([]interface{}, colsNum)
for i := range refs {
@ -349,13 +372,21 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
// execute insert sql dbQuerier with given struct reflect.Value.
func (d *dbBase) Insert(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
names := make([]string, 0, len(mi.fields.dbcols)-1)
values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz)
names := make([]string, 0, len(mi.fields.dbcols))
values, autoFields, err := d.collectValues(mi, ind, mi.fields.dbcols, false, true, &names, tz)
if err != nil {
return 0, err
}
return d.InsertValue(q, mi, false, names, values)
id, err := d.InsertValue(q, mi, false, names, values)
if err != nil {
return 0, err
}
if len(autoFields) > 0 {
err = d.ins.setval(q, mi, autoFields)
}
return id, err
}
// multi-insert sql with given slice struct reflect.Value.
@ -369,7 +400,7 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
// typ := reflect.Indirect(mi.addrField).Type()
length := sind.Len()
length, autoFields := sind.Len(), make([]string, 0, 1)
for i := 1; i <= length; i++ {
@ -381,16 +412,18 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
// }
if i == 1 {
vus, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz)
var (
vus []interface{}
err error
)
vus, autoFields, err = d.collectValues(mi, ind, mi.fields.dbcols, false, true, &names, tz)
if err != nil {
return cnt, err
}
values = make([]interface{}, bulk*len(vus))
nums += copy(values, vus)
} else {
vus, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
vus, _, err := d.collectValues(mi, ind, mi.fields.dbcols, false, true, nil, tz)
if err != nil {
return cnt, err
}
@ -412,7 +445,12 @@ func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bul
}
}
return cnt, nil
var err error
if len(autoFields) > 0 {
err = d.ins.setval(q, mi, autoFields)
}
return cnt, err
}
// execute insert sql with given struct and given values.
@ -455,6 +493,110 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
return id, err
}
// InsertOrUpdate a row
// If your primary key or unique column conflict will update
// If no will insert
func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
args0 := ""
iouStr := ""
argsMap := map[string]string{}
switch a.Driver {
case DRMySQL:
iouStr = "ON DUPLICATE KEY UPDATE"
case DRPostgres:
if len(args) == 0 {
return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName)
} else {
args0 = strings.ToLower(args[0])
iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0)
}
default:
return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName)
}
//Get on the key-value pairs
for _, v := range args {
kv := strings.Split(v, "=")
if len(kv) == 2 {
argsMap[strings.ToLower(kv[0])] = kv[1]
}
}
isMulti := false
names := make([]string, 0, len(mi.fields.dbcols)-1)
Q := d.ins.TableQuote()
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
if err != nil {
return 0, err
}
marks := make([]string, len(names))
updateValues := make([]interface{}, 0)
updates := make([]string, len(names))
var conflitValue interface{}
for i, v := range names {
marks[i] = "?"
valueStr := argsMap[strings.ToLower(v)]
if v == args0 {
conflitValue = values[i]
}
if valueStr != "" {
switch a.Driver {
case DRMySQL:
updates[i] = v + "=" + valueStr
case DRPostgres:
if conflitValue != nil {
//postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args0)
updateValues = append(updateValues, conflitValue)
} else {
return 0, fmt.Errorf("`%s` must be in front of `%s` in your struct", args0, v)
}
}
} else {
updates[i] = v + "=?"
updateValues = append(updateValues, values[i])
}
}
values = append(values, updateValues...)
sep := fmt.Sprintf("%s, %s", Q, Q)
qmarks := strings.Join(marks, ", ")
qupdates := strings.Join(updates, ", ")
columns := strings.Join(names, sep)
multi := len(values) / len(names)
if isMulti {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
//conflitValue maybe is a int,can`t use fmt.Sprintf
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
d.ins.ReplaceMarks(&query)
if isMulti || !d.ins.HasReturningID(mi, &query) {
res, err := q.Exec(query, values...)
if err == nil {
if isMulti {
return res.RowsAffected()
}
return res.LastInsertId()
}
return 0, err
}
row := q.QueryRow(query, values...)
var id int64
err = row.Scan(&id)
if err.Error() == `pq: syntax error at or near "ON"` {
err = fmt.Errorf("postgres version must 9.5 or higher")
}
return id, err
}
// execute update sql dbQuerier with given struct reflect.Value.
func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
pkName, pkValue, ok := getExistPk(mi, ind)
@ -472,7 +614,7 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
setNames = make([]string, 0, len(cols))
}
setValues, err := d.collectValues(mi, ind, cols, true, false, &setNames, tz)
setValues, _, err := d.collectValues(mi, ind, cols, true, false, &setNames, tz)
if err != nil {
return 0, err
}
@ -497,18 +639,36 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
// execute delete sql dbQuerier with given struct reflect.Value.
// delete index is pk.
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
pkName, pkValue, ok := getExistPk(mi, ind)
if ok == false {
return 0, ErrMissPK
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
var whereCols []string
var args []interface{}
// if specify cols length > 0, then use it for where condition.
if len(cols) > 0 {
var err error
whereCols = make([]string, 0, len(cols))
args, _, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
if err != nil {
return 0, err
}
} else {
// default use pk value as where condtion.
pkColumn, pkValue, ok := getExistPk(mi, ind)
if ok == false {
return 0, ErrMissPK
}
whereCols = []string{pkColumn}
args = append(args, pkValue)
}
Q := d.ins.TableQuote()
query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, pkName, Q)
sep := fmt.Sprintf("%s = ? AND %s", Q, Q)
wheres := strings.Join(whereCols, sep)
query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, wheres, Q)
d.ins.ReplaceMarks(&query)
res, err := q.Exec(query, pkValue)
res, err := q.Exec(query, args...)
if err == nil {
num, err := res.RowsAffected()
if err != nil {
@ -516,13 +676,13 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
}
if num > 0 {
if mi.fields.pk.auto {
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
ind.Field(mi.fields.pk.fieldIndex).SetUint(0)
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0)
} else {
ind.Field(mi.fields.pk.fieldIndex).SetInt(0)
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
}
}
err := d.deleteRels(q, mi, []interface{}{pkValue}, tz)
err := d.deleteRels(q, mi, args, tz)
if err != nil {
return num, err
}
@ -859,13 +1019,13 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
mmi = fi.relModelInfo
field := last
if last.Kind() != reflect.Invalid {
field = reflect.Indirect(last.Field(fi.fieldIndex))
field = reflect.Indirect(last.FieldByIndex(fi.fieldIndex))
if field.IsValid() {
d.setColsValues(mmi, &field, mmi.fields.dbcols, trefs[:len(mmi.fields.dbcols)], tz)
for _, fi := range mmi.fields.fieldsReverse {
if fi.inModel && fi.reverseFieldInfo.mi == lastm {
if fi.reverseFieldInfo != nil {
f := field.Field(fi.fieldIndex)
f := field.FieldByIndex(fi.fieldIndex)
if f.Kind() == reflect.Ptr {
f.Set(last.Addr())
}
@ -927,12 +1087,17 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
tables.parseRelated(qs.related, qs.relDepth)
where, args := tables.getCondSQL(cond, false, tz)
groupBy := tables.getGroupSQL(qs.groups)
tables.getOrderSQL(qs.orders)
join := tables.getJoinSQL()
Q := d.ins.TableQuote()
query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s", Q, mi.table, Q, join, where)
query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s", Q, mi.table, Q, join, where, groupBy)
if groupBy != "" {
query = fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS T", query)
}
d.ins.ReplaceMarks(&query)
@ -1014,7 +1179,7 @@ func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string,
fi := mi.fields.GetByColumn(column)
field := ind.Field(fi.fieldIndex)
field := ind.FieldByIndex(fi.fieldIndex)
value, err := d.convertValueFromDB(fi, val, tz)
if err != nil {
@ -1071,13 +1236,13 @@ setValue:
}
value = b
}
case fieldType == TypeCharField || fieldType == TypeTextField:
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
if str == nil {
value = ToStr(val)
} else {
value = str.String()
}
case fieldType == TypeDateField || fieldType == TypeDateTimeField:
case fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField:
if str == nil {
switch t := val.(type) {
case time.Time:
@ -1097,15 +1262,20 @@ setValue:
if len(s) >= 19 {
s = s[:19]
t, err = time.ParseInLocation(formatDateTime, s, tz)
} else {
} else if len(s) >= 10 {
if len(s) > 10 {
s = s[:10]
}
t, err = time.ParseInLocation(formatDate, s, tz)
} else if len(s) >= 8 {
if len(s) > 8 {
s = s[:8]
}
t, err = time.ParseInLocation(formatTime, s, tz)
}
t = t.In(DefaultTimeLoc)
if err != nil && s != "0000-00-00" && s != "0000-00-00 00:00:00" {
if err != nil && s != "00:00:00" && s != "0000-00-00" && s != "0000-00-00 00:00:00" {
tErr = err
goto end
}
@ -1140,7 +1310,7 @@ setValue:
tErr = err
goto end
}
if fieldType&IsPostiveIntegerField > 0 {
if fieldType&IsPositiveIntegerField > 0 {
v, _ := str.Uint64()
value = v
} else {
@ -1212,7 +1382,7 @@ setValue:
field.SetBool(value.(bool))
}
}
case fieldType == TypeCharField || fieldType == TypeTextField:
case fieldType == TypeCharField || fieldType == TypeTextField || fieldType == TypeJSONField || fieldType == TypeJsonbField:
if isNative {
if ns, ok := field.Interface().(sql.NullString); ok {
if value == nil {
@ -1234,12 +1404,18 @@ setValue:
field.SetString(value.(string))
}
}
case fieldType == TypeDateField || fieldType == TypeDateTimeField:
case fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField:
if isNative {
if value == nil {
value = time.Time{}
} else if field.Kind() == reflect.Ptr {
if value != nil {
v := value.(time.Time)
field.Set(reflect.ValueOf(&v))
}
} else {
field.Set(reflect.ValueOf(value))
}
field.Set(reflect.ValueOf(value))
}
case fieldType == TypePositiveBitField && field.Kind() == reflect.Ptr:
if value != nil {
@ -1292,7 +1468,7 @@ setValue:
field.Set(reflect.ValueOf(&v))
}
case fieldType&IsIntegerField > 0:
if fieldType&IsPostiveIntegerField > 0 {
if fieldType&IsPositiveIntegerField > 0 {
if isNative {
if value == nil {
value = uint64(0)
@ -1350,7 +1526,7 @@ setValue:
fieldType = fi.relModelInfo.fields.pk.fieldType
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
field.Set(mf)
f := mf.Elem().Field(fi.relModelInfo.fields.pk.fieldIndex)
f := mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
field = f
goto setValue
}
@ -1440,7 +1616,11 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
sels := strings.Join(cols, ", ")
query := fmt.Sprintf("SELECT %s FROM %s%s%s T0 %s%s%s%s%s", sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
sqlSelect := "SELECT"
if qs.distinct {
sqlSelect += " DISTINCT"
}
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
d.ins.ReplaceMarks(&query)
@ -1562,6 +1742,11 @@ func (d *dbBase) HasReturningID(*modelInfo, *string) bool {
return false
}
// sync auto key
func (d *dbBase) setval(db dbQuerier, mi *modelInfo, autoFields []string) error {
return nil
}
// convert time from db.
func (d *dbBase) TimeFromDB(t *time.Time, tz *time.Location) {
*t = t.In(tz)

View File

@ -59,6 +59,7 @@ var (
"postgres": DRPostgres,
"sqlite3": DRSqlite,
"tidb": DRTiDB,
"oracle": DROracle,
}
dbBasers = map[DriverType]dbBaser{
DRMySQL: newdbBaseMysql(),
@ -79,7 +80,7 @@ type _dbCache struct {
func (ac *_dbCache) add(name string, al *alias) (added bool) {
ac.mux.Lock()
defer ac.mux.Unlock()
if _, ok := ac.cache[name]; ok == false {
if _, ok := ac.cache[name]; !ok {
ac.cache[name] = al
added = true
}
@ -151,7 +152,7 @@ func detectTZ(al *alias) {
al.Engine = "INNODB"
}
case DRSqlite:
case DRSqlite, DROracle:
al.TZ = time.UTC
case DRPostgres:

View File

@ -16,6 +16,8 @@ package orm
import (
"fmt"
"reflect"
"strings"
)
// mysql operators.
@ -96,6 +98,83 @@ func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool
return cnt > 0
}
// InsertOrUpdate a row
// If your primary key or unique column conflict will update
// If no will insert
// Add "`" for mysql sql building
func (d *dbBaseMysql) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) {
iouStr := ""
argsMap := map[string]string{}
iouStr = "ON DUPLICATE KEY UPDATE"
//Get on the key-value pairs
for _, v := range args {
kv := strings.Split(v, "=")
if len(kv) == 2 {
argsMap[strings.ToLower(kv[0])] = kv[1]
}
}
isMulti := false
names := make([]string, 0, len(mi.fields.dbcols)-1)
Q := d.ins.TableQuote()
values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ)
if err != nil {
return 0, err
}
marks := make([]string, len(names))
updateValues := make([]interface{}, 0)
updates := make([]string, len(names))
for i, v := range names {
marks[i] = "?"
valueStr := argsMap[strings.ToLower(v)]
if valueStr != "" {
updates[i] = "`" + v + "`" + "=" + valueStr
} else {
updates[i] = "`" + v + "`" + "=?"
updateValues = append(updateValues, values[i])
}
}
values = append(values, updateValues...)
sep := fmt.Sprintf("%s, %s", Q, Q)
qmarks := strings.Join(marks, ", ")
qupdates := strings.Join(updates, ", ")
columns := strings.Join(names, sep)
multi := len(values) / len(names)
if isMulti {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
//conflitValue maybe is a int,can`t use fmt.Sprintf
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr)
d.ins.ReplaceMarks(&query)
if isMulti || !d.ins.HasReturningID(mi, &query) {
res, err := q.Exec(query, values...)
if err == nil {
if isMulti {
return res.RowsAffected()
}
return res.LastInsertId()
}
return 0, err
}
row := q.QueryRow(query, values...)
var id int64
err = row.Scan(&id)
return id, err
}
// create new mysql dbBaser.
func newdbBaseMysql() dbBaser {
b := new(dbBaseMysql)

View File

@ -14,6 +14,41 @@
package orm
import (
"fmt"
"strings"
)
// oracle operators.
var oracleOperators = map[string]string{
"exact": "= ?",
"gt": "> ?",
"gte": ">= ?",
"lt": "< ?",
"lte": "<= ?",
"//iendswith": "LIKE ?",
}
// oracle column field types.
var oracleTypes = map[string]string{
"pk": "NOT NULL PRIMARY KEY",
"bool": "bool",
"string": "VARCHAR2(%d)",
"string-text": "VARCHAR2(%d)",
"time.Time-date": "DATE",
"time.Time": "TIMESTAMP",
"int8": "INTEGER",
"int16": "INTEGER",
"int32": "INTEGER",
"int64": "INTEGER",
"uint8": "INTEGER",
"uint16": "INTEGER",
"uint32": "INTEGER",
"uint64": "INTEGER",
"float64": "NUMBER",
"float64-decimal": "NUMBER(%d, %d)",
}
// oracle dbBaser
type dbBaseOracle struct {
dbBase
@ -27,3 +62,35 @@ func newdbBaseOracle() dbBaser {
b.ins = b
return b
}
// OperatorSQL get oracle operator.
func (d *dbBaseOracle) OperatorSQL(operator string) string {
return oracleOperators[operator]
}
// DbTypes get oracle table field types.
func (d *dbBaseOracle) DbTypes() map[string]string {
return oracleTypes
}
//ShowTablesQuery show all the tables in database
func (d *dbBaseOracle) ShowTablesQuery() string {
return "SELECT TABLE_NAME FROM USER_TABLES"
}
// Oracle
func (d *dbBaseOracle) ShowColumnsQuery(table string) string {
return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+
"WHERE TABLE_NAME ='%s'", strings.ToUpper(table))
}
// check index is exist
func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool {
row := db.QueryRow("SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+
"WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+
"AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name))
var cnt int
row.Scan(&cnt)
return cnt > 0
}

View File

@ -56,6 +56,8 @@ var postgresTypes = map[string]string{
"uint64": `bigint CHECK("%COL%" >= 0)`,
"float64": "double precision",
"float64-decimal": "numeric(%d, %d)",
"json": "json",
"jsonb": "jsonb",
}
// postgresql dbBaser.
@ -123,14 +125,35 @@ func (d *dbBasePostgres) ReplaceMarks(query *string) {
}
// make returning sql support for postgresql.
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) (has bool) {
if mi.fields.pk.auto {
if query != nil {
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, mi.fields.pk.column)
}
has = true
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) bool {
fi := mi.fields.pk
if fi.fieldType&IsPositiveIntegerField == 0 && fi.fieldType&IsIntegerField == 0 {
return false
}
return
if query != nil {
*query = fmt.Sprintf(`%s RETURNING "%s"`, *query, fi.column)
}
return true
}
// sync auto key
func (d *dbBasePostgres) setval(db dbQuerier, mi *modelInfo, autoFields []string) error {
if len(autoFields) == 0 {
return nil
}
Q := d.ins.TableQuote()
for _, name := range autoFields {
query := fmt.Sprintf("SELECT setval(pg_get_serial_sequence('%s', '%s'), (SELECT MAX(%s%s%s) FROM %s%s%s));",
mi.table, name,
Q, name, Q,
Q, mi.table, Q)
if _, err := db.Exec(query); err != nil {
return err
}
}
return nil
}
// show table sql for postgresql.

View File

@ -32,14 +32,14 @@ func getDbAlias(name string) *alias {
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
fi := mi.fields.pk
v := ind.Field(fi.fieldIndex)
if fi.fieldType&IsPostiveIntegerField > 0 {
v := ind.FieldByIndex(fi.fieldIndex)
if fi.fieldType&IsPositiveIntegerField > 0 {
vu := v.Uint()
exist = vu > 0
value = vu
} else if fi.fieldType&IsIntegerField > 0 {
vu := v.Int()
exist = vu > 0
exist = true
value = vu
} else {
vu := v.String()
@ -74,24 +74,32 @@ outFor:
case reflect.String:
v := val.String()
if fi != nil {
if fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
if fi.fieldType == TypeTimeField || fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
var t time.Time
var err error
if len(v) >= 19 {
s := v[:19]
t, err = time.ParseInLocation(formatDateTime, s, DefaultTimeLoc)
} else {
} else if len(v) >= 10 {
s := v
if len(v) > 10 {
s = v[:10]
}
t, err = time.ParseInLocation(formatDate, s, tz)
} else {
s := v
if len(s) > 8 {
s = v[:8]
}
t, err = time.ParseInLocation(formatTime, s, tz)
}
if err == nil {
if fi.fieldType == TypeDateField {
v = t.In(tz).Format(formatDate)
} else {
} else if fi.fieldType == TypeDateTimeField {
v = t.In(tz).Format(formatDateTime)
} else {
v = t.In(tz).Format(formatTime)
}
}
}
@ -137,6 +145,10 @@ outFor:
if v, ok := arg.(time.Time); ok {
if fi != nil && fi.fieldType == TypeDateField {
arg = v.In(tz).Format(formatDate)
} else if fi != nil && fi.fieldType == TypeDateTimeField {
arg = v.In(tz).Format(formatDateTime)
} else if fi != nil && fi.fieldType == TypeTimeField {
arg = v.In(tz).Format(formatTime)
} else {
arg = v.In(tz).Format(formatDateTime)
}
@ -144,7 +156,7 @@ outFor:
typ := val.Type()
name := getFullName(typ)
var value interface{}
if mmi, ok := modelCache.getByFN(name); ok {
if mmi, ok := modelCache.getByFullName(name); ok {
if _, vu, exist := getExistPk(mmi, val); exist {
value = vu
}

View File

@ -29,39 +29,18 @@ const (
var (
modelCache = &_modelCache{
cache: make(map[string]*modelInfo),
cacheByFN: make(map[string]*modelInfo),
}
supportTag = map[string]int{
"-": 1,
"null": 1,
"index": 1,
"unique": 1,
"pk": 1,
"auto": 1,
"auto_now": 1,
"auto_now_add": 1,
"size": 2,
"column": 2,
"default": 2,
"rel": 2,
"reverse": 2,
"rel_table": 2,
"rel_through": 2,
"digits": 2,
"decimals": 2,
"on_delete": 2,
"type": 2,
cache: make(map[string]*modelInfo),
cacheByFullName: make(map[string]*modelInfo),
}
)
// model info collection
type _modelCache struct {
sync.RWMutex
orders []string
cache map[string]*modelInfo
cacheByFN map[string]*modelInfo
done bool
sync.RWMutex // only used outsite for bootStrap
orders []string
cache map[string]*modelInfo
cacheByFullName map[string]*modelInfo
done bool
}
// get all model info
@ -88,9 +67,9 @@ func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) {
return
}
// get model info by field name
func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
mi, ok = mc.cacheByFN[name]
// get model info by full name
func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) {
mi, ok = mc.cacheByFullName[name]
return
}
@ -98,7 +77,7 @@ func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
mii := mc.cache[table]
mc.cache[table] = mi
mc.cacheByFN[mi.fullName] = mi
mc.cacheByFullName[mi.fullName] = mi
if mii == nil {
mc.orders = append(mc.orders, table)
}
@ -109,7 +88,7 @@ func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
func (mc *_modelCache) clean() {
mc.orders = make([]string, 0)
mc.cache = make(map[string]*modelInfo)
mc.cacheByFN = make(map[string]*modelInfo)
mc.cacheByFullName = make(map[string]*modelInfo)
mc.done = false
}

View File

@ -15,7 +15,6 @@
package orm
import (
"errors"
"fmt"
"os"
"reflect"
@ -23,24 +22,34 @@ import (
)
// register models.
// prefix means table name prefix.
func registerModel(prefix string, model interface{}) {
// PrefixOrSuffix means table name prefix or suffix.
// isPrefix whether the prefix is prefix or suffix
func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
val := reflect.ValueOf(model)
ind := reflect.Indirect(val)
typ := ind.Type()
typ := reflect.Indirect(val).Type()
if val.Kind() != reflect.Ptr {
panic(fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ)))
}
// For this case:
// u := &User{}
// registerModel(&u)
if typ.Kind() == reflect.Ptr {
panic(fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ))
}
table := getTableName(val)
if prefix != "" {
table = prefix + table
if PrefixOrSuffix != "" {
if isPrefix {
table = PrefixOrSuffix + table
} else {
table = table + PrefixOrSuffix
}
}
// models's fullname is pkgpath + struct name
name := getFullName(typ)
if _, ok := modelCache.getByFN(name); ok {
if _, ok := modelCache.getByFullName(name); ok {
fmt.Printf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
os.Exit(2)
}
@ -50,34 +59,34 @@ func registerModel(prefix string, model interface{}) {
os.Exit(2)
}
info := newModelInfo(val)
if info.fields.pk == nil {
mi := newModelInfo(val)
if mi.fields.pk == nil {
outFor:
for _, fi := range info.fields.fieldsDB {
for _, fi := range mi.fields.fieldsDB {
if strings.ToLower(fi.name) == "id" {
switch fi.addrValue.Elem().Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
fi.auto = true
fi.pk = true
info.fields.pk = fi
mi.fields.pk = fi
break outFor
}
}
}
if info.fields.pk == nil {
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field\n", name)
if mi.fields.pk == nil {
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field, default use 'id' if not set\n", name)
os.Exit(2)
}
}
info.table = table
info.pkg = typ.PkgPath()
info.model = model
info.manual = true
mi.table = table
mi.pkg = typ.PkgPath()
mi.model = model
mi.manual = true
modelCache.set(table, info)
modelCache.set(table, mi)
}
// boostrap models
@ -85,30 +94,29 @@ func bootStrap() {
if modelCache.done {
return
}
var (
err error
models map[string]*modelInfo
)
if dataBaseCache.getDefault() == nil {
err = fmt.Errorf("must have one register DataBase alias named `default`")
goto end
}
// set rel and reverse model
// RelManyToMany set the relTable
models = modelCache.all()
for _, mi := range models {
for _, fi := range mi.fields.columns {
if fi.rel || fi.reverse {
elm := fi.addrValue.Type().Elem()
switch fi.fieldType {
case RelReverseMany, RelManyToMany:
if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany {
elm = elm.Elem()
}
// check the rel or reverse model already register
name := getFullName(elm)
mii, ok := modelCache.getByFN(name)
if ok == false || mii.pkg != elm.PkgPath() {
mii, ok := modelCache.getByFullName(name)
if !ok || mii.pkg != elm.PkgPath() {
err = fmt.Errorf("can not found rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
goto end
}
@ -117,20 +125,17 @@ func bootStrap() {
switch fi.fieldType {
case RelManyToMany:
if fi.relThrough != "" {
msg := fmt.Sprintf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
pn := fi.relThrough[:i]
rmi, ok := modelCache.getByFN(fi.relThrough)
rmi, ok := modelCache.getByFullName(fi.relThrough)
if ok == false || pn != rmi.pkg {
err = errors.New(msg + " cannot find table")
err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough)
goto end
}
fi.relThroughModelInfo = rmi
fi.relTable = rmi.table
} else {
err = errors.New(msg)
err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
goto end
}
} else {
@ -138,7 +143,6 @@ func bootStrap() {
if fi.relTable != "" {
i.table = fi.relTable
}
if v := modelCache.set(i.table, i); v != nil {
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
goto end
@ -153,6 +157,8 @@ func bootStrap() {
}
}
// check the rel filed while the relModelInfo also has filed point to current model
// if not exist, add a new field to the relModelInfo
models = modelCache.all()
for _, mi := range models {
for _, fi := range mi.fields.fieldsRel {
@ -165,7 +171,6 @@ func bootStrap() {
break
}
}
if inModel == false {
rmi := fi.relModelInfo
ffi := new(fieldInfo)
@ -216,7 +221,6 @@ func bootStrap() {
}
}
}
if fi.reverseFieldInfoTwo == nil {
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
fi.relThroughModelInfo.fullName)
@ -300,17 +304,31 @@ end:
// RegisterModel register models
func RegisterModel(models ...interface{}) {
if modelCache.done {
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
}
RegisterModelWithPrefix("", models...)
}
// RegisterModelWithPrefix register models with a prefix
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
if modelCache.done {
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
panic(fmt.Errorf("RegisterModelWithPrefix must be run before BootStrap"))
}
for _, model := range models {
registerModel(prefix, model)
registerModel(prefix, model, true)
}
}
// RegisterModelWithSuffix register models with a suffix
func RegisterModelWithSuffix(suffix string, models ...interface{}) {
if modelCache.done {
panic(fmt.Errorf("RegisterModelWithSuffix must be run before BootStrap"))
}
for _, model := range models {
registerModel(suffix, model, false)
}
}
@ -320,7 +338,6 @@ func BootStrap() {
if modelCache.done {
return
}
modelCache.Lock()
defer modelCache.Unlock()
bootStrap()

View File

@ -25,6 +25,7 @@ const (
TypeBooleanField = 1 << iota
TypeCharField
TypeTextField
TypeTimeField
TypeDateField
TypeDateTimeField
TypeBitField
@ -37,6 +38,8 @@ const (
TypePositiveBigIntegerField
TypeFloatField
TypeDecimalField
TypeJSONField
TypeJsonbField
RelForeignKey
RelOneToOne
RelManyToMany
@ -46,10 +49,10 @@ const (
// Define some logic enum
const (
IsIntegerField = ^-TypePositiveBigIntegerField >> 4 << 5
IsPostiveIntegerField = ^-TypePositiveBigIntegerField >> 8 << 9
IsRelField = ^-RelReverseMany >> 14 << 15
IsFieldType = ^-RelReverseMany<<1 + 1
IsIntegerField = ^-TypePositiveBigIntegerField >> 5 << 6
IsPositiveIntegerField = ^-TypePositiveBigIntegerField >> 9 << 10
IsRelField = ^-RelReverseMany >> 17 << 18
IsFieldType = ^-RelReverseMany<<1 + 1
)
// BooleanField A true/false field.
@ -145,6 +148,65 @@ func (e *CharField) RawValue() interface{} {
// verify CharField implement Fielder
var _ Fielder = new(CharField)
// TimeField A time, represented in go by a time.Time instance.
// only time values like 10:00:00
// Has a few extra, optional attr tag:
//
// auto_now:
// Automatically set the field to now every time the object is saved. Useful for “last-modified” timestamps.
// Note that the current date is always used; its not just a default value that you can override.
//
// auto_now_add:
// Automatically set the field to now when the object is first created. Useful for creation of timestamps.
// Note that the current date is always used; its not just a default value that you can override.
//
// eg: `orm:"auto_now"` or `orm:"auto_now_add"`
type TimeField time.Time
// Value return the time.Time
func (e TimeField) Value() time.Time {
return time.Time(e)
}
// Set set the TimeField's value
func (e *TimeField) Set(d time.Time) {
*e = TimeField(d)
}
// String convert time to string
func (e *TimeField) String() string {
return e.Value().String()
}
// FieldType return enum type Date
func (e *TimeField) FieldType() int {
return TypeDateField
}
// SetRaw convert the interface to time.Time. Allow string and time.Time
func (e *TimeField) SetRaw(value interface{}) error {
switch d := value.(type) {
case time.Time:
e.Set(d)
case string:
v, err := timeParse(d, formatTime)
if err != nil {
e.Set(v)
}
return err
default:
return fmt.Errorf("<TimeField.SetRaw> unknown value `%s`", value)
}
return nil
}
// RawValue return time value
func (e *TimeField) RawValue() interface{} {
return e.Value()
}
var _ Fielder = new(TimeField)
// DateField A date, represented in go by a time.Time instance.
// only date values like 2006-01-02
// Has a few extra, optional attr tag:
@ -627,3 +689,87 @@ func (e *TextField) RawValue() interface{} {
// verify TextField implement Fielder
var _ Fielder = new(TextField)
// JSONField postgres json field.
type JSONField string
// Value return JSONField value
func (j JSONField) Value() string {
return string(j)
}
// Set the JSONField value
func (j *JSONField) Set(d string) {
*j = JSONField(d)
}
// String convert JSONField to string
func (j *JSONField) String() string {
return j.Value()
}
// FieldType return enum type
func (j *JSONField) FieldType() int {
return TypeJSONField
}
// SetRaw convert interface string to string
func (j *JSONField) SetRaw(value interface{}) error {
switch d := value.(type) {
case string:
j.Set(d)
default:
return fmt.Errorf("<JSONField.SetRaw> unknown value `%s`", value)
}
return nil
}
// RawValue return JSONField value
func (j *JSONField) RawValue() interface{} {
return j.Value()
}
// verify JSONField implement Fielder
var _ Fielder = new(JSONField)
// JsonbField postgres json field.
type JsonbField string
// Value return JsonbField value
func (j JsonbField) Value() string {
return string(j)
}
// Set the JsonbField value
func (j *JsonbField) Set(d string) {
*j = JsonbField(d)
}
// String convert JsonbField to string
func (j *JsonbField) String() string {
return j.Value()
}
// FieldType return enum type
func (j *JsonbField) FieldType() int {
return TypeJsonbField
}
// SetRaw convert interface string to string
func (j *JsonbField) SetRaw(value interface{}) error {
switch d := value.(type) {
case string:
j.Set(d)
default:
return fmt.Errorf("<JsonbField.SetRaw> unknown value `%s`", value)
}
return nil
}
// RawValue return JsonbField value
func (j *JsonbField) RawValue() interface{} {
return j.Value()
}
// verify JsonbField implement Fielder
var _ Fielder = new(JsonbField)

View File

@ -102,9 +102,9 @@ func newFields() *fields {
// single field info
type fieldInfo struct {
mi *modelInfo
fieldIndex int
fieldIndex []int
fieldType int
dbcol bool
dbcol bool // table column fk and onetoone
inModel bool
name string
fullName string
@ -116,12 +116,13 @@ type fieldInfo struct {
null bool
index bool
unique bool
colDefault bool
initial StrTo
colDefault bool // whether has default tag
initial StrTo // store the default value
size int
toText bool
autoNow bool
autoNowAdd bool
rel bool
rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true
reverse bool
reverseField string
reverseFieldInfo *fieldInfo
@ -133,16 +134,16 @@ type fieldInfo struct {
relModelInfo *modelInfo
digits int
decimals int
isFielder bool
isFielder bool // implement Fielder interface
onDelete string
}
// new field info
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) {
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) {
var (
tag string
tagValue string
initial StrTo
initial StrTo // store the default value
fieldType int
attrs map[string]bool
tags map[string]string
@ -151,6 +152,10 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (f
fi = new(fieldInfo)
// if field which CanAddr is the follow type
// A value is addressable if it is an element of a slice,
// an element of an addressable array, a field of an
// addressable struct, or the result of dereferencing a pointer.
addrField = field
if field.CanAddr() && field.Kind() != reflect.Ptr {
addrField = field.Addr()
@ -161,7 +166,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (f
}
}
parseStructTag(sf.Tag.Get(defaultStructTagName), &attrs, &tags)
attrs, tags = parseStructTag(sf.Tag.Get(defaultStructTagName))
if _, ok := attrs["-"]; ok {
return nil, errSkipField
@ -187,7 +192,7 @@ checkType:
}
fieldType = f.FieldType()
if fieldType&IsRelField > 0 {
err = fmt.Errorf("unsupport rel type custom field")
err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/astaxie/beego/blob/master/orm/models_fields.go#L24-L42")
goto end
}
default:
@ -210,7 +215,7 @@ checkType:
}
break checkType
default:
err = fmt.Errorf("error")
err = fmt.Errorf("rel only allow these value: fk, one, m2m")
goto wrongTag
}
}
@ -230,7 +235,7 @@ checkType:
}
break checkType
default:
err = fmt.Errorf("error")
err = fmt.Errorf("reverse only allow these value: one, many")
goto wrongTag
}
}
@ -239,8 +244,15 @@ checkType:
if err != nil {
goto end
}
if fieldType == TypeCharField && tags["type"] == "text" {
fieldType = TypeTextField
if fieldType == TypeCharField {
switch tags["type"] {
case "text":
fieldType = TypeTextField
case "json":
fieldType = TypeJSONField
case "jsonb":
fieldType = TypeJsonbField
}
}
if fieldType == TypeFloatField && (digits != "" || decimals != "") {
fieldType = TypeDecimalField
@ -248,8 +260,14 @@ checkType:
if fieldType == TypeDateTimeField && tags["type"] == "date" {
fieldType = TypeDateField
}
if fieldType == TypeTimeField && tags["type"] == "time" {
fieldType = TypeTimeField
}
}
// check the rel and reverse type
// rel should Ptr
// reverse should slice []*struct
switch fieldType {
case RelForeignKey, RelOneToOne, RelReverseOne:
if field.Kind() != reflect.Ptr {
@ -278,7 +296,7 @@ checkType:
fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
fi.addrValue = addrField
fi.sf = sf
fi.fullName = mi.fullName + "." + sf.Name
fi.fullName = mi.fullName + mName + "." + sf.Name
fi.null = attrs["null"]
fi.index = attrs["index"]
@ -339,7 +357,7 @@ checkType:
switch fieldType {
case TypeBooleanField:
case TypeCharField:
case TypeCharField, TypeJSONField, TypeJsonbField:
if size != "" {
v, e := StrTo(size).Int32()
if e != nil {
@ -349,11 +367,12 @@ checkType:
}
} else {
fi.size = 255
fi.toText = true
}
case TypeTextField:
fi.index = false
fi.unique = false
case TypeDateField, TypeDateTimeField:
case TypeTimeField, TypeDateField, TypeDateTimeField:
if attrs["auto_now"] {
fi.autoNow = true
} else if attrs["auto_now_add"] {
@ -387,14 +406,12 @@ checkType:
if fi.auto || fi.pk {
if fi.auto {
switch addrField.Elem().Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
default:
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
goto end
}
fi.pk = true
}
fi.null = false
@ -406,8 +423,8 @@ checkType:
fi.index = false
}
if fi.auto || fi.pk || fi.unique || fieldType == TypeDateField || fieldType == TypeDateTimeField {
// can not set default
// can not set default for these type
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
initial.Clear()
}

View File

@ -29,93 +29,94 @@ type modelInfo struct {
model interface{}
fields *fields
manual bool
addrField reflect.Value
addrField reflect.Value //store the original struct value
uniques []string
isThrough bool
}
// new model info
func newModelInfo(val reflect.Value) (info *modelInfo) {
func newModelInfo(val reflect.Value) (mi *modelInfo) {
mi = &modelInfo{}
mi.fields = newFields()
ind := reflect.Indirect(val)
mi.addrField = val
mi.name = ind.Type().Name()
mi.fullName = getFullName(ind.Type())
addModelFields(mi, ind, "", []int{})
return
}
// index: FieldByIndex returns the nested field corresponding to index
func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int) {
var (
err error
fi *fieldInfo
sf reflect.StructField
)
info = &modelInfo{}
info.fields = newFields()
ind := reflect.Indirect(val)
typ := ind.Type()
info.addrField = val
info.name = typ.Name()
info.fullName = getFullName(typ)
for i := 0; i < ind.NumField(); i++ {
field := ind.Field(i)
sf = ind.Type().Field(i)
// if the field is unexported skip
if sf.PkgPath != "" {
continue
}
fi, err = newFieldInfo(info, field, sf)
if err != nil {
if err == errSkipField {
err = nil
continue
}
break
// add anonymous struct fields
if sf.Anonymous {
addModelFields(mi, field, mName+"."+sf.Name, append(index, i))
continue
}
added := info.fields.Add(fi)
if added == false {
fi, err = newFieldInfo(mi, field, sf, mName)
if err == errSkipField {
err = nil
continue
} else if err != nil {
break
}
//record current field index
fi.fieldIndex = append(index, i)
fi.mi = mi
fi.inModel = true
if mi.fields.Add(fi) == false {
err = fmt.Errorf("duplicate column name: %s", fi.column)
break
}
if fi.pk {
if info.fields.pk != nil {
if mi.fields.pk != nil {
err = fmt.Errorf("one model must have one pk field only")
break
} else {
info.fields.pk = fi
mi.fields.pk = fi
}
}
fi.fieldIndex = i
fi.mi = info
fi.inModel = true
}
if err != nil {
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
os.Exit(2)
}
return
}
// combine related model info to new model info.
// prepare for relation models query.
func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
info = new(modelInfo)
info.fields = newFields()
info.table = m1.table + "_" + m2.table + "s"
info.name = camelString(info.table)
info.fullName = m1.pkg + "." + info.name
func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) {
mi = new(modelInfo)
mi.fields = newFields()
mi.table = m1.table + "_" + m2.table + "s"
mi.name = camelString(mi.table)
mi.fullName = m1.pkg + "." + mi.name
fa := new(fieldInfo)
f1 := new(fieldInfo)
f2 := new(fieldInfo)
fa := new(fieldInfo) // pk
f1 := new(fieldInfo) // m1 table RelForeignKey
f2 := new(fieldInfo) // m2 table RelForeignKey
fa.fieldType = TypeBigIntegerField
fa.auto = true
fa.pk = true
fa.dbcol = true
fa.name = "Id"
fa.column = "id"
fa.fullName = info.fullName + "." + fa.name
fa.fullName = mi.fullName + "." + fa.name
f1.dbcol = true
f2.dbcol = true
@ -123,8 +124,8 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
f2.fieldType = RelForeignKey
f1.name = camelString(m1.table)
f2.name = camelString(m2.table)
f1.fullName = info.fullName + "." + f1.name
f2.fullName = info.fullName + "." + f2.name
f1.fullName = mi.fullName + "." + f1.name
f2.fullName = mi.fullName + "." + f2.name
f1.column = m1.table + "_id"
f2.column = m2.table + "_id"
f1.rel = true
@ -133,14 +134,14 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
f2.relTable = m2.table
f1.relModelInfo = m1
f2.relModelInfo = m2
f1.mi = info
f2.mi = info
f1.mi = mi
f2.mi = mi
info.fields.Add(fa)
info.fields.Add(f1)
info.fields.Add(f2)
info.fields.pk = fa
mi.fields.Add(fa)
mi.fields.Add(f1)
mi.fields.Add(f2)
mi.fields.pk = fa
info.uniques = []string{f1.column, f2.column}
mi.uniques = []string{f1.column, f2.column}
return
}

View File

@ -25,7 +25,6 @@ import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
// As tidb can't use go get, so disable the tidb testing now
// _ "github.com/pingcap/tidb"
)
@ -79,40 +78,43 @@ func (e *SliceStringField) RawValue() interface{} {
var _ Fielder = new(SliceStringField)
// A json field.
type JSONField struct {
type JSONFieldTest struct {
Name string
Data string
}
func (e *JSONField) String() string {
func (e *JSONFieldTest) String() string {
data, _ := json.Marshal(e)
return string(data)
}
func (e *JSONField) FieldType() int {
func (e *JSONFieldTest) FieldType() int {
return TypeTextField
}
func (e *JSONField) SetRaw(value interface{}) error {
func (e *JSONFieldTest) SetRaw(value interface{}) error {
switch d := value.(type) {
case string:
return json.Unmarshal([]byte(d), e)
default:
return fmt.Errorf("<JsonField.SetRaw> unknown value `%v`", value)
return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value)
}
}
func (e *JSONField) RawValue() interface{} {
func (e *JSONFieldTest) RawValue() interface{} {
return e.String()
}
var _ Fielder = new(JSONField)
var _ Fielder = new(JSONFieldTest)
type Data struct {
ID int `orm:"column(id)"`
Boolean bool
Char string `orm:"size(50)"`
Text string `orm:"type(text)"`
JSON string `orm:"type(json);default({\"name\":\"json\"})"`
Jsonb string `orm:"type(jsonb)"`
Time time.Time `orm:"type(time)"`
Date time.Time `orm:"type(date)"`
DateTime time.Time `orm:"column(datetime)"`
Byte byte
@ -137,6 +139,9 @@ type DataNull struct {
Boolean bool `orm:"null"`
Char string `orm:"null;size(50)"`
Text string `orm:"null;type(text)"`
JSON string `orm:"type(json);null"`
Jsonb string `orm:"type(jsonb);null"`
Time time.Time `orm:"null;type(time)"`
Date time.Time `orm:"null;type(date)"`
DateTime time.Time `orm:"null;column(datetime)"`
Byte byte `orm:"null"`
@ -176,6 +181,9 @@ type DataNull struct {
Float32Ptr *float32 `orm:"null"`
Float64Ptr *float64 `orm:"null"`
DecimalPtr *float64 `orm:"digits(8);decimals(4);null"`
TimePtr *time.Time `orm:"null;type(time)"`
DatePtr *time.Time `orm:"null;type(date)"`
DateTimePtr *time.Time `orm:"null"`
}
type String string
@ -238,7 +246,7 @@ type User struct {
ShouldSkip string `orm:"-"`
Nums int
Langs SliceStringField `orm:"size(100)"`
Extra JSONField `orm:"type(text)"`
Extra JSONFieldTest `orm:"type(text)"`
unexport bool `orm:"-"`
unexportBool bool
}
@ -352,6 +360,52 @@ type GroupPermissions struct {
Permission *Permission `orm:"rel(fk)"`
}
type ModelID struct {
ID int64
}
type ModelBase struct {
ModelID
Created time.Time `orm:"auto_now_add;type(datetime)"`
Updated time.Time `orm:"auto_now;type(datetime)"`
}
type InLine struct {
// Common Fields
ModelBase
// Other Fields
Name string `orm:"unique"`
Email string
}
func NewInLine() *InLine {
return new(InLine)
}
type InLineOneToOne struct {
// Common Fields
ModelBase
Note string
InLine *InLine `orm:"rel(fk);column(inline)"`
}
func NewInLineOneToOne() *InLineOneToOne {
return new(InLineOneToOne)
}
type IntegerPk struct {
ID int64 `orm:"pk"`
Value string
}
type UintPk struct {
ID uint32 `orm:"pk"`
Name string
}
var DBARGS = struct {
Driver string
Source string

View File

@ -22,25 +22,47 @@ import (
"time"
)
// 1 is attr
// 2 is tag
var supportTag = map[string]int{
"-": 1,
"null": 1,
"index": 1,
"unique": 1,
"pk": 1,
"auto": 1,
"auto_now": 1,
"auto_now_add": 1,
"size": 2,
"column": 2,
"default": 2,
"rel": 2,
"reverse": 2,
"rel_table": 2,
"rel_through": 2,
"digits": 2,
"decimals": 2,
"on_delete": 2,
"type": 2,
}
// get reflect.Type name with package path.
func getFullName(typ reflect.Type) string {
return typ.PkgPath() + "." + typ.Name()
}
// get table name. method, or field name. auto snaked.
// getTableName get struct table name.
// If the struct implement the TableName, then get the result as tablename
// else use the struct name which will apply snakeString.
func getTableName(val reflect.Value) string {
ind := reflect.Indirect(val)
fun := val.MethodByName("TableName")
if fun.IsValid() {
if fun := val.MethodByName("TableName"); fun.IsValid() {
vals := fun.Call([]reflect.Value{})
if len(vals) > 0 {
val := vals[0]
if val.Kind() == reflect.String {
return val.String()
}
// has return and the first val is string
if len(vals) > 0 && vals[0].Kind() == reflect.String {
return vals[0].String()
}
}
return snakeString(ind.Type().Name())
return snakeString(reflect.Indirect(val).Type().Name())
}
// get table engine, mysiam or innodb.
@ -48,11 +70,8 @@ func getTableEngine(val reflect.Value) string {
fun := val.MethodByName("TableEngine")
if fun.IsValid() {
vals := fun.Call([]reflect.Value{})
if len(vals) > 0 {
val := vals[0]
if val.Kind() == reflect.String {
return val.String()
}
if len(vals) > 0 && vals[0].Kind() == reflect.String {
return vals[0].String()
}
}
return ""
@ -63,12 +82,9 @@ func getTableIndex(val reflect.Value) [][]string {
fun := val.MethodByName("TableIndex")
if fun.IsValid() {
vals := fun.Call([]reflect.Value{})
if len(vals) > 0 {
val := vals[0]
if val.CanInterface() {
if d, ok := val.Interface().([][]string); ok {
return d
}
if len(vals) > 0 && vals[0].CanInterface() {
if d, ok := vals[0].Interface().([][]string); ok {
return d
}
}
}
@ -80,12 +96,9 @@ func getTableUnique(val reflect.Value) [][]string {
fun := val.MethodByName("TableUnique")
if fun.IsValid() {
vals := fun.Call([]reflect.Value{})
if len(vals) > 0 {
val := vals[0]
if val.CanInterface() {
if d, ok := val.Interface().([][]string); ok {
return d
}
if len(vals) > 0 && vals[0].CanInterface() {
if d, ok := vals[0].Interface().([][]string); ok {
return d
}
}
}
@ -137,6 +150,8 @@ func getFieldType(val reflect.Value) (ft int, err error) {
ft = TypeBooleanField
case reflect.TypeOf(new(string)):
ft = TypeCharField
case reflect.TypeOf(new(time.Time)):
ft = TypeDateTimeField
default:
elm := reflect.Indirect(val)
switch elm.Kind() {
@ -187,21 +202,25 @@ func getFieldType(val reflect.Value) (ft int, err error) {
}
// parse struct tag string
func parseStructTag(data string, attrs *map[string]bool, tags *map[string]string) {
attr := make(map[string]bool)
tag := make(map[string]string)
func parseStructTag(data string) (attrs map[string]bool, tags map[string]string) {
attrs = make(map[string]bool)
tags = make(map[string]string)
for _, v := range strings.Split(data, defaultStructTagDelim) {
if v == "" {
continue
}
v = strings.TrimSpace(v)
if supportTag[v] == 1 {
attr[v] = true
if t := strings.ToLower(v); supportTag[t] == 1 {
attrs[t] = true
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
name := v[:i]
name := t[:i]
if supportTag[name] == 2 {
v = v[i+1 : len(v)-1]
tag[name] = v
tags[name] = v
}
} else {
DebugLog.Println("unsupport orm tag", v)
}
}
*attrs = attr
*tags = tag
return
}

View File

@ -68,7 +68,7 @@ const (
// Define common vars
var (
Debug = false
DebugLog = NewLog(os.Stderr)
DebugLog = NewLog(os.Stdout)
DefaultRowsLimit = 1000
DefaultRelsDepth = 2
DefaultTimeLoc = time.Local
@ -104,7 +104,7 @@ func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect
panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
}
name := getFullName(typ)
if mi, ok := modelCache.getByFN(name); ok {
if mi, ok := modelCache.getByFullName(name); ok {
return mi, ind
}
panic(fmt.Errorf("<Ormer> table: `%s` not found, maybe not RegisterModel", name))
@ -122,7 +122,17 @@ func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
// read data to model
func (o *orm) Read(md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
if err != nil {
return err
}
return nil
}
// read data to model, like Read(), but use "SELECT FOR UPDATE" form
func (o *orm) ReadForUpdate(md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true)
if err != nil {
return err
}
@ -133,14 +143,21 @@ func (o *orm) Read(md interface{}, cols ...string) error {
func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
cols = append([]string{col1}, cols...)
mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
if err == ErrNoRows {
// Create
id, err := o.Insert(md)
return (err == nil), id, err
}
return false, ind.Field(mi.fields.pk.fieldIndex).Int(), err
id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex)
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
id = int64(vid.Uint())
} else {
id = vid.Int()
}
return false, id, err
}
// insert model data to database
@ -159,10 +176,10 @@ func (o *orm) Insert(md interface{}) (int64, error) {
// set auto pk field
func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) {
if mi.fields.pk.auto {
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
ind.Field(mi.fields.pk.fieldIndex).SetUint(uint64(id))
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
} else {
ind.Field(mi.fields.pk.fieldIndex).SetInt(id)
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
}
}
}
@ -184,7 +201,7 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) {
if bulk <= 1 {
for i := 0; i < sind.Len(); i++ {
ind := sind.Index(i)
ind := reflect.Indirect(sind.Index(i))
mi, _ := o.getMiInd(ind.Interface(), false)
id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ)
if err != nil {
@ -202,6 +219,19 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) {
return cnt, nil
}
// InsertOrUpdate data to database
func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...)
if err != nil {
return id, err
}
o.setPk(mi, ind, id)
return id, nil
}
// update model to database.
// cols set the columns those want to update.
func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
@ -214,9 +244,10 @@ func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
}
// delete model in database
func (o *orm) Delete(md interface{}) (int64, error) {
// cols shows the delete conditions values read from. deafult is pk
func (o *orm) Delete(md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ)
num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols)
if err != nil {
return num, err
}
@ -290,7 +321,7 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int
qs.orders = []string{order}
}
find := ind.Field(fi.fieldIndex)
find := ind.FieldByIndex(fi.fieldIndex)
var nums int64
var err error
@ -407,7 +438,7 @@ func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
}
} else {
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
if mi, ok := modelCache.getByFN(name); ok {
if mi, ok := modelCache.getByFullName(name); ok {
qs = newQuerySet(o, mi)
}
}

View File

@ -75,6 +75,19 @@ func (c *Condition) AndCond(cond *Condition) *Condition {
return c
}
// AndNotCond combine a AND NOT condition to current condition
func (c *Condition) AndNotCond(cond *Condition) *Condition {
c = c.clone()
if c == cond {
panic(fmt.Errorf("<Condition.AndNotCond> cannot use self as sub cond"))
}
if cond != nil {
c.params = append(c.params, condValue{cond: cond, isCond: true, isNot: true})
}
return c
}
// Or add OR expression to condition
func (c Condition) Or(expr string, args ...interface{}) *Condition {
if expr == "" || len(args) == 0 {
@ -105,6 +118,19 @@ func (c *Condition) OrCond(cond *Condition) *Condition {
return c
}
// OrNotCond combine a OR NOT condition to current condition
func (c *Condition) OrNotCond(cond *Condition) *Condition {
c = c.clone()
if c == cond {
panic(fmt.Errorf("<Condition.OrNotCond> cannot use self as sub cond"))
}
if cond != nil {
c.params = append(c.params, condValue{cond: cond, isCond: true, isNot: true, isOr: true})
}
return c
}
// IsEmpty check the condition arguments are empty or not.
func (c *Condition) IsEmpty() bool {
return len(c.params) == 0

View File

@ -31,7 +31,7 @@ type Log struct {
// NewLog set io.Writer to create a Logger.
func NewLog(out io.Writer) *Log {
d := new(Log)
d.Logger = log.New(out, "[ORM]", 1e9)
d.Logger = log.New(out, "[ORM]", log.LstdFlags)
return d
}
@ -42,7 +42,7 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error
if err != nil {
flag = "FAIL"
}
con := fmt.Sprintf(" - %s - [Queries/%s] - [%s / %11s / %7.1fms] - [%s]", t.Format(formatDateTime), alias.Name, flag, operaton, elsp, query)
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
cons := make([]string, 0, len(args))
for _, arg := range args {
cons = append(cons, fmt.Sprintf("%v", arg))

View File

@ -50,10 +50,10 @@ func (o *insertSet) Insert(md interface{}) (int64, error) {
}
if id > 0 {
if o.mi.fields.pk.auto {
if o.mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
ind.Field(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
if o.mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id))
} else {
ind.Field(o.mi.fields.pk.fieldIndex).SetInt(id)
ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id)
}
}
}

View File

@ -192,16 +192,18 @@ func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
// query one row data and map to containers.
// cols means the columns when querying.
func (o *querySet) One(container interface{}, cols ...string) error {
o.limit = 1
num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
if err != nil {
return err
}
if num > 1 {
return ErrMultiRows
}
if num == 0 {
return ErrNoRows
}
if num > 1 {
return ErrMultiRows
}
return nil
}

View File

@ -286,7 +286,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
structMode = true
fn := getFullName(typ)
if mi, ok := modelCache.getByFN(fn); ok {
if mi, ok := modelCache.getByFullName(fn); ok {
sMi = mi
}
} else {
@ -342,19 +342,22 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
for _, col := range columns {
if fi := sMi.fields.GetByColumn(col); fi != nil {
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value)
field := ind.FieldByIndex(fi.fieldIndex)
if fi.fieldType&IsRelField > 0 {
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
field.Set(mf)
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
}
o.setFieldValue(field, value)
}
}
} else {
for i := 0; i < ind.NumField(); i++ {
f := ind.Field(i)
fe := ind.Type().Field(i)
var attrs map[string]bool
var tags map[string]string
parseStructTag(fe.Tag.Get("orm"), &attrs, &tags)
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
var col string
if col = tags["column"]; len(col) == 0 {
if col = tags["column"]; col == "" {
col = snakeString(fe.Name)
}
if v, ok := columnsMp[col]; ok {
@ -416,7 +419,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
structMode = true
fn := getFullName(typ)
if mi, ok := modelCache.getByFN(fn); ok {
if mi, ok := modelCache.getByFullName(fn); ok {
sMi = mi
}
} else {
@ -480,19 +483,22 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
for _, col := range columns {
if fi := sMi.fields.GetByColumn(col); fi != nil {
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value)
field := ind.FieldByIndex(fi.fieldIndex)
if fi.fieldType&IsRelField > 0 {
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
field.Set(mf)
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
}
o.setFieldValue(field, value)
}
}
} else {
for i := 0; i < ind.NumField(); i++ {
f := ind.Field(i)
fe := ind.Type().Field(i)
var attrs map[string]bool
var tags map[string]string
parseStructTag(fe.Tag.Get("orm"), &attrs, &tags)
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
var col string
if col = tags["column"]; len(col) == 0 {
if col = tags["column"]; col == "" {
col = snakeString(fe.Name)
}
if v, ok := columnsMp[col]; ok {

View File

@ -19,6 +19,7 @@ import (
"database/sql"
"fmt"
"io/ioutil"
"math"
"os"
"path/filepath"
"reflect"
@ -33,6 +34,7 @@ var _ = os.PathSeparator
var (
testDate = formatDate + " -0700"
testDateTime = formatDateTime + " -0700"
testTime = formatTime + " -0700"
)
type argAny []interface{}
@ -187,6 +189,10 @@ func TestSyncDb(t *testing.T) {
RegisterModel(new(Group))
RegisterModel(new(Permission))
RegisterModel(new(GroupPermissions))
RegisterModel(new(InLine))
RegisterModel(new(InLineOneToOne))
RegisterModel(new(IntegerPk))
RegisterModel(new(UintPk))
err := RunSyncdb("default", true, Debug)
throwFail(t, err)
@ -206,6 +212,10 @@ func TestRegisterModels(t *testing.T) {
RegisterModel(new(Group))
RegisterModel(new(Permission))
RegisterModel(new(GroupPermissions))
RegisterModel(new(InLine))
RegisterModel(new(InLineOneToOne))
RegisterModel(new(IntegerPk))
RegisterModel(new(UintPk))
BootStrap()
@ -217,7 +227,7 @@ func TestModelSyntax(t *testing.T) {
user := &User{}
ind := reflect.ValueOf(user).Elem()
fn := getFullName(ind.Type())
mi, ok := modelCache.getByFN(fn)
mi, ok := modelCache.getByFullName(fn)
throwFail(t, AssertIs(ok, true))
mi, ok = modelCache.get("user")
@ -231,6 +241,9 @@ var DataValues = map[string]interface{}{
"Boolean": true,
"Char": "char",
"Text": "text",
"JSON": `{"name":"json"}`,
"Jsonb": `{"name": "jsonb"}`,
"Time": time.Now(),
"Date": time.Now(),
"DateTime": time.Now(),
"Byte": byte(1<<8 - 1),
@ -255,10 +268,12 @@ func TestDataTypes(t *testing.T) {
ind := reflect.Indirect(reflect.ValueOf(&d))
for name, value := range DataValues {
if name == "JSON" {
continue
}
e := ind.FieldByName(name)
e.Set(reflect.ValueOf(value))
}
id, err := dORM.Insert(&d)
throwFail(t, err)
throwFail(t, AssertIs(id, 1))
@ -279,6 +294,9 @@ func TestDataTypes(t *testing.T) {
case "DateTime":
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDateTime)
value = value.(time.Time).In(DefaultTimeLoc).Format(testDateTime)
case "Time":
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime)
value = value.(time.Time).In(DefaultTimeLoc).Format(testTime)
}
throwFail(t, AssertIs(vu == value, true), value, vu)
}
@ -297,10 +315,18 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, err)
throwFail(t, AssertIs(id, 1))
data := `{"ok":1,"data":{"arr":[1,2],"msg":"gopher"}}`
d = DataNull{ID: 1, JSON: data}
num, err := dORM.Update(&d)
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
d = DataNull{ID: 1}
err = dORM.Read(&d)
throwFail(t, err)
throwFail(t, AssertIs(d.JSON, data))
throwFail(t, AssertIs(d.NullBool.Valid, false))
throwFail(t, AssertIs(d.NullString.Valid, false))
throwFail(t, AssertIs(d.NullInt64.Valid, false))
@ -324,6 +350,9 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, AssertIs(d.Float32Ptr, nil))
throwFail(t, AssertIs(d.Float64Ptr, nil))
throwFail(t, AssertIs(d.DecimalPtr, nil))
throwFail(t, AssertIs(d.TimePtr, nil))
throwFail(t, AssertIs(d.DatePtr, nil))
throwFail(t, AssertIs(d.DateTimePtr, nil))
_, err = dORM.Raw(`INSERT INTO data_null (boolean) VALUES (?)`, nil).Exec()
throwFail(t, err)
@ -350,6 +379,9 @@ func TestNullDataTypes(t *testing.T) {
float32Ptr := float32(42.0)
float64Ptr := float64(42.0)
decimalPtr := float64(42.0)
timePtr := time.Now()
datePtr := time.Now()
dateTimePtr := time.Now()
d = DataNull{
DateTime: time.Now(),
@ -375,6 +407,9 @@ func TestNullDataTypes(t *testing.T) {
Float32Ptr: &float32Ptr,
Float64Ptr: &float64Ptr,
DecimalPtr: &decimalPtr,
TimePtr: &timePtr,
DatePtr: &datePtr,
DateTimePtr: &dateTimePtr,
}
id, err = dORM.Insert(&d)
@ -415,6 +450,9 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
throwFail(t, AssertIs((*d.TimePtr).Format(testTime), timePtr.Format(testTime)))
throwFail(t, AssertIs((*d.DatePtr).Format(testDate), datePtr.Format(testDate)))
throwFail(t, AssertIs((*d.DateTimePtr).Format(testDateTime), dateTimePtr.Format(testDateTime)))
}
func TestDataCustomTypes(t *testing.T) {
@ -539,6 +577,10 @@ func TestCRUD(t *testing.T) {
err = dORM.Read(&ub)
throwFail(t, err)
throwFail(t, AssertIs(ub.Name, "name"))
num, err = dORM.Delete(&ub, "name")
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
}
func TestInsertTestData(t *testing.T) {
@ -867,6 +909,16 @@ func TestSetCond(t *testing.T) {
num, err = qs.SetCond(cond2).Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 2))
cond3 := cond.AndNotCond(cond.And("status__in", 1))
num, err = qs.SetCond(cond3).Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 2))
cond4 := cond.And("user_name", "slene").OrNotCond(cond.And("user_name", "slene"))
num, err = qs.SetCond(cond4).Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 3))
}
func TestLimit(t *testing.T) {
@ -967,12 +1019,19 @@ func TestOne(t *testing.T) {
var user User
qs := dORM.QueryTable("user")
err := qs.One(&user)
throwFail(t, AssertIs(err, ErrMultiRows))
throwFail(t, err)
user = User{}
err = qs.OrderBy("Id").Limit(1).One(&user)
throwFailNow(t, err)
throwFail(t, AssertIs(user.UserName, "slene"))
throwFail(t, AssertNot(err, ErrMultiRows))
user = User{}
err = qs.OrderBy("-Id").Limit(100).One(&user)
throwFailNow(t, err)
throwFail(t, AssertIs(user.UserName, "nobody"))
throwFail(t, AssertNot(err, ErrMultiRows))
err = qs.Filter("user_name", "nothing").One(&user)
throwFail(t, AssertIs(err, ErrNoRows))
@ -1512,6 +1571,7 @@ func TestRawQueryRow(t *testing.T) {
Boolean bool
Char string
Text string
Time time.Time
Date time.Time
DateTime time.Time
Byte byte
@ -1540,14 +1600,14 @@ func TestRawQueryRow(t *testing.T) {
Q := dDbBaser.TableQuote()
cols := []string{
"id", "boolean", "char", "text", "date", "datetime", "byte", "rune", "int", "int8", "int16", "int32",
"id", "boolean", "char", "text", "time", "date", "datetime", "byte", "rune", "int", "int8", "int16", "int32",
"int64", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "decimal",
}
sep := fmt.Sprintf("%s, %s", Q, Q)
query := fmt.Sprintf("SELECT %s%s%s FROM data WHERE id = ?", Q, strings.Join(cols, sep), Q)
var id int
values := []interface{}{
&id, &Boolean, &Char, &Text, &Date, &DateTime, &Byte, &Rune, &Int, &Int8, &Int16, &Int32,
&id, &Boolean, &Char, &Text, &Time, &Date, &DateTime, &Byte, &Rune, &Int, &Int8, &Int16, &Int32,
&Int64, &Uint, &Uint8, &Uint16, &Uint32, &Uint64, &Float32, &Float64, &Decimal,
}
err := dORM.Raw(query, 1).QueryRow(values...)
@ -1558,6 +1618,10 @@ func TestRawQueryRow(t *testing.T) {
switch col {
case "id":
throwFail(t, AssertIs(id, 1))
case "time":
v = v.(time.Time).In(DefaultTimeLoc)
value := dataValues[col].(time.Time).In(DefaultTimeLoc)
throwFail(t, AssertIs(v, value, testTime))
case "date":
v = v.(time.Time).In(DefaultTimeLoc)
value := dataValues[col].(time.Time).In(DefaultTimeLoc)
@ -1605,6 +1669,9 @@ func TestQueryRows(t *testing.T) {
e := ind.FieldByName(name)
vu := e.Interface()
switch name {
case "Time":
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime)
value = value.(time.Time).In(DefaultTimeLoc).Format(testTime)
case "Date":
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate)
value = value.(time.Time).In(DefaultTimeLoc).Format(testDate)
@ -1629,6 +1696,9 @@ func TestQueryRows(t *testing.T) {
e := ind.FieldByName(name)
vu := e.Interface()
switch name {
case "Time":
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testTime)
value = value.(time.Time).In(DefaultTimeLoc).Format(testTime)
case "Date":
vu = vu.(time.Time).In(DefaultTimeLoc).Format(testDate)
value = value.(time.Time).In(DefaultTimeLoc).Format(testDate)
@ -1928,3 +1998,279 @@ func TestReadOrCreate(t *testing.T) {
dORM.Delete(u)
}
func TestInLine(t *testing.T) {
name := "inline"
email := "hello@go.com"
inline := NewInLine()
inline.Name = name
inline.Email = email
id, err := dORM.Insert(inline)
throwFail(t, err)
throwFail(t, AssertIs(id, 1))
il := NewInLine()
il.ID = 1
err = dORM.Read(il)
throwFail(t, err)
throwFail(t, AssertIs(il.Name, name))
throwFail(t, AssertIs(il.Email, email))
throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate))
throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime))
}
func TestInLineOneToOne(t *testing.T) {
name := "121"
email := "121@go.com"
inline := NewInLine()
inline.Name = name
inline.Email = email
id, err := dORM.Insert(inline)
throwFail(t, err)
throwFail(t, AssertIs(id, 2))
note := "one2one"
il121 := NewInLineOneToOne()
il121.Note = note
il121.InLine = inline
_, err = dORM.Insert(il121)
throwFail(t, err)
throwFail(t, AssertIs(il121.ID, 1))
il := NewInLineOneToOne()
err = dORM.QueryTable(il).Filter("Id", 1).RelatedSel().One(il)
throwFail(t, err)
throwFail(t, AssertIs(il.Note, note))
throwFail(t, AssertIs(il.InLine.ID, id))
throwFail(t, AssertIs(il.InLine.Name, name))
throwFail(t, AssertIs(il.InLine.Email, email))
rinline := NewInLine()
err = dORM.QueryTable(rinline).Filter("InLineOneToOne__Id", 1).One(rinline)
throwFail(t, err)
throwFail(t, AssertIs(rinline.ID, id))
throwFail(t, AssertIs(rinline.Name, name))
throwFail(t, AssertIs(rinline.Email, email))
}
func TestIntegerPk(t *testing.T) {
its := []IntegerPk{
{ID: math.MinInt64, Value: "-"},
{ID: 0, Value: "0"},
{ID: math.MaxInt64, Value: "+"},
}
num, err := dORM.InsertMulti(len(its), its)
throwFail(t, err)
throwFail(t, AssertIs(num, len(its)))
for _, intPk := range its {
out := IntegerPk{ID: intPk.ID}
err = dORM.Read(&out)
throwFail(t, err)
throwFail(t, AssertIs(out.Value, intPk.Value))
}
num, err = dORM.InsertMulti(1, []*IntegerPk{{
ID: 1, Value: "ok",
}})
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
}
func TestInsertAuto(t *testing.T) {
u := &User{
UserName: "autoPre",
Email: "autoPre@gmail.com",
}
id, err := dORM.Insert(u)
throwFail(t, err)
id += 100
su := &User{
ID: int(id),
UserName: "auto",
Email: "auto@gmail.com",
}
nid, err := dORM.Insert(su)
throwFail(t, err)
throwFail(t, AssertIs(nid, id))
users := []User{
{ID: int(id + 100), UserName: "auto_100"},
{ID: int(id + 110), UserName: "auto_110"},
{ID: int(id + 120), UserName: "auto_120"},
}
num, err := dORM.InsertMulti(100, users)
throwFail(t, err)
throwFail(t, AssertIs(num, 3))
u = &User{
UserName: "auto_121",
}
nid, err = dORM.Insert(u)
throwFail(t, err)
throwFail(t, AssertIs(nid, id+120+1))
}
func TestUintPk(t *testing.T) {
name := "go"
u := &UintPk{
ID: 8,
Name: name,
}
created, pk, err := dORM.ReadOrCreate(u, "ID")
throwFail(t, err)
throwFail(t, AssertIs(created, true))
throwFail(t, AssertIs(u.Name, name))
nu := &UintPk{ID: 8}
created, pk, err = dORM.ReadOrCreate(nu, "ID")
throwFail(t, err)
throwFail(t, AssertIs(created, false))
throwFail(t, AssertIs(nu.ID, u.ID))
throwFail(t, AssertIs(pk, u.ID))
throwFail(t, AssertIs(nu.Name, name))
dORM.Delete(u)
}
func TestSnake(t *testing.T) {
cases := map[string]string{
"i": "i",
"I": "i",
"iD": "i_d",
"ID": "i_d",
"NO": "n_o",
"NOO": "n_o_o",
"NOOooOOoo": "n_o_ooo_o_ooo",
"OrderNO": "order_n_o",
"tagName": "tag_name",
"tag_Name": "tag__name",
"tag_name": "tag_name",
"_tag_name": "_tag_name",
"tag_666name": "tag_666name",
"tag_666Name": "tag_666_name",
}
for name, want := range cases {
got := snakeString(name)
throwFail(t, AssertIs(got, want))
}
}
func TestIgnoreCaseTag(t *testing.T) {
type testTagModel struct {
ID int `orm:"pk"`
NOO string `orm:"column(n)"`
Name01 string `orm:"NULL"`
Name02 string `orm:"COLUMN(Name)"`
Name03 string `orm:"Column(name)"`
}
modelCache.clean()
RegisterModel(&testTagModel{})
info, ok := modelCache.get("test_tag_model")
throwFail(t, AssertIs(ok, true))
throwFail(t, AssertNot(info, nil))
if t == nil {
return
}
throwFail(t, AssertIs(info.fields.GetByName("NOO").column, "n"))
throwFail(t, AssertIs(info.fields.GetByName("Name01").null, true))
throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name"))
throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name"))
}
func TestInsertOrUpdate(t *testing.T) {
RegisterModel(new(User))
user := User{UserName: "unique_username133", Status: 1, Password: "o"}
user1 := User{UserName: "unique_username133", Status: 2, Password: "o"}
user2 := User{UserName: "unique_username133", Status: 3, Password: "oo"}
dORM.Insert(&user)
test := User{UserName: "unique_username133"}
fmt.Println(dORM.Driver().Name())
if dORM.Driver().Name() == "sqlite3" {
fmt.Println("sqlite3 is nonsupport")
return
}
//test1
_, err := dORM.InsertOrUpdate(&user1, "user_name")
if err != nil {
fmt.Println(err)
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
} else {
throwFailNow(t, err)
}
} else {
dORM.Read(&test, "user_name")
throwFailNow(t, AssertIs(user1.Status, test.Status))
}
//test2
_, err = dORM.InsertOrUpdate(&user2, "user_name")
if err != nil {
fmt.Println(err)
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
} else {
throwFailNow(t, err)
}
} else {
dORM.Read(&test, "user_name")
throwFailNow(t, AssertIs(user2.Status, test.Status))
throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password)))
}
//test3 +
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1")
if err != nil {
fmt.Println(err)
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
} else {
throwFailNow(t, err)
}
} else {
dORM.Read(&test, "user_name")
throwFailNow(t, AssertIs(user2.Status+1, test.Status))
}
//test4 -
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status-1")
if err != nil {
fmt.Println(err)
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
} else {
throwFailNow(t, err)
}
} else {
dORM.Read(&test, "user_name")
throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status))
}
//test5 *
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status*3")
if err != nil {
fmt.Println(err)
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
} else {
throwFailNow(t, err)
}
} else {
dORM.Read(&test, "user_name")
throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status))
}
//test6 /
_, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3")
if err != nil {
fmt.Println(err)
if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" {
} else {
throwFailNow(t, err)
}
} else {
dORM.Read(&test, "user_name")
throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status))
}
}

View File

@ -19,6 +19,7 @@ import "errors"
// QueryBuilder is the Query builder interface
type QueryBuilder interface {
Select(fields ...string) QueryBuilder
ForUpdate() QueryBuilder
From(tables ...string) QueryBuilder
InnerJoin(table string) QueryBuilder
LeftJoin(table string) QueryBuilder

View File

@ -34,6 +34,12 @@ func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder {
return qb
}
// ForUpdate add the FOR UPDATE clause
func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder {
qb.Tokens = append(qb.Tokens, "FOR UPDATE")
return qb
}
// From join the tables
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace))

View File

@ -31,6 +31,12 @@ func (qb *TiDBQueryBuilder) Select(fields ...string) QueryBuilder {
return qb
}
// ForUpdate add the FOR UPDATE clause
func (qb *TiDBQueryBuilder) ForUpdate() QueryBuilder {
qb.Tokens = append(qb.Tokens, "FOR UPDATE")
return qb
}
// From join the tables
func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace))

View File

@ -45,6 +45,9 @@ type Ormer interface {
// u = &User{UserName: "astaxie", Password: "pass"}
// err = Ormer.Read(u, "UserName")
Read(md interface{}, cols ...string) error
// Like Read(), but with "FOR UPDATE" clause, useful in transaction.
// Some databases are not support this feature.
ReadForUpdate(md interface{}, cols ...string) error
// Try to read a row from the database, or insert one if it doesn't exist
ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
// insert model data to database
@ -53,6 +56,11 @@ type Ormer interface {
// id, err = Ormer.Insert(user)
// user must a pointer and Insert will set user's pk field
Insert(interface{}) (int64, error)
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
// postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value")
// if colu type is integer : can use(+-*/), string : colu || "value"
InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)
// insert some models to database
InsertMulti(bulk int, mds interface{}) (int64, error)
// update model to database.
@ -66,7 +74,7 @@ type Ormer interface {
// num, err = Ormer.Update(&user, "Langs", "Extra")
Update(md interface{}, cols ...string) (int64, error)
// delete model in database
Delete(md interface{}) (int64, error)
Delete(md interface{}, cols ...string) (int64, error)
// load related models to md model.
// args are limit, offset int and order string.
//
@ -148,6 +156,10 @@ type QuerySeter interface {
// add OFFSET value
// same as Limit function's args[0]
Offset(offset interface{}) QuerySeter
// add GROUP BY expression
// for example:
// qs.GroupBy("id")
GroupBy(exprs ...string) QuerySeter
// add ORDER expression.
// "column" means ASC, "-column" means DESC.
// for example:
@ -162,6 +174,12 @@ type QuerySeter interface {
// qs.RelatedSel("profile").One(&user)
// user.Profile.Age = 32
RelatedSel(params ...interface{}) QuerySeter
// Set Distinct
// for example:
// o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
// Distinct().
// All(&permissions)
Distinct() QuerySeter
// return QuerySeter execution result number
// for example:
// num, err = qs.Filter("profile__age__gt", 28).Count()
@ -379,13 +397,14 @@ type txEnder interface {
// base database struct
type dbBaser interface {
Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) error
Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error
Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error)
ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
SupportUpdateJoin() bool
UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
@ -410,4 +429,5 @@ type dbBaser interface {
ShowColumnsQuery(string) string
IndexExists(dbQuerier, string, string) bool
collectFieldValue(*modelInfo, *fieldInfo, reflect.Value, bool, *time.Location) (interface{}, error)
setval(dbQuerier, *modelInfo, []string) error
}

View File

@ -16,6 +16,7 @@ package orm
import (
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
@ -87,6 +88,14 @@ func (f StrTo) Int32() (int32, error) {
// Int64 string to int64
func (f StrTo) Int64() (int64, error) {
v, err := strconv.ParseInt(f.String(), 10, 64)
if err != nil {
i := new(big.Int)
ni, ok := i.SetString(f.String(), 10) // octal
if !ok {
return int64(v), err
}
return ni.Int64(), nil
}
return int64(v), err
}
@ -117,6 +126,14 @@ func (f StrTo) Uint32() (uint32, error) {
// Uint64 string to uint64
func (f StrTo) Uint64() (uint64, error) {
v, err := strconv.ParseUint(f.String(), 10, 64)
if err != nil {
i := new(big.Int)
ni, ok := i.SetString(f.String(), 10)
if !ok {
return uint64(v), err
}
return ni.Uint64(), nil
}
return uint64(v), err
}
@ -181,7 +198,7 @@ func ToInt64(value interface{}) (d int64) {
return
}
// snake string, XxYy to xx_yy
// snake string, XxYy to xx_yy , XxYY to xx_yy
func snakeString(s string) string {
data := make([]byte, 0, len(s)*2)
j := false

View File

@ -23,10 +23,11 @@ import (
"go/token"
"io/ioutil"
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils"
)
@ -48,17 +49,18 @@ var (
genInfoList map[string][]ControllerComments
)
const coomentPrefix = "commentsRouter_"
const commentPrefix = "commentsRouter_"
func init() {
pkgLastupdate = make(map[string]int64)
}
func parserPkg(pkgRealpath, pkgpath string) error {
rep := strings.NewReplacer("/", "_", ".", "_")
commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go"
rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go"
if !compareFile(pkgRealpath) {
Info(pkgRealpath + " has not changed, not reloading")
logs.Info(pkgRealpath + " no changed")
return nil
}
genInfoList = make(map[string][]ControllerComments)
@ -86,7 +88,7 @@ func parserPkg(pkgRealpath, pkgpath string) error {
}
}
}
genRouterCode()
genRouterCode(pkgRealpath)
savetoFile(pkgRealpath)
return nil
}
@ -99,7 +101,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
elements := strings.TrimLeft(t, "@router ")
e1 := strings.SplitN(elements, " ", 2)
if len(e1) < 1 {
return errors.New("you should has router infomation")
return errors.New("you should has router information")
}
key := pkgpath + ":" + controllerName
cc := ControllerComments{}
@ -129,9 +131,9 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
return nil
}
func genRouterCode() {
os.Mkdir("routers", 0755)
Info("generate router from comments")
func genRouterCode(pkgRealpath string) {
os.Mkdir(getRouterDir(pkgRealpath), 0755)
logs.Info("generate router from comments")
var (
globalinfo string
sortKey []string
@ -164,15 +166,15 @@ func genRouterCode() {
globalinfo = globalinfo + `
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
beego.ControllerComments{
"` + strings.TrimSpace(c.Method) + `",
` + "`" + c.Router + "`" + `,
` + allmethod + `,
` + params + `})
Method: "` + strings.TrimSpace(c.Method) + `",
` + "Router: `" + c.Router + "`" + `,
AllowHTTPMethods: ` + allmethod + `,
Params: ` + params + `})
`
}
}
if globalinfo != "" {
f, err := os.Create(path.Join("routers", commentFilename))
f, err := os.Create(filepath.Join(getRouterDir(pkgRealpath), commentFilename))
if err != nil {
panic(err)
}
@ -182,7 +184,7 @@ func genRouterCode() {
}
func compareFile(pkgRealpath string) bool {
if !utils.FileExists(path.Join("routers", commentFilename)) {
if !utils.FileExists(filepath.Join(getRouterDir(pkgRealpath), commentFilename)) {
return true
}
if utils.FileExists(lastupdateFilename) {
@ -229,3 +231,19 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) {
}
return lastupdate, nil
}
func getRouterDir(pkgRealpath string) string {
dir := filepath.Dir(pkgRealpath)
for {
d := filepath.Join(dir, "routers")
if utils.FileExists(d) {
return d
}
if r, _ := filepath.Rel(dir, AppPath); r == "." {
return d
}
// Parent dir.
dir = filepath.Dir(dir)
}
}

View File

@ -35,7 +35,7 @@
//
// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360))
//
// Infomation:
// Information:
//
// In the request user should include these params in the query
//
@ -119,7 +119,7 @@ func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
return
}
if ctx.Input.Query("signature") !=
Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URI()) {
Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URL()) {
ctx.ResponseWriter.WriteHeader(403)
ctx.WriteString("auth failed")
}
@ -127,7 +127,7 @@ func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
}
// Signature used to generate signature with the appsecret/method/params/RequestURI
func Signature(appsecret, method string, params url.Values, RequestURI string) (result string) {
func Signature(appsecret, method string, params url.Values, RequestURL string) (result string) {
var query string
pa := make(map[string]string)
for k, v := range params {
@ -143,7 +143,7 @@ func Signature(appsecret, method string, params url.Values, RequestURI string) (
query = fmt.Sprintf("%v%v%v", query, vs.Keys[i], vs.Vals[i])
}
}
stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, query, RequestURI)
stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, query, RequestURL)
sha256 := sha256.New
hash := hmac.New(sha256, []byte(appsecret))

View File

@ -36,6 +36,7 @@
package cors
import (
"net/http"
"regexp"
"strconv"
"strings"
@ -215,6 +216,7 @@ func Allow(opts *Options) beego.FilterFunc {
for key, value := range headers {
ctx.Output.Header(key, value)
}
ctx.ResponseWriter.WriteHeader(http.StatusOK)
return
}
headers = opts.Header(origin)

97
policy.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2016 beego authors. 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 (
"strings"
"github.com/astaxie/beego/context"
)
// PolicyFunc defines a policy function which is invoked before the controller handler is executed.
type PolicyFunc func(*context.Context)
// FindRouter Find Router info for URL
func (p *ControllerRegister) FindPolicy(cont *context.Context) []PolicyFunc {
var urlPath = cont.Input.URL()
if !BConfig.RouterCaseSensitive {
urlPath = strings.ToLower(urlPath)
}
httpMethod := cont.Input.Method()
isWildcard := false
// Find policy for current method
t, ok := p.policies[httpMethod]
// If not found - find policy for whole controller
if !ok {
t, ok = p.policies["*"]
isWildcard = true
}
if ok {
runObjects := t.Match(urlPath, cont)
if r, ok := runObjects.([]PolicyFunc); ok {
return r
} else if !isWildcard {
// If no policies found and we checked not for "*" method - try to find it
t, ok = p.policies["*"]
if ok {
runObjects = t.Match(urlPath, cont)
if r, ok = runObjects.([]PolicyFunc); ok {
return r
}
}
}
}
return nil
}
func (p *ControllerRegister) addToPolicy(method, pattern string, r ...PolicyFunc) {
method = strings.ToUpper(method)
p.enablePolicy = true
if !BConfig.RouterCaseSensitive {
pattern = strings.ToLower(pattern)
}
if t, ok := p.policies[method]; ok {
t.AddRouter(pattern, r)
} else {
t := NewTree()
t.AddRouter(pattern, r)
p.policies[method] = t
}
}
// Register new policy in beego
func Policy(pattern, method string, policy ...PolicyFunc) {
BeeApp.Handlers.addToPolicy(method, pattern, policy...)
}
// Find policies and execute if were found
func (p *ControllerRegister) execPolicy(cont *context.Context, urlPath string) (started bool) {
if !p.enablePolicy {
return false
}
// Find Policy for method
policyList := p.FindPolicy(cont)
if len(policyList) > 0 {
// Run policies
for _, runPolicy := range policyList {
runPolicy(cont)
if cont.ResponseWriter.Started {
return true
}
}
return false
}
return false
}

457
router.go
View File

@ -28,6 +28,7 @@ import (
"time"
beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/toolbox"
"github.com/astaxie/beego/utils"
)
@ -62,12 +63,12 @@ var (
}
// these beego.Controller's methods shouldn't reflect to AutoRouter
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJson", "ServeJsonp",
"ServeXml", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
"ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
"GetControllerAndAction"}
"GetControllerAndAction", "ServeFormatted"}
urlPlaceholder = "{{placeholder}}"
// DefaultAccessLogFilter will skip the accesslog if return true
@ -113,16 +114,18 @@ type controllerInfo struct {
// ControllerRegister containers registered router rules, controller handlers and filters.
type ControllerRegister struct {
routers map[string]*Tree
enablePolicy bool
policies map[string]*Tree
enableFilter bool
filters map[int][]*FilterRouter
filters [FinishRouter + 1][]*FilterRouter
pool sync.Pool
}
// NewControllerRegister returns a new ControllerRegister.
func NewControllerRegister() *ControllerRegister {
cr := &ControllerRegister{
routers: make(map[string]*Tree),
filters: make(map[int][]*FilterRouter),
routers: make(map[string]*Tree),
policies: make(map[string]*Tree),
}
cr.pool.New = func() interface{} {
return beecontext.NewContext()
@ -321,7 +324,8 @@ func (p *ControllerRegister) Any(pattern string, f FilterFunc) {
// ctx.Output.Body("hello world")
// })
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
if _, ok := HTTPMETHOD[strings.ToUpper(method)]; method != "*" && !ok {
method = strings.ToUpper(method)
if _, ok := HTTPMETHOD[method]; method != "*" && !ok {
panic("not support http method: " + method)
}
route := &controllerInfo{}
@ -334,7 +338,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
methods[val] = val
}
} else {
methods[strings.ToUpper(method)] = strings.ToUpper(method)
methods[method] = method
}
route.methods = methods
for k := range methods {
@ -405,29 +409,39 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface)
}
// InsertFilter Add a FilterFunc with pattern rule and action constant.
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
// params is for:
// 1. setting the returnOnOutput value (false allows multiple filters to execute)
// 2. determining whether or not params need to be reset.
func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error {
mr := new(FilterRouter)
mr.tree = NewTree()
mr.pattern = pattern
mr.filterFunc = filter
if !BConfig.RouterCaseSensitive {
pattern = strings.ToLower(pattern)
mr := &FilterRouter{
tree: NewTree(),
pattern: pattern,
filterFunc: filter,
returnOnOutput: true,
}
if len(params) == 0 {
mr.returnOnOutput = true
} else {
if !BConfig.RouterCaseSensitive {
mr.pattern = strings.ToLower(pattern)
}
paramsLen := len(params)
if paramsLen > 0 {
mr.returnOnOutput = params[0]
}
if paramsLen > 1 {
mr.resetParams = params[1]
}
mr.tree.AddRouter(pattern, true)
return p.insertFilterRouter(pos, mr)
}
// add Filter into
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) error {
p.filters[pos] = append(p.filters[pos], mr)
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) {
if pos < BeforeStatic || pos > FinishRouter {
err = fmt.Errorf("can not find your filter position")
return
}
p.enableFilter = true
p.filters[pos] = append(p.filters[pos], mr)
return nil
}
@ -436,11 +450,11 @@ func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) error
func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) string {
paths := strings.Split(endpoint, ".")
if len(paths) <= 1 {
Warn("urlfor endpoint must like path.controller.method")
logs.Warn("urlfor endpoint must like path.controller.method")
return ""
}
if len(values)%2 != 0 {
Warn("urlfor params must key-value pair")
logs.Warn("urlfor params must key-value pair")
return ""
}
params := make(map[string]string)
@ -576,21 +590,27 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
return false, ""
}
func (p *ControllerRegister) execFilter(context *beecontext.Context, pos int, urlPath string) (started bool) {
if p.enableFilter {
if l, ok := p.filters[pos]; ok {
for _, filterR := range l {
if filterR.returnOnOutput && context.ResponseWriter.Started {
return true
}
if ok := filterR.ValidRouter(urlPath, context); ok {
filterR.filterFunc(context)
}
if filterR.returnOnOutput && context.ResponseWriter.Started {
return true
func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath string, pos int) (started bool) {
var preFilterParams map[string]string
for _, filterR := range p.filters[pos] {
if filterR.returnOnOutput && context.ResponseWriter.Started {
return true
}
if filterR.resetParams {
preFilterParams = context.Input.Params()
}
if ok := filterR.ValidRouter(urlPath, context); ok {
filterR.filterFunc(context)
if filterR.resetParams {
context.Input.ResetParams()
for k, v := range preFilterParams {
context.Input.SetParam(k, v)
}
}
}
if filterR.returnOnOutput && context.ResponseWriter.Started {
return true
}
}
return false
}
@ -603,11 +623,15 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
findRouter bool
runMethod string
routerInfo *controllerInfo
isRunnable bool
)
context := p.pool.Get().(*beecontext.Context)
context.Reset(rw, r)
defer p.pool.Put(context)
defer p.recoverPanic(context)
if BConfig.RecoverFunc != nil {
defer BConfig.RecoverFunc(context)
}
context.Output.EnableGzip = BConfig.EnableGzip
@ -615,11 +639,10 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
context.Output.Header("Server", BConfig.ServerName)
}
var urlPath string
var urlPath = r.URL.Path
if !BConfig.RouterCaseSensitive {
urlPath = strings.ToLower(r.URL.Path)
} else {
urlPath = r.URL.Path
urlPath = strings.ToLower(urlPath)
}
// filter wrong http method
@ -629,30 +652,17 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
// filter for static file
if p.execFilter(context, BeforeStatic, urlPath) {
if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) {
goto Admin
}
serverStaticRouter(context)
if context.ResponseWriter.Started {
findRouter = true
goto Admin
}
// session init
if BConfig.WebConfig.Session.SessionOn {
var err error
context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r)
if err != nil {
Error(err)
exception("503", context)
return
}
defer func() {
context.Input.CruSession.SessionRelease(rw)
}()
}
if r.Method != "GET" && r.Method != "HEAD" {
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
context.Input.CopyBody(BConfig.MaxMemory)
@ -660,25 +670,32 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
}
if p.execFilter(context, BeforeRouter, urlPath) {
// session init
if BConfig.WebConfig.Session.SessionOn {
var err error
context.Input.CruSession, err = GlobalSessions.SessionStart(rw, r)
if err != nil {
logs.Error(err)
exception("503", context)
goto Admin
}
defer func() {
if context.Input.CruSession != nil {
context.Input.CruSession.SessionRelease(rw)
}
}()
}
if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) {
goto Admin
}
if !findRouter {
httpMethod := r.Method
if t, ok := p.routers[httpMethod]; ok {
runObject := t.Match(urlPath, context)
if r, ok := runObject.(*controllerInfo); ok {
routerInfo = r
findRouter = true
if splat := context.Input.Param(":splat"); splat != "" {
for k, v := range strings.Split(splat, "/") {
context.Input.SetParam(strconv.Itoa(k), v)
}
}
}
}
// 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 {
routerInfo, findRouter = p.FindRouter(context)
}
//if no matches to url, throw a not found exception
@ -686,121 +703,133 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
exception("404", context)
goto Admin
}
if findRouter {
//execute middleware filters
if p.execFilter(context, BeforeExec, urlPath) {
goto Admin
}
isRunnable := false
if routerInfo != nil {
if routerInfo.routerType == routerTypeRESTFul {
if _, ok := routerInfo.methods[r.Method]; ok {
isRunnable = true
routerInfo.runFunction(context)
} else {
exception("405", context)
goto Admin
}
} else if routerInfo.routerType == routerTypeHandler {
isRunnable = true
routerInfo.handler.ServeHTTP(rw, r)
} else {
runRouter = routerInfo.controllerType
method := r.Method
if r.Method == "POST" && context.Input.Query("_method") == "PUT" {
method = "PUT"
}
if r.Method == "POST" && context.Input.Query("_method") == "DELETE" {
method = "DELETE"
}
if m, ok := routerInfo.methods[method]; ok {
runMethod = m
} else if m, ok = routerInfo.methods["*"]; ok {
runMethod = m
} else {
runMethod = method
}
}
}
// also defined runRouter & runMethod from filter
if !isRunnable {
//Invoke the request handler
vc := reflect.New(runRouter)
execController, ok := vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
//call the controller init function
execController.Init(context, runRouter.Name(), runMethod, vc.Interface())
//call prepare function
execController.Prepare()
//if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
if BConfig.WebConfig.EnableXSRF {
execController.XSRFToken()
if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
(r.Method == "POST" && (context.Input.Query("_method") == "DELETE" || context.Input.Query("_method") == "PUT")) {
execController.CheckXSRFCookie()
}
}
execController.URLMapping()
if !context.ResponseWriter.Started {
//exec main logic
switch runMethod {
case "GET":
execController.Get()
case "POST":
execController.Post()
case "DELETE":
execController.Delete()
case "PUT":
execController.Put()
case "HEAD":
execController.Head()
case "PATCH":
execController.Patch()
case "OPTIONS":
execController.Options()
default:
if !execController.HandlerFunc(runMethod) {
var in []reflect.Value
method := vc.MethodByName(runMethod)
method.Call(in)
}
}
//render template
if !context.ResponseWriter.Started && context.Output.Status == 0 {
if BConfig.WebConfig.AutoRender {
if err := execController.Render(); err != nil {
panic(err)
}
}
}
}
// finish all runRouter. release resource
execController.Finish()
}
//execute middleware filters
if p.execFilter(context, AfterExec, urlPath) {
goto Admin
if splat := context.Input.Param(":splat"); splat != "" {
for k, v := range strings.Split(splat, "/") {
context.Input.SetParam(strconv.Itoa(k), v)
}
}
p.execFilter(context, FinishRouter, urlPath)
//execute middleware filters
if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
goto Admin
}
//check policies
if p.execPolicy(context, urlPath) {
goto Admin
}
if routerInfo != nil {
//store router pattern into context
context.Input.SetData("RouterPattern", routerInfo.pattern)
if routerInfo.routerType == routerTypeRESTFul {
if _, ok := routerInfo.methods[r.Method]; ok {
isRunnable = true
routerInfo.runFunction(context)
} else {
exception("405", context)
goto Admin
}
} else if routerInfo.routerType == routerTypeHandler {
isRunnable = true
routerInfo.handler.ServeHTTP(rw, r)
} else {
runRouter = routerInfo.controllerType
method := r.Method
if r.Method == "POST" && context.Input.Query("_method") == "PUT" {
method = "PUT"
}
if r.Method == "POST" && context.Input.Query("_method") == "DELETE" {
method = "DELETE"
}
if m, ok := routerInfo.methods[method]; ok {
runMethod = m
} else if m, ok = routerInfo.methods["*"]; ok {
runMethod = m
} else {
runMethod = method
}
}
}
// also defined runRouter & runMethod from filter
if !isRunnable {
//Invoke the request handler
vc := reflect.New(runRouter)
execController, ok := vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
//call the controller init function
execController.Init(context, runRouter.Name(), runMethod, vc.Interface())
//call prepare function
execController.Prepare()
//if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
if BConfig.WebConfig.EnableXSRF {
execController.XSRFToken()
if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
(r.Method == "POST" && (context.Input.Query("_method") == "DELETE" || context.Input.Query("_method") == "PUT")) {
execController.CheckXSRFCookie()
}
}
execController.URLMapping()
if !context.ResponseWriter.Started {
//exec main logic
switch runMethod {
case "GET":
execController.Get()
case "POST":
execController.Post()
case "DELETE":
execController.Delete()
case "PUT":
execController.Put()
case "HEAD":
execController.Head()
case "PATCH":
execController.Patch()
case "OPTIONS":
execController.Options()
default:
if !execController.HandlerFunc(runMethod) {
var in []reflect.Value
method := vc.MethodByName(runMethod)
method.Call(in)
}
}
//render template
if !context.ResponseWriter.Started && context.Output.Status == 0 {
if BConfig.WebConfig.AutoRender {
if err := execController.Render(); err != nil {
logs.Error(err)
}
}
}
}
// finish all runRouter. release resource
execController.Finish()
}
//execute middleware filters
if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) {
goto Admin
}
if len(p.filters[FinishRouter]) > 0 && p.execFilter(context, urlPath, FinishRouter) {
goto Admin
}
Admin:
timeDur := time.Since(startTime)
//admin module record QPS
if BConfig.Listen.EnableAdmin {
timeDur := time.Since(startTime)
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur) {
if runRouter != nil {
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
@ -811,18 +840,36 @@ Admin:
}
if BConfig.RunMode == DEV || BConfig.Log.AccessLogs {
timeDur := time.Since(startTime)
var devInfo string
statusCode := context.ResponseWriter.Status
if statusCode == 0 {
statusCode = 200
}
iswin := (runtime.GOOS == "windows")
statusColor := logs.ColorByStatus(iswin, statusCode)
methodColor := logs.ColorByMethod(iswin, r.Method)
resetColor := logs.ColorByMethod(iswin, "")
if findRouter {
if routerInfo != nil {
devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, timeDur.String(), "match", routerInfo.pattern)
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path,
routerInfo.pattern)
} else {
devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeDur.String(), "match")
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path)
}
} else {
devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeDur.String(), "notmatch")
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path)
}
if DefaultAccessLogFilter == nil || !DefaultAccessLogFilter.Filter(context) {
Debug(devInfo)
if iswin {
logs.W32Debug(devInfo)
} else {
logs.Debug(devInfo)
}
}
@ -832,36 +879,20 @@ Admin:
}
}
func (p *ControllerRegister) recoverPanic(context *beecontext.Context) {
if err := recover(); err != nil {
if err == ErrAbort {
return
}
if !BConfig.RecoverPanic {
panic(err)
} else {
if BConfig.EnableErrorsShow {
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
exception(fmt.Sprint(err), context)
return
}
}
var stack string
Critical("the request url is ", context.Input.URL())
Critical("Handler crashed with error", err)
for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
Critical(fmt.Sprintf("%s:%d", file, line))
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
}
if BConfig.RunMode == DEV {
showErr(err, context, stack)
}
// FindRouter Find Router info for URL
func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo *controllerInfo, isFind bool) {
var urlPath = context.Input.URL()
if !BConfig.RouterCaseSensitive {
urlPath = strings.ToLower(urlPath)
}
httpMethod := context.Input.Method()
if t, ok := p.routers[httpMethod]; ok {
runObject := t.Match(urlPath, context)
if r, ok := runObject.(*controllerInfo); ok {
return r, true
}
}
return
}
func toURL(params map[string]string) string {

View File

@ -21,6 +21,7 @@ import (
"testing"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
)
type TestController struct {
@ -65,6 +66,11 @@ func (tc *TestController) GetManyRouter() {
tc.Ctx.WriteString(tc.Ctx.Input.Query(":id") + tc.Ctx.Input.Query(":page"))
}
func (tc *TestController) GetEmptyBody() {
var res []byte
tc.Ctx.Output.Body(res)
}
type ResStatus struct {
Code int
Msg string
@ -89,7 +95,7 @@ func TestUrlFor(t *testing.T) {
handler.Add("/api/list", &TestController{}, "*:List")
handler.Add("/person/:last/:first", &TestController{}, "*:Param")
if a := handler.URLFor("TestController.List"); a != "/api/list" {
Info(a)
logs.Info(a)
t.Errorf("TestController.List must equal to /api/list")
}
if a := handler.URLFor("TestController.Param", ":last", "xie", ":first", "asta"); a != "/person/xie/asta" {
@ -115,24 +121,24 @@ func TestUrlFor2(t *testing.T) {
handler.Add("/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", &TestController{}, "*:Param")
handler.Add("/:year:int/:month:int/:title/:entid", &TestController{})
if handler.URLFor("TestController.GetURL", ":username", "astaxie") != "/v1/astaxie/edit" {
Info(handler.URLFor("TestController.GetURL"))
logs.Info(handler.URLFor("TestController.GetURL"))
t.Errorf("TestController.List must equal to /v1/astaxie/edit")
}
if handler.URLFor("TestController.List", ":v", "za", ":id", "12", ":page", "123") !=
"/v1/za/cms_12_123.html" {
Info(handler.URLFor("TestController.List"))
logs.Info(handler.URLFor("TestController.List"))
t.Errorf("TestController.List must equal to /v1/za/cms_12_123.html")
}
if handler.URLFor("TestController.Param", ":v", "za", ":id", "12", ":page", "123") !=
"/v1/za_cms/ttt_12_123.html" {
Info(handler.URLFor("TestController.Param"))
logs.Info(handler.URLFor("TestController.Param"))
t.Errorf("TestController.List must equal to /v1/za_cms/ttt_12_123.html")
}
if handler.URLFor("TestController.Get", ":year", "1111", ":month", "11",
":title", "aaaa", ":entid", "aaaa") !=
"/1111/11/aaaa/aaaa" {
Info(handler.URLFor("TestController.Get"))
logs.Info(handler.URLFor("TestController.Get"))
t.Errorf("TestController.Get must equal to /1111/11/aaaa/aaaa")
}
}
@ -239,6 +245,21 @@ func TestManyRoute(t *testing.T) {
}
}
// Test for issue #1669
func TestEmptyResponse(t *testing.T) {
r, _ := http.NewRequest("GET", "/beego-empty.html", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Add("/beego-empty.html", &TestController{}, "get:GetEmptyBody")
handler.ServeHTTP(w, r)
if body := w.Body.String(); body != "" {
t.Error("want empty body")
}
}
func TestNotFound(t *testing.T) {
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
@ -399,6 +420,74 @@ func testRequest(method, path string) (*httptest.ResponseRecorder, *http.Request
return recorder, request
}
// Expectation: A Filter with the correct configuration should be created given
// specific parameters.
func TestInsertFilter(t *testing.T) {
testName := "TestInsertFilter"
mux := NewControllerRegister()
mux.InsertFilter("*", BeforeRouter, func(*context.Context) {})
if !mux.filters[BeforeRouter][0].returnOnOutput {
t.Errorf(
"%s: passing no variadic params should set returnOnOutput to true",
testName)
}
if mux.filters[BeforeRouter][0].resetParams {
t.Errorf(
"%s: passing no variadic params should set resetParams to false",
testName)
}
mux = NewControllerRegister()
mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, false)
if mux.filters[BeforeRouter][0].returnOnOutput {
t.Errorf(
"%s: passing false as 1st variadic param should set returnOnOutput to false",
testName)
}
mux = NewControllerRegister()
mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, true, true)
if !mux.filters[BeforeRouter][0].resetParams {
t.Errorf(
"%s: passing true as 2nd variadic param should set resetParams to true",
testName)
}
}
// Expectation: the second variadic arg should cause the execution of the filter
// to preserve the parameters from before its execution.
func TestParamResetFilter(t *testing.T) {
testName := "TestParamResetFilter"
route := "/beego/*" // splat
path := "/beego/routes/routes"
mux := NewControllerRegister()
mux.InsertFilter("*", BeforeExec, beegoResetParams, true, true)
mux.Get(route, beegoHandleResetParams)
rw, r := testRequest("GET", path)
mux.ServeHTTP(rw, r)
// The two functions, `beegoResetParams` and `beegoHandleResetParams` add
// a response header of `Splat`. The expectation here is that that Header
// value should match what the _request's_ router set, not the filter's.
headers := rw.HeaderMap
if len(headers["Splat"]) != 1 {
t.Errorf(
"%s: There was an error in the test. Splat param not set in Header",
testName)
}
if headers["Splat"][0] != "routes/routes" {
t.Errorf(
"%s: expected `:splat` param to be [routes/routes] but it was [%s]",
testName, headers["Splat"][0])
}
}
// Execution point: BeforeRouter
// expectation: only BeforeRouter function is executed, notmatch output as router doesn't handle
func TestFilterBeforeRouter(t *testing.T) {
@ -591,3 +680,10 @@ func beegoFinishRouter1(ctx *context.Context) {
func beegoFinishRouter2(ctx *context.Context) {
ctx.WriteString("|FinishRouter2")
}
func beegoResetParams(ctx *context.Context) {
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
}
func beegoHandleResetParams(ctx *context.Context) {
ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat"))
}

View File

@ -115,7 +115,6 @@ func (st *SessionStore) SessionRelease(w http.ResponseWriter) {
}
st.c.Exec("UPDATE "+TableName+" set `session_data`=?, `session_expiry`=? where session_key=?",
b, time.Now().Unix(), st.sid)
}
// Provider mysql session provider

View File

@ -98,18 +98,13 @@ func (rs *SessionStore) SessionID() string {
// SessionRelease save session values to redis
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(rs.values)
if err != nil {
return
}
c := rs.p.Get()
defer c.Close()
// Update session value if exists or error.
if existed, err := redis.Bool(c.Do("EXISTS", rs.sid)); existed || err != nil {
c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
}
c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
}
// Provider redis session provider

View File

@ -1,64 +0,0 @@
// Copyright 2016 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package redis
import (
"testing"
)
func TestSessionRelease(t *testing.T) {
provider := Provider{}
if err := provider.SessionInit(3, "127.0.0.1:6379"); err != nil {
t.Fatal("init session err,", err)
}
sessionID := "beegosessionid_00001"
session, err := provider.SessionRegenerate("", sessionID)
if err != nil {
t.Fatal("new session error,", err)
}
// set item.
session.Set("k1", "v1")
// update.
session.SessionRelease(nil)
session, err = provider.SessionRead(sessionID)
if err != nil {
t.Fatal("read session error,", err)
}
if v1 := session.Get("k1"); v1 == nil {
t.Fatal("want v1 got nil")
} else if v, _ := v1.(string); v != "v1" {
t.Fatalf("want v1 got %s", v)
}
// delete
provider.SessionDestroy(sessionID)
session.Set("k2", "v2")
session.SessionRelease(nil)
session, err = provider.SessionRead(sessionID)
if err != nil {
t.Fatal("read session error,", err)
}
if session.Get("k1") != nil || session.Get("k2") != nil {
t.Fatalf("want emtpy session value,got %s,%s", session.Get("k1"), session.Get("k2"))
}
}

View File

@ -15,6 +15,7 @@
package session
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
@ -23,7 +24,11 @@ import (
func TestCookie(t *testing.T) {
config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
globalSessions, err := NewManager("cookie", config)
conf := new(ManagerConfig)
if err := json.Unmarshal([]byte(config), conf); err != nil {
t.Fatal("json decode error", err)
}
globalSessions, err := NewManager("cookie", conf)
if err != nil {
t.Fatal("init cookie session err", err)
}
@ -56,7 +61,11 @@ func TestCookie(t *testing.T) {
func TestDestorySessionCookie(t *testing.T) {
config := `{"cookieName":"gosessionid","enableSetCookie":true,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}`
globalSessions, err := NewManager("cookie", config)
conf := new(ManagerConfig)
if err := json.Unmarshal([]byte(config), conf); err != nil {
t.Fatal("json decode error", err)
}
globalSessions, err := NewManager("cookie", conf)
if err != nil {
t.Fatal("init cookie session err", err)
}

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