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

1218 Commits

Author SHA1 Message Date
323a1c4214 Merge pull request #2485 from astaxie/develop
beego 1.8.0
2017-03-06 21:59:04 +08:00
c5f838e785 beego 1.8.0 2017-03-06 21:29:41 +08:00
f53e98d11b Merge pull request #2348 from hongliang81/develop
Add Aliyun Logger
2017-03-05 22:45:32 +08:00
c2f7f3efa7 Merge pull request #2380 from fugr/config
config:fix handle include other.conf
2017-03-05 22:41:45 +08:00
1e5051e112 Merge pull request #2381 from chesedo/OrmStrongRelationships
Add strong relationship support to orm
2017-03-05 22:39:45 +08:00
fa44ff54bb Merge pull request #2479 from marianofevola/develop
Fix typo
2017-03-05 22:07:08 +08:00
4c6de379e0 Merge pull request #2480 from ysqi/fix
Fixed #2456 and strengthen bind
2017-03-05 22:06:46 +08:00
6d997366ed Fixed 2456 and strengthen bind 2017-03-04 20:23:55 +08:00
e0250e2871 Fix typo 2017-03-03 16:24:02 +00:00
3d629e7320 Merge pull request #2468 from antony66/ssdb_cache_fix_is_exist
Fixes issue #2467 with ssdb cache IsExist
2017-02-27 18:58:53 +08:00
74045090cc This fixes issue #2467 with ssdb cache IsExist 2017-02-27 14:14:16 +05:00
50e294be32 fix the broken test 2017-02-27 09:41:15 +08:00
b235b48de4 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2017-02-26 22:21:37 +08:00
28011a5835 fix #2462 2017-02-26 22:20:11 +08:00
b03b0779dc Merge pull request #2455 from jyk1987/develop
Improve json coding performance
2017-02-25 19:01:12 +08:00
393e4c4969 Improve json coding performance 2017-02-22 17:38:26 +08:00
1d49a4bcbd Merge pull request #2417 from eyalpost/develop
support for multiple view paths
2017-02-13 12:37:17 +08:00
fc2c0f4fba Don't allow AddViewPath after beego run 2017-02-11 22:00:30 +02:00
2117aecf65 Merge pull request #2428 from awengo/master
Add http methods
2017-02-11 00:31:43 +08:00
8a2b697625 Add http methods 2017-02-10 17:45:47 +09:00
be586572e0 Merge pull request #2423 from ansiz/master
Add config field EnableErrorsRender
2017-02-10 13:19:20 +08:00
4e047bd483 Merge pull request #2427 from code1986/develop
Develop
2017-02-10 13:17:21 +08:00
db67ffbb94 1.simplify reading and writing file code
2.add apiauth test
2017-02-10 09:35:23 +08:00
90b03d34cc Merge pull request #2421 from code1986/develop
close mysql connection
2017-02-09 14:36:39 +08:00
872a964145 1.add "defer f.close()" in SessionRead to fix file handle leak if DecodeGob failed
2.rewrite SessionRegenerate
2017-02-09 14:18:30 +08:00
dade92d98b close mysql connection 2017-02-09 14:16:23 +08:00
dfa74faf43 support for multiple view paths 2017-02-07 22:18:33 +02:00
95562cff41 Merge pull request #2406 from xqbumu/patch-1
fix the bug to prevent rewrite t
2017-02-07 11:07:37 +08:00
9b714a7518 Add config field EnableErrorsRender
Add config field EnableErrorsRender to disable errors output
  with the template data, sometimes we do not want errors output
  with it even in dev mode, especially in API projects.
2017-02-06 12:33:15 +08:00
2a660820c9 Merge pull request #2389 from amrfaissal/feature-env-support
Package env for working with environment variables inside Beego
2017-01-23 21:15:06 +08:00
b55e20ac60 Merge pull request #2395 from duyazhe/fix_bug_orm_ne
fix filter with __ne bug
2017-01-23 21:12:58 +08:00
b0dcb5b91d Merge pull request #2400 from kerwin/master
Add GetCond func to querySet
2017-01-23 21:12:29 +08:00
5c76f62103 Add GetCond func to querySet 2017-01-18 17:04:23 +08:00
126dbdae2f use BeeMap instead of a regular map 2017-01-16 10:08:53 +01:00
24d8290a3f fix filter with __ne bug 2017-01-16 10:32:33 +08:00
957c0630c0 moved the env package to config/ 2017-01-14 10:15:02 +01:00
e32d173b0d fix the bug to prevent rewrite t
the t have paresed in line 212.
2017-01-14 15:03:49 +08:00
fbc9f8e640 Package env for working with env variables inside Beego
The package env makes it trivial to work with environment variables.
It allows getting values with defaults as a fallback.
New ENV variables can be set safely on the current process environment.
2017-01-13 18:10:25 +01:00
82d2ace3bd Add strong relationship support to orm 2017-01-11 20:16:38 +02:00
3fa7fc6e41 config:fix handle include other.conf
When include other.conf,other.conf is either absolute directory or under beego in default temporary directory(/tmp/beego).
maybe replace by current directory is better.
2017-01-11 18:55:53 +08:00
d1b58a00ce Merge pull request #2373 from fugr/config
avoid creating tmp files to read/parse config
2017-01-11 09:34:57 +01:00
6a2ee371a5 avoid creating new file to implements Config
There is no need to create new file in ParseData(data []byte) (Configer, error).Tet's make code simply.
2017-01-09 21:04:11 +08:00
9266ece7a4 fix the retried 2017-01-05 18:27:23 +08:00
09c405990c Add Aliyun Logger 2017-01-04 15:53:20 +08:00
61e694f388 add retry 2017-01-03 22:50:45 +08:00
195c9b24eb Merge pull request #2359 from kbynd/patch-1
content-length not set in case EnableGZIP=true
2017-01-02 11:49:11 +08:00
2f6da122fd Update output.go 2017-01-02 09:17:17 +05:30
f0d1d7149b Update output.go 2016-12-31 16:14:38 +05:30
96387e9a9b EnableGZip=true,then content-length header missing
This results in responses with Content-Type as gzip as opposed to original content type.
This affects ServeJSON() function.
2016-12-31 16:04:34 +05:30
86f6470fb2 Merge pull request #2353 from skariuki/master
Fixed typo in orm/models_boot.go
2016-12-29 22:50:41 +01:00
d77160dafe ignore .vscode folder 2016-12-29 22:30:56 +01:00
caca5e37ba fixed typo in models_boot 2016-12-29 12:26:20 -05:00
189c73986b Merge pull request #2351 from amrfaissal/add-safemap-count
Add Count method to BeeMap struct
2016-12-29 11:42:47 +01:00
fe21305bb3 Removes redundant check if key exists in BeeMap 2016-12-29 11:11:39 +01:00
75ec8d33a2 Rewrite safemap_test suite 2016-12-28 12:44:35 +01:00
d736d0ca87 Adds Count method to BeeMap struct
This adds a Count() method to BeeMap struct that returns the number of
items within the safe map.
2016-12-28 12:44:35 +01:00
99093693c8 Merge pull request #2346 from legendtkl/master
Modify func camelString() to be more robust
2016-12-26 20:24:49 +08:00
c9c284be27 Modify func camelString to be more robust
1. In previous edition, for case "pic_url_1", the func will return
"PicUrl_1", but "PicUrl1" seems to be more reasonable.
2. More test cases please refer to utils_test.go
2016-12-25 21:09:06 +08:00
fa12dcf792 Merge pull request #2341 from kabab/content_type
Changing content type for template
2016-12-23 23:06:52 +08:00
b93f5c6f9c Merge pull request #2311 from amrfaissal/fix-2310
Ability to register pre/post signal handlers
2016-12-23 20:06:25 +08:00
f9791c1221 Merge pull request #2319 from mlgd/patch-1
Remove a regression on AppPath
2016-12-23 20:03:32 +08:00
8fac2d8d58 Don't rewrite content-type 2016-12-14 17:19:31 +00:00
e90f4bee1a Remove a regression on AppPath
The application path is incorrect on Windows with the command line "go run". AppPath is assigned to the temp directory instead the folder project
2016-12-09 09:37:10 +01:00
eb50221a15 Added method to register Pre/Post signal handlers 2016-12-06 13:48:31 +01:00
fc4801494d Added hookable signals 2016-12-06 13:48:31 +01:00
eba6afd6fb Merge pull request #2307 from CodeJuan/master
set perm of rotated log to 440
2016-12-06 15:10:00 +08:00
81328b72fa Merge pull request #2309 from mengskysama/patch-1
statistics lock
2016-12-06 15:03:10 +08:00
c0c113036b statistics lock 2016-12-06 14:57:15 +08:00
b788d74fd1 set perm of rotated log to 440 2016-12-06 12:44:00 +08:00
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
92d0b9ae95 golint the config.go 2016-01-18 00:22:31 +08:00
6f786e1dea golint config.go 2016-01-18 00:22:11 +08:00
eb0bc084ec make the code mode readable
golint all the files
2016-01-18 00:18:52 +08:00
f925bb9058 golint all the files 2016-01-18 00:18:21 +08:00
da36d1d0e7 fix the wrong io.Writer 2016-01-18 00:03:39 +08:00
48f19b4191 Merge pull request #1571 from astaxie/develop
beego 1.6.0 released
2016-01-17 23:58:34 +08:00
9adf20d72e gofmt -s 2016-01-17 23:57:07 +08:00
90d1349665 fix typo 2016-01-17 23:48:17 +08:00
9b2597be68 fix the mail send empty subject 2016-01-17 23:48:09 +08:00
895748d632 beego 1.6.0 released 2016-01-17 22:55:09 +08:00
566aab4354 mail send support Address format 2016-01-17 22:49:02 +08:00
a304bb9c25 Revert "add test case for tidb"
This reverts commit f70d2cc373.
2016-01-17 18:35:11 +08:00
f70d2cc373 add test case for tidb 2016-01-17 18:24:29 +08:00
897cf57840 use travis-ci.org as build-status 2016-01-15 14:59:16 +08:00
8e6b0a6d7b Merge pull request #1567 from JessonChan/develop
some typo fixed
2016-01-15 14:40:14 +08:00
797571c85f fix the ORM test case 2016-01-15 14:36:45 +08:00
814e4ac031 rename the docsSepc to docs_spec 2016-01-15 14:14:09 +08:00
52083de720 typo fixed
seperator => separator
2016-01-15 14:07:37 +08:00
a069c73b3a test case bool default value is true 2016-01-15 14:02:08 +08:00
7dbeb2c39a fix the default value 2016-01-15 08:43:02 +08:00
4375ca84d1 fix the sqlite m2m 2016-01-14 23:49:28 +08:00
0eaf923a27 Merge pull request #1560 from JessonChan/log_enhancement
Log enhancement
2016-01-13 22:57:39 +08:00
9c55985915 update readme 2016-01-13 22:20:34 +08:00
6976c0f51d add CONTRIBUTING.md 2016-01-13 22:02:36 +08:00
5befc67389 Merge remote-tracking branch 'remotes/upstream/develop' into log_enhancement 2016-01-13 10:15:00 +08:00
fb5b04506a code refactor and format 2016-01-13 09:24:27 +08:00
7663d50c97 remove the lock writer 2016-01-13 09:21:55 +08:00
58730e3528 test file modify 2016-01-13 09:21:32 +08:00
e1b73b33d0 improve code 2016-01-13 08:21:44 +08:00
c535dc386e fast format 2016-01-12 22:54:39 +08:00
69804afc1b use pool to logMsg 2016-01-12 22:39:40 +08:00
164366ae0d return error 2016-01-12 22:33:52 +08:00
2b9d7ff714 remove log package 2016-01-12 22:32:36 +08:00
bd0f3c29fa decr malloc new object 2016-01-12 22:32:20 +08:00
6660720ce6 update some config name 2016-01-12 21:55:02 +08:00
12e7f0f94a extract a func to write to every logger 2016-01-12 21:15:25 +08:00
482b7a62bd use array not map 2016-01-12 21:05:06 +08:00
b90a28bafb Merge pull request #1546 from youngsterxyf/develop
fix #1530
2016-01-12 20:01:51 +08:00
baa2e9d64a code format 2016-01-12 19:44:28 +08:00
5511e03b52 embedding file writer 2016-01-12 19:25:33 +08:00
9507e59c2f camel name fixed 2016-01-12 19:10:08 +08:00
2479e61db9 add asynchronous and call depth benchmark 2016-01-12 19:09:00 +08:00
c9b890b10e add asynchronous benchmark 2016-01-12 17:59:23 +08:00
e32e3a759c console type no need to bench 2016-01-12 17:46:10 +08:00
391f897eb1 simplify sessionID 2016-01-11 16:49:56 +08:00
dc278da17c fix the sqlite3 & cache sleep 11 second 2016-01-08 23:30:19 +08:00
c7146d22f4 add all dependence 2016-01-08 23:20:25 +08:00
1aff26cc31 add dependence database 2016-01-08 23:10:50 +08:00
c59bc431e3 mulit variable 2016-01-08 23:00:22 +08:00
d0dd68351a update travis 2016-01-08 22:34:23 +08:00
0c48738841 for issue #1530, fix incompatible bug 2016-01-08 21:29:12 +08:00
0b0904db13 for issue #1530, accept @JessonChan's suggestion 2016-01-08 20:16:58 +08:00
fd608d2bf6 disable tidb testing 2016-01-08 19:59:20 +08:00
5b028796b8 fix the test case for input 2016-01-08 16:24:59 +08:00
d2de71d8ab update the dependence 2016-01-08 16:01:07 +08:00
302b1ef7df fix the data 2016-01-08 15:52:57 +08:00
77fa891499 update dependence 2016-01-08 15:47:13 +08:00
69bcbcdb31 add travis 2016-01-08 15:34:02 +08:00
bb43d3a78c fix #1530 2016-01-08 13:47:14 +08:00
01012fa898 admin configure 2016-01-08 01:40:19 +08:00
9167587929 add Params for input 2016-01-08 01:20:34 +08:00
c68505e451 read config from app.conf for session 2016-01-07 23:55:55 +08:00
98f3fecc03 fix the typo 2016-01-07 23:35:01 +08:00
c6141f5d94 add httpsaddr 2016-01-07 23:31:33 +08:00
43ca13b516 Merge pull request #1543 from miraclesu/validation
Add validation custom function
2016-01-07 23:24:31 +08:00
687266fb64 Add 179 to valid Phone number 2016-01-07 23:03:32 +08:00
21f767784b Add custom validation function doc 2016-01-07 22:55:12 +08:00
103ac3ee5b Add custom validation function 2016-01-07 22:42:04 +08:00
db2918b0aa Merge pull request #1542 from ysqi/develop
fix issues  #1473, #1502
2016-01-07 21:02:42 +08:00
6eff2e433f fix #1502,Notes error repair 2016-01-07 20:55:28 +08:00
58e2a7c099 fix #1473,Only update redis session if it already exist 2016-01-07 20:42:26 +08:00
434544060a Merge pull request #1540 from ysqi/develop
TplNames renamed TplName ,fix #1229,Remember modify bee tool.
2016-01-07 17:03:22 +08:00
4c0c0ec2a7 TplNames renamed TplName ,fix #1229,Remember modify bee tool. 2016-01-07 16:16:39 +08:00
ecc6bcba3f Merge pull request #1539 from ysqi/develop
change get sessionID logic from cookie
2016-01-07 13:49:33 +08:00
80912b6210 change get sessionID logic from cookie 2016-01-07 13:15:40 +08:00
3fdf72f14c Merge pull request #1532 from JessonChan/develop
mem cache put function fixed
2016-01-07 12:56:47 +08:00
3821b2cb26 createdTime typo fixed 2016-01-07 09:37:50 +08:00
8aed4c13d7 isExist func will check if the value is expired 2016-01-07 09:36:23 +08:00
6465dbd703 no more goroutine ,i will be GCed at a gc goroutine 2016-01-07 09:28:40 +08:00
98e0626f0c rename vals 2016-01-07 09:25:06 +08:00
b0b9812de6 extract a expire fun 2016-01-07 09:13:47 +08:00
eff200e014 modify as xuxiaohei suggest
https://github.com/astaxie/beego/issues/1259
2016-01-07 09:08:00 +08:00
8832334d6a tiny fix for error description and comment 2016-01-06 15:12:25 +08:00
0b39091292 mem cache put function fixed
when a expire duration==0,it means forever
https://github.com/astaxie/beego/issues/1260
2016-01-06 15:05:29 +08:00
a411042416 fix the init parse for config 2016-01-06 14:55:18 +08:00
8929814126 fix the log carsh before init 2016-01-06 11:48:23 +08:00
bef6bca397 Add function to set validation default messages 2016-01-05 21:14:35 +08:00
f7ef4aa7e5 recover for conn.Close fix #1333 2016-01-04 23:34:45 +08:00
5c18d02b17 fix #1268 2016-01-04 22:41:25 +08:00
3bb22d149e add the comments 2016-01-04 22:22:42 +08:00
f0be45dfff fix the comments and json tag 2016-01-04 22:18:59 +08:00
7fbaf82897 fix #1424 2016-01-04 22:10:18 +08:00
73168d2f7d Merge pull request #1527 from JessonChan/develop
reuse compress writer
2016-01-04 14:36:40 +08:00
a03fa0fb73 improve cache modules. support mulit instances 2016-01-04 10:50:04 +08:00
fd2ded190b EnableGzip bug fixed 2016-01-04 09:27:58 +08:00
d23291ccc7 remove a dump err 2016-01-04 08:50:59 +08:00
92d157736b add testing to test #1511 2016-01-03 21:06:35 +08:00
6585e66f97 all the browser should support delete and put now 2016-01-03 20:36:16 +08:00
3ebf275157 fixed camel style name 2016-01-03 15:40:44 +08:00
ee2322e83b add any level compress 2016-01-03 15:35:32 +08:00
59fa248292 use sync.Pool to decrease new compression writer 2015-12-31 18:50:52 +08:00
9519fc6c96 Merge pull request #1519 from vvelikodny/develop
Refactoring: Move dev & prod runmodes to const
2015-12-30 21:03:54 +08:00
4b368d9f5e Refactoring: keep config package beego independent 2015-12-30 11:22:09 +03:00
48fd9675ad Refactoring: Move dev & prod runmodes to const 2015-12-29 21:32:37 +03:00
ac3a447479 fix the session update issues 2015-12-27 14:09:20 +08:00
37dff6be28 Merge pull request #1507 from yydzero/develop
Retrieve session identifier from cookie and query parameters
2015-12-27 11:01:04 +08:00
cdde5bdc62 Merge pull request #1516 from johndoejdg/patch-2
Add link to russian
2015-12-27 11:00:24 +08:00
2d4cc6e33d Add link to russian
Add link to russian
2015-12-26 16:43:49 +03:00
5336e83469 Merge pull request #1506 from fuxiaohei/develop
clean code in docs.go, error.go, filter.go and hooks.go
2015-12-23 12:12:08 +08:00
da39082d4f Retrieve session identifier from cookie and query parameters 2015-12-22 10:30:44 +08:00
fud
cd514803a4 simplify filter.go hooks.go 2015-12-22 10:02:59 +08:00
fud
2ddda59605 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-12-22 09:57:47 +08:00
25337aec27 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-12-21 22:54:22 +08:00
351dfac653 context.Response should implement Hijack/Flush/CloseNotify 2015-12-21 22:51:18 +08:00
9105fee453 Merge pull request #1503 from fuxiaohei/develop
simplfy config.go and controller.go
2015-12-21 19:49:22 +08:00
fud
bbd42ce152 simplify docs.go and error.go, use http.StatusText instead of string codes 2015-12-21 17:16:58 +08:00
fud
92711e80a3 refactor controller.go 2015-12-21 16:23:31 +08:00
fud
c43e3d6684 fix type mismatch error 2015-12-21 16:05:26 +08:00
fud
f6c508f138 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-12-21 15:49:23 +08:00
130ce7eb1f Merge pull request #1501 from nkbai/develop
cors test benchmark doesn't use b.N
2015-12-19 13:45:04 +08:00
083c155079 cors test benchmark doesn't use b.N 2015-12-19 10:29:12 +08:00
a71c4283e8 Merge pull request #1498 from JessonChan/develop
compress level test fixed
2015-12-18 10:18:42 +08:00
80ac8aa40e compress level test fixed 2015-12-18 09:28:40 +08:00
5e249772d5 reduce loop 2015-12-18 00:14:28 +08:00
a4f674e7f4 Merge pull request #1483 from nkbai/develop
为orm接口添加注释
2015-12-17 22:23:32 +08:00
ae52d4aa18 improve the splitPath 2015-12-17 21:31:44 +08:00
f9138c5a99 simplify config.go 2015-12-17 20:05:00 +08:00
e5096be32b Merge pull request #1490 from pjoe/orm_pk_rel_many
Fix joins for reverse(many) with custom pk
2015-12-17 14:56:06 +08:00
9b87f528ec Merge pull request #1494 from astaxie/revert-1489-develop
Revert "go test fixed"
2015-12-17 14:45:17 +08:00
c3d1e4d088 Revert "go test fixed" 2015-12-17 14:45:10 +08:00
77113e843c Merge pull request #1489 from JessonChan/develop
go test fixed
2015-12-17 14:44:29 +08:00
46aa340b1d Merge pull request #1478 from fuxiaohei/develop
clean compliated codes, refactor if sections in app.go
2015-12-17 14:44:14 +08:00
8771634fe4 Merge remote-tracking branch 'remotes/upstream/develop' into develop 2015-12-17 09:25:15 +08:00
2aa50c240f Merge pull request #1486 from KilledKenny/oomDos
Added MaxMemory limit to CopyBody() Supersedes #1484
2015-12-16 23:44:42 +08:00
dbc4ac6945 reduce the slicegrow 2015-12-16 23:43:32 +08:00
29752e2575 refactor router 2015-12-16 23:11:03 +08:00
52c4c1fb98 Added MaxMemory limit to CopyBody()
Beego only uses the MaxMemory flag when using go's built in functions
for parsing forms. However the CopyBody() function have no limit an will
coppy anny amount of data into memory using ioutil.ReedAll() on the
request body whitout anny size validation or limit.

This fix wrapps input.Requst.Body in a LimitedReader using the same
memory limit as ParseFormOrMulitForm()
2015-12-16 10:37:21 +01:00
906637ae8b Fix issue with reverse(many) for models with custom pk
- Also add test covering the issue
2015-12-15 17:39:08 +01:00
3daaaeb32b add commit for orm/types.go 2015-12-15 19:48:28 +08:00
ccc008c257 compress fixed 2015-12-15 14:29:07 +08:00
fd9a6ff7bb Merge remote-tracking branch 'remotes/upstream/develop' into develop 2015-12-15 14:27:43 +08:00
e63d24637d go test fixed 2015-12-15 14:08:58 +08:00
7dcbcf0748 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-12-15 14:05:58 +08:00
1576add9a2 Merge pull request #1488 from astaxie/revert-1487-develop
Revert "compress method fixed"
2015-12-15 14:05:43 +08:00
58aa0545b6 Revert "compress method fixed" 2015-12-15 14:05:33 +08:00
10cbe7a867 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-12-15 13:18:09 +08:00
d6f23afdbb Merge pull request #1487 from JessonChan/develop
compress method fixed
2015-12-15 11:49:19 +08:00
499e2b59e4 compress method fixed
in http,the deflate is zlib compress method accoding to the sec
http://tools.ietf.org/html/rfc2616#section-3.5
The "zlib" format defined in RFC 1950 [31] in combination with
        the "deflate" compression mechanism described in RFC 1951 [29].
2015-12-15 11:34:26 +08:00
d99c62df1f Merge remote-tracking branch 'upstream/develop' into develop 2015-12-14 15:09:06 +08:00
b079456fcf add comments for orm/types.go 2015-12-14 15:04:17 +08:00
a06022f75c clean compliated codes, refactor if sections in app.go 2015-12-12 14:10:02 +08:00
2b651fbae2 reuse map in tree.Match 2015-12-11 13:51:01 +08:00
80bc372f17 pool.Put 2015-12-11 00:20:17 +08:00
f70f338025 use sync.Pool to reuse Context 2015-12-10 21:59:54 +08:00
f2edfbe7ae make the other three beauty 2015-12-10 00:12:52 +08:00
4f829af7aa gofmt the config default 2015-12-10 00:11:24 +08:00
9f2a2507fd fmt it and remove the init var 2015-12-09 23:47:44 +08:00
be60f47488 Merge pull request #1455 from nkbai/develop
windows下静态文件映射找不到问题以及 grace init延后
2015-12-09 23:44:45 +08:00
d1bba02958 refact beego config 2015-12-09 23:35:04 +08:00
e8fe859a58 格式调整 2015-12-09 09:03:10 +08:00
82e0105d22 Revert "windows下静态文件映射找不到问题,"
windows下路径需要filepath.ToSlash

This reverts commit 2c8cb5693e.
2015-12-04 23:15:06 +08:00
ff0762cc19 Merge remote-tracking branch 'upstream/develop' into develop 2015-12-03 15:50:21 +08:00
3d4ad560f8 Merge pull request #1423 from JessonChan/fargo
fix the  accept encoding
2015-12-02 21:08:34 +08:00
36664634f9 Merge pull request #1467 from astaxie/revert-1464-master
Revert "Update orm_querym2m.go"
2015-12-02 16:35:44 +08:00
ed99c013a6 Revert "Update orm_querym2m.go" 2015-12-02 16:35:36 +08:00
a1d5f958c5 Merge pull request #1464 from gobenon/master
Update orm_querym2m.go
2015-12-02 16:35:05 +08:00
5d902264e5 Merge pull request #1459 from ryanchapman/develop
Log config parsing errors
2015-12-02 16:34:35 +08:00
50f8f3bd20 Update orm_querym2m.go 2015-11-30 18:23:58 +02:00
74ebcd28b2 grace init中的工作可以延后进行
实际上如果没有使用graceful启动,这些init的工作完全没用
当然其他模块也应该存在这样的工作,比如session中的sess_utils.go中的init工作
2015-11-30 16:15:35 +08:00
7151b96465 Log config parsing errors
Beego currently handles the case of "conf/app.conf"
not existing, but all other errors are not logged.
This fixes that.

I ran into an issue where beego was not listening on the
correct port, and it turned out that the conf/app.conf file
had a colon ":" instead of an equal sign "=" (confusion of
INI vs YAML formats).  When there was a parsing error, beego
would give up on parsing app.conf and not log anything to the
console.
2015-11-29 15:09:28 -07:00
2c8cb5693e windows下静态文件映射找不到问题,
path.Clean和filepath.Clean是有区别的
2015-11-27 12:10:39 +08:00
d693ecf046 Revert "这种if型的初始化是有问题的"
This reverts commit 50132df809.
2015-11-26 20:37:36 +08:00
6aaca2eca8 router.go Header 毫无用处
context/output.go 简化一下代码,更清晰
2015-11-26 14:56:39 +08:00
f0474214fe GetInt等函数稍微简化一下,这样看上去更合理一些 2015-11-25 23:39:26 +08:00
2c94d9eab2 Merge pull request #1451 from gobenon/master
fix issue 1438 opened by Ayelet Regev
2015-11-25 09:29:28 +08:00
40700a8532 Merge remote-tracking branch 'remotes/upstream/develop' into fargo
# Conflicts:
#	memzipfile.go
2015-11-25 09:11:32 +08:00
fb7314f8ac Merge pull request #1450 from astaxie/revert-1439-master
Revert "Adding support for junction tables with other fields which arent FK and PK."
2015-11-24 23:00:15 +08:00
b7fcd4f0b9 Revert "Adding support for junction tables with other fields which arent FK and PK." 2015-11-24 23:00:07 +08:00
e48e09a4ab Merge pull request #1439 from gobenon/master
Adding support for junction tables with other fields which arent FK and PK.
2015-11-24 23:00:04 +08:00
235b58504a Merge pull request #1449 from nkbai/master
controller_test.go 既然叫test,那就按照go的规则进行test吧
2015-11-24 15:54:46 +08:00
5e915cb614 controller_test.go 既然叫test,那就按照go的规则进行test吧 2015-11-24 14:55:59 +08:00
9170b91075 go style format (remove the blank after comments) 2015-11-21 08:46:19 +08:00
25320bf86a Merge pull request #1441 from nkbai/master
fix #1433
2015-11-20 21:50:55 +08:00
50132df809 这种if型的初始化是有问题的 2015-11-20 21:34:01 +08:00
83696a40f6 fix #1433 2015-11-20 11:18:45 +08:00
efd30bdba7 Update orm_querym2m.go 2015-11-19 16:46:14 +02:00
01aa1085e0 Merge pull request #1 from gobenon/gobenon-m2mpatch-1
Update orm_querym2m.go
2015-11-19 14:32:24 +02:00
ca37557a26 Update orm_querym2m.go 2015-11-19 14:30:14 +02:00
5a1e821a42 Merge pull request #1434 from WnP/pg-fix
fix postgres syntax error during migration
2015-11-19 12:25:21 +08:00
29ac961c10 fix postgres syntax error during migration 2015-11-18 06:46:44 -06:00
5d01afe3a6 isOk to check whether the file is latest 2015-11-16 14:05:05 +08:00
d963bb79bd avoid map-lock delete 2015-11-16 10:31:27 +08:00
bc2195b07f code simplify 2015-11-12 16:59:07 +08:00
46fbeaadad refactor accept encoder ,simplify the struct 2015-11-12 12:03:53 +08:00
214030fad4 bytes reader replace string reader 2015-11-12 11:44:29 +08:00
f8db8ae9c3 add some comments 2015-11-12 10:48:36 +08:00
a9881388f7 accept encoder header setting fixed 2015-11-12 10:08:57 +08:00
1200b7c347 method refactor 2015-11-11 18:06:18 +08:00
b25a1355f9 remove old code 2015-11-11 16:27:41 +08:00
d2c60619fa new static file support code 2015-11-11 16:22:40 +08:00
82c50b972d new test file 2015-11-11 16:22:05 +08:00
15b0b9b66d delete old static file code 2015-11-11 16:21:04 +08:00
e4c6e5d2e1 change package 2015-11-11 13:47:47 +08:00
f457ea0fe9 refactor encoder package 2015-11-11 13:47:36 +08:00
7ef9b3d55b runnable typo fixed 2015-11-10 14:07:26 +08:00
39e29caf9b refactor to fix encoder type bug 2015-11-10 13:42:10 +08:00
7ccf049a50 bug fixed 2015-11-10 13:27:33 +08:00
7964f7f163 test bug fixed 2015-11-10 13:16:16 +08:00
8603127c81 beego package file path rewrite 2015-11-10 13:10:42 +08:00
07c93cd32c mem zip file test ,add license 2015-11-10 11:59:32 +08:00
83ec39d02e refactor max age cookies setting 2015-11-10 11:47:10 +08:00
8f42193610 better compress func design 2015-11-10 11:32:18 +08:00
891be34fc6 encoding should be specify 2015-11-10 11:20:13 +08:00
0bc70e88f0 ignore the other compress method 2015-11-10 11:00:29 +08:00
3872c48349 accept encoding refactor and bug fixed 2015-11-10 10:55:47 +08:00
dc28e37606 Merge branch 'develop' into fargo 2015-11-09 17:18:00 +08:00
821b2f832e fix the type assert 2015-11-09 11:03:57 +08:00
9b725c73c3 Merge pull request #1376 from JessonChan/develop
static file code refactor and bug fixed
2015-11-08 23:21:16 +08:00
860568cb6c modified as astaxie reviews
https://github.com/astaxie/beego/pull/1376
2015-11-06 18:51:53 +08:00
dc3e324f38 Merge pull request #1418 from ElvizLai/patch-1
Update context.go
2015-11-05 22:39:34 +08:00
b8fc42d38d Update context.go
all this status was setting in error.go, this line will cause multi-resp
2015-11-05 21:20:57 +08:00
a26dee556d fix #1335 2015-11-05 00:19:09 +08:00
fd4630c6dd impove the ResponseWriter. fix #1410 2015-11-04 23:52:42 +08:00
e3120226fa Merge pull request #1414 from FlamingTree/develop
bugfix: graceful failed when both enable http and https
2015-11-04 23:27:27 +08:00
25bec8bbe9 Merge pull request #1381 from ADone/m2m_reverse_bug
fix #671
2015-11-04 23:20:22 +08:00
a257a924a1 Merge pull request #1379 from pjoe/non_int_fk
orm: Fix handling of rel(fk) to model with string pk
2015-11-04 22:26:33 +08:00
c7e9a86b00 Merge pull request #1415 from johndeng/develop
Fixed typos
2015-11-04 13:08:37 +08:00
205de8418d Fixed typos 2015-11-03 23:43:34 +08:00
f81929c28c bugfix: graceful failed when both enable http and https 2015-11-03 14:53:26 +08:00
58ed1436cc Merge pull request #1409 from superhacker777/patch-2
XSRFFormHtml() should also generate XSRF token.
2015-10-28 15:54:07 +08:00
5d18a7466c XSRFFormHtml() should also generate XSRF token. 2015-10-27 20:16:47 +03:00
912abe3272 fix #1388 2015-10-12 21:26:18 +08:00
4ba50e5df5 fix #1385 2015-10-12 20:50:58 +08:00
332fa44231 Merge pull request #1384 from pjoe/update_err_fix
Fix dbBase.Update not returning error on failure
2015-10-12 20:43:22 +08:00
174e758d19 Fix dbBase.Update not returning error 2015-09-28 14:07:35 +02:00
1f2f0b30f4 mem zip file refactor and test 2015-09-22 22:02:56 +08:00
cfcce4f5dc Fix handling of rel(fk) to model with string pk 2015-09-22 12:23:51 +02:00
1abf85ed2a simplify the switch code 2015-09-22 15:18:24 +08:00
d4f3dfd527 return when find static path 2015-09-22 14:15:41 +08:00
6ad215a9bb mem zip file var refactor 2015-09-22 14:11:02 +08:00
9c17f73489 code refactor 2015-09-22 13:48:34 +08:00
4995f91547 code refactor 2015-09-22 13:46:20 +08:00
936cb735e1 file extensions bug fixed 2015-09-22 13:27:35 +08:00
f708ce0299 当有设置的压缩类型时,丢弃默认类型(css,js) 2015-09-22 12:24:52 +08:00
dc38b324e0 code bug fixed 2015-09-22 12:19:31 +08:00
b9fb3a62f5 static file name default lower case 2015-09-22 11:59:48 +08:00
95ef4c7136 server index.html in beego with ServeContent 2015-09-21 23:56:24 +08:00
eb85e8e328 path.Clean can't clean window separate .."
"
2015-09-20 19:59:30 +08:00
e26720496f remove the dupl 2015-09-19 20:05:57 +08:00
8af8936ee0 Merge pull request #1368 from JessonChan/fargo
error bug fixed and clean code
2015-09-19 15:10:22 +08:00
07a424581d // beego.Run("localhost") 2015-09-19 05:53:28 +08:00
69bee9ef3c // beego.Run("localhost") 2015-09-19 05:52:52 +08:00
caf3714495 revert exceptMethod 2015-09-19 05:41:10 +08:00
983bac986a runFunction camel name 2015-09-18 18:34:07 +08:00
56032c67af runFunction camel name 2015-09-18 18:31:06 +08:00
40cb8e0cf1 use reflect to ensure all methods been except 2015-09-18 18:18:12 +08:00
0ac690d2c8 method name refactor 2015-09-18 17:59:28 +08:00
cc5abc6b30 default atoi func to handle exception 2015-09-18 17:03:00 +08:00
cb0400dcd4 file add the config for Perm 2015-09-18 12:12:02 +08:00
fda28fa2ff fix the conv test case 2015-09-18 12:11:48 +08:00
2a96f33543 more clean code 2015-09-18 10:36:16 +08:00
8df2cca627 add comment 2015-09-18 10:32:21 +08:00
ead635e62f default exception handler 2015-09-18 10:31:10 +08:00
4823a0f114 remove the dead code 2015-09-17 23:47:26 +08:00
e665a7dd32 Merge pull request #1367 from dvwallin/develop
added a check to parser to not panic (in develop)
2015-09-17 23:41:59 +08:00
6e24b78b62 fix the wrong response 2015-09-17 23:27:34 +08:00
edbad60782 Merge branch 'develop' of github.com:dvwallin/beego into develop 2015-09-17 17:07:06 +02:00
bb6062857b fix the error refactor 2015-09-17 23:05:45 +08:00
0d100fef7d Merge pull request #1364 from JessonChan/fargo
error and hook refactor
2015-09-17 23:02:32 +08:00
eac09e6fb6 Merge pull request #1349 from ElvizLai/patch-4
Update tree.go
2015-09-17 23:01:32 +08:00
3df0fa462d golint tidb 2015-09-17 23:00:05 +08:00
dfbb1b5ee5 Merge pull request #1366 from ngaut/master
Add support for TiDB
2015-09-17 21:25:12 +08:00
09b7457ac6 orm_test: Skip relation test 2015-09-17 17:05:40 +08:00
c841a77ad6 Orm: Add tidb for query builder 2015-09-17 17:04:23 +08:00
c73e0395ed Orm: Support TiDB 2015-09-17 17:04:23 +08:00
de20960458 error map refactor 2015-09-17 10:36:29 +08:00
bb776cc4cb error map refactor 2015-09-17 10:33:12 +08:00
cce8d1e934 refactor hooks function code 2015-09-17 10:31:53 +08:00
c6448727de golint utils 2015-09-14 23:35:13 +08:00
5015614fdc golint pagination 2015-09-14 23:17:33 +08:00
7b81617a95 golint captcha 2015-09-14 23:13:51 +08:00
2389bc72f9 golint validation 2015-09-13 00:13:19 +08:00
1d200da472 golint toolbox 2015-09-12 23:28:24 +08:00
be7accc94c golint testing 2015-09-12 23:19:18 +08:00
a289b08e64 golint swagger 2015-09-12 23:15:23 +08:00
172894efe8 golint session 2015-09-12 22:53:55 +08:00
ea2039c1dc golint plugins 2015-09-12 22:03:45 +08:00
68ec133aa8 golint orm 2015-09-12 21:46:43 +08:00
542e143e55 golint migration 2015-09-11 23:16:05 +08:00
0a5fa04062 remove i18n.go 2015-09-11 23:09:37 +08:00
34877c52a9 golint logs 2015-09-11 23:08:24 +08:00
657995092a golint httplib 2015-09-11 22:28:28 +08:00
f6d4629103 added a check to parser to not panic 2015-09-10 11:35:57 +02:00
65fb7ce391 golint grace 2015-09-10 16:35:40 +08:00
01a5e54264 delete example from the source code 2015-09-10 15:40:46 +08:00
ff5b09fc19 golint context 2015-09-10 15:31:09 +08:00
bdd6a6ae40 golint config 2015-09-10 14:53:19 +08:00
d7aaf2ebeb golint cache package 2015-09-09 00:15:03 +08:00
62e528ca4c golint tree.go 2015-09-08 23:49:24 +08:00
bcb1db256d golint templatefunc 2015-09-08 23:41:41 +08:00
44bd3beb5e golint happy with template 2015-09-08 23:29:58 +08:00
8615f875f8 make golint happy staticfile.go 2015-09-08 22:07:44 +08:00
b2048e8653 make router test passed 2015-09-08 22:05:38 +08:00
c11740b647 make golint happy router.go 2015-09-08 22:01:13 +08:00
21fffc446b make golint happy parser.go 2015-09-08 21:45:45 +08:00
67b36d7c48 make golint happy 2015-09-08 21:41:38 +08:00
61570ac2f7 make golint happy with controller.go 2015-09-08 10:43:42 +08:00
f28a941e26 make golint happy and also make the config readable 2015-09-07 23:19:42 +08:00
152127c2af make golint happy 2015-09-07 21:38:53 +08:00
919675e793 update the comments 2015-09-07 19:29:52 +08:00
fe9c52fb69 optimize init admin 2015-09-07 19:27:53 +08:00
284dfc0843 move the template related fun to template.go 2015-09-07 19:21:55 +08:00
85d8ec5ca6 optimize the beego structure 2015-09-07 19:18:04 +08:00
eb3479b753 optimize the app structure 2015-09-06 23:00:42 +08:00
a2a6ec954b Update tree.go
go fmt
2015-09-06 22:13:58 +08:00
45b72b0674 Merge pull request #1334 from ElvizLai/patch-2
Update beego.go
2015-09-06 22:12:13 +08:00
9e969957de Merge pull request #1348 from sidbusy/develop
allows custom the TableName of Session
2015-09-06 21:22:05 +08:00
7b0f3a83dc Merge pull request #1351 from leekchan/cache
Fix a wrong test name & update a outdated information in README
2015-09-06 18:46:59 +08:00
fe1ec1675f Fix a wrong url (http 404). 2015-09-06 17:22:33 +09:00
4ad743fc8b Update a outdated information in README. 2015-09-06 17:20:18 +09:00
06ec3a931d Fix a wrong test name. 2015-09-06 17:13:55 +09:00
1377d16559 Update tree_test.go 2015-09-06 12:17:16 +08:00
ddd9bf1305 Update tree.go 2015-09-06 12:16:05 +08:00
508a57be1e Update tree_test.go 2015-09-06 12:07:12 +08:00
5ad999a3d1 Update tree.go
fix routers for:
```
/topic/:id/?:auth
/topic/:id/?:auth:int
```
2015-09-06 12:01:50 +08:00
f55bbbdff4 allows custom the TableName of Session 2015-09-05 10:31:31 +08:00
34aa9002bb fix the httplib test case timeout 2015-09-04 23:03:10 +08:00
f9fe89fff0 fix the file rotate test case issues 2015-09-04 22:33:03 +08:00
9ab7466d5c fix the cappital 2015-09-04 22:20:55 +08:00
2e75c04ffb Merge pull request #1345 from f0r/develop
为querySeter添加GroupBy方法
2015-09-04 21:48:47 +08:00
9038cdfaae Merge pull request #1343 from onealtang/oneal-dev
always use server's locale to parse date
2015-09-04 21:32:18 +08:00
f0r
a074df9c2e 为querySeter添加GroupBy方法 2015-09-03 00:45:09 +08:00
adca455804 always use server's locale to parse date
When parsing the date without time, it's always using UTC date, which is
unexpected. If we want to use UTC date, it's recommend to set the
server's timezone as UTC, and keep the code flexible.
2015-09-02 15:50:40 +08:00
9fd571830d Update beego.go
Maybe the `Hard Coding` should have a higher priority
2015-09-01 17:52:44 +08:00
dd4cbdda66 update the gitignore 2015-08-31 11:58:11 +08:00
ad6547936e fix the http: multiple response.WriteHeader calls 2015-08-28 23:08:00 +08:00
306effa300 Merge pull request #1329 from ElvizLai/patch-1
Update error.go
2015-08-28 22:33:20 +08:00
c516819c56 Update error.go
this caused `http: multiple response.WriteHeader calls` when using method `CustomAbort` or `Abort` when status is already in errMap like 404.
2015-08-28 16:54:49 +08:00
4202fe8fe0 Merge pull request #941 from lei-cao/develop
Added JWT plugin
2015-08-27 22:57:04 +08:00
0c5f4b48d4 Merge pull request #1276 from pjoe/orm_distinct
Fix issue #1274: Add QuerySeter.Distinct()
2015-08-27 22:54:39 +08:00
7aa893612e Merge pull request #1308 from zhangshuai/master
httplib请求参数支持[]string
2015-08-27 22:50:44 +08:00
cfaae5ab8c Merge pull request #1326 from hvnsweeting/patch-1
fix typo
2015-08-27 22:49:38 +08:00
cbb6591bdb fix typo 2015-08-26 15:57:28 +07:00
0c33673197 Revert spaces > tabs change 2015-08-24 09:41:10 +02:00
862ea226e5 优化设置参数 2015-08-24 00:16:56 +08:00
437349f776 Merge pull request #1247 from Skycrab/develop
fix example/chat i/o timeout
2015-08-23 22:18:38 +08:00
bc1f0ac6fd Merge pull request #1298 from wulove/develop
全局变量AutoRender为false时,run时不再编译模版;针对开发模式下,每个请求渲染模版时支持单独编译当前请求相关模版
2015-08-23 22:13:15 +08:00
db2b1ee54f Merge pull request #1318 from tabalt/patch-1
fixed mux1 to mux
2015-08-21 10:57:42 +08:00
99b1c5c54b fixed mux1 to mux 2015-08-21 10:53:59 +08:00
38ddb61199 files is []string, use len(files)==0 2015-08-21 10:32:53 +08:00
d9e4836715 Merge pull request #1305 from Hepri/develop
Added MapGet template func
2015-08-20 22:33:07 +08:00
0e3fe64c69 Added TestMapGet 2015-08-20 19:04:43 +05:00
4081311a37 Merge pull request #1309 from smallfish/develop
Update router.go, add Flush for responseWriter
2015-08-19 15:25:24 +08:00
506f54a080 Update router.go, add Flush for responseWriter 2015-08-19 15:23:50 +08:00
ff92f22d84 删除Param中的断言 2015-08-18 23:19:24 +08:00
860006bfda httplib请求参数支持[]string 2015-08-18 19:28:17 +08:00
9107fd8898 remove the default timeout setting 2015-08-17 22:33:28 +08:00
d91840779a Update templatefunc.go 2015-08-17 01:18:29 +05:00
d4e15c0bd0 Added MapGet template func 2015-08-17 00:08:02 +05:00
877b5c233e 增加编译模版函数BuildTemplate可变参数,使之支持单个或多个模版的编译,同时针对开发模式,每个请求只编译当前请求相关模版
增加编译模版函数BuildTemplate可变参数,使之支持单个或多个模版的编译,同时针对开发模式,每个请求只编译当前请求相关模版,不再每次请求都编译全部模版
2015-08-06 10:09:34 +08:00
57fdc308e3 AutoRender为空时,不再编译模版
AutoRender为空,Controller.Render()不再执行,故无需编译模版
2015-08-06 09:36:43 +08:00
4857e38471 Merge pull request #1284 from wallclockbuilder/develop
Fix #1269 extract godoc to own file.
2015-07-28 11:57:27 +08:00
42fab96cd4 Merge pull request #1285 from JessonChan/beego_develop
typo fixed
2015-07-28 11:56:45 +08:00
b26ef5b2e5 typo fixed
registor==>register
innner ==> inner
2015-07-27 08:44:58 +08:00
b622d5d369 Fix #1269 extract documentation
Fix #1271 Add description from docs on beego.me to README
and also add same description to godoc
2015-07-26 17:06:55 +00:00
19d82ab62c Fix #1274: Add QuerySeter.Distinct() 2015-07-22 18:12:57 +02:00
9775e3e3a4 Merge pull request #1265 from fugr/develop
set DoRotate fname like xx.2013-01-01.2.log
2015-07-17 01:56:48 +08:00
160d82d1d2 Merge pull request #1267 from wallclockbuilder/develop
Fix for #1256. Indent the code sample lines properly.
2015-07-17 01:50:09 +08:00
45d693e6d6 Add quick start example from website to README 2015-07-16 04:32:07 +00:00
0564956fd6 Fix for #1256. Indent the code sample lines properly. 2015-07-15 12:04:18 +00:00
59b903d557 set DoRotate fname like xx.2013-01-01.2.log
fix fname,by extension to identify the file type on mac and windows.
2015-07-15 17:00:48 +08:00
5612f61a93 fix #671 2015-07-08 17:42:00 +03:00
3becd2e0d8 Merge pull request #1249 from wallclockbuilder/patch-1
Fix #1237
2015-07-08 12:38:48 +08:00
8c0ad5ef88 fix example/chat i/o timeout 2015-07-06 21:12:03 +08:00
079993b9f7 fix #1245 2015-07-06 13:54:14 +08:00
c15aaad85b Merge pull request #1244 from simman/develop
Update validators.go
2015-07-04 20:48:47 +08:00
06b25deab2 Update validators.go
Support virtual operators paragraph 170!
2015-07-04 17:55:01 +08:00
002302818d Fix #1237
Package description uses same text as the README.
2015-07-01 04:17:53 +00:00
a89f14d80d Merge pull request #1227 from oiooj/develop
fix FilterHandler crash issue
2015-06-19 11:43:15 +08:00
87e8bcc9be fix FilterHandler crash issue
Filter Handler will crash with error assignment to entry in nil map , params from function Tree.Match() maybe nil.
2015-06-19 11:19:35 +08:00
31e5edbdcf Merge pull request #2 from astaxie/develop
pull from stable
2015-06-19 11:09:11 +08:00
f7f390dfec fix #1221 2015-06-16 14:53:38 +08:00
8f7246e17b change to version 1.5.0 2015-06-15 23:49:13 +08:00
c8f6e0f156 remove the hardcode in runtime.Caller 2015-06-15 20:53:49 +08:00
0207caab6f keep the shortname for logs info/warn/debug 2015-06-15 20:44:14 +08:00
d629c1d3d0 change the comments 2015-06-15 20:22:05 +08:00
817650aa33 keep the short name for logs 2015-06-15 20:20:37 +08:00
ba1232dfaf filter should be always the same 2015-06-14 18:35:46 +08:00
64d4f6518b fix #1213 2015-06-14 18:10:10 +08:00
9f05db8475 Merge pull request #1212 from astaxie/revert-1211-revert-1210-develop
Revert "Revert "fix multiple filters execute issue""
2015-06-14 01:14:42 +08:00
b275d7c6f5 Revert "Revert "fix multiple filters execute issue"" 2015-06-14 01:14:33 +08:00
73770fbe22 Merge pull request #1211 from astaxie/revert-1210-develop
Revert "fix multiple filters execute issue"
2015-06-14 01:13:42 +08:00
fc11169ee3 Revert "fix multiple filters execute issue" 2015-06-14 01:13:34 +08:00
b54589fa9d Merge pull request #1210 from oiooj/develop
fix multiple filters execute issue
2015-06-14 01:08:51 +08:00
2af0c569a5 The last filterFunc with returnOnOutput=ture won't be executed
ex:
	beego.InsertFilter("/*", beego.BeforeExec, FilterLoginCheck1,false)
	beego.InsertFilter("/*", beego.BeforeExec, FilterLoginCheck2)

In function  FilterLoginCheck1 , I'll write data via ResponseWriter, and w.started = true
FilterLoginCheck2 won't be executed, it should be.
2015-06-14 01:02:41 +08:00
27b7a8f743 Merge pull request #1 from astaxie/develop
Develop
2015-06-14 00:35:38 +08:00
c143a6ec19 fix #1090 add Getfiles to support mulit file upload 2015-06-13 16:20:26 +08:00
e619d83990 fix the filter router issues 2015-06-13 12:47:01 +08:00
27b452cd95 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-06-13 11:15:44 +08:00
b776e43962 merge bat/httplib to httplib 2015-06-13 11:15:13 +08:00
cb89cd577d Merge pull request #1201 from kongjian/develop
support eq&ne for orm
2015-06-13 10:54:47 +08:00
6b777f0c5e Merge pull request #1207 from oiooj/develop
Don't overwrite the params from function  ValidRouter
2015-06-13 09:16:07 +08:00
491238ce7d Don't overwrite the params from function ValidRouter
just add new params to context.Input.Params
2015-06-13 01:04:46 +08:00
d9bb1a3592 logs support elasticsearch adapter 2015-06-13 00:25:48 +08:00
9c6775c22c log default use synchro, and support async 2015-06-13 00:25:21 +08:00
4d70b22f96 Merge pull request #1157 from ziyel/master
Let filter function get more params info from ctx.Input.Params
2015-06-11 14:38:19 +08:00
d943d16d52 gofmt 2015-06-10 21:26:04 +08:00
bbb6f31f16 support eq&ne for orm 2015-06-09 10:18:21 +08:00
364cacf659 record the critical logs in Prod 2015-06-08 22:00:28 +08:00
21586586ba Merge pull request #1198 from kongjian/develop
remove space after int()& add sort for commentsRouter file
2015-06-08 20:20:40 +08:00
e1d7bc8826 remove space after int()& add sort for commentsRouter file 2015-06-08 17:25:46 +08:00
499ee09d4b Merge pull request #1194 from zieckey/GetMulti
Add GetMulti method for Cache interface
2015-06-08 08:36:20 +08:00
970f0b460c Add GetMulti method for Cache interface 2015-06-07 21:33:01 +08:00
9280683935 Merge pull request #1193 from zieckey/auth
Execute AUTH command when the "password" is configured
2015-06-07 20:34:32 +08:00
a58c8180e8 Execute AUTH command when the "password" is configured 2015-06-07 16:26:23 +08:00
b9852df51c Merge pull request #1190 from xboston/patch-1
fix session table
2015-06-04 22:09:26 +08:00
8e71d31dbe fix session table 2015-06-04 18:40:10 +05:00
db06e954b5 fix the session memcache bug 2015-05-28 12:04:19 +08:00
3abd01799d split into small files 2015-05-27 23:46:45 +08:00
ae37689314 fix #1176 grace support windows 2015-05-27 23:22:05 +08:00
40974365e6 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2015-05-25 09:10:59 +08:00
edbaa080f1 update the string 2015-05-25 09:10:38 +08:00
d56491ab3a sync beeApp.Server to graceful 2015-05-25 09:10:38 +08:00
9fd7acf663 fix #1152 2015-05-25 09:10:37 +08:00
2dca48f26e fix sesseion redis db error 2015-05-25 09:10:37 +08:00
4138fe0217 beego suppot graceful restart application 2015-05-25 09:10:37 +08:00
245762f7d9 add apk mime 2015-05-25 09:10:37 +08:00
18bdec951d fix #1143 2015-05-25 09:10:37 +08:00
9252301fa0 Fix save config ini file 2015-05-25 09:10:37 +08:00
1053b63bbc Improve documentation of filter.go. 2015-05-25 09:10:37 +08:00
3bd6caae0a set default timeout 2015-05-25 09:10:37 +08:00
d8734cf58d set default timeout 2015-05-25 09:10:37 +08:00
51161361db more fixed 2015-05-25 09:10:37 +08:00
6da0cdb9e2 no need lock here 2015-05-25 09:10:37 +08:00
9f070c622b no need defer here 2015-05-25 09:10:37 +08:00
738e22e389 zero timeout means wait until resp 2015-05-25 09:10:37 +08:00
69fc22f0df typo fixed 2015-05-25 09:10:37 +08:00
4cd7177ece typo fixed 2015-05-25 09:10:37 +08:00
8e618192c2 better code and fixed 2015-05-25 09:10:37 +08:00
3415a5b091 better go style 2015-05-25 09:10:37 +08:00
dfe055c47c remove useless comments 2015-05-25 09:10:36 +08:00
74b22649c8 remove the double isStruct/isStructPtr check 2015-05-25 09:10:36 +08:00
4255630564 add Recursively validation 2015-05-25 09:10:36 +08:00
7e3b5e5307 remove unreached code 2015-05-25 09:10:36 +08:00
0222b8d693 fixed: when RelatedSel have multi string/relation, it only get last string 2015-05-25 09:10:36 +08:00
23268b788a *feature) 增加logcalldepth接口,使得能简单再次封装log系列函数。 2015-05-25 09:10:36 +08:00
ee4fd60e4d no output the dump 2015-05-25 09:10:36 +08:00
4414659df4 fix the init struct 2015-05-25 09:10:36 +08:00
68ccd8e5a4 fix the dump has no body 2015-05-25 09:10:36 +08:00
6f802b0a05 fix the params 2015-05-25 09:10:36 +08:00
743628a946 add DumpRequest 2015-05-25 09:10:36 +08:00
d90ce15707 httplib support gzip 2015-05-25 09:10:36 +08:00
23457ed2a0 add sethost 2015-05-25 09:10:36 +08:00
d7791ba837 Update validators.go
//176 中国联通
//177 中国电信
//145 中国联通
//147 中国移动
//149 中国电信
2015-05-25 09:10:36 +08:00
676595213f fix a comment error. 2015-05-25 09:10:36 +08:00
d446b5b011 improve the defaultval 2015-05-25 09:10:36 +08:00
2ba12ad1e1 config read and set support Runmode 2015-05-25 09:10:35 +08:00
8cc57e2fc8 fix #1112 2015-05-25 09:10:35 +08:00
322b208566 Update session.go
remove = in if statement
2015-05-25 09:10:35 +08:00
fd610d6777 Update session.go
move expire in line 154 to 247, because it will cause session_cookie not writen to explorer
2015-05-25 09:10:35 +08:00
1148359570 session cookie support IE 2015-05-25 09:10:35 +08:00
55b1d6a897 Update validators.go
edit mobile regular expression ; add 184 Section No.
2015-05-25 09:10:35 +08:00
c4c9a50c42 fix #1081 2015-05-25 09:10:35 +08:00
a311d712a5 Update output.go 2015-05-25 09:10:35 +08:00
bb5351bb9f Update output.go
fix cookie not work in IE
2015-05-25 09:10:35 +08:00
26130a5df6 fix #1073 2015-05-25 09:10:35 +08:00
2c9363d29b add tests to ensure bool value require test always return true. 2015-05-25 09:10:35 +08:00
91886a4547 bugfix: if a form field type is bool, valid Required should always return true instead of return its value. 2015-05-25 09:10:35 +08:00
a7e60c93dc Set ErrorsMap to nil in Validation.Clear function 2015-05-25 09:10:35 +08:00
5b1705b2d6 Do not check log level in writerMsg() because the check is already done outside. 2015-05-25 09:10:35 +08:00
34940d00c0 Remove unnecessary optional group flag '?' since has to match one of comma or end of string 2015-05-25 09:10:35 +08:00
1a6ea693b5 Added to input.go: AcceptHtml, AcceptsXml and AcceptsJson functions which check the header agains a regex for simpler mult-content-type handling. 2015-05-25 09:10:35 +08:00
2e51c124f1 For enhancing performance, check log level before fmt.Sprintf() 2015-05-25 09:10:35 +08:00
1d8afdc9c9 gofmt -s & go_vet 2015-05-25 09:10:34 +08:00
1592e9c04d 验证码reload问题
当页面放置一段时间,验证码将从缓存中失效。当用户再来刷新验证码将出现验证码404。对于reload操作应该直接生成验证码。
2015-05-25 09:10:34 +08:00
bf6b0d3e1f add JsonBody 2015-05-25 09:10:34 +08:00
af71289c25 Merge pull request #1171 from astaxie/add-license
add missed LICENSE of captcha
2015-05-25 09:09:21 +08:00
b1efae6ff8 add missed LICENSE of captcha 2015-05-24 23:59:39 +08:00
92f3de4027 update the string 2015-05-20 11:09:30 +08:00
185089299c sync beeApp.Server to graceful 2015-05-20 11:07:23 +08:00
59c1e74e13 Merge pull request #1158 from mlgd/master
Fix save config ini file
2015-05-18 22:38:59 +08:00
9bb9855153 Let filter function get more params info from ctx.Input.Params 2015-05-18 14:42:18 +08:00
519602a553 fix #1152 2015-05-15 15:04:08 +08:00
740a526105 fix sesseion redis db error 2015-05-13 21:20:50 +08:00
98289cd8de beego suppot graceful restart application 2015-05-13 21:17:47 +08:00
56e2143630 add apk mime 2015-05-09 14:54:30 +08:00
c0cfb5277c Merge pull request #1127 from vanthanh2305/RelatedSel-multi-string/relation
RelatedSel multi string/relation
2015-05-07 23:12:29 +08:00
b29700c3c3 Merge pull request #1131 from JessonChan/develop
httplib more fixed
2015-05-07 22:59:41 +08:00
47be2fadb5 fix #1143 2015-05-05 21:36:31 +08:00
1d72629334 Fix save config ini file 2015-05-04 15:54:03 +02:00
b4880c5e1d Merge pull request #1140 from cr7pt0gr4ph7/doc-pull-request
Improve documentation of filter.go.
2015-05-04 09:47:25 +08:00
c0bb5b9237 Improve documentation of filter.go. 2015-05-03 23:21:32 +02:00
1e1068e81c Merge pull request #1132 from dafang/master
Recursively validations
2015-04-28 11:53:45 +08:00
6c3e274b6e set default timeout 2015-04-26 16:10:18 +08:00
f56bdb6284 set default timeout 2015-04-26 16:08:25 +08:00
d8fa118727 more fixed 2015-04-26 15:42:10 +08:00
0afd04ec6f no need lock here 2015-04-26 15:24:04 +08:00
973306b28d no need defer here 2015-04-26 02:19:38 +08:00
da8c3c3910 zero timeout means wait until resp 2015-04-26 02:17:46 +08:00
0c1bb6409a typo fixed 2015-04-26 02:06:19 +08:00
cddb4fdb60 typo fixed 2015-04-26 02:05:50 +08:00
e1d20aea5d better code and fixed 2015-04-26 02:04:34 +08:00
4498a02c15 better go style 2015-04-26 01:23:18 +08:00
5534258e22 remove useless comments 2015-04-24 11:17:12 +08:00
73650e1f2b remove the double isStruct/isStructPtr check 2015-04-24 11:14:49 +08:00
d0e7dd686b add Recursively validation 2015-04-24 10:58:46 +08:00
dc58ec8316 remove unreached code 2015-04-19 15:40:23 +08:00
6d72fc63ab fixed: when RelatedSel have multi string/relation, it only get last string 2015-04-15 17:41:41 +07:00
dd4afac5dc Merge pull request #1125 from wuranbo/dev_track
*feature) add BeeLogger.GetLogFuncCallDepth make simple wrapper log method aviable.
2015-04-13 12:49:51 +08:00
e229a4762f *feature) 增加logcalldepth接口,使得能简单再次封装log系列函数。 2015-04-13 12:17:44 +08:00
ecb27f34e6 no output the dump 2015-04-09 00:18:02 +08:00
d55997e520 fix the init struct 2015-04-09 00:12:41 +08:00
44c8534477 fix the dump has no body 2015-04-09 00:11:25 +08:00
44a39a6b3e fix the params 2015-04-08 23:00:08 +08:00
cc5ca458ab add DumpRequest 2015-04-08 22:58:37 +08:00
6f6b412709 httplib support gzip 2015-04-08 21:45:00 +08:00
642a69de02 add sethost 2015-04-08 20:12:10 +08:00
466f3c49c1 Merge pull request #1120 from peeped/develop
Update validators.go
2015-04-07 18:48:44 +08:00
d1c9cb2281 Update validators.go
//176 中国联通
//177 中国电信
//145 中国联通
//147 中国移动
//149 中国电信
2015-04-07 17:48:51 +08:00
9c9ffa202a Merge pull request #1119 from lionel0806/develop
fix a comment error.
2015-04-07 11:12:54 +08:00
56dfe418dd fix a comment error. 2015-04-07 10:35:18 +08:00
7caeb91f9b improve the defaultval 2015-04-05 23:23:35 +08:00
47848fa77b config read and set support Runmode 2015-04-05 23:21:13 +08:00
e0e8b98622 fix #1112 2015-04-05 23:12:29 +08:00
5c84ada389 Merge pull request #1106 from peeped/develop
Update validators.go
2015-04-04 20:46:43 +08:00
ac6203b81b Merge pull request #1114 from ElvizLai/patch-2
Patch 2
2015-04-04 13:03:44 +08:00
5e1e618d0f Update session.go
remove = in if statement
2015-04-04 00:44:22 +08:00
a5124a1d45 Update session.go
move expire in line 154 to 247, because it will cause session_cookie not writen to explorer
2015-04-03 17:41:09 +08:00
e675594e46 session cookie support IE 2015-04-02 14:02:39 +08:00
d02e32fa51 Merge pull request #1103 from ElvizLai/patch-1
Update output.go
2015-04-02 13:35:08 +08:00
8a82e25e85 Update validators.go
edit mobile regular expression ; add 184 Section No.
2015-04-02 12:07:12 +08:00
49c0f8906f fix #1081 2015-04-01 23:31:40 +08:00
9261c80509 Update output.go 2015-03-31 12:36:39 +08:00
217e24815b Update output.go
fix cookie not work in IE
2015-03-31 12:30:47 +08:00
840fd3b64f Merge pull request #1083 from supiyun/patch-1
验证码reload问题
2015-03-30 21:40:57 +08:00
eedaea2fea fix #1073 2015-03-30 20:35:57 +08:00
a002f78443 Merge pull request #1097 from pylemon/develop
form required validate for bool field bugfix
2015-03-27 23:04:07 +08:00
cdf9ff401f Merge pull request #1096 from byrnedo/develop
Set ErrorsMap to nil in Validation.Clear function
2015-03-27 14:46:45 +08:00
caa260f053 add tests to ensure bool value require test always return true. 2015-03-27 13:43:20 +08:00
5fa55ca2d3 bugfix: if a form field type is bool, valid Required should always return true instead of return its value. 2015-03-27 13:30:59 +08:00
260b5b1951 Set ErrorsMap to nil in Validation.Clear function 2015-03-26 20:23:00 +01:00
54ae4bc25b Merge pull request #1094 from toalexjin/check_log_level
For enhancing performance, check log level before fmt.Sprintf()
2015-03-26 21:51:23 +08:00
162bee1b43 Merge pull request #1095 from byrnedo/develop
Added AcceptHtml/Json/Xml function to input
2015-03-26 21:50:41 +08:00
533b00ae56 Merge branch 'master' into develop 2015-03-26 08:40:28 +01:00
d3cdebbee2 Do not check log level in writerMsg() because the check is already done outside. 2015-03-26 14:40:12 +08:00
06b5c7f644 Remove unnecessary optional group flag '?' since has to match one of comma or end of string 2015-03-25 14:54:39 +01:00
185ee872c4 Added to input.go: AcceptHtml, AcceptsXml and AcceptsJson functions which check the header agains a regex for simpler mult-content-type handling. 2015-03-25 14:47:20 +01:00
74e0af4a9a For enhancing performance, check log level before fmt.Sprintf() 2015-03-25 15:14:57 +08:00
8aa9455900 gofmt -s & go_vet 2015-03-19 22:29:01 -07:00
2d26f7df2f 验证码reload问题
当页面放置一段时间,验证码将从缓存中失效。当用户再来刷新验证码将出现验证码404。对于reload操作应该直接生成验证码。
2015-03-16 17:40:55 +08:00
3d6408cfc2 Merge pull request #1070 from fugr/patch-5
add JsonBody
2015-03-13 23:16:18 +08:00
223f57bb4c add JsonBody 2015-03-06 14:12:24 +08:00
c4aa33fb1b Merge pull request #1052 from dockercn/develop
Fix the wrong parameter bug in ledis session support cause build failure
2015-03-01 13:58:21 +08:00
20cc5b261e Reform the ledis_session.go 2015-03-01 12:59:34 +08:00
7ec2a077d9 Fix the wrong parameter bug in ledis session. 2015-03-01 12:03:03 +08:00
9f70561f21 Merge remote-tracking branch 'upstream/master' into develop 2015-03-01 11:47:02 +08:00
647e6ae1c4 Added JWT plugin 2014-11-24 23:21:03 +08:00
200 changed files with 17203 additions and 6883 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?

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
.idea .idea
.vscode
.DS_Store .DS_Store
*.swp *.swp
*.swo *.swo
beego.iml

51
.travis.yml Normal file
View File

@ -0,0 +1,51 @@
language: go
go:
- 1.6
- 1.5.3
- 1.4.3
services:
- redis-server
- mysql
- postgresql
- memcached
env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
before_install:
- git clone git://github.com/ideawu/ssdb.git
- cd ssdb
- make
- cd ..
install:
- go get github.com/lib/pq
- go get github.com/go-sql-driver/mysql
- go get github.com/mattn/go-sqlite3
- go get github.com/bradfitz/gomemcache/memcache
- go get github.com/garyburd/redigo/redis
- go get github.com/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/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis
- go get github.com/ssdb/gossdb/ssdb
- go get github.com/cloudflare/golz4
- go get github.com/gogo/protobuf/proto
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"

52
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,52 @@
# Contributing to beego
beego is an open source project.
It is the work of hundreds of contributors. We appreciate your help!
Here are instructions to get you started. They are probably not perfect,
please let us know if anything feels wrong or incomplete.
## Contribution guidelines
### Pull requests
First of all. beego follow the gitflow. So please send you pull request
to **develop** branch. We will close the pull request to master branch.
We are always happy to receive pull requests, and do our best to
review them as fast as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be
discouraged! Sometimes we can make a mistake, please do more explaining
for us. We will appreciate it.
We're trying very hard to keep beego simple and fast. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. But we will give you some advice on how to
do it in other way.
### Create issues
Any significant improvement should be documented as [a GitHub
issue](https://github.com/astaxie/beego/issues) before anybody
starts working on it.
Also when filing an issue, make sure to answer these five questions:
- What version of beego are you using (bee version)?
- What operating system and processor architecture are you using?
- What did you do?
- What did you expect to see?
- What did you see instead?
### but check existing issues and docs first!
Please take a moment to check that an issue doesn't already exist
documenting your bug report or improvement proposal. If it does, it
never hurts to add a quick "+1" or "I have this problem too". This will
help prioritize the most common problems and requests.
Also if you don't know how to use it. please make sure you have read though
the docs in http://beego.me/docs

View File

@ -1,16 +1,39 @@
## Beego ## Beego
[![Build Status](https://drone.io/github.com/astaxie/beego/status.png)](https://drone.io/github.com/astaxie/beego/latest) [![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) [![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 an open-source, high-performance, modular, full-stack web framework. 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.
More info [beego.me](http://beego.me) More info [beego.me](http://beego.me)
## Installation ##Quick Start
######Download and install
go get github.com/astaxie/beego go get github.com/astaxie/beego
######Create file `hello.go`
```go
package main
import "github.com/astaxie/beego"
func main(){
beego.Run()
}
```
######Build and run
```bash
go build hello.go
./hello
```
######Congratulations!
You just built your first beego app.
Open your browser and visit `http://localhost:8080`.
Please see [Documentation](http://beego.me/docs) for more.
## Features ## Features
* RESTful support * RESTful support
@ -26,12 +49,14 @@ More info [beego.me](http://beego.me)
* [English](http://beego.me/docs/intro/) * [English](http://beego.me/docs/intro/)
* [中文文档](http://beego.me/docs/intro/) * [中文文档](http://beego.me/docs/intro/)
* [Русский](http://beego.me/docs/intro/)
## Community ## Community
* [http://beego.me/community](http://beego.me/community) * [http://beego.me/community](http://beego.me/community)
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
## LICENSE ## LICENSE
beego is licensed under the Apache Licence, Version 2.0 beego source code is licensed under the Apache Licence, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0.html). (http://www.apache.org/licenses/LICENSE-2.0.html).

419
admin.go
View File

@ -19,9 +19,14 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"os"
"text/template" "text/template"
"time" "time"
"reflect"
"github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/toolbox" "github.com/astaxie/beego/toolbox"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
@ -63,24 +68,15 @@ func init() {
// AdminIndex is the default http.Handler for admin module. // AdminIndex is the default http.Handler for admin module.
// it matches url pattern "/". // it matches url pattern "/".
func adminIndex(rw http.ResponseWriter, r *http.Request) { func adminIndex(rw http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
tmpl = template.Must(tmpl.Parse(indexTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
data := make(map[interface{}]interface{})
tmpl.Execute(rw, data)
} }
// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. // QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
// it's registered with url pattern "/qbs" in admin module. // it's registered with url pattern "/qbs" in admin module.
func qpsIndex(rw http.ResponseWriter, r *http.Request) { func qpsIndex(rw http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(qpsTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
data := make(map[interface{}]interface{}) data := make(map[interface{}]interface{})
data["Content"] = toolbox.StatisticsMap.GetMap() data["Content"] = toolbox.StatisticsMap.GetMap()
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
tmpl.Execute(rw, data)
} }
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair. // ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
@ -88,178 +84,119 @@ func qpsIndex(rw http.ResponseWriter, r *http.Request) {
func listConf(rw http.ResponseWriter, r *http.Request) { func listConf(rw http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
command := r.Form.Get("command") command := r.Form.Get("command")
if command != "" { if command == "" {
data := make(map[interface{}]interface{}) rw.Write([]byte("command not support"))
switch command { return
case "conf": }
m := make(map[string]interface{})
m["AppName"] = AppName data := make(map[interface{}]interface{})
m["AppPath"] = AppPath switch command {
m["AppConfigPath"] = AppConfigPath case "conf":
m["StaticDir"] = StaticDir m := make(map[string]interface{})
m["StaticExtensionsToGzip"] = StaticExtensionsToGzip list("BConfig", BConfig, m)
m["HttpAddr"] = HttpAddr m["AppConfigPath"] = appConfigPath
m["HttpPort"] = HttpPort m["AppConfigProvider"] = appConfigProvider
m["HttpTLS"] = EnableHttpTLS tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
m["HttpCertFile"] = HttpCertFile tmpl = template.Must(tmpl.Parse(configTpl))
m["HttpKeyFile"] = HttpKeyFile tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
m["RecoverPanic"] = RecoverPanic
m["AutoRender"] = AutoRender
m["ViewsPath"] = ViewsPath
m["RunMode"] = RunMode
m["SessionOn"] = SessionOn
m["SessionProvider"] = SessionProvider
m["SessionName"] = SessionName
m["SessionGCMaxLifetime"] = SessionGCMaxLifetime
m["SessionSavePath"] = SessionSavePath
m["SessionCookieLifeTime"] = SessionCookieLifeTime
m["UseFcgi"] = UseFcgi
m["MaxMemory"] = MaxMemory
m["EnableGzip"] = EnableGzip
m["DirectoryIndex"] = DirectoryIndex
m["HttpServerTimeOut"] = HttpServerTimeOut
m["ErrorsShow"] = ErrorsShow
m["XSRFKEY"] = XSRFKEY
m["EnableXSRF"] = EnableXSRF
m["XSRFExpire"] = XSRFExpire
m["CopyRequestBody"] = CopyRequestBody
m["TemplateLeft"] = TemplateLeft
m["TemplateRight"] = TemplateRight
m["BeegoServerName"] = BeegoServerName
m["EnableAdmin"] = EnableAdmin
m["AdminHttpAddr"] = AdminHttpAddr
m["AdminHttpPort"] = AdminHttpPort
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) data["Content"] = m
tmpl = template.Must(tmpl.Parse(configTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
data["Content"] = m tmpl.Execute(rw, data)
tmpl.Execute(rw, data) case "router":
var (
case "router": content = map[string]interface{}{
content := make(map[string]interface{}) "Fields": []string{
"Router Pattern",
var fields = []string{ "Methods",
fmt.Sprintf("Router Pattern"), "Controller",
fmt.Sprintf("Methods"), },
fmt.Sprintf("Controller"),
} }
content["Fields"] = fields methods = []string{}
methodsData = make(map[string]interface{})
)
for method, t := range BeeApp.Handlers.routers {
methods := []string{} resultList := new([][]string)
methodsData := make(map[string]interface{})
for method, t := range BeeApp.Handlers.routers {
resultList := new([][]string) printTree(resultList, t)
printTree(resultList, t) methods = append(methods, method)
methodsData[method] = resultList
methods = append(methods, method) }
methodsData[method] = resultList
} content["Data"] = methodsData
content["Methods"] = methods
content["Data"] = methodsData data["Content"] = content
content["Methods"] = methods data["Title"] = "Routers"
data["Content"] = content execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
data["Title"] = "Routers" case "filter":
var (
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) content = map[string]interface{}{
tmpl = template.Must(tmpl.Parse(routerAndFilterTpl)) "Fields": []string{
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) "Router Pattern",
tmpl.Execute(rw, data) "Filter Function",
case "filter": },
content := make(map[string]interface{}) }
filterTypes = []string{}
var fields = []string{ filterTypeData = make(map[string]interface{})
fmt.Sprintf("Router Pattern"), )
fmt.Sprintf("Filter Function"),
} if BeeApp.Handlers.enableFilter {
content["Fields"] = fields var filterType string
for k, fr := range map[int]string{
filterTypes := []string{} BeforeStatic: "Before Static",
filterTypeData := make(map[string]interface{}) BeforeRouter: "Before Router",
BeforeExec: "Before Exec",
if BeeApp.Handlers.enableFilter { AfterExec: "After Exec",
var filterType string FinishRouter: "Finish Router"} {
if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
if bf, ok := BeeApp.Handlers.filters[BeforeRouter]; ok { filterType = fr
filterType = "Before Router" filterTypes = append(filterTypes, filterType)
filterTypes = append(filterTypes, filterType) resultList := new([][]string)
resultList := new([][]string) for _, f := range bf {
for _, f := range bf { var result = []string{
fmt.Sprintf("%s", f.pattern),
var result = []string{ fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
fmt.Sprintf("%s", f.pattern), }
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), *resultList = append(*resultList, result)
} }
*resultList = append(*resultList, result) filterTypeData[filterType] = resultList
} }
filterTypeData[filterType] = resultList }
} }
if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok { content["Data"] = filterTypeData
filterType = "Before Exec" content["Methods"] = filterTypes
filterTypes = append(filterTypes, filterType)
resultList := new([][]string) data["Content"] = content
for _, f := range bf { data["Title"] = "Filters"
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
var result = []string{ default:
fmt.Sprintf("%s", f.pattern), rw.Write([]byte("command not support"))
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), }
} }
*resultList = append(*resultList, result)
} func list(root string, p interface{}, m map[string]interface{}) {
filterTypeData[filterType] = resultList pt := reflect.TypeOf(p)
} pv := reflect.ValueOf(p)
if pt.Kind() == reflect.Ptr {
if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok { pt = pt.Elem()
filterType = "After Exec" pv = pv.Elem()
filterTypes = append(filterTypes, filterType) }
resultList := new([][]string) for i := 0; i < pv.NumField(); i++ {
for _, f := range bf { var key string
if root == "" {
var result = []string{ key = pt.Field(i).Name
fmt.Sprintf("%s", f.pattern), } else {
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)), key = root + "." + pt.Field(i).Name
} }
*resultList = append(*resultList, result) if pv.Field(i).Kind() == reflect.Struct {
} list(key, pv.Field(i).Interface(), m)
filterTypeData[filterType] = resultList } else {
} m[key] = pv.Field(i).Interface()
if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok {
filterType = "Finish Router"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
var result = []string{
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
}
content["Data"] = filterTypeData
content["Methods"] = filterTypes
data["Content"] = content
data["Title"] = "Filters"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(routerAndFilterTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
tmpl.Execute(rw, data)
default:
rw.Write([]byte("command not support"))
} }
} else {
} }
} }
@ -274,23 +211,23 @@ func printTree(resultList *[][]string, t *Tree) {
if v, ok := l.runObject.(*controllerInfo); ok { if v, ok := l.runObject.(*controllerInfo); ok {
if v.routerType == routerTypeBeego { if v.routerType == routerTypeBeego {
var result = []string{ var result = []string{
fmt.Sprintf("%s", v.pattern), v.pattern,
fmt.Sprintf("%s", v.methods), fmt.Sprintf("%s", v.methods),
fmt.Sprintf("%s", v.controllerType), fmt.Sprintf("%s", v.controllerType),
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} else if v.routerType == routerTypeRESTFul { } else if v.routerType == routerTypeRESTFul {
var result = []string{ var result = []string{
fmt.Sprintf("%s", v.pattern), v.pattern,
fmt.Sprintf("%s", v.methods), fmt.Sprintf("%s", v.methods),
fmt.Sprintf(""), "",
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} else if v.routerType == routerTypeHandler { } else if v.routerType == routerTypeHandler {
var result = []string{ var result = []string{
fmt.Sprintf("%s", v.pattern), v.pattern,
fmt.Sprintf(""), "",
fmt.Sprintf(""), "",
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} }
@ -303,54 +240,49 @@ func printTree(resultList *[][]string, t *Tree) {
func profIndex(rw http.ResponseWriter, r *http.Request) { func profIndex(rw http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
command := r.Form.Get("command") command := r.Form.Get("command")
format := r.Form.Get("format") if command == "" {
data := make(map[string]interface{}) return
}
var result bytes.Buffer var (
if command != "" { format = r.Form.Get("format")
toolbox.ProcessInput(command, &result) data = make(map[interface{}]interface{})
data["Content"] = result.String() result bytes.Buffer
)
toolbox.ProcessInput(command, &result)
data["Content"] = result.String()
if format == "json" && command == "gc summary" { if format == "json" && command == "gc summary" {
dataJson, err := json.Marshal(data) dataJSON, err := json.Marshal(data)
if err != nil { if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError) http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.Write(dataJson)
return return
} }
data["Title"] = command rw.Header().Set("Content-Type", "application/json")
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) rw.Write(dataJSON)
tmpl = template.Must(tmpl.Parse(profillingTpl)) return
if command == "gc summary" {
tmpl = template.Must(tmpl.Parse(gcAjaxTpl))
} else {
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
}
tmpl.Execute(rw, data)
} else {
} }
data["Title"] = command
defaultTpl := defaultScriptsTpl
if command == "gc summary" {
defaultTpl = gcAjaxTpl
}
execTpl(rw, data, profillingTpl, defaultTpl)
} }
// Healthcheck is a http.Handler calling health checking and showing the result. // Healthcheck is a http.Handler calling health checking and showing the result.
// it's in "/healthcheck" pattern in admin module. // it's in "/healthcheck" pattern in admin module.
func healthcheck(rw http.ResponseWriter, req *http.Request) { func healthcheck(rw http.ResponseWriter, req *http.Request) {
data := make(map[interface{}]interface{}) var (
data = make(map[interface{}]interface{})
var result = []string{} result = []string{}
fields := []string{ resultList = new([][]string)
fmt.Sprintf("Name"), content = map[string]interface{}{
fmt.Sprintf("Message"), "Fields": []string{"Name", "Message", "Status"},
fmt.Sprintf("Status"), }
} )
resultList := new([][]string)
content := make(map[string]interface{})
for name, h := range toolbox.AdminCheckList { for name, h := range toolbox.AdminCheckList {
if err := h.Check(); err != nil { if err := h.Check(); err != nil {
@ -370,16 +302,10 @@ func healthcheck(rw http.ResponseWriter, req *http.Request) {
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} }
content["Fields"] = fields
content["Data"] = resultList content["Data"] = resultList
data["Content"] = content data["Content"] = content
data["Title"] = "Health Check" data["Title"] = "Health Check"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
tmpl = template.Must(tmpl.Parse(healthCheckTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
tmpl.Execute(rw, data)
} }
// TaskStatus is a http.Handler with running task status (task name, status and the last execution). // TaskStatus is a http.Handler with running task status (task name, status and the last execution).
@ -391,10 +317,8 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
req.ParseForm() req.ParseForm()
taskname := req.Form.Get("taskname") taskname := req.Form.Get("taskname")
if taskname != "" { if taskname != "" {
if t, ok := toolbox.AdminTaskList[taskname]; ok { if t, ok := toolbox.AdminTaskList[taskname]; ok {
err := t.Run() if err := t.Run(); err != nil {
if err != nil {
data["Message"] = []string{"error", fmt.Sprintf("%s", err)} data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
} }
data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus())} data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus())}
@ -408,18 +332,18 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
resultList := new([][]string) resultList := new([][]string)
var result = []string{} var result = []string{}
var fields = []string{ var fields = []string{
fmt.Sprintf("Task Name"), "Task Name",
fmt.Sprintf("Task Spec"), "Task Spec",
fmt.Sprintf("Task Status"), "Task Status",
fmt.Sprintf("Last Time"), "Last Time",
fmt.Sprintf(""), "",
} }
for tname, tk := range toolbox.AdminTaskList { for tname, tk := range toolbox.AdminTaskList {
result = []string{ result = []string{
fmt.Sprintf("%s", tname), tname,
fmt.Sprintf("%s", tk.GetSpec()), fmt.Sprintf("%s", tk.GetSpec()),
fmt.Sprintf("%s", tk.GetStatus()), fmt.Sprintf("%s", tk.GetStatus()),
fmt.Sprintf("%s", tk.GetPrev().String()), tk.GetPrev().String(),
} }
*resultList = append(*resultList, result) *resultList = append(*resultList, result)
} }
@ -428,9 +352,14 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
content["Data"] = resultList content["Data"] = resultList
data["Content"] = content data["Content"] = content
data["Title"] = "Tasks" data["Title"] = "Tasks"
execTpl(rw, data, tasksTpl, defaultScriptsTpl)
}
func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl)) tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(tasksTpl)) for _, tpl := range tpls {
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl)) tmpl = template.Must(tmpl.Parse(tpl))
}
tmpl.Execute(rw, data) tmpl.Execute(rw, data)
} }
@ -450,17 +379,23 @@ func (admin *adminApp) Run() {
if len(toolbox.AdminTaskList) > 0 { if len(toolbox.AdminTaskList) > 0 {
toolbox.StartTask() toolbox.StartTask()
} }
addr := AdminHttpAddr addr := BConfig.Listen.AdminAddr
if AdminHttpPort != 0 { if BConfig.Listen.AdminPort != 0 {
addr = fmt.Sprintf("%s:%d", AdminHttpAddr, AdminHttpPort) addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort)
} }
for p, f := range admin.routers { for p, f := range admin.routers {
http.Handle(p, f) http.Handle(p, f)
} }
BeeLogger.Info("Admin server Running on %s", addr) logs.Info("Admin server Running on %s", addr)
err := http.ListenAndServe(addr, nil)
var err error
if BConfig.Listen.Graceful {
err = grace.ListenAndServe(addr, nil)
} else {
err = http.ListenAndServe(addr, nil)
}
if err != nil { if err != nil {
BeeLogger.Critical("Admin ListenAndServe: ", err) 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}} {{range $i, $elem := .Content.Data}}
<tr> <tr>
{{range $elem}} <td>{{index $elem 0}}</td>
<td> <td>{{index $elem 1}}</td>
{{.}} <td>{{index $elem 2}}</td>
</td> <td data-order="{{index $elem 3}}">{{index $elem 4}}</td>
{{end}} <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> </tr>
{{end}} {{end}}
</tbody> </tbody>

364
app.go
View File

@ -20,14 +20,27 @@ import (
"net/http" "net/http"
"net/http/fcgi" "net/http/fcgi"
"os" "os"
"path"
"time" "time"
"github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
var (
// BeeApp is an application instance
BeeApp *App
)
func init() {
// create beego application
BeeApp = NewApp()
}
// App defines beego application with a new PatternServeMux. // App defines beego application with a new PatternServeMux.
type App struct { type App struct {
Handlers *ControllerRegistor Handlers *ControllerRegister
Server *http.Server Server *http.Server
} }
@ -40,93 +53,314 @@ func NewApp() *App {
// Run beego application. // Run beego application.
func (app *App) Run() { func (app *App) Run() {
addr := HttpAddr addr := BConfig.Listen.HTTPAddr
if HttpPort != 0 { if BConfig.Listen.HTTPPort != 0 {
addr = fmt.Sprintf("%s:%d", HttpAddr, HttpPort) addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort)
} }
var ( var (
err error err error
l net.Listener l net.Listener
endRunning = make(chan bool, 1)
) )
endRunning := make(chan bool, 1)
if UseFcgi { // run cgi server
if UseStdIo { if BConfig.Listen.EnableFcgi {
err = fcgi.Serve(nil, app.Handlers) // standard I/O if BConfig.Listen.EnableStdIo {
if err == nil { 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 { } else {
BeeLogger.Info("Cannot use FCGI via standard I/O", err) logs.Critical("Cannot use FCGI via standard I/O", err)
} }
} else { return
if HttpPort == 0 {
// remove the Socket file before start
if utils.FileExists(addr) {
os.Remove(addr)
}
l, err = net.Listen("unix", addr)
} else {
l, err = net.Listen("tcp", addr)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
} }
} else { if BConfig.Listen.HTTPPort == 0 {
app.Server.Addr = addr // remove the Socket file before start
app.Server.Handler = app.Handlers if utils.FileExists(addr) {
app.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second os.Remove(addr)
app.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second }
l, err = net.Listen("unix", addr)
} else {
l, err = net.Listen("tcp", addr)
}
if err != nil {
logs.Critical("Listen: ", err)
}
if err = fcgi.Serve(l, app.Handlers); err != nil {
logs.Critical("fcgi.Serve: ", err)
}
return
}
if EnableHttpTLS { 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 {
httpsAddr := BConfig.Listen.HTTPSAddr
app.Server.Addr = httpsAddr
if BConfig.Listen.EnableHTTPS {
go func() { go func() {
time.Sleep(20 * time.Microsecond) time.Sleep(20 * time.Microsecond)
if HttpsPort != 0 { if BConfig.Listen.HTTPSPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort) httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
app.Server.Addr = httpsAddr
} }
BeeLogger.Info("https server Running on %s", app.Server.Addr) server := grace.NewServer(httpsAddr, app.Handlers)
err := app.Server.ListenAndServeTLS(HttpCertFile, HttpKeyFile) server.Server.ReadTimeout = app.Server.ReadTimeout
if err != nil { server.Server.WriteTimeout = app.Server.WriteTimeout
BeeLogger.Critical("ListenAndServeTLS: ", err) if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond) time.Sleep(100 * time.Microsecond)
endRunning <- true endRunning <- true
} }
}() }()
} }
if BConfig.Listen.EnableHTTP {
if EnableHttpListen {
go func() { go func() {
app.Server.Addr = addr server := grace.NewServer(addr, app.Handlers)
BeeLogger.Info("http server Running on %s", app.Server.Addr) server.Server.ReadTimeout = app.Server.ReadTimeout
if ListenTCP4 && HttpAddr == "" { server.Server.WriteTimeout = app.Server.WriteTimeout
ln, err := net.Listen("tcp4", app.Server.Addr) if BConfig.Listen.ListenTCP4 {
if err != nil { server.Network = "tcp4"
BeeLogger.Critical("ListenAndServe: ", err) }
time.Sleep(100 * time.Microsecond) if err := server.ListenAndServe(); err != nil {
endRunning <- true logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
return time.Sleep(100 * time.Microsecond)
} endRunning <- true
err = app.Server.Serve(ln)
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
} else {
err := app.Server.ListenAndServe()
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
} }
}() }()
} }
<-endRunning
return
} }
// run normal mode
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
}
logs.Info("https server Running on https://%s", app.Server.Addr)
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
}
if BConfig.Listen.EnableHTTP {
go func() {
app.Server.Addr = 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 {
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
if err = app.Server.Serve(ln); err != nil {
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
} else {
if err := app.Server.ListenAndServe(); err != nil {
logs.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}
}()
}
<-endRunning <-endRunning
} }
// Router adds a patterned controller handler to BeeApp.
// it's an alias method of App.Router.
// usage:
// simple router
// beego.Router("/admin", &admin.UserController{})
// beego.Router("/admin/index", &admin.ArticleController{})
//
// regex router
//
// beego.Router("/api/:id([0-9]+)", &controllers.RController{})
//
// custom rules
// beego.Router("/api/list",&RestController{},"*:ListFood")
// beego.Router("/api/create",&RestController{},"post:CreateFood")
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
return BeeApp
}
// Include will generate router file in the router/xxx.go from the controller's comments
// usage:
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
// type BankAccount struct{
// beego.Controller
// }
//
// register the function
// func (b *BankAccount)Mapping(){
// b.Mapping("ShowAccount" , b.ShowAccount)
// b.Mapping("ModifyAccount", b.ModifyAccount)
//}
//
// //@router /account/:id [get]
// func (b *BankAccount) ShowAccount(){
// //logic
// }
//
//
// //@router /account/:id [post]
// func (b *BankAccount) ModifyAccount(){
// //logic
// }
//
// the comments @router url methodlist
// url support all the function Router's pattern
// methodlist [get post head put delete options *]
func Include(cList ...ControllerInterface) *App {
BeeApp.Handlers.Include(cList...)
return BeeApp
}
// RESTRouter adds a restful controller handler to BeeApp.
// its' controller implements beego.ControllerInterface and
// defines a param "pattern/:objectId" to visit each resource.
func RESTRouter(rootpath string, c ControllerInterface) *App {
Router(rootpath, c)
Router(path.Join(rootpath, ":objectId"), c)
return BeeApp
}
// AutoRouter adds defined controller handler to BeeApp.
// it's same to App.AutoRouter.
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
// visit the url /main/list to exec List function or /main/page to exec Page function.
func AutoRouter(c ControllerInterface) *App {
BeeApp.Handlers.AddAuto(c)
return BeeApp
}
// AutoPrefix adds controller handler to BeeApp with prefix.
// it's same to App.AutoRouterWithPrefix.
// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page,
// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function.
func AutoPrefix(prefix string, c ControllerInterface) *App {
BeeApp.Handlers.AddAutoPrefix(prefix, c)
return BeeApp
}
// Get used to register router for Get method
// usage:
// beego.Get("/", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Get(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Get(rootpath, f)
return BeeApp
}
// Post used to register router for Post method
// usage:
// beego.Post("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Post(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Post(rootpath, f)
return BeeApp
}
// Delete used to register router for Delete method
// usage:
// beego.Delete("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Delete(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Delete(rootpath, f)
return BeeApp
}
// Put used to register router for Put method
// usage:
// beego.Put("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Put(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Put(rootpath, f)
return BeeApp
}
// Head used to register router for Head method
// usage:
// beego.Head("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Head(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Head(rootpath, f)
return BeeApp
}
// Options used to register router for Options method
// usage:
// beego.Options("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Options(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Options(rootpath, f)
return BeeApp
}
// Patch used to register router for Patch method
// usage:
// beego.Patch("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Patch(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Patch(rootpath, f)
return BeeApp
}
// Any used to register router for all methods
// usage:
// beego.Any("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Any(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Any(rootpath, f)
return BeeApp
}
// Handler used to register a Handler router
// usage:
// beego.Handler("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
BeeApp.Handlers.Handler(rootpath, h, options...)
return BeeApp
}
// InsertFilter adds a FilterFunc with pattern condition and action constant.
// The pos means action constant including
// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
return BeeApp
}

336
beego.go
View File

@ -12,243 +12,34 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// beego is an open-source, high-performance, modularity, full-stack web framework
//
// package main
//
// import "github.com/astaxie/beego"
//
// func main() {
// beego.Run()
// }
//
// more infomation: http://beego.me
package beego package beego
import ( import (
"net/http"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"github.com/astaxie/beego/session"
) )
// beego web framework version. const (
const VERSION = "1.4.3" // VERSION represent beego web framework version.
VERSION = "1.8.0"
type hookfunc func() error //hook function to run // DEV is for develop
var hooks []hookfunc //hook function slice to store the hookfunc DEV = "dev"
// PROD is for production
PROD = "prod"
)
// Router adds a patterned controller handler to BeeApp. //hook function to run
// it's an alias method of App.Router. type hookfunc func() error
// usage:
// simple router
// beego.Router("/admin", &admin.UserController{})
// beego.Router("/admin/index", &admin.ArticleController{})
//
// regex router
//
// beego.Router("/api/:id([0-9]+)", &controllers.RController{})
//
// custom rules
// beego.Router("/api/list",&RestController{},"*:ListFood")
// beego.Router("/api/create",&RestController{},"post:CreateFood")
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
return BeeApp
}
// Router add list from var (
// usage: hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{}) )
// type BankAccount struct{
// beego.Controller
// }
//
// register the function
// func (b *BankAccount)Mapping(){
// b.Mapping("ShowAccount" , b.ShowAccount)
// b.Mapping("ModifyAccount", b.ModifyAccount)
//}
//
// //@router /account/:id [get]
// func (b *BankAccount) ShowAccount(){
// //logic
// }
//
//
// //@router /account/:id [post]
// func (b *BankAccount) ModifyAccount(){
// //logic
// }
//
// the comments @router url methodlist
// url support all the function Router's pattern
// methodlist [get post head put delete options *]
func Include(cList ...ControllerInterface) *App {
BeeApp.Handlers.Include(cList...)
return BeeApp
}
// RESTRouter adds a restful controller handler to BeeApp. // AddAPPStartHook is used to register the hookfunc
// its' controller implements beego.ControllerInterface and // The hookfuncs will run in beego.Run()
// defines a param "pattern/:objectId" to visit each resource.
func RESTRouter(rootpath string, c ControllerInterface) *App {
Router(rootpath, c)
Router(path.Join(rootpath, ":objectId"), c)
return BeeApp
}
// AutoRouter adds defined controller handler to BeeApp.
// it's same to App.AutoRouter.
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
// visit the url /main/list to exec List function or /main/page to exec Page function.
func AutoRouter(c ControllerInterface) *App {
BeeApp.Handlers.AddAuto(c)
return BeeApp
}
// AutoPrefix adds controller handler to BeeApp with prefix.
// it's same to App.AutoRouterWithPrefix.
// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page,
// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function.
func AutoPrefix(prefix string, c ControllerInterface) *App {
BeeApp.Handlers.AddAutoPrefix(prefix, c)
return BeeApp
}
// register router for Get method
// usage:
// beego.Get("/", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Get(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Get(rootpath, f)
return BeeApp
}
// register router for Post method
// usage:
// beego.Post("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Post(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Post(rootpath, f)
return BeeApp
}
// register router for Delete method
// usage:
// beego.Delete("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Delete(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Delete(rootpath, f)
return BeeApp
}
// register router for Put method
// usage:
// beego.Put("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Put(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Put(rootpath, f)
return BeeApp
}
// register router for Head method
// usage:
// beego.Head("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Head(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Head(rootpath, f)
return BeeApp
}
// register router for Options method
// usage:
// beego.Options("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Options(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Options(rootpath, f)
return BeeApp
}
// register router for Patch method
// usage:
// beego.Patch("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Patch(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Patch(rootpath, f)
return BeeApp
}
// register router for all method
// usage:
// beego.Any("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Any(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Any(rootpath, f)
return BeeApp
}
// register router for own Handler
// usage:
// beego.Handler("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
BeeApp.Handlers.Handler(rootpath, h, options...)
return BeeApp
}
// SetViewsPath sets view directory path in beego application.
func SetViewsPath(path string) *App {
ViewsPath = path
return BeeApp
}
// SetStaticPath sets static directory path and proper url pattern in beego application.
// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public".
func SetStaticPath(url string, path string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
StaticDir[url] = path
return BeeApp
}
// DelStaticPath removes the static folder setting in this url pattern in beego application.
func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
delete(StaticDir, url)
return BeeApp
}
// InsertFilter adds a FilterFunc with pattern condition and action constant.
// The pos means action constant including
// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
return BeeApp
}
// The hookfunc will run in beego.Run()
// such as sessionInit, middlerware start, buildtemplate, admin start // such as sessionInit, middlerware start, buildtemplate, admin start
func AddAPPStartHook(hf hookfunc) { func AddAPPStartHook(hf hookfunc) {
hooks = append(hooks, hf) hooks = append(hooks, hf)
@ -256,97 +47,54 @@ func AddAPPStartHook(hf hookfunc) {
// Run beego application. // Run beego application.
// beego.Run() default run on HttpPort // beego.Run() default run on HttpPort
// beego.Run("localhost")
// beego.Run(":8089") // beego.Run(":8089")
// beego.Run("127.0.0.1:8089") // beego.Run("127.0.0.1:8089")
func Run(params ...string) { func Run(params ...string) {
initBeforeHTTPRun()
if len(params) > 0 && params[0] != "" { if len(params) > 0 && params[0] != "" {
strs := strings.Split(params[0], ":") strs := strings.Split(params[0], ":")
if len(strs) > 0 && strs[0] != "" { if len(strs) > 0 && strs[0] != "" {
HttpAddr = strs[0] BConfig.Listen.HTTPAddr = strs[0]
} }
if len(strs) > 1 && strs[1] != "" { if len(strs) > 1 && strs[1] != "" {
HttpPort, _ = strconv.Atoi(strs[1]) BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
} }
} }
initBeforeHttpRun()
if EnableAdmin {
go beeAdminApp.Run()
}
BeeApp.Run() BeeApp.Run()
} }
func initBeforeHttpRun() { func initBeforeHTTPRun() {
// if AppConfigPath not In the conf/app.conf reParse config //init hooks
if AppConfigPath != filepath.Join(AppPath, "conf", "app.conf") { AddAPPStartHook(registerMime)
err := ParseConfig() AddAPPStartHook(registerDefaultErrorHandler)
if err != nil && AppConfigPath != filepath.Join(workPath, "conf", "app.conf") { AddAPPStartHook(registerSession)
// configuration is critical to app, panic here if parse failed AddAPPStartHook(registerTemplate)
panic(err) AddAPPStartHook(registerAdmin)
} AddAPPStartHook(registerGzip)
}
//init mime
AddAPPStartHook(initMime)
// do hooks function
for _, hk := range hooks { for _, hk := range hooks {
err := hk() if err := hk(); err != nil {
if err != nil {
panic(err) panic(err)
} }
} }
if SessionOn {
var err error
sessionConfig := AppConfig.String("sessionConfig")
if sessionConfig == "" {
sessionConfig = `{"cookieName":"` + SessionName + `",` +
`"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` +
`"providerConfig":"` + filepath.ToSlash(SessionSavePath) + `",` +
`"secure":` + strconv.FormatBool(EnableHttpTLS) + `,` +
`"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
`"domain":"` + SessionDomain + `",` +
`"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
}
GlobalSessions, err = session.NewManager(SessionProvider,
sessionConfig)
if err != nil {
panic(err)
}
go GlobalSessions.GC()
}
err := BuildTemplate(ViewsPath)
if err != nil {
if RunMode == "dev" {
Warn(err)
}
}
registerDefaultErrorHandler()
if EnableDocs {
Get("/docs", serverDocs)
Get("/docs/*", serverDocs)
}
} }
// this function is for test package init // TestBeegoInit is for test package init
func TestBeegoInit(apppath string) { func TestBeegoInit(ap string) {
AppPath = apppath path := filepath.Join(ap, "conf", "app.conf")
RunMode = "test" os.Chdir(ap)
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf") InitBeegoBeforeTest(path)
err := ParseConfig()
if err != nil && !os.IsNotExist(err) {
// for init if doesn't have app.conf will not panic
Info(err)
}
os.Chdir(AppPath)
initBeforeHttpRun()
} }
func init() { // InitBeegoBeforeTest is for test package init
hooks = make([]hookfunc, 0) func InitBeegoBeforeTest(appConfigPath string) {
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
panic(err)
}
BConfig.RunMode = "test"
initBeforeHTTPRun()
} }

6
cache/README.md vendored
View File

@ -26,7 +26,7 @@ Then init a Cache (example with memory adapter)
Use it like this: Use it like this:
bm.Put("astaxie", 1, 10) bm.Put("astaxie", 1, 10 * time.Second)
bm.Get("astaxie") bm.Get("astaxie")
bm.IsExist("astaxie") bm.IsExist("astaxie")
bm.Delete("astaxie") bm.Delete("astaxie")
@ -43,7 +43,7 @@ interval means the gc time. The cache will check at each time interval, whether
## Memcache adapter ## Memcache adapter
Memcache adapter use the vitess's [Memcache](http://code.google.com/p/vitess/go/memcache) client. Memcache adapter use the [gomemcache](http://github.com/bradfitz/gomemcache) client.
Configure like this: Configure like this:
@ -52,7 +52,7 @@ Configure like this:
## Redis adapter ## Redis adapter
Redis adapter use the [redigo](http://github.com/garyburd/redigo/redis) client. Redis adapter use the [redigo](http://github.com/garyburd/redigo) client.
Configure like this: Configure like this:

24
cache/cache.go vendored
View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package cache provide a Cache interface and some implemetn engine
// Usage: // Usage:
// //
// import( // import(
@ -22,7 +23,7 @@
// //
// Use it like this: // Use it like this:
// //
// bm.Put("astaxie", 1, 10) // bm.Put("astaxie", 1, 10 * time.Second)
// bm.Get("astaxie") // bm.Get("astaxie")
// bm.IsExist("astaxie") // bm.IsExist("astaxie")
// bm.Delete("astaxie") // bm.Delete("astaxie")
@ -32,13 +33,14 @@ package cache
import ( import (
"fmt" "fmt"
"time"
) )
// Cache interface contains all behaviors for cache adapter. // Cache interface contains all behaviors for cache adapter.
// usage: // usage:
// cache.Register("file",cache.NewFileCache()) // this operation is run in init method of file.go. // cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go.
// c,err := cache.NewCache("file","{....}") // c,err := cache.NewCache("file","{....}")
// c.Put("key",value,3600) // c.Put("key",value, 3600 * time.Second)
// v := c.Get("key") // v := c.Get("key")
// //
// c.Incr("counter") // now is 1 // c.Incr("counter") // now is 1
@ -47,8 +49,10 @@ import (
type Cache interface { type Cache interface {
// get cached value by key. // get cached value by key.
Get(key string) interface{} Get(key string) interface{}
// GetMulti is a batch version of Get.
GetMulti(keys []string) []interface{}
// set cached value with key and expire time. // set cached value with key and expire time.
Put(key string, val interface{}, timeout int64) error Put(key string, val interface{}, timeout time.Duration) error
// delete cached value by key. // delete cached value by key.
Delete(key string) error Delete(key string) error
// increase cached int value by key, as a counter. // increase cached int value by key, as a counter.
@ -63,12 +67,15 @@ type Cache interface {
StartAndGC(config string) error StartAndGC(config string) error
} }
var adapters = make(map[string]Cache) // Instance is a function create a new Cache Instance
type Instance func() Cache
var adapters = make(map[string]Instance)
// Register makes a cache adapter available by the adapter name. // Register makes a cache adapter available by the adapter name.
// If Register is called twice with the same name or if driver is nil, // If Register is called twice with the same name or if driver is nil,
// it panics. // it panics.
func Register(name string, adapter Cache) { func Register(name string, adapter Instance) {
if adapter == nil { if adapter == nil {
panic("cache: Register adapter is nil") panic("cache: Register adapter is nil")
} }
@ -78,15 +85,16 @@ func Register(name string, adapter Cache) {
adapters[name] = adapter adapters[name] = adapter
} }
// Create a new cache driver by adapter name and config string. // NewCache Create a new cache driver by adapter name and config string.
// config need to be correct JSON as string: {"interval":360}. // config need to be correct JSON as string: {"interval":360}.
// it will start gc automatically. // it will start gc automatically.
func NewCache(adapterName, config string) (adapter Cache, err error) { func NewCache(adapterName, config string) (adapter Cache, err error) {
adapter, ok := adapters[adapterName] instanceFunc, ok := adapters[adapterName]
if !ok { if !ok {
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
return return
} }
adapter = instanceFunc()
err = adapter.StartAndGC(config) err = adapter.StartAndGC(config)
if err != nil { if err != nil {
adapter = nil adapter = nil

61
cache/cache_test.go vendored
View File

@ -25,7 +25,8 @@ func TestCache(t *testing.T) {
if err != nil { if err != nil {
t.Error("init err") t.Error("init err")
} }
if err = bm.Put("astaxie", 1, 10); err != nil { timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
if !bm.IsExist("astaxie") { if !bm.IsExist("astaxie") {
@ -42,7 +43,7 @@ func TestCache(t *testing.T) {
t.Error("check err") t.Error("check err")
} }
if err = bm.Put("astaxie", 1, 10); err != nil { if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
@ -65,6 +66,35 @@ func TestCache(t *testing.T) {
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("delete err") t.Error("delete err")
} }
//test GetMulti
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(string) != "author" {
t.Error("get err")
}
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
} }
func TestFileCache(t *testing.T) { func TestFileCache(t *testing.T) {
@ -72,7 +102,8 @@ func TestFileCache(t *testing.T) {
if err != nil { if err != nil {
t.Error("init err") t.Error("init err")
} }
if err = bm.Put("astaxie", 1, 10); err != nil { timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
if !bm.IsExist("astaxie") { if !bm.IsExist("astaxie") {
@ -102,16 +133,36 @@ func TestFileCache(t *testing.T) {
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("delete err") t.Error("delete err")
} }
//test string //test string
if err = bm.Put("astaxie", "author", 10); err != nil { if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
if !bm.IsExist("astaxie") { if !bm.IsExist("astaxie") {
t.Error("check err") t.Error("check err")
} }
if v := bm.Get("astaxie"); v.(string) != "author" { if v := bm.Get("astaxie"); v.(string) != "author" {
t.Error("get err") t.Error("get err")
} }
//test GetMulti
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if vv[0].(string) != "author" {
t.Error("GetMulti ERROR")
}
if vv[1].(string) != "author1" {
t.Error("GetMulti ERROR")
}
os.RemoveAll("cache") os.RemoveAll("cache")
} }

22
cache/conv.go vendored
View File

@ -19,7 +19,7 @@ import (
"strconv" "strconv"
) )
// convert interface to string. // GetString convert interface to string.
func GetString(v interface{}) string { func GetString(v interface{}) string {
switch result := v.(type) { switch result := v.(type) {
case string: case string:
@ -34,7 +34,7 @@ func GetString(v interface{}) string {
return "" return ""
} }
// convert interface to int. // GetInt convert interface to int.
func GetInt(v interface{}) int { func GetInt(v interface{}) int {
switch result := v.(type) { switch result := v.(type) {
case int: case int:
@ -52,7 +52,7 @@ func GetInt(v interface{}) int {
return 0 return 0
} }
// convert interface to int64. // GetInt64 convert interface to int64.
func GetInt64(v interface{}) int64 { func GetInt64(v interface{}) int64 {
switch result := v.(type) { switch result := v.(type) {
case int: case int:
@ -71,7 +71,7 @@ func GetInt64(v interface{}) int64 {
return 0 return 0
} }
// convert interface to float64. // GetFloat64 convert interface to float64.
func GetFloat64(v interface{}) float64 { func GetFloat64(v interface{}) float64 {
switch result := v.(type) { switch result := v.(type) {
case float64: case float64:
@ -85,7 +85,7 @@ func GetFloat64(v interface{}) float64 {
return 0 return 0
} }
// convert interface to bool. // GetBool convert interface to bool.
func GetBool(v interface{}) bool { func GetBool(v interface{}) bool {
switch result := v.(type) { switch result := v.(type) {
case bool: case bool:
@ -98,15 +98,3 @@ func GetBool(v interface{}) bool {
} }
return false return false
} }
// convert interface to byte slice.
func getByteArray(v interface{}) []byte {
switch result := v.(type) {
case []byte:
return result
case string:
return []byte(result)
default:
return nil
}
}

29
cache/conv_test.go vendored
View File

@ -27,7 +27,7 @@ func TestGetString(t *testing.T) {
if "test2" != GetString(t2) { if "test2" != GetString(t2) {
t.Error("get string from byte array error") t.Error("get string from byte array error")
} }
var t3 int = 1 var t3 = 1
if "1" != GetString(t3) { if "1" != GetString(t3) {
t.Error("get string from int error") t.Error("get string from int error")
} }
@ -35,7 +35,7 @@ func TestGetString(t *testing.T) {
if "1" != GetString(t4) { if "1" != GetString(t4) {
t.Error("get string from int64 error") t.Error("get string from int64 error")
} }
var t5 float64 = 1.1 var t5 = 1.1
if "1.1" != GetString(t5) { if "1.1" != GetString(t5) {
t.Error("get string from float64 error") t.Error("get string from float64 error")
} }
@ -46,7 +46,7 @@ func TestGetString(t *testing.T) {
} }
func TestGetInt(t *testing.T) { func TestGetInt(t *testing.T) {
var t1 int = 1 var t1 = 1
if 1 != GetInt(t1) { if 1 != GetInt(t1) {
t.Error("get int from int error") t.Error("get int from int error")
} }
@ -69,7 +69,7 @@ func TestGetInt(t *testing.T) {
func TestGetInt64(t *testing.T) { func TestGetInt64(t *testing.T) {
var i int64 = 1 var i int64 = 1
var t1 int = 1 var t1 = 1
if i != GetInt64(t1) { if i != GetInt64(t1) {
t.Error("get int64 from int error") t.Error("get int64 from int error")
} }
@ -91,12 +91,12 @@ func TestGetInt64(t *testing.T) {
} }
func TestGetFloat64(t *testing.T) { func TestGetFloat64(t *testing.T) {
var f float64 = 1.11 var f = 1.11
var t1 float32 = 1.11 var t1 float32 = 1.11
if f != GetFloat64(t1) { if f != GetFloat64(t1) {
t.Error("get float64 from float32 error") t.Error("get float64 from float32 error")
} }
var t2 float64 = 1.11 var t2 = 1.11
if f != GetFloat64(t2) { if f != GetFloat64(t2) {
t.Error("get float64 from float64 error") t.Error("get float64 from float64 error")
} }
@ -106,7 +106,7 @@ func TestGetFloat64(t *testing.T) {
} }
var f2 float64 = 1 var f2 float64 = 1
var t4 int = 1 var t4 = 1
if f2 != GetFloat64(t4) { if f2 != GetFloat64(t4) {
t.Error("get float64 from int error") t.Error("get float64 from int error")
} }
@ -130,21 +130,6 @@ func TestGetBool(t *testing.T) {
} }
} }
func TestGetByteArray(t *testing.T) {
var b = []byte("test")
var t1 = []byte("test")
if !byteArrayEquals(b, getByteArray(t1)) {
t.Error("get byte array from byte array error")
}
var t2 = "test"
if !byteArrayEquals(b, getByteArray(t2)) {
t.Error("get byte array from string error")
}
if nil != getByteArray(nil) {
t.Error("get byte array from nil error")
}
}
func byteArrayEquals(a []byte, b []byte) bool { func byteArrayEquals(a []byte, b []byte) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false

106
cache/file.go vendored
View File

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
@ -29,23 +30,20 @@ import (
"time" "time"
) )
func init() {
Register("file", NewFileCache())
}
// FileCacheItem is basic unit of file cache adapter. // FileCacheItem is basic unit of file cache adapter.
// it contains data and expire time. // it contains data and expire time.
type FileCacheItem struct { type FileCacheItem struct {
Data interface{} Data interface{}
Lastaccess int64 Lastaccess time.Time
Expired int64 Expired time.Time
} }
// FileCache Config
var ( var (
FileCachePath string = "cache" // cache directory FileCachePath = "cache" // cache directory
FileCacheFileSuffix string = ".bin" // cache file suffix FileCacheFileSuffix = ".bin" // cache file suffix
FileCacheDirectoryLevel int = 2 // cache file deep level if auto generated cache files. FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
FileCacheEmbedExpiry int64 = 0 // cache expire time, default is no expire forever. FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
) )
// FileCache is cache adapter for file storage. // FileCache is cache adapter for file storage.
@ -56,14 +54,14 @@ type FileCache struct {
EmbedExpiry int EmbedExpiry int
} }
// Create new file cache with no config. // NewFileCache Create new file cache with no config.
// the level and expiry need set in method StartAndGC as config string. // the level and expiry need set in method StartAndGC as config string.
func NewFileCache() *FileCache { func NewFileCache() Cache {
// return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
return &FileCache{} return &FileCache{}
} }
// Start and begin gc for file cache. // StartAndGC will start and begin gc for file cache.
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0} // the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
func (fc *FileCache) StartAndGC(config string) error { func (fc *FileCache) StartAndGC(config string) error {
@ -79,7 +77,7 @@ func (fc *FileCache) StartAndGC(config string) error {
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
} }
if _, ok := cfg["EmbedExpiry"]; !ok { if _, ok := cfg["EmbedExpiry"]; !ok {
cfg["EmbedExpiry"] = strconv.FormatInt(FileCacheEmbedExpiry, 10) cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
} }
fc.CachePath = cfg["CachePath"] fc.CachePath = cfg["CachePath"]
fc.FileSuffix = cfg["FileSuffix"] fc.FileSuffix = cfg["FileSuffix"]
@ -120,36 +118,46 @@ func (fc *FileCache) getCacheFileName(key string) string {
// Get value from file cache. // Get value from file cache.
// if non-exist or expired, return empty string. // if non-exist or expired, return empty string.
func (fc *FileCache) Get(key string) interface{} { func (fc *FileCache) Get(key string) interface{} {
fileData, err := File_get_contents(fc.getCacheFileName(key)) fileData, err := FileGetContents(fc.getCacheFileName(key))
if err != nil { if err != nil {
return "" return ""
} }
var to FileCacheItem var to FileCacheItem
Gob_decode(fileData, &to) GobDecode(fileData, &to)
if to.Expired < time.Now().Unix() { if to.Expired.Before(time.Now()) {
return "" return ""
} }
return to.Data return to.Data
} }
// GetMulti gets values from file cache.
// if non-exist or expired, return empty string.
func (fc *FileCache) GetMulti(keys []string) []interface{} {
var rc []interface{}
for _, key := range keys {
rc = append(rc, fc.Get(key))
}
return rc
}
// Put value into file cache. // Put value into file cache.
// timeout means how long to keep this file, unit of ms. // timeout means how long to keep this file, unit of ms.
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever. // if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
func (fc *FileCache) Put(key string, val interface{}, timeout int64) error { func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
gob.Register(val) gob.Register(val)
item := FileCacheItem{Data: val} item := FileCacheItem{Data: val}
if timeout == FileCacheEmbedExpiry { if timeout == FileCacheEmbedExpiry {
item.Expired = time.Now().Unix() + (86400 * 365 * 10) // ten years item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
} else { } else {
item.Expired = time.Now().Unix() + timeout item.Expired = time.Now().Add(timeout)
} }
item.Lastaccess = time.Now().Unix() item.Lastaccess = time.Now()
data, err := Gob_encode(item) data, err := GobEncode(item)
if err != nil { if err != nil {
return err return err
} }
return File_put_contents(fc.getCacheFileName(key), data) return FilePutContents(fc.getCacheFileName(key), data)
} }
// Delete file cache value. // Delete file cache value.
@ -161,7 +169,7 @@ func (fc *FileCache) Delete(key string) error {
return nil return nil
} }
// Increase cached int value. // Incr will increase cached int value.
// fc value is saving forever unless Delete. // fc value is saving forever unless Delete.
func (fc *FileCache) Incr(key string) error { func (fc *FileCache) Incr(key string) error {
data := fc.Get(key) data := fc.Get(key)
@ -175,7 +183,7 @@ func (fc *FileCache) Incr(key string) error {
return nil return nil
} }
// Decrease cached int value. // Decr will decrease cached int value.
func (fc *FileCache) Decr(key string) error { func (fc *FileCache) Decr(key string) error {
data := fc.Get(key) data := fc.Get(key)
var decr int var decr int
@ -188,13 +196,13 @@ func (fc *FileCache) Decr(key string) error {
return nil return nil
} }
// Check value is exist. // IsExist check value is exist.
func (fc *FileCache) IsExist(key string) bool { func (fc *FileCache) IsExist(key string) bool {
ret, _ := exists(fc.getCacheFileName(key)) ret, _ := exists(fc.getCacheFileName(key))
return ret return ret
} }
// Clean cached files. // ClearAll will clean cached files.
// not implemented. // not implemented.
func (fc *FileCache) ClearAll() error { func (fc *FileCache) ClearAll() error {
return nil return nil
@ -212,40 +220,20 @@ func exists(path string) (bool, error) {
return false, err return false, err
} }
// Get bytes to file. // FileGetContents Get bytes to file.
// if non-exist, create this file. // if non-exist, create this file.
func File_get_contents(filename string) (data []byte, e error) { func FileGetContents(filename string) (data []byte, e error) {
f, e := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) return ioutil.ReadFile(filename)
if e != nil {
return
}
defer f.Close()
stat, e := f.Stat()
if e != nil {
return
}
data = make([]byte, stat.Size())
result, e := f.Read(data)
if e != nil || int64(result) != stat.Size() {
return nil, e
}
return
} }
// Put bytes to file. // FilePutContents Put bytes to file.
// if non-exist, create this file. // if non-exist, create this file.
func File_put_contents(filename string, content []byte) error { func FilePutContents(filename string, content []byte) error {
fp, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) return ioutil.WriteFile(filename, content, os.ModePerm)
if err != nil {
return err
}
defer fp.Close()
_, err = fp.Write(content)
return err
} }
// Gob encodes file cache item. // GobEncode Gob encodes file cache item.
func Gob_encode(data interface{}) ([]byte, error) { func GobEncode(data interface{}) ([]byte, error) {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
enc := gob.NewEncoder(buf) enc := gob.NewEncoder(buf)
err := enc.Encode(data) err := enc.Encode(data)
@ -255,9 +243,13 @@ func Gob_encode(data interface{}) ([]byte, error) {
return buf.Bytes(), err return buf.Bytes(), err
} }
// Gob decodes file cache item. // GobDecode Gob decodes file cache item.
func Gob_decode(data []byte, to *FileCacheItem) error { func GobDecode(data []byte, to *FileCacheItem) error {
buf := bytes.NewBuffer(data) buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf) dec := gob.NewDecoder(buf)
return dec.Decode(&to) return dec.Decode(&to)
} }
func init() {
Register("file", NewFileCache)
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// package memcahe for cache provider // Package memcache for cache provider
// //
// depend on github.com/bradfitz/gomemcache/memcache // depend on github.com/bradfitz/gomemcache/memcache
// //
@ -33,53 +33,81 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"strings" "strings"
"time"
"github.com/bradfitz/gomemcache/memcache"
"github.com/astaxie/beego/cache" "github.com/astaxie/beego/cache"
"github.com/bradfitz/gomemcache/memcache"
) )
// Memcache adapter. // Cache Memcache adapter.
type MemcacheCache struct { type Cache struct {
conn *memcache.Client conn *memcache.Client
conninfo []string conninfo []string
} }
// create new memcache adapter. // NewMemCache create new memcache adapter.
func NewMemCache() *MemcacheCache { func NewMemCache() cache.Cache {
return &MemcacheCache{} return &Cache{}
} }
// get value from memcache. // Get get value from memcache.
func (rc *MemcacheCache) Get(key string) interface{} { func (rc *Cache) Get(key string) interface{} {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return err return err
} }
} }
if item, err := rc.conn.Get(key); err == nil { if item, err := rc.conn.Get(key); err == nil {
return string(item.Value) return item.Value
} }
return nil return nil
} }
// put value to memcache. only support string. // GetMulti get value from memcache.
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error { func (rc *Cache) GetMulti(keys []string) []interface{} {
size := len(keys)
var rv []interface{}
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
for i := 0; i < size; i++ {
rv = append(rv, err)
}
return rv
}
}
mv, err := rc.conn.GetMulti(keys)
if err == nil {
for _, v := range mv {
rv = append(rv, v.Value)
}
return rv
}
for i := 0; i < size; i++ {
rv = append(rv, err)
}
return rv
}
// Put put value to memcache.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return err return err
} }
} }
v, ok := val.(string) item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
if !ok { if v, ok := val.([]byte); ok {
return errors.New("val must string") 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)}
return rc.conn.Set(&item) return rc.conn.Set(&item)
} }
// delete value in memcache. // Delete delete value in memcache.
func (rc *MemcacheCache) Delete(key string) error { func (rc *Cache) Delete(key string) error {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return err return err
@ -88,8 +116,8 @@ func (rc *MemcacheCache) Delete(key string) error {
return rc.conn.Delete(key) return rc.conn.Delete(key)
} }
// increase counter. // Incr increase counter.
func (rc *MemcacheCache) Incr(key string) error { func (rc *Cache) Incr(key string) error {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return err return err
@ -99,8 +127,8 @@ func (rc *MemcacheCache) Incr(key string) error {
return err return err
} }
// decrease counter. // Decr decrease counter.
func (rc *MemcacheCache) Decr(key string) error { func (rc *Cache) Decr(key string) error {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return err return err
@ -110,8 +138,8 @@ func (rc *MemcacheCache) Decr(key string) error {
return err return err
} }
// check value exists in memcache. // IsExist check value exists in memcache.
func (rc *MemcacheCache) IsExist(key string) bool { func (rc *Cache) IsExist(key string) bool {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return false return false
@ -124,8 +152,8 @@ func (rc *MemcacheCache) IsExist(key string) bool {
return true return true
} }
// clear all cached in memcache. // ClearAll clear all cached in memcache.
func (rc *MemcacheCache) ClearAll() error { func (rc *Cache) ClearAll() error {
if rc.conn == nil { if rc.conn == nil {
if err := rc.connectInit(); err != nil { if err := rc.connectInit(); err != nil {
return err return err
@ -134,10 +162,10 @@ func (rc *MemcacheCache) ClearAll() error {
return rc.conn.FlushAll() return rc.conn.FlushAll()
} }
// start memcache adapter. // StartAndGC start memcache adapter.
// config string is like {"conn":"connection info"}. // config string is like {"conn":"connection info"}.
// if connecting error, return. // if connecting error, return.
func (rc *MemcacheCache) StartAndGC(config string) error { func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string var cf map[string]string
json.Unmarshal([]byte(config), &cf) json.Unmarshal([]byte(config), &cf)
if _, ok := cf["conn"]; !ok { if _, ok := cf["conn"]; !ok {
@ -153,11 +181,11 @@ func (rc *MemcacheCache) StartAndGC(config string) error {
} }
// connect to memcache and keep the connection. // connect to memcache and keep the connection.
func (rc *MemcacheCache) connectInit() error { func (rc *Cache) connectInit() error {
rc.conn = memcache.New(rc.conninfo...) rc.conn = memcache.New(rc.conninfo...)
return nil return nil
} }
func init() { func init() {
cache.Register("memcache", NewMemCache()) cache.Register("memcache", NewMemCache)
} }

108
cache/memcache/memcache_test.go vendored Normal file
View File

@ -0,0 +1,108 @@
// 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 memcache
import (
_ "github.com/bradfitz/gomemcache/memcache"
"strconv"
"testing"
"time"
"github.com/astaxie/beego/cache"
)
func TestMemcacheCache(t *testing.T) {
bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`)
if err != nil {
t.Error("init err")
}
timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
time.Sleep(11 * time.Second)
if bm.IsExist("astaxie") {
t.Error("check err")
}
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
t.Error("get err")
}
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
t.Error("get err")
}
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
//test string
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie").([]byte); string(v) != "author" {
t.Error("get err")
}
//test GetMulti
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
t.Error("GetMulti ERROR")
}
if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
t.Error("GetMulti ERROR")
}
// test clear all
if err = bm.ClearAll(); err != nil {
t.Error("clear all err")
}
}

126
cache/memory.go vendored
View File

@ -17,34 +17,41 @@ package cache
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"sync" "sync"
"time" "time"
) )
var ( var (
// clock time of recycling the expired cache items in memory. // DefaultEvery means the clock time of recycling the expired cache items in memory.
DefaultEvery int = 60 // 1 minute DefaultEvery = 60 // 1 minute
) )
// Memory cache item. // MemoryItem store memory cache item.
type MemoryItem struct { type MemoryItem struct {
val interface{} val interface{}
Lastaccess time.Time createdTime time.Time
expired int64 lifespan time.Duration
} }
// Memory cache adapter. func (mi *MemoryItem) isExpire() bool {
// 0 means forever
if mi.lifespan == 0 {
return false
}
return time.Now().Sub(mi.createdTime) > mi.lifespan
}
// MemoryCache is Memory cache adapter.
// it contains a RW locker for safe map storage. // it contains a RW locker for safe map storage.
type MemoryCache struct { type MemoryCache struct {
lock sync.RWMutex sync.RWMutex
dur time.Duration dur time.Duration
items map[string]*MemoryItem items map[string]*MemoryItem
Every int // run an expiration check Every clock time Every int // run an expiration check Every clock time
} }
// NewMemoryCache returns a new MemoryCache. // NewMemoryCache returns a new MemoryCache.
func NewMemoryCache() *MemoryCache { func NewMemoryCache() Cache {
cache := MemoryCache{items: make(map[string]*MemoryItem)} cache := MemoryCache{items: make(map[string]*MemoryItem)}
return &cache return &cache
} }
@ -52,11 +59,10 @@ func NewMemoryCache() *MemoryCache {
// Get cache from memory. // Get cache from memory.
// if non-existed or expired, return nil. // if non-existed or expired, return nil.
func (bc *MemoryCache) Get(name string) interface{} { func (bc *MemoryCache) Get(name string) interface{} {
bc.lock.RLock() bc.RLock()
defer bc.lock.RUnlock() defer bc.RUnlock()
if itm, ok := bc.items[name]; ok { if itm, ok := bc.items[name]; ok {
if (time.Now().Unix() - itm.Lastaccess.Unix()) > itm.expired { if itm.isExpire() {
go bc.Delete(name)
return nil return nil
} }
return itm.val return itm.val
@ -64,23 +70,33 @@ func (bc *MemoryCache) Get(name string) interface{} {
return nil return nil
} }
// GetMulti gets caches from memory.
// if non-existed or expired, return nil.
func (bc *MemoryCache) GetMulti(names []string) []interface{} {
var rc []interface{}
for _, name := range names {
rc = append(rc, bc.Get(name))
}
return rc
}
// Put cache to memory. // Put cache to memory.
// if expired is 0, it will be cleaned by next gc operation ( default gc clock is 1 minute). // if lifespan is 0, it will be forever till restart.
func (bc *MemoryCache) Put(name string, value interface{}, expired int64) error { func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
bc.lock.Lock() bc.Lock()
defer bc.lock.Unlock() defer bc.Unlock()
bc.items[name] = &MemoryItem{ bc.items[name] = &MemoryItem{
val: value, val: value,
Lastaccess: time.Now(), createdTime: time.Now(),
expired: expired, lifespan: lifespan,
} }
return nil return nil
} }
/// Delete cache in memory. // Delete cache in memory.
func (bc *MemoryCache) Delete(name string) error { func (bc *MemoryCache) Delete(name string) error {
bc.lock.Lock() bc.Lock()
defer bc.lock.Unlock() defer bc.Unlock()
if _, ok := bc.items[name]; !ok { if _, ok := bc.items[name]; !ok {
return errors.New("key not exist") return errors.New("key not exist")
} }
@ -91,11 +107,11 @@ func (bc *MemoryCache) Delete(name string) error {
return nil return nil
} }
// Increase cache counter in memory. // Incr increase cache counter in memory.
// it supports int,int64,int32,uint,uint64,uint32. // it supports int,int32,int64,uint,uint32,uint64.
func (bc *MemoryCache) Incr(key string) error { func (bc *MemoryCache) Incr(key string) error {
bc.lock.RLock() bc.RLock()
defer bc.lock.RUnlock() defer bc.RUnlock()
itm, ok := bc.items[key] itm, ok := bc.items[key]
if !ok { if !ok {
return errors.New("key not exist") return errors.New("key not exist")
@ -103,10 +119,10 @@ func (bc *MemoryCache) Incr(key string) error {
switch itm.val.(type) { switch itm.val.(type) {
case int: case int:
itm.val = itm.val.(int) + 1 itm.val = itm.val.(int) + 1
case int64:
itm.val = itm.val.(int64) + 1
case int32: case int32:
itm.val = itm.val.(int32) + 1 itm.val = itm.val.(int32) + 1
case int64:
itm.val = itm.val.(int64) + 1
case uint: case uint:
itm.val = itm.val.(uint) + 1 itm.val = itm.val.(uint) + 1
case uint32: case uint32:
@ -114,15 +130,15 @@ func (bc *MemoryCache) Incr(key string) error {
case uint64: case uint64:
itm.val = itm.val.(uint64) + 1 itm.val = itm.val.(uint64) + 1
default: default:
return errors.New("item val is not int int64 int32") return errors.New("item val is not (u)int (u)int32 (u)int64")
} }
return nil return nil
} }
// Decrease counter in memory. // Decr decrease counter in memory.
func (bc *MemoryCache) Decr(key string) error { func (bc *MemoryCache) Decr(key string) error {
bc.lock.RLock() bc.RLock()
defer bc.lock.RUnlock() defer bc.RUnlock()
itm, ok := bc.items[key] itm, ok := bc.items[key]
if !ok { if !ok {
return errors.New("key not exist") return errors.New("key not exist")
@ -158,23 +174,25 @@ func (bc *MemoryCache) Decr(key string) error {
return nil return nil
} }
// check cache exist in memory. // IsExist check cache exist in memory.
func (bc *MemoryCache) IsExist(name string) bool { func (bc *MemoryCache) IsExist(name string) bool {
bc.lock.RLock() bc.RLock()
defer bc.lock.RUnlock() defer bc.RUnlock()
_, ok := bc.items[name] if v, ok := bc.items[name]; ok {
return ok return !v.isExpire()
}
return false
} }
// delete all cache in memory. // ClearAll will delete all cache in memory.
func (bc *MemoryCache) ClearAll() error { func (bc *MemoryCache) ClearAll() error {
bc.lock.Lock() bc.Lock()
defer bc.lock.Unlock() defer bc.Unlock()
bc.items = make(map[string]*MemoryItem) bc.items = make(map[string]*MemoryItem)
return nil return nil
} }
// start memory cache. it will check expiration in every clock time. // StartAndGC start memory cache. it will check expiration in every clock time.
func (bc *MemoryCache) StartAndGC(config string) error { func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int var cf map[string]int
json.Unmarshal([]byte(config), &cf) json.Unmarshal([]byte(config), &cf)
@ -182,10 +200,7 @@ func (bc *MemoryCache) StartAndGC(config string) error {
cf = make(map[string]int) cf = make(map[string]int)
cf["interval"] = DefaultEvery cf["interval"] = DefaultEvery
} }
dur, err := time.ParseDuration(fmt.Sprintf("%ds", cf["interval"])) dur := time.Duration(cf["interval"]) * time.Second
if err != nil {
return err
}
bc.Every = cf["interval"] bc.Every = cf["interval"]
bc.dur = dur bc.dur = dur
go bc.vaccuum() go bc.vaccuum()
@ -202,21 +217,22 @@ func (bc *MemoryCache) vaccuum() {
if bc.items == nil { if bc.items == nil {
return return
} }
for name, _ := range bc.items { for name := range bc.items {
bc.item_expired(name) bc.itemExpired(name)
} }
} }
} }
// item_expired returns true if an item is expired. // itemExpired returns true if an item is expired.
func (bc *MemoryCache) item_expired(name string) bool { func (bc *MemoryCache) itemExpired(name string) bool {
bc.lock.Lock() bc.Lock()
defer bc.lock.Unlock() defer bc.Unlock()
itm, ok := bc.items[name] itm, ok := bc.items[name]
if !ok { if !ok {
return true return true
} }
if time.Now().Unix()-itm.Lastaccess.Unix() >= itm.expired { if itm.isExpire() {
delete(bc.items, name) delete(bc.items, name)
return true return true
} }
@ -224,5 +240,5 @@ func (bc *MemoryCache) item_expired(name string) bool {
} }
func init() { func init() {
Register("memory", NewMemoryCache()) Register("memory", NewMemoryCache)
} }

105
cache/redis/redis.go vendored
View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// package redis for cache provider // Package redis for cache provider
// //
// depend on github.com/garyburd/redigo/redis // depend on github.com/garyburd/redigo/redis
// //
@ -41,25 +41,26 @@ import (
) )
var ( var (
// the collection name of redis for cache adapter. // DefaultKey the collection name of redis for cache adapter.
DefaultKey string = "beecacheRedis" DefaultKey = "beecacheRedis"
) )
// Redis cache adapter. // Cache is Redis cache adapter.
type RedisCache struct { type Cache struct {
p *redis.Pool // redis connection pool p *redis.Pool // redis connection pool
conninfo string conninfo string
dbNum int dbNum int
key string key string
password string
} }
// create new redis cache with default collection name. // NewRedisCache create new redis cache with default collection name.
func NewRedisCache() *RedisCache { func NewRedisCache() cache.Cache {
return &RedisCache{key: DefaultKey} return &Cache{key: DefaultKey}
} }
// actually do the redis cmds // actually do the redis cmds
func (rc *RedisCache) do(commandName string, args ...interface{}) (reply interface{}, err error) { func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
c := rc.p.Get() c := rc.p.Get()
defer c.Close() defer c.Close()
@ -67,17 +68,50 @@ func (rc *RedisCache) do(commandName string, args ...interface{}) (reply interfa
} }
// Get cache from redis. // Get cache from redis.
func (rc *RedisCache) Get(key string) interface{} { func (rc *Cache) Get(key string) interface{} {
if v, err := rc.do("GET", key); err == nil { if v, err := rc.do("GET", key); err == nil {
return v return v
} }
return nil return nil
} }
// put cache to redis. // GetMulti get cache from redis.
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error { func (rc *Cache) GetMulti(keys []string) []interface{} {
size := len(keys)
var rv []interface{}
c := rc.p.Get()
defer c.Close()
var err error var err error
if _, err = rc.do("SETEX", key, timeout, val); err != nil { for _, key := range keys {
err = c.Send("GET", key)
if err != nil {
goto ERROR
}
}
if err = c.Flush(); err != nil {
goto ERROR
}
for i := 0; i < size; i++ {
if v, err := c.Receive(); err == nil {
rv = append(rv, v.([]byte))
} else {
rv = append(rv, err)
}
}
return rv
ERROR:
rv = rv[0:0]
for i := 0; i < size; i++ {
rv = append(rv, nil)
}
return rv
}
// Put put cache to redis.
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
var err error
if _, err = rc.do("SETEX", key, int64(timeout/time.Second), val); err != nil {
return err return err
} }
@ -87,8 +121,8 @@ func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
return err return err
} }
// delete cache in redis. // Delete delete cache in redis.
func (rc *RedisCache) Delete(key string) error { func (rc *Cache) Delete(key string) error {
var err error var err error
if _, err = rc.do("DEL", key); err != nil { if _, err = rc.do("DEL", key); err != nil {
return err return err
@ -97,8 +131,8 @@ func (rc *RedisCache) Delete(key string) error {
return err return err
} }
// check cache's existence in redis. // IsExist check cache's existence in redis.
func (rc *RedisCache) IsExist(key string) bool { func (rc *Cache) IsExist(key string) bool {
v, err := redis.Bool(rc.do("EXISTS", key)) v, err := redis.Bool(rc.do("EXISTS", key))
if err != nil { if err != nil {
return false return false
@ -111,20 +145,20 @@ func (rc *RedisCache) IsExist(key string) bool {
return v return v
} }
// increase counter in redis. // Incr increase counter in redis.
func (rc *RedisCache) Incr(key string) error { func (rc *Cache) Incr(key string) error {
_, err := redis.Bool(rc.do("INCRBY", key, 1)) _, err := redis.Bool(rc.do("INCRBY", key, 1))
return err return err
} }
// decrease counter in redis. // Decr decrease counter in redis.
func (rc *RedisCache) Decr(key string) error { func (rc *Cache) Decr(key string) error {
_, err := redis.Bool(rc.do("INCRBY", key, -1)) _, err := redis.Bool(rc.do("INCRBY", key, -1))
return err return err
} }
// clean all cache in redis. delete this redis collection. // ClearAll clean all cache in redis. delete this redis collection.
func (rc *RedisCache) ClearAll() error { func (rc *Cache) ClearAll() error {
cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key)) cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key))
if err != nil { if err != nil {
return err return err
@ -138,27 +172,31 @@ func (rc *RedisCache) ClearAll() error {
return err return err
} }
// start redis cache adapter. // StartAndGC start redis cache adapter.
// config is like {"key":"collection key","conn":"connection info","dbNum":"0"} // config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
// the cache item in redis are stored forever, // the cache item in redis are stored forever,
// so no gc operation. // so no gc operation.
func (rc *RedisCache) StartAndGC(config string) error { func (rc *Cache) StartAndGC(config string) error {
var cf map[string]string var cf map[string]string
json.Unmarshal([]byte(config), &cf) json.Unmarshal([]byte(config), &cf)
if _, ok := cf["key"]; !ok { if _, ok := cf["key"]; !ok {
cf["key"] = DefaultKey cf["key"] = DefaultKey
} }
if _, ok := cf["conn"]; !ok { if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key") return errors.New("config has no conn key")
} }
if _, ok := cf["dbNum"]; !ok { if _, ok := cf["dbNum"]; !ok {
cf["dbNum"] = "0" cf["dbNum"] = "0"
} }
if _, ok := cf["password"]; !ok {
cf["password"] = ""
}
rc.key = cf["key"] rc.key = cf["key"]
rc.conninfo = cf["conn"] rc.conninfo = cf["conn"]
rc.dbNum, _ = strconv.Atoi(cf["dbNum"]) rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
rc.password = cf["password"]
rc.connectInit() rc.connectInit()
c := rc.p.Get() c := rc.p.Get()
@ -168,9 +206,20 @@ func (rc *RedisCache) StartAndGC(config string) error {
} }
// connect to redis. // connect to redis.
func (rc *RedisCache) connectInit() { func (rc *Cache) connectInit() {
dialFunc := func() (c redis.Conn, err error) { dialFunc := func() (c redis.Conn, err error) {
c, err = redis.Dial("tcp", rc.conninfo) c, err = redis.Dial("tcp", rc.conninfo)
if err != nil {
return nil, err
}
if rc.password != "" {
if _, err := c.Do("AUTH", rc.password); err != nil {
c.Close()
return nil, err
}
}
_, selecterr := c.Do("SELECT", rc.dbNum) _, selecterr := c.Do("SELECT", rc.dbNum)
if selecterr != nil { if selecterr != nil {
c.Close() c.Close()
@ -187,5 +236,5 @@ func (rc *RedisCache) connectInit() {
} }
func init() { func init() {
cache.Register("redis", NewRedisCache()) cache.Register("redis", NewRedisCache)
} }

View File

@ -18,9 +18,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/garyburd/redigo/redis"
"github.com/astaxie/beego/cache" "github.com/astaxie/beego/cache"
"github.com/garyburd/redigo/redis"
) )
func TestRedisCache(t *testing.T) { func TestRedisCache(t *testing.T) {
@ -28,19 +27,20 @@ func TestRedisCache(t *testing.T) {
if err != nil { if err != nil {
t.Error("init err") t.Error("init err")
} }
if err = bm.Put("astaxie", 1, 10); err != nil { timeoutDuration := 10 * time.Second
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
if !bm.IsExist("astaxie") { if !bm.IsExist("astaxie") {
t.Error("check err") t.Error("check err")
} }
time.Sleep(10 * time.Second) time.Sleep(11 * time.Second)
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("check err") t.Error("check err")
} }
if err = bm.Put("astaxie", 1, 10); err != nil { if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
@ -67,8 +67,9 @@ func TestRedisCache(t *testing.T) {
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("delete err") t.Error("delete err")
} }
//test string //test string
if err = bm.Put("astaxie", "author", 10); err != nil { if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
t.Error("set Error", err) t.Error("set Error", err)
} }
if !bm.IsExist("astaxie") { if !bm.IsExist("astaxie") {
@ -78,6 +79,26 @@ func TestRedisCache(t *testing.T) {
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" { if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
t.Error("get err") t.Error("get err")
} }
//test GetMulti
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie1") {
t.Error("check err")
}
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
if len(vv) != 2 {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[0], nil); v != "author" {
t.Error("GetMulti ERROR")
}
if v, _ := redis.String(vv[1], nil); v != "author1" {
t.Error("GetMulti ERROR")
}
// test clear all // test clear all
if err = bm.ClearAll(); err != nil { if err = bm.ClearAll(); err != nil {
t.Error("clear all err") t.Error("clear all err")

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 len(resp) == 2 && 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")
}
}

800
config.go
View File

@ -16,161 +16,464 @@ package beego
import ( import (
"fmt" "fmt"
"html/template"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"strings" "strings"
"github.com/astaxie/beego/config" "github.com/astaxie/beego/config"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
var ( // Config is the main struct for BConfig
BeeApp *App // beego application type Config struct {
AppName string AppName string //Application name
AppPath string RunMode string //Running Mode: dev | prod
workPath string RouterCaseSensitive bool
AppConfigPath string ServerName string
StaticDir map[string]string RecoverPanic bool
TemplateCache map[string]*template.Template // template caching map RecoverFunc func(*context.Context)
StaticExtensionsToGzip []string // files with should be compressed with gzip (.js,.css,etc) CopyRequestBody bool
EnableHttpListen bool EnableGzip bool
HttpAddr string MaxMemory int64
HttpPort int EnableErrorsShow bool
ListenTCP4 bool EnableErrorsRender bool
EnableHttpTLS bool Listen Listen
HttpsPort int WebConfig WebConfig
HttpCertFile string Log LogConfig
HttpKeyFile string
RecoverPanic bool // flag of auto recover panic
AutoRender bool // flag of render template automatically
ViewsPath string
AppConfig *beegoAppConfig
RunMode string // run mode, "dev" or "prod"
GlobalSessions *session.Manager // global session mananger
SessionOn bool // flag of starting session auto. default is false.
SessionProvider string // default session provider, memory, mysql , redis ,etc.
SessionName string // the cookie name when saving session id into cookie.
SessionGCMaxLifetime int64 // session gc time for auto cleaning expired session.
SessionSavePath string // if use mysql/redis/file provider, define save path to connection info.
SessionCookieLifeTime int // the life time of session id in cookie.
SessionAutoSetCookie bool // auto setcookie
SessionDomain string // the cookie domain default is empty
UseFcgi bool
UseStdIo bool
MaxMemory int64
EnableGzip bool // flag of enable gzip
DirectoryIndex bool // flag of display directory index. default is false.
HttpServerTimeOut int64
ErrorsShow bool // flag of show errors in page. if true, show error and trace info in page rendered with error template.
XSRFKEY string // xsrf hash salt string.
EnableXSRF bool // flag of enable xsrf.
XSRFExpire int // the expiry of xsrf value.
CopyRequestBody bool // flag of copy raw request body in context.
TemplateLeft string
TemplateRight string
BeegoServerName string // beego server name exported in response header.
EnableAdmin bool // flag of enable admin module to log every request info.
AdminHttpAddr string // http server configurations for admin module.
AdminHttpPort int
FlashName string // name of the flash variable found in response header and cookie
FlashSeperator string // used to seperate flash key:value
AppConfigProvider string // config provider
EnableDocs bool // enable generate docs & server docs API Swagger
RouterCaseSensitive bool // router case sensitive default is true
AccessLogs bool // print access logs, default is false
)
type beegoAppConfig struct {
innerConfig config.ConfigContainer
} }
func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, error) { // Listen holds for http and https related config
ac, err := config.NewConfig(AppConfigProvider, AppConfigPath) type Listen struct {
Graceful bool // Graceful means use graceful module to start the server
ServerTimeOut int64
ListenTCP4 bool
EnableHTTP bool
HTTPAddr string
HTTPPort int
EnableHTTPS bool
HTTPSAddr string
HTTPSPort int
HTTPSCertFile string
HTTPSKeyFile string
EnableAdmin bool
AdminAddr string
AdminPort int
EnableFcgi bool
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
}
// WebConfig holds web related config
type WebConfig struct {
AutoRender bool
EnableDocs bool
FlashName string
FlashSeparator string
DirectoryIndex bool
StaticDir map[string]string
StaticExtensionsToGzip []string
TemplateLeft string
TemplateRight string
ViewsPath string
EnableXSRF bool
XSRFKey string
XSRFExpire int
Session SessionConfig
}
// SessionConfig holds session related config
type SessionConfig struct {
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
type LogConfig struct {
AccessLogs bool
FileLineNum bool
Outputs map[string]string // Store Adaptor : config
}
var (
// BConfig is the default config for Application
BConfig *Config
// AppConfig is the instance of Config, store the config information from file
AppConfig *beegoAppConfig
// 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 = 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)
}
}
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 && BConfig.EnableErrorsRender {
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
EnableErrorsShow: true,
EnableErrorsRender: true,
Listen: Listen{
Graceful: false,
ServerTimeOut: 0,
ListenTCP4: false,
EnableHTTP: true,
HTTPAddr: "",
HTTPPort: 8080,
EnableHTTPS: false,
HTTPSAddr: "",
HTTPSPort: 10443,
HTTPSCertFile: "",
HTTPSKeyFile: "",
EnableAdmin: false,
AdminAddr: "",
AdminPort: 8088,
EnableFcgi: false,
EnableStdIo: false,
},
WebConfig: WebConfig{
AutoRender: true,
EnableDocs: false,
FlashName: "BEEGO_FLASH",
FlashSeparator: "BEEGOFLASH",
DirectoryIndex: false,
StaticDir: map[string]string{"/static": "static"},
StaticExtensionsToGzip: []string{".css", ".js"},
TemplateLeft: "{{",
TemplateRight: "}}",
ViewsPath: "views",
EnableXSRF: false,
XSRFKey: "beegoxsrf",
XSRFExpire: 0,
Session: SessionConfig{
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{
AccessLogs: false,
FileLineNum: true,
Outputs: map[string]string{"console": ""},
},
}
}
// now only support ini, next will support json.
func parseConfig(appConfigPath string) (err error) {
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
if err != nil {
return err
}
return assignConfig(AppConfig)
}
func assignConfig(ac config.Configer) error {
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
assignSingleConfig(i, ac)
}
// set the run mode first
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
BConfig.RunMode = envRunMode
} else if runMode := ac.String("RunMode"); runMode != "" {
BConfig.RunMode = runMode
}
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.Trim(url2fsmap[0], "/")] = url2fsmap[1]
} else {
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0]
}
}
}
if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" {
extensions := strings.Split(sgz, ",")
fileExts := []string{}
for _, ext := range extensions {
ext = strings.TrimSpace(ext)
if ext == "" {
continue
}
if !strings.HasPrefix(ext, ".") {
ext = "." + ext
}
fileExts = append(fileExts, ext)
}
if len(fileExts) > 0 {
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rac := &beegoAppConfig{ac} return &beegoAppConfig{ac}, nil
return rac, nil
} }
func (b *beegoAppConfig) Set(key, val string) error { func (b *beegoAppConfig) Set(key, val string) error {
if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil {
return err
}
return b.innerConfig.Set(key, val) return b.innerConfig.Set(key, val)
} }
func (b *beegoAppConfig) String(key string) string { func (b *beegoAppConfig) String(key string) string {
v := b.innerConfig.String(RunMode + "::" + key) if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" {
if v == "" { return v
return b.innerConfig.String(key)
} }
return v return b.innerConfig.String(key)
} }
func (b *beegoAppConfig) Strings(key string) []string { func (b *beegoAppConfig) Strings(key string) []string {
v := b.innerConfig.Strings(RunMode + "::" + key) if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 {
if v[0] == "" { return v
return b.innerConfig.Strings(key)
} }
return v return b.innerConfig.Strings(key)
} }
func (b *beegoAppConfig) Int(key string) (int, error) { func (b *beegoAppConfig) Int(key string) (int, error) {
v, err := b.innerConfig.Int(RunMode + "::" + key) if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil {
if err != nil { return v, nil
return b.innerConfig.Int(key)
} }
return v, nil return b.innerConfig.Int(key)
} }
func (b *beegoAppConfig) Int64(key string) (int64, error) { func (b *beegoAppConfig) Int64(key string) (int64, error) {
v, err := b.innerConfig.Int64(RunMode + "::" + key) if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil {
if err != nil { return v, nil
return b.innerConfig.Int64(key)
} }
return v, nil return b.innerConfig.Int64(key)
} }
func (b *beegoAppConfig) Bool(key string) (bool, error) { func (b *beegoAppConfig) Bool(key string) (bool, error) {
v, err := b.innerConfig.Bool(RunMode + "::" + key) if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil {
if err != nil { return v, nil
return b.innerConfig.Bool(key)
} }
return v, nil return b.innerConfig.Bool(key)
} }
func (b *beegoAppConfig) Float(key string) (float64, error) { func (b *beegoAppConfig) Float(key string) (float64, error) {
v, err := b.innerConfig.Float(RunMode + "::" + key) if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil {
if err != nil { return v, nil
return b.innerConfig.Float(key)
} }
return v, nil return b.innerConfig.Float(key)
} }
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string { func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
return b.innerConfig.DefaultString(key, defaultval) if v := b.String(key); v != "" {
return v
}
return defaultVal
} }
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string { func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
return b.innerConfig.DefaultStrings(key, defaultval) if v := b.Strings(key); len(v) != 0 {
return v
}
return defaultVal
} }
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int { func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
return b.innerConfig.DefaultInt(key, defaultval) if v, err := b.Int(key); err == nil {
return v
}
return defaultVal
} }
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 { func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
return b.innerConfig.DefaultInt64(key, defaultval) if v, err := b.Int64(key); err == nil {
return v
}
return defaultVal
} }
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool { func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
return b.innerConfig.DefaultBool(key, defaultval) if v, err := b.Bool(key); err == nil {
return v
}
return defaultVal
} }
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 { func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
return b.innerConfig.DefaultFloat(key, defaultval) if v, err := b.Float(key); err == nil {
return v
}
return defaultVal
} }
func (b *beegoAppConfig) DIY(key string) (interface{}, error) { func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
@ -184,302 +487,3 @@ func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) {
func (b *beegoAppConfig) SaveConfigFile(filename string) error { func (b *beegoAppConfig) SaveConfigFile(filename string) error {
return b.innerConfig.SaveConfigFile(filename) return b.innerConfig.SaveConfigFile(filename)
} }
func init() {
// create beego application
BeeApp = NewApp()
workPath, _ = os.Getwd()
workPath, _ = filepath.Abs(workPath)
// initialize default configurations
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if workPath != AppPath {
if utils.FileExists(AppConfigPath) {
os.Chdir(AppPath)
} else {
AppConfigPath = filepath.Join(workPath, "conf", "app.conf")
}
}
AppConfigProvider = "ini"
StaticDir = make(map[string]string)
StaticDir["/static"] = "static"
StaticExtensionsToGzip = []string{".css", ".js"}
TemplateCache = make(map[string]*template.Template)
// set this to 0.0.0.0 to make this app available to externally
EnableHttpListen = true //default enable http Listen
HttpAddr = ""
HttpPort = 8080
HttpsPort = 10443
AppName = "beego"
RunMode = "dev" //default runmod
AutoRender = true
RecoverPanic = true
ViewsPath = "views"
SessionOn = false
SessionProvider = "memory"
SessionName = "beegosessionID"
SessionGCMaxLifetime = 3600
SessionSavePath = ""
SessionCookieLifeTime = 0 //set cookie default is the brower life
SessionAutoSetCookie = true
UseFcgi = false
UseStdIo = false
MaxMemory = 1 << 26 //64MB
EnableGzip = false
HttpServerTimeOut = 0
ErrorsShow = true
XSRFKEY = "beegoxsrf"
XSRFExpire = 0
TemplateLeft = "{{"
TemplateRight = "}}"
BeegoServerName = "beegoServer:" + VERSION
EnableAdmin = false
AdminHttpAddr = "127.0.0.1"
AdminHttpPort = 8088
FlashName = "BEEGO_FLASH"
FlashSeperator = "BEEGOFLASH"
RouterCaseSensitive = true
runtime.GOMAXPROCS(runtime.NumCPU())
// init BeeLogger
BeeLogger = logs.NewLogger(10000)
err := BeeLogger.SetLogger("console", "")
if err != nil {
fmt.Println("init console log error:", err)
}
SetLogFuncCall(true)
err = ParseConfig()
if err != nil && os.IsNotExist(err) {
// for init if doesn't have app.conf will not panic
ac := config.NewFakeConfig()
AppConfig = &beegoAppConfig{ac}
Warning(err)
}
}
// ParseConfig parsed default config file.
// now only support ini, next will support json.
func ParseConfig() (err error) {
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
if err != nil {
return err
}
envRunMode := os.Getenv("BEEGO_RUNMODE")
// set the runmode first
if envRunMode != "" {
RunMode = envRunMode
} else if runmode := AppConfig.String("RunMode"); runmode != "" {
RunMode = runmode
}
HttpAddr = AppConfig.String("HttpAddr")
if v, err := AppConfig.Int("HttpPort"); err == nil {
HttpPort = v
}
if v, err := AppConfig.Bool("ListenTCP4"); err == nil {
ListenTCP4 = v
}
if v, err := AppConfig.Bool("EnableHttpListen"); err == nil {
EnableHttpListen = v
}
if maxmemory, err := AppConfig.Int64("MaxMemory"); err == nil {
MaxMemory = maxmemory
}
if appname := AppConfig.String("AppName"); appname != "" {
AppName = appname
}
if autorender, err := AppConfig.Bool("AutoRender"); err == nil {
AutoRender = autorender
}
if autorecover, err := AppConfig.Bool("RecoverPanic"); err == nil {
RecoverPanic = autorecover
}
if views := AppConfig.String("ViewsPath"); views != "" {
ViewsPath = views
}
if sessionon, err := AppConfig.Bool("SessionOn"); err == nil {
SessionOn = sessionon
}
if sessProvider := AppConfig.String("SessionProvider"); sessProvider != "" {
SessionProvider = sessProvider
}
if sessName := AppConfig.String("SessionName"); sessName != "" {
SessionName = sessName
}
if sesssavepath := AppConfig.String("SessionSavePath"); sesssavepath != "" {
SessionSavePath = sesssavepath
}
if sessMaxLifeTime, err := AppConfig.Int64("SessionGCMaxLifetime"); err == nil && sessMaxLifeTime != 0 {
SessionGCMaxLifetime = sessMaxLifeTime
}
if sesscookielifetime, err := AppConfig.Int("SessionCookieLifeTime"); err == nil && sesscookielifetime != 0 {
SessionCookieLifeTime = sesscookielifetime
}
if usefcgi, err := AppConfig.Bool("UseFcgi"); err == nil {
UseFcgi = usefcgi
}
if enablegzip, err := AppConfig.Bool("EnableGzip"); err == nil {
EnableGzip = enablegzip
}
if directoryindex, err := AppConfig.Bool("DirectoryIndex"); err == nil {
DirectoryIndex = directoryindex
}
if timeout, err := AppConfig.Int64("HttpServerTimeOut"); err == nil {
HttpServerTimeOut = timeout
}
if errorsshow, err := AppConfig.Bool("ErrorsShow"); err == nil {
ErrorsShow = errorsshow
}
if copyrequestbody, err := AppConfig.Bool("CopyRequestBody"); err == nil {
CopyRequestBody = copyrequestbody
}
if xsrfkey := AppConfig.String("XSRFKEY"); xsrfkey != "" {
XSRFKEY = xsrfkey
}
if enablexsrf, err := AppConfig.Bool("EnableXSRF"); err == nil {
EnableXSRF = enablexsrf
}
if expire, err := AppConfig.Int("XSRFExpire"); err == nil {
XSRFExpire = expire
}
if tplleft := AppConfig.String("TemplateLeft"); tplleft != "" {
TemplateLeft = tplleft
}
if tplright := AppConfig.String("TemplateRight"); tplright != "" {
TemplateRight = tplright
}
if httptls, err := AppConfig.Bool("EnableHttpTLS"); err == nil {
EnableHttpTLS = httptls
}
if httpsport, err := AppConfig.Int("HttpsPort"); err == nil {
HttpsPort = httpsport
}
if certfile := AppConfig.String("HttpCertFile"); certfile != "" {
HttpCertFile = certfile
}
if keyfile := AppConfig.String("HttpKeyFile"); keyfile != "" {
HttpKeyFile = keyfile
}
if serverName := AppConfig.String("BeegoServerName"); serverName != "" {
BeegoServerName = serverName
}
if flashname := AppConfig.String("FlashName"); flashname != "" {
FlashName = flashname
}
if flashseperator := AppConfig.String("FlashSeperator"); flashseperator != "" {
FlashSeperator = flashseperator
}
if sd := AppConfig.String("StaticDir"); sd != "" {
for k := range StaticDir {
delete(StaticDir, k)
}
sds := strings.Fields(sd)
for _, v := range sds {
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1]
} else {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0]
}
}
}
if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
extensions := strings.Split(sgz, ",")
if len(extensions) > 0 {
StaticExtensionsToGzip = []string{}
for _, ext := range extensions {
if len(ext) == 0 {
continue
}
extWithDot := ext
if extWithDot[:1] != "." {
extWithDot = "." + extWithDot
}
StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
}
}
}
if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil {
EnableAdmin = enableadmin
}
if adminhttpaddr := AppConfig.String("AdminHttpAddr"); adminhttpaddr != "" {
AdminHttpAddr = adminhttpaddr
}
if adminhttpport, err := AppConfig.Int("AdminHttpPort"); err == nil {
AdminHttpPort = adminhttpport
}
if enabledocs, err := AppConfig.Bool("EnableDocs"); err == nil {
EnableDocs = enabledocs
}
if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil {
RouterCaseSensitive = casesensitive
}
return nil
}

View File

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package config is used to parse config.
// Usage: // Usage:
// import( // import "github.com/astaxie/beego/config"
// "github.com/astaxie/beego/config" //Examples.
// )
// //
// cnf, err := config.NewConfig("ini", "config.conf") // cnf, err := config.NewConfig("ini", "config.conf")
// //
@ -28,47 +28,49 @@
// cnf.Int64(key string) (int64, error) // cnf.Int64(key string) (int64, error)
// cnf.Bool(key string) (bool, error) // cnf.Bool(key string) (bool, error)
// cnf.Float(key string) (float64, error) // cnf.Float(key string) (float64, error)
// cnf.DefaultString(key string, defaultval string) string // cnf.DefaultString(key string, defaultVal string) string
// cnf.DefaultStrings(key string, defaultval []string) []string // cnf.DefaultStrings(key string, defaultVal []string) []string
// cnf.DefaultInt(key string, defaultval int) int // cnf.DefaultInt(key string, defaultVal int) int
// cnf.DefaultInt64(key string, defaultval int64) int64 // cnf.DefaultInt64(key string, defaultVal int64) int64
// cnf.DefaultBool(key string, defaultval bool) bool // cnf.DefaultBool(key string, defaultVal bool) bool
// cnf.DefaultFloat(key string, defaultval float64) float64 // cnf.DefaultFloat(key string, defaultVal float64) float64
// cnf.DIY(key string) (interface{}, error) // cnf.DIY(key string) (interface{}, error)
// cnf.GetSection(section string) (map[string]string, error) // cnf.GetSection(section string) (map[string]string, error)
// cnf.SaveConfigFile(filename 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 package config
import ( import (
"fmt" "fmt"
"os"
"reflect"
"time"
) )
// ConfigContainer defines how to get and set value from configuration raw data. // Configer defines how to get and set value from configuration raw data.
type ConfigContainer interface { type Configer interface {
Set(key, val string) error // support section::key type in given key when using ini type. Set(key, val string) error //support section::key type in given key when using ini type.
String(key string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. String(key string) string //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
Strings(key string) []string //get string slice Strings(key string) []string //get string slice
Int(key string) (int, error) Int(key string) (int, error)
Int64(key string) (int64, error) Int64(key string) (int64, error)
Bool(key string) (bool, error) Bool(key string) (bool, error)
Float(key string) (float64, error) Float(key string) (float64, error)
DefaultString(key string, defaultval string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same. DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
DefaultStrings(key string, defaultval []string) []string //get string slice DefaultStrings(key string, defaultVal []string) []string //get string slice
DefaultInt(key string, defaultval int) int DefaultInt(key string, defaultVal int) int
DefaultInt64(key string, defaultval int64) int64 DefaultInt64(key string, defaultVal int64) int64
DefaultBool(key string, defaultval bool) bool DefaultBool(key string, defaultVal bool) bool
DefaultFloat(key string, defaultval float64) float64 DefaultFloat(key string, defaultVal float64) float64
DIY(key string) (interface{}, error) DIY(key string) (interface{}, error)
GetSection(section string) (map[string]string, error) GetSection(section string) (map[string]string, error)
SaveConfigFile(filename string) error SaveConfigFile(filename string) error
} }
// Config is the adapter interface for parsing config file to get raw data to ConfigContainer. // Config is the adapter interface for parsing config file to get raw data to Configer.
type Config interface { type Config interface {
Parse(key string) (ConfigContainer, error) Parse(key string) (Configer, error)
ParseData(data []byte) (ConfigContainer, error) ParseData(data []byte) (Configer, error)
} }
var adapters = make(map[string]Config) var adapters = make(map[string]Config)
@ -86,22 +88,155 @@ func Register(name string, adapter Config) {
adapters[name] = adapter adapters[name] = adapter
} }
// adapterName is ini/json/xml/yaml. // NewConfig adapterName is ini/json/xml/yaml.
// filename is the config file path. // filename is the config file path.
func NewConfig(adapterName, fileaname string) (ConfigContainer, error) { func NewConfig(adapterName, filename string) (Configer, error) {
adapter, ok := adapters[adapterName] adapter, ok := adapters[adapterName]
if !ok { if !ok {
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName) return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
} }
return adapter.Parse(fileaname) return adapter.Parse(filename)
} }
// adapterName is ini/json/xml/yaml. // NewConfigData adapterName is ini/json/xml/yaml.
// data is the config data. // data is the config data.
func NewConfigData(adapterName string, data []byte) (ConfigContainer, error) { func NewConfigData(adapterName string, data []byte) (Configer, error) {
adapter, ok := adapters[adapterName] adapter, ok := adapters[adapterName]
if !ok { if !ok {
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName) return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
} }
return adapter.ParseData(data) 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)
}
}
}

85
config/env/env.go vendored Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2014 beego Author. All Rights Reserved.
// Copyright 2017 Faissal Elamraoui. 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 env
import (
"fmt"
"os"
"strings"
"github.com/astaxie/beego/utils"
)
var env *utils.BeeMap
func init() {
env = utils.NewBeeMap()
for _, e := range os.Environ() {
splits := strings.Split(e, "=")
env.Set(splits[0], os.Getenv(splits[0]))
}
}
// Get returns a value by key.
// If the key does not exist, the default value will be returned.
func Get(key string, defVal string) string {
if val := env.Get(key); val != nil {
return val.(string)
}
return defVal
}
// MustGet returns a value by key.
// If the key does not exist, it will return an error.
func MustGet(key string) (string, error) {
if val := env.Get(key); val != nil {
return val.(string), nil
}
return "", fmt.Errorf("no env variable with %s", key)
}
// Set sets a value in the ENV copy.
// This does not affect the child process environment.
func Set(key string, value string) {
env.Set(key, value)
}
// MustSet sets a value in the ENV copy and the child process environment.
// It returns an error in case the set operation failed.
func MustSet(key string, value string) error {
err := os.Setenv(key, value)
if err != nil {
return err
}
env.Set(key, value)
return nil
}
// GetAll returns all keys/values in the current child process environment.
func GetAll() map[string]string {
items := env.Items()
envs := make(map[string]string, env.Count())
for key, val := range items {
switch key := key.(type) {
case string:
switch val := val.(type) {
case string:
envs[key] = val
}
}
}
return envs
}

75
config/env/env_test.go vendored Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2014 beego Author. All Rights Reserved.
// Copyright 2017 Faissal Elamraoui. 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 env
import (
"os"
"testing"
)
func TestEnvGet(t *testing.T) {
gopath := Get("GOPATH", "")
if gopath != os.Getenv("GOPATH") {
t.Error("expected GOPATH not empty.")
}
noExistVar := Get("NOEXISTVAR", "foo")
if noExistVar != "foo" {
t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar)
}
}
func TestEnvMustGet(t *testing.T) {
gopath, err := MustGet("GOPATH")
if err != nil {
t.Error(err)
}
if gopath != os.Getenv("GOPATH") {
t.Errorf("expected GOPATH to be the same, got %s.", gopath)
}
_, err = MustGet("NOEXISTVAR")
if err == nil {
t.Error("expected error to be non-nil")
}
}
func TestEnvSet(t *testing.T) {
Set("MYVAR", "foo")
myVar := Get("MYVAR", "bar")
if myVar != "foo" {
t.Errorf("expected MYVAR to equal foo, got %s.", myVar)
}
}
func TestEnvMustSet(t *testing.T) {
err := MustSet("FOO", "bar")
if err != nil {
t.Error(err)
}
fooVar := os.Getenv("FOO")
if fooVar != "bar" {
t.Errorf("expected FOO variable to equal bar, got %s.", fooVar)
}
}
func TestEnvGetAll(t *testing.T) {
envMap := GetAll()
if len(envMap) == 0 {
t.Error("expected environment not empty.")
}
}

View File

@ -38,23 +38,27 @@ func (c *fakeConfigContainer) String(key string) string {
} }
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.getData(key); v == "" { v := c.String(key)
if v == "" {
return defaultval return defaultval
} else {
return v
} }
return v
} }
func (c *fakeConfigContainer) Strings(key string) []string { 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 { func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 { v := c.Strings(key)
if v == nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
func (c *fakeConfigContainer) Int(key string) (int, error) { func (c *fakeConfigContainer) Int(key string) (int, error) {
@ -62,11 +66,11 @@ func (c *fakeConfigContainer) Int(key string) (int, error) {
} }
func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int { func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil { v, err := c.Int(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
func (c *fakeConfigContainer) Int64(key string) (int64, error) { func (c *fakeConfigContainer) Int64(key string) (int64, error) {
@ -74,23 +78,23 @@ func (c *fakeConfigContainer) Int64(key string) (int64, error) {
} }
func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil { v, err := c.Int64(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
func (c *fakeConfigContainer) Bool(key string) (bool, error) { 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 { func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil { v, err := c.Bool(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
func (c *fakeConfigContainer) Float(key string) (float64, error) { func (c *fakeConfigContainer) Float(key string) (float64, error) {
@ -98,11 +102,11 @@ func (c *fakeConfigContainer) Float(key string) (float64, error) {
} }
func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil { v, err := c.Float(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
func (c *fakeConfigContainer) DIY(key string) (interface{}, error) { func (c *fakeConfigContainer) DIY(key string) (interface{}, error) {
@ -120,9 +124,10 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
return errors.New("not implement in the fakeConfigContainer") return errors.New("not implement in the fakeConfigContainer")
} }
var _ ConfigContainer = new(fakeConfigContainer) var _ Configer = new(fakeConfigContainer)
func NewFakeConfig() ConfigContainer { // NewFakeConfig return a fake Congiger
func NewFakeConfig() Configer {
return &fakeConfigContainer{ return &fakeConfigContainer{
data: make(map[string]string), data: make(map[string]string),
} }

View File

@ -18,58 +18,57 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"unicode"
) )
var ( var (
DEFAULT_SECTION = "default" // default section means if some ini items not in a section, make them in default section, defaultSection = "default" // default section means if some ini items not in a section, make them in default section,
bNumComment = []byte{'#'} // number signal bNumComment = []byte{'#'} // number signal
bSemComment = []byte{';'} // semicolon signal bSemComment = []byte{';'} // semicolon signal
bEmpty = []byte{} bEmpty = []byte{}
bEqual = []byte{'='} // equal signal bEqual = []byte{'='} // equal signal
bDQuote = []byte{'"'} // quote signal bDQuote = []byte{'"'} // quote signal
sectionStart = []byte{'['} // section start signal sectionStart = []byte{'['} // section start signal
sectionEnd = []byte{']'} // section end signal sectionEnd = []byte{']'} // section end signal
lineBreak = "\n" lineBreak = "\n"
) )
// IniConfig implements Config to parse ini file. // IniConfig implements Config to parse ini file.
type IniConfig struct { type IniConfig struct {
} }
// ParseFile creates a new Config and parses the file configuration from the named file. // Parse creates a new Config and parses the file configuration from the named file.
func (ini *IniConfig) Parse(name string) (ConfigContainer, error) { func (ini *IniConfig) Parse(name string) (Configer, error) {
return ini.parseFile(name) return ini.parseFile(name)
} }
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
file, err := os.Open(name) data, err := ioutil.ReadFile(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ini.parseData(filepath.Dir(name), data)
}
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
cfg := &IniConfigContainer{ cfg := &IniConfigContainer{
file.Name(), data: make(map[string]map[string]string),
make(map[string]map[string]string), sectionComment: make(map[string]string),
make(map[string]string), keyComment: make(map[string]string),
make(map[string]string), RWMutex: sync.RWMutex{},
sync.RWMutex{},
} }
cfg.Lock() cfg.Lock()
defer cfg.Unlock() defer cfg.Unlock()
defer file.Close()
var comment bytes.Buffer var comment bytes.Buffer
buf := bufio.NewReader(file) buf := bufio.NewReader(bytes.NewBuffer(data))
// check the BOM // check the BOM
head, err := buf.Peek(3) head, err := buf.Peek(3)
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 { if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
@ -77,17 +76,20 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
buf.ReadByte() buf.ReadByte()
} }
} }
section := DEFAULT_SECTION section := defaultSection
for { for {
line, _, err := buf.ReadLine() line, _, err := buf.ReadLine()
if err == io.EOF { if err == io.EOF {
break 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) { if bytes.Equal(line, bEmpty) {
continue continue
} }
line = bytes.TrimSpace(line)
var bComment []byte var bComment []byte
switch { switch {
case bytes.HasPrefix(line, bNumComment): case bytes.HasPrefix(line, bNumComment):
@ -97,9 +99,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
} }
if bComment != nil { if bComment != nil {
line = bytes.TrimLeft(line, string(bComment)) 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.Write(line)
comment.WriteByte('\n')
continue continue
} }
@ -125,16 +129,20 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
// handle include "other.conf" // handle include "other.conf"
if len(keyValue) == 1 && strings.HasPrefix(key, "include") { if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
includefiles := strings.Fields(key) includefiles := strings.Fields(key)
if includefiles[0] == "include" && len(includefiles) == 2 { if includefiles[0] == "include" && len(includefiles) == 2 {
otherfile := strings.Trim(includefiles[1], "\"") otherfile := strings.Trim(includefiles[1], "\"")
if !path.IsAbs(otherfile) { if !filepath.IsAbs(otherfile) {
otherfile = path.Join(path.Dir(name), otherfile) otherfile = filepath.Join(dir, otherfile)
} }
i, err := ini.parseFile(otherfile) i, err := ini.parseFile(otherfile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for sec, dt := range i.data { for sec, dt := range i.data {
if _, ok := cfg.data[sec]; !ok { if _, ok := cfg.data[sec]; !ok {
cfg.data[sec] = make(map[string]string) cfg.data[sec] = make(map[string]string)
@ -143,12 +151,15 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
cfg.data[sec][k] = v cfg.data[sec][k] = v
} }
} }
for sec, comm := range i.sectionComment { for sec, comm := range i.sectionComment {
cfg.sectionComment[sec] = comm cfg.sectionComment[sec] = comm
} }
for k, comm := range i.keyComment { for k, comm := range i.keyComment {
cfg.keyComment[k] = comm cfg.keyComment[k] = comm
} }
continue continue
} }
} }
@ -161,7 +172,7 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
val = bytes.Trim(val, `"`) val = bytes.Trim(val, `"`)
} }
cfg.data[section][key] = string(val) cfg.data[section][key] = ExpandValueEnv(string(val))
if comment.Len() > 0 { if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String() cfg.keyComment[section+"."+key] = comment.String()
comment.Reset() comment.Reset()
@ -171,20 +182,19 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
return cfg, nil return cfg, nil
} }
func (ini *IniConfig) ParseData(data []byte) (ConfigContainer, error) { // ParseData parse ini the data
// Save memory data to temporary file // When include other.conf,other.conf is either absolute directory
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) // or under beego in default temporary directory(/tmp/beego).
os.MkdirAll(path.Dir(tmpName), os.ModePerm) func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { dir := filepath.Join(os.TempDir(), "beego")
return nil, err os.MkdirAll(dir, os.ModePerm)
}
return ini.Parse(tmpName) return ini.parseData(dir, data)
} }
// A Config represents the ini configuration. // IniConfigContainer A Config represents the ini configuration.
// When set and get value, support key as section:name type. // When set and get value, support key as section:name type.
type IniConfigContainer struct { type IniConfigContainer struct {
filename string
data map[string]map[string]string // section=> key:val data map[string]map[string]string // section=> key:val
sectionComment map[string]string // section : comment sectionComment map[string]string // section : comment
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment. keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
@ -193,17 +203,17 @@ type IniConfigContainer struct {
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) { 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. // DefaultBool returns the boolean value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil { v, err := c.Bool(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Int returns the integer value for a given key. // Int returns the integer value for a given key.
@ -214,11 +224,11 @@ func (c *IniConfigContainer) Int(key string) (int, error) {
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil { v, err := c.Int(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Int64 returns the int64 value for a given key. // Int64 returns the int64 value for a given key.
@ -229,11 +239,11 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) {
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil { v, err := c.Int64(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Float returns the float value for a given key. // Float returns the float value for a given key.
@ -244,11 +254,11 @@ func (c *IniConfigContainer) Float(key string) (float64, error) {
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil { v, err := c.Float(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// String returns the string value for a given key. // String returns the string value for a given key.
@ -259,38 +269,44 @@ func (c *IniConfigContainer) String(key string) string {
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" { v := c.String(key)
if v == "" {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Strings returns the []string value for a given key. // 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 { 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. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 { v := c.Strings(key)
if v == nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// GetSection returns map for the given section // GetSection returns map for the given section
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) { func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok { if v, ok := c.data[section]; ok {
return v, nil return v, nil
} else {
return nil, errors.New("not exist setction")
} }
return nil, errors.New("not exist section")
} }
// 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) { func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)
@ -299,27 +315,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
} }
defer f.Close() 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) buf := bytes.NewBuffer(nil)
for section, dt := range c.data { // Save default section at first place
// Write section comments. if dt, ok := c.data[defaultSection]; ok {
if v, ok := c.sectionComment[section]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
return err
}
}
if section != DEFAULT_SECTION {
// Write section name.
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
return err
}
}
for key, val := range dt { for key, val := range dt {
if key != " " { if key != " " {
// Write key comments. // Write key comments.
if v, ok := c.keyComment[key]; ok { if v := getCommentStr(defaultSection, key); len(v) > 0 {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { if _, err = buf.WriteString(v + lineBreak); err != nil {
return err return err
} }
} }
@ -336,6 +360,43 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
return err return err
} }
} }
// Save named sections
for section, dt := range c.data {
if section != defaultSection {
// Write section comments.
if v := getCommentStr(section, ""); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
// Write section name.
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
return err
}
for key, val := range dt {
if key != " " {
// Write key comments.
if v := getCommentStr(section, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
// Write key and value.
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
return err
}
}
}
// Put a line between sections.
if _, err = buf.WriteString(lineBreak); err != nil {
return err
}
}
}
if _, err = buf.WriteTo(f); err != nil { if _, err = buf.WriteTo(f); err != nil {
return err return err
@ -343,7 +404,7 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
return nil return nil
} }
// WriteValue writes a new value for key. // Set writes a new value for key.
// if write to one section, the key need be "section::key". // if write to one section, the key need be "section::key".
// if the section is not existed, it panics. // if the section is not existed, it panics.
func (c *IniConfigContainer) Set(key, value string) error { func (c *IniConfigContainer) Set(key, value string) error {
@ -355,14 +416,14 @@ func (c *IniConfigContainer) Set(key, value string) error {
var ( var (
section, k string section, k string
sectionKey []string = strings.Split(key, "::") sectionKey = strings.Split(key, "::")
) )
if len(sectionKey) >= 2 { if len(sectionKey) >= 2 {
section = sectionKey[0] section = sectionKey[0]
k = sectionKey[1] k = sectionKey[1]
} else { } else {
section = DEFAULT_SECTION section = defaultSection
k = sectionKey[0] k = sectionKey[0]
} }
@ -391,13 +452,13 @@ func (c *IniConfigContainer) getdata(key string) string {
var ( var (
section, k string section, k string
sectionKey []string = strings.Split(strings.ToLower(key), "::") sectionKey = strings.Split(strings.ToLower(key), "::")
) )
if len(sectionKey) >= 2 { if len(sectionKey) >= 2 {
section = sectionKey[0] section = sectionKey[0]
k = sectionKey[1] k = sectionKey[1]
} else { } else {
section = DEFAULT_SECTION section = defaultSection
k = sectionKey[0] k = sectionKey[0]
} }
if v, ok := c.data[section]; ok { if v, ok := c.data[section]; ok {

View File

@ -15,11 +15,17 @@
package config package config
import ( import (
"fmt"
"io/ioutil"
"os" "os"
"strings"
"testing" "testing"
) )
var inicontext = ` func TestIni(t *testing.T) {
var (
inicontext = `
;comment one ;comment one
#comment two #comment two
appname = beeapi appname = beeapi
@ -29,14 +35,52 @@ PI = 3.1415976
runmode = "dev" runmode = "dev"
autorender = false autorender = false
copyrequestbody = true copyrequestbody = true
session= on
cookieon= off
newreg = OFF
needlogin = ON
enableSession = Y
enableCookie = N
flag = 1
path1 = ${GOPATH}
path2 = ${GOPATH||/home/go}
[demo] [demo]
key1="asta" key1="asta"
key2 = "xie" key2 = "xie"
CaseInsensitive = true CaseInsensitive = true
peers = one;two;three 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") f, err := os.Create("testini.conf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -52,31 +96,31 @@ func TestIni(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if iniconf.String("appname") != "beeapi" { for k, v := range keyValue {
t.Fatal("appname not equal to beeapi") var err error
} var value interface{}
if port, err := iniconf.Int("httpport"); err != nil || port != 8080 { switch v.(type) {
t.Error(port) case int:
t.Fatal(err) value, err = iniconf.Int(k)
} case int64:
if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 { value, err = iniconf.Int64(k)
t.Error(port) case float64:
t.Fatal(err) value, err = iniconf.Float(k)
} case bool:
if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 { value, err = iniconf.Bool(k)
t.Error(pi) case []string:
t.Fatal(err) value = iniconf.Strings(k)
} case string:
if iniconf.String("runmode") != "dev" { value = iniconf.String(k)
t.Fatal("runmode not equal to dev") default:
} value, err = iniconf.DIY(k)
if v, err := iniconf.Bool("autorender"); err != nil || v != false { }
t.Error(v) if err != nil {
t.Fatal(err) t.Fatalf("get key %q value fail,err %s", k, err)
} } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true { t.Fatalf("get key %q value, want %v got %v .", k, v, value)
t.Error(v) }
t.Fatal(err)
} }
if err = iniconf.Set("name", "astaxie"); err != nil { if err = iniconf.Set("name", "astaxie"); err != nil {
t.Fatal(err) t.Fatal(err)
@ -84,20 +128,63 @@ func TestIni(t *testing.T) {
if iniconf.String("name") != "astaxie" { if iniconf.String("name") != "astaxie" {
t.Fatal("get name error") 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,18 +17,19 @@ package config
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"sync" "sync"
) )
// JsonConfig is a json config parser and implements Config interface. // JSONConfig is a json config parser and implements Config interface.
type JsonConfig struct { type JSONConfig struct {
} }
// Parse returns a ConfigContainer with parsed json config map. // Parse returns a ConfigContainer with parsed json config map.
func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) { func (js *JSONConfig) Parse(filename string) (Configer, error) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -43,8 +44,8 @@ func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) {
} }
// ParseData returns a ConfigContainer with json string // ParseData returns a ConfigContainer with json string
func (js *JsonConfig) ParseData(data []byte) (ConfigContainer, error) { func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
x := &JsonConfigContainer{ x := &JSONConfigContainer{
data: make(map[string]interface{}), data: make(map[string]interface{}),
} }
err := json.Unmarshal(data, &x.data) err := json.Unmarshal(data, &x.data)
@ -56,31 +57,31 @@ func (js *JsonConfig) ParseData(data []byte) (ConfigContainer, error) {
} }
x.data["rootArray"] = wrappingArray x.data["rootArray"] = wrappingArray
} }
x.data = ExpandValueEnvForMap(x.data)
return x, nil return x, nil
} }
// A Config represents the json configuration. // JSONConfigContainer A Config represents the json configuration.
// Only when get value, support key as section:name type. // Only when get value, support key as section:name type.
type JsonConfigContainer struct { type JSONConfigContainer struct {
data map[string]interface{} data map[string]interface{}
sync.RWMutex sync.RWMutex
} }
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *JsonConfigContainer) Bool(key string) (bool, error) { func (c *JSONConfigContainer) Bool(key string) (bool, error) {
val := c.getData(key) val := c.getData(key)
if val != nil { if val != nil {
if v, ok := val.(bool); ok { return ParseBool(val)
return v, nil
}
return false, errors.New("not bool value")
} }
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 // DefaultBool return the bool value if has no error
// otherwise return the defaultval // otherwise return the defaultval
func (c *JsonConfigContainer) DefaultBool(key string, defaultval bool) bool { func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err == nil { if v, err := c.Bool(key); err == nil {
return v return v
} }
@ -88,7 +89,7 @@ func (c *JsonConfigContainer) DefaultBool(key string, defaultval bool) bool {
} }
// Int returns the integer value for a given key. // Int returns the integer value for a given key.
func (c *JsonConfigContainer) Int(key string) (int, error) { func (c *JSONConfigContainer) Int(key string) (int, error) {
val := c.getData(key) val := c.getData(key)
if val != nil { if val != nil {
if v, ok := val.(float64); ok { if v, ok := val.(float64); ok {
@ -101,7 +102,7 @@ func (c *JsonConfigContainer) Int(key string) (int, error) {
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *JsonConfigContainer) DefaultInt(key string, defaultval int) int { func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err == nil { if v, err := c.Int(key); err == nil {
return v return v
} }
@ -109,7 +110,7 @@ func (c *JsonConfigContainer) DefaultInt(key string, defaultval int) int {
} }
// Int64 returns the int64 value for a given key. // Int64 returns the int64 value for a given key.
func (c *JsonConfigContainer) Int64(key string) (int64, error) { func (c *JSONConfigContainer) Int64(key string) (int64, error) {
val := c.getData(key) val := c.getData(key)
if val != nil { if val != nil {
if v, ok := val.(float64); ok { if v, ok := val.(float64); ok {
@ -122,7 +123,7 @@ func (c *JsonConfigContainer) Int64(key string) (int64, error) {
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *JsonConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err == nil { if v, err := c.Int64(key); err == nil {
return v return v
} }
@ -130,7 +131,7 @@ func (c *JsonConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
} }
// Float returns the float value for a given key. // Float returns the float value for a given key.
func (c *JsonConfigContainer) Float(key string) (float64, error) { func (c *JSONConfigContainer) Float(key string) (float64, error) {
val := c.getData(key) val := c.getData(key)
if val != nil { if val != nil {
if v, ok := val.(float64); ok { if v, ok := val.(float64); ok {
@ -143,7 +144,7 @@ func (c *JsonConfigContainer) Float(key string) (float64, error) {
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *JsonConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err == nil { if v, err := c.Float(key); err == nil {
return v return v
} }
@ -151,7 +152,7 @@ func (c *JsonConfigContainer) DefaultFloat(key string, defaultval float64) float
} }
// String returns the string value for a given key. // String returns the string value for a given key.
func (c *JsonConfigContainer) String(key string) string { func (c *JSONConfigContainer) String(key string) string {
val := c.getData(key) val := c.getData(key)
if val != nil { if val != nil {
if v, ok := val.(string); ok { if v, ok := val.(string); ok {
@ -163,8 +164,8 @@ func (c *JsonConfigContainer) String(key string) string {
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *JsonConfigContainer) DefaultString(key string, defaultval string) string { func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
// TODO FIXME should not use "" to replace non existance // TODO FIXME should not use "" to replace non existence
if v := c.String(key); v != "" { if v := c.String(key); v != "" {
return v return v
} }
@ -172,25 +173,25 @@ func (c *JsonConfigContainer) DefaultString(key string, defaultval string) strin
} }
// Strings returns the []string value for a given key. // Strings returns the []string value for a given key.
func (c *JsonConfigContainer) Strings(key string) []string { func (c *JSONConfigContainer) Strings(key string) []string {
stringVal := c.String(key) stringVal := c.String(key)
if stringVal == "" { if stringVal == "" {
return []string{} return nil
} }
return strings.Split(c.String(key), ";") return strings.Split(c.String(key), ";")
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *JsonConfigContainer) DefaultStrings(key string, defaultval []string) []string { 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 v
} }
return defaultval return defaultval
} }
// GetSection returns map for the given section // GetSection returns map for the given section
func (c *JsonConfigContainer) GetSection(section string) (map[string]string, error) { func (c *JSONConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok { if v, ok := c.data[section]; ok {
return v.(map[string]string), nil return v.(map[string]string), nil
} }
@ -198,7 +199,7 @@ func (c *JsonConfigContainer) GetSection(section string) (map[string]string, err
} }
// SaveConfigFile save the config into file // SaveConfigFile save the config into file
func (c *JsonConfigContainer) SaveConfigFile(filename string) (err error) { func (c *JSONConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
@ -214,7 +215,7 @@ func (c *JsonConfigContainer) SaveConfigFile(filename string) (err error) {
} }
// Set writes a new value for key. // Set writes a new value for key.
func (c *JsonConfigContainer) Set(key, val string) error { func (c *JSONConfigContainer) Set(key, val string) error {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
c.data[key] = val c.data[key] = val
@ -222,7 +223,7 @@ func (c *JsonConfigContainer) Set(key, val string) error {
} }
// DIY returns the raw value by a given key. // DIY returns the raw value by a given key.
func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) { func (c *JSONConfigContainer) DIY(key string) (v interface{}, err error) {
val := c.getData(key) val := c.getData(key)
if val != nil { if val != nil {
return val, nil return val, nil
@ -231,7 +232,7 @@ func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) {
} }
// section.key or key // section.key or key
func (c *JsonConfigContainer) getData(key string) interface{} { func (c *JSONConfigContainer) getData(key string) interface{} {
if len(key) == 0 { if len(key) == 0 {
return nil return nil
} }
@ -261,5 +262,5 @@ func (c *JsonConfigContainer) getData(key string) interface{} {
} }
func init() { func init() {
Register("json", &JsonConfig{}) Register("json", &JSONConfig{})
} }

View File

@ -15,34 +15,14 @@
package config package config
import ( import (
"fmt"
"os" "os"
"testing" "testing"
) )
var jsoncontext = `{ func TestJsonStartsWithArray(t *testing.T) {
"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"
}
}
}`
var jsoncontextwitharray = `[ const jsoncontextwitharray = `[
{ {
"url": "user", "url": "user",
"serviceAPI": "http://www.test.com/user" "serviceAPI": "http://www.test.com/user"
@ -52,8 +32,6 @@ var jsoncontextwitharray = `[
"serviceAPI": "http://www.test.com/employee" "serviceAPI": "http://www.test.com/employee"
} }
]` ]`
func TestJsonStartsWithArray(t *testing.T) {
f, err := os.Create("testjsonWithArray.conf") f, err := os.Create("testjsonWithArray.conf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -90,6 +68,70 @@ func TestJsonStartsWithArray(t *testing.T) {
} }
func TestJson(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") f, err := os.Create("testjson.conf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -105,37 +147,32 @@ func TestJson(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if jsonconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi") for k, v := range keyValue {
} var err error
if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 { var value interface{}
t.Error(port) switch v.(type) {
t.Fatal(err) case int:
} value, err = jsonconf.Int(k)
if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 { case int64:
t.Error(port) value, err = jsonconf.Int64(k)
t.Fatal(err) case float64:
} value, err = jsonconf.Float(k)
if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 { case bool:
t.Error(pi) value, err = jsonconf.Bool(k)
t.Fatal(err) case []string:
} value = jsonconf.Strings(k)
if jsonconf.String("runmode") != "dev" { case string:
t.Fatal("runmode not equal to dev") value = jsonconf.String(k)
} default:
if v := jsonconf.Strings("unknown"); len(v) > 0 { value, err = jsonconf.DIY(k)
t.Fatal("unknown strings, the length should be 0") }
} if err != nil {
if v := jsonconf.Strings("testnames"); len(v) != 2 { t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
t.Fatal("testnames length should be 2") } else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
} t.Fatalf("get key %q value, want %v got %v .", k, v, value)
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)
} }
if err = jsonconf.Set("name", "astaxie"); err != nil { if err = jsonconf.Set("name", "astaxie"); err != nil {
t.Fatal(err) t.Fatal(err)
@ -143,15 +180,7 @@ func TestJson(t *testing.T) {
if jsonconf.String("name") != "astaxie" { if jsonconf.String("name") != "astaxie" {
t.Fatal("get name error") 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 { if db, err := jsonconf.DIY("database"); err != nil {
t.Fatal(err) t.Fatal(err)
} else if m, ok := db.(map[string]interface{}); !ok { } 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 // See the License for the specific language governing permissions and
// limitations under the License. // 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: // Usage:
// import( // import(
// _ "github.com/astaxie/beego/config/xml" // _ "github.com/astaxie/beego/config/xml"
// "github.com/astaxie/beego/config" // "github.com/astaxie/beego/config"
// ) // )
// //
// cnf, err := config.NewConfig("xml", "config.xml") // 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 package xml
import ( import (
@ -35,122 +35,115 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/astaxie/beego/config" "github.com/astaxie/beego/config"
"github.com/beego/x2j" "github.com/beego/x2j"
) )
// XmlConfig is a xml config parser and implements Config interface. // Config is a xml config parser and implements Config interface.
// xml configurations should be included in <config></config> tag. // xml configurations should be included in <config></config> tag.
// only support key/value pair as <key>value</key> as each item. // only support key/value pair as <key>value</key> as each item.
type XMLConfig struct{} type Config struct{}
// Parse returns a ConfigContainer with parsed xml config map. // Parse returns a ConfigContainer with parsed xml config map.
func (xc *XMLConfig) Parse(filename string) (config.ConfigContainer, error) { func (xc *Config) Parse(filename string) (config.Configer, error) {
file, err := os.Open(filename) context, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
defer file.Close()
x := &XMLConfigContainer{data: make(map[string]interface{})}
content, err := ioutil.ReadAll(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
d, err := x2j.DocToMap(string(content)) return xc.ParseData(context)
}
// ParseData xml data
func (xc *Config) ParseData(data []byte) (config.Configer, error) {
x := &ConfigContainer{data: make(map[string]interface{})}
d, err := x2j.DocToMap(string(data))
if err != nil { if err != nil {
return nil, err return nil, err
} }
x.data = d["config"].(map[string]interface{}) x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{}))
return x, nil return x, nil
} }
func (x *XMLConfig) ParseData(data []byte) (config.ConfigContainer, error) { // ConfigContainer A Config represents the xml configuration.
// Save memory data to temporary file type ConfigContainer struct {
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond()))
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
return nil, err
}
return x.Parse(tmpName)
}
// A Config represents the xml configuration.
type XMLConfigContainer struct {
data map[string]interface{} data map[string]interface{}
sync.Mutex sync.Mutex
} }
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *XMLConfigContainer) Bool(key string) (bool, error) { 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 // DefaultBool return the bool value if has no error
// otherwise return the defaultval // otherwise return the defaultval
func (c *XMLConfigContainer) DefaultBool(key string, defaultval bool) bool { func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil { v, err := c.Bool(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Int returns the integer value for a given key. // Int returns the integer value for a given key.
func (c *XMLConfigContainer) Int(key string) (int, error) { func (c *ConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.data[key].(string)) return strconv.Atoi(c.data[key].(string))
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *XMLConfigContainer) DefaultInt(key string, defaultval int) int { func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil { v, err := c.Int(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Int64 returns the int64 value for a given key. // Int64 returns the int64 value for a given key.
func (c *XMLConfigContainer) Int64(key string) (int64, error) { func (c *ConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.data[key].(string), 10, 64) return strconv.ParseInt(c.data[key].(string), 10, 64)
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *XMLConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil { v, err := c.Int64(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Float returns the float value for a given key. // Float returns the float value for a given key.
func (c *XMLConfigContainer) Float(key string) (float64, error) { func (c *ConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.data[key].(string), 64) return strconv.ParseFloat(c.data[key].(string), 64)
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *XMLConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil { v, err := c.Float(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// String returns the string value for a given key. // String returns the string value for a given key.
func (c *XMLConfigContainer) String(key string) string { func (c *ConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok { if v, ok := c.data[key].(string); ok {
return v return v
} }
@ -159,40 +152,47 @@ func (c *XMLConfigContainer) String(key string) string {
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *XMLConfigContainer) DefaultString(key string, defaultval string) string { func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" { v := c.String(key)
if v == "" {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Strings returns the []string value for a given key. // Strings returns the []string value for a given key.
func (c *XMLConfigContainer) Strings(key string) []string { 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. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *XMLConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 { v := c.Strings(key)
if v == nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// GetSection returns map for the given section // GetSection returns map for the given section
func (c *XMLConfigContainer) GetSection(section string) (map[string]string, error) { func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok { if v, ok := c.data[section].(map[string]interface{}); ok {
return v.(map[string]string), nil mapstr := make(map[string]string)
} else { for k, val := range v {
return nil, errors.New("not exist setction") mapstr[k] = config.ToString(val)
}
return mapstr, nil
} }
return nil, fmt.Errorf("section '%s' not found", section)
} }
// SaveConfigFile save the config into file // SaveConfigFile save the config into file
func (c *XMLConfigContainer) SaveConfigFile(filename string) (err error) { func (c *ConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
@ -207,8 +207,8 @@ func (c *XMLConfigContainer) SaveConfigFile(filename string) (err error) {
return err return err
} }
// WriteValue writes a new value for key. // Set writes a new value for key.
func (c *XMLConfigContainer) Set(key, val string) error { func (c *ConfigContainer) Set(key, val string) error {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
c.data[key] = val c.data[key] = val
@ -216,7 +216,7 @@ func (c *XMLConfigContainer) Set(key, val string) error {
} }
// DIY returns the raw value by a given key. // DIY returns the raw value by a given key.
func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) { func (c *ConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[key]; ok { if v, ok := c.data[key]; ok {
return v, nil return v, nil
} }
@ -224,5 +224,5 @@ func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) {
} }
func init() { func init() {
config.Register("xml", &XMLConfig{}) config.Register("xml", &Config{})
} }

View File

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

View File

@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// package yaml for config provider // Package yaml for config provider
// //
// depend on github.com/beego/goyaml2 // depend on github.com/beego/goyaml2
// //
// go install github.com/beego/goyaml2 // go install github.com/beego/goyaml2
// //
// Usage: // Usage:
// import( // import(
// _ "github.com/astaxie/beego/config/yaml" // _ "github.com/astaxie/beego/config/yaml"
// "github.com/astaxie/beego/config" // "github.com/astaxie/beego/config"
// ) // )
// //
// cnf, err := config.NewConfig("yaml", "config.yaml") // 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 package yaml
import ( import (
@ -37,51 +37,54 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path"
"strings" "strings"
"sync" "sync"
"time"
"github.com/astaxie/beego/config" "github.com/astaxie/beego/config"
"github.com/beego/goyaml2" "github.com/beego/goyaml2"
) )
// YAMLConfig is a yaml config parser and implements Config interface. // Config is a yaml config parser and implements Config interface.
type YAMLConfig struct{} type Config struct{}
// Parse returns a ConfigContainer with parsed yaml config map. // Parse returns a ConfigContainer with parsed yaml config map.
func (yaml *YAMLConfig) Parse(filename string) (y config.ConfigContainer, err error) { func (yaml *Config) Parse(filename string) (y config.Configer, err error) {
cnf, err := ReadYmlReader(filename) cnf, err := ReadYmlReader(filename)
if err != nil { if err != nil {
return return
} }
y = &YAMLConfigContainer{ y = &ConfigContainer{
data: cnf, data: cnf,
} }
return return
} }
func (yaml *YAMLConfig) ParseData(data []byte) (config.ConfigContainer, error) { // ParseData parse yaml data
// Save memory data to temporary file func (yaml *Config) ParseData(data []byte) (config.Configer, error) {
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) cnf, err := parseYML(data)
os.MkdirAll(path.Dir(tmpName), os.ModePerm) if err != nil {
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
return nil, err return nil, err
} }
return yaml.Parse(tmpName)
return &ConfigContainer{
data: cnf,
}, nil
} }
// Read yaml file to map. // ReadYmlReader Read yaml file to map.
// if json like, use json package, unless goyaml2 package. // if json like, use json package, unless goyaml2 package.
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) { func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
f, err := os.Open(path) buf, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return return
} }
defer f.Close()
buf, err := ioutil.ReadAll(f) return parseYML(buf)
if err != nil || len(buf) < 3 { }
// parseYML parse yaml formatted []byte to map.
func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
if len(buf) < 3 {
return return
} }
@ -109,131 +112,151 @@ func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
log.Println("Not a Map? >> ", string(buf), data) log.Println("Not a Map? >> ", string(buf), data)
cnf = nil cnf = nil
} }
cnf = config.ExpandValueEnvForMap(cnf)
return return
} }
// A Config represents the yaml configuration. // ConfigContainer A Config represents the yaml configuration.
type YAMLConfigContainer struct { type ConfigContainer struct {
data map[string]interface{} data map[string]interface{}
sync.Mutex sync.Mutex
} }
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *YAMLConfigContainer) Bool(key string) (bool, error) { func (c *ConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key].(bool); ok { v, err := c.getData(key)
return v, nil 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 // DefaultBool return the bool value if has no error
// otherwise return the defaultval // otherwise return the defaultval
func (c *YAMLConfigContainer) DefaultBool(key string, defaultval bool) bool { func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil { v, err := c.Bool(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Int returns the integer value for a given key. // Int returns the integer value for a given key.
func (c *YAMLConfigContainer) Int(key string) (int, error) { func (c *ConfigContainer) Int(key string) (int, error) {
if v, ok := c.data[key].(int64); ok { if v, err := c.getData(key); err != nil {
return int(v), 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") return 0, errors.New("not int value")
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultInt(key string, defaultval int) int { func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil { v, err := c.Int(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Int64 returns the int64 value for a given key. // Int64 returns the int64 value for a given key.
func (c *YAMLConfigContainer) Int64(key string) (int64, error) { func (c *ConfigContainer) Int64(key string) (int64, error) {
if v, ok := c.data[key].(int64); ok { if v, err := c.getData(key); err != nil {
return v, nil return 0, err
} else if vv, ok := v.(int64); ok {
return vv, nil
} }
return 0, errors.New("not bool value") return 0, errors.New("not bool value")
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil { v, err := c.Int64(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Float returns the float value for a given key. // Float returns the float value for a given key.
func (c *YAMLConfigContainer) Float(key string) (float64, error) { func (c *ConfigContainer) Float(key string) (float64, error) {
if v, ok := c.data[key].(float64); ok { if v, err := c.getData(key); err != nil {
return v, 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") return 0.0, errors.New("not float64 value")
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil { v, err := c.Float(key)
if err != nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// String returns the string value for a given key. // String returns the string value for a given key.
func (c *YAMLConfigContainer) String(key string) string { func (c *ConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok { if v, err := c.getData(key); err == nil {
return v if vv, ok := v.(string); ok {
return vv
}
} }
return "" return ""
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultString(key string, defaultval string) string { func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" { v := c.String(key)
if v == "" {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// Strings returns the []string value for a given key. // Strings returns the []string value for a given key.
func (c *YAMLConfigContainer) Strings(key string) []string { 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. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 { v := c.Strings(key)
if v == nil {
return defaultval return defaultval
} else {
return v
} }
return v
} }
// GetSection returns map for the given section // GetSection returns map for the given section
func (c *YAMLConfigContainer) GetSection(section string) (map[string]string, error) { func (c *ConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok { if v, ok := c.data[section]; ok {
return v.(map[string]string), nil return v.(map[string]string), nil
} else {
return nil, errors.New("not exist setction")
} }
return nil, errors.New("not exist section")
} }
// SaveConfigFile save the config into file // SaveConfigFile save the config into file
func (c *YAMLConfigContainer) SaveConfigFile(filename string) (err error) { func (c *ConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
@ -244,8 +267,8 @@ func (c *YAMLConfigContainer) SaveConfigFile(filename string) (err error) {
return err return err
} }
// WriteValue writes a new value for key. // Set writes a new value for key.
func (c *YAMLConfigContainer) Set(key, val string) error { func (c *ConfigContainer) Set(key, val string) error {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
c.data[key] = val c.data[key] = val
@ -253,13 +276,22 @@ func (c *YAMLConfigContainer) Set(key, val string) error {
} }
// DIY returns the raw value by a given key. // DIY returns the raw value by a given key.
func (c *YAMLConfigContainer) DIY(key string) (v interface{}, err error) { 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 { if v, ok := c.data[key]; ok {
return v, nil return v, nil
} }
return nil, errors.New("not exist key") return nil, fmt.Errorf("not exist key %q", key)
} }
func init() { func init() {
config.Register("yaml", &YAMLConfig{}) config.Register("yaml", &Config{})
} }

View File

@ -15,13 +15,17 @@
package yaml package yaml
import ( import (
"fmt"
"os" "os"
"testing" "testing"
"github.com/astaxie/beego/config" "github.com/astaxie/beego/config"
) )
var yamlcontext = ` func TestYaml(t *testing.T) {
var (
yamlcontext = `
"appname": beeapi "appname": beeapi
"httpport": 8080 "httpport": 8080
"mysqlport": 3600 "mysqlport": 3600
@ -29,9 +33,27 @@ var yamlcontext = `
"runmode": dev "runmode": dev
"autorender": false "autorender": false
"copyrequestbody": true "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") f, err := os.Create("testyaml.conf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -47,36 +69,47 @@ func TestYaml(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if yamlconf.String("appname") != "beeapi" { if yamlconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi") t.Fatal("appname not equal to beeapi")
} }
if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port) for k, v := range keyValue {
t.Fatal(err)
} var (
if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 { value interface{}
t.Error(port) err error
t.Fatal(err) )
}
if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 { switch v.(type) {
t.Error(pi) case int:
t.Fatal(err) value, err = yamlconf.Int(k)
} case int64:
if yamlconf.String("runmode") != "dev" { value, err = yamlconf.Int64(k)
t.Fatal("runmode not equal to dev") case float64:
} value, err = yamlconf.Float(k)
if v, err := yamlconf.Bool("autorender"); err != nil || v != false { case bool:
t.Error(v) value, err = yamlconf.Bool(k)
t.Fatal(err) case []string:
} value = yamlconf.Strings(k)
if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true { case string:
t.Error(v) value = yamlconf.String(k)
t.Fatal(err) 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 { if err = yamlconf.Set("name", "astaxie"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if yamlconf.String("name") != "astaxie" { if yamlconf.String("name") != "astaxie" {
t.Fatal("get name error") t.Fatal("get name error")
} }
} }

View File

@ -15,15 +15,124 @@
package beego package beego
import ( import (
"encoding/json"
"reflect"
"testing" "testing"
"github.com/astaxie/beego/config"
) )
func TestDefaults(t *testing.T) { func TestDefaults(t *testing.T) {
if FlashName != "BEEGO_FLASH" { if BConfig.WebConfig.FlashName != "BEEGO_FLASH" {
t.Errorf("FlashName was not set to default.") t.Errorf("FlashName was not set to default.")
} }
if FlashSeperator != "BEEGOFLASH" { if BConfig.WebConfig.FlashSeparator != "BEEGOFLASH" {
t.Errorf("FlashName was not set to default.") 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()
}
}

231
context/acceptencoder.go Normal file
View File

@ -0,0 +1,231 @@
// Copyright 2015 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 (
"bytes"
"compress/flate"
"compress/gzip"
"compress/zlib"
"io"
"net/http"
"os"
"strconv"
"strings"
"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)
}
type nopResetWriter struct {
io.Writer
}
func (n nopResetWriter) Reset(w io.Writer) {
//do nothing
}
type acceptEncoder struct {
name string
levelEncode func(int) resetWriter
customCompressLevelPool *sync.Pool
bestCompressionPool *sync.Pool
}
func (ac acceptEncoder) encode(wr io.Writer, level int) resetWriter {
if ac.customCompressLevelPool == nil || ac.bestCompressionPool == nil {
return nopResetWriter{wr}
}
var rwr resetWriter
switch level {
case flate.BestSpeed:
rwr = ac.customCompressLevelPool.Get().(resetWriter)
case flate.BestCompression:
rwr = ac.bestCompressionPool.Get().(resetWriter)
default:
rwr = ac.levelEncode(level)
}
rwr.Reset(wr)
return rwr
}
func (ac acceptEncoder) put(wr resetWriter, level int) {
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 gzipCompressLevel:
ac.customCompressLevelPool.Put(wr)
case flate.BestCompression:
ac.bestCompressionPool.Put(wr)
}
}
var (
noneCompressEncoder = acceptEncoder{"", nil, nil, nil}
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{
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 }},
}
)
var (
encoderMap = map[string]acceptEncoder{ // all the other compress methods will ignore
"gzip": gzipCompressEncoder,
"deflate": deflateCompressEncoder,
"*": gzipCompressEncoder, // * means any compress will accept,we prefer gzip
"identity": noneCompressEncoder, // identity means none-compress
}
)
// WriteFile reads from file and writes to writer by the specific encoding(gzip/deflate)
func WriteFile(encoding string, writer io.Writer, file *os.File) (bool, string, error) {
return writeLevel(encoding, writer, file, flate.BestCompression)
}
// WriteBody reads writes content to writer by the specific encoding(gzip/deflate)
func WriteBody(encoding string, writer io.Writer, content []byte) (bool, string, error) {
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
// the compress level is defined by deflate package
func writeLevel(encoding string, writer io.Writer, reader io.Reader, level int) (bool, string, error) {
var outputWriter resetWriter
var err error
var ce = noneCompressEncoder
if cf, ok := encoderMap[encoding]; ok {
ce = cf
}
encoding = ce.name
outputWriter = ce.encode(writer, level)
defer ce.put(outputWriter, level)
_, err = io.Copy(outputWriter, reader)
if err != nil {
return false, "", err
}
switch outputWriter.(type) {
case io.WriteCloser:
outputWriter.(io.WriteCloser).Close()
}
return encoding != "", encoding, nil
}
// ParseEncoding will extract the right encoding for response
// the Accept-Encoding's sec is here:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
func ParseEncoding(r *http.Request) string {
if r == nil {
return ""
}
if (getMethodOnly && r.Method == "GET") || includedMethods[r.Method] {
return parseEncoding(r)
}
return ""
}
type q struct {
name string
value float64
}
func parseEncoding(r *http.Request) string {
acceptEncoding := r.Header.Get("Accept-Encoding")
if acceptEncoding == "" {
return ""
}
var lastQ q
for _, v := range strings.Split(acceptEncoding, ",") {
v = strings.TrimSpace(v)
if v == "" {
continue
}
vs := strings.Split(v, ";")
var cf acceptEncoder
var ok bool
if cf, ok = encoderMap[vs[0]]; !ok {
continue
}
if len(vs) == 1 {
return cf.name
}
if len(vs) == 2 {
f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64)
if f == 0 {
continue
}
if f > lastQ.value {
lastQ = q{cf.name, f}
}
}
}
return lastQ.name
}

View File

@ -0,0 +1,59 @@
// Copyright 2015 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"
"testing"
)
func Test_ExtractEncoding(t *testing.T) {
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip,deflate"}}}) != "gzip" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"deflate,gzip"}}}) != "deflate" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=.5,deflate"}}}) != "deflate" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=.5,deflate;q=0.3"}}}) != "gzip" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0,deflate"}}}) != "deflate" {
t.Fail()
}
if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"deflate;q=0.5,gzip;q=0.5,identity"}}}) != "" {
t.Fail()
}
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

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package context provide the context utils
// Usage: // Usage:
// //
// import "github.com/astaxie/beego/context" // import "github.com/astaxie/beego/context"
@ -22,10 +23,13 @@
package context package context
import ( import (
"bufio"
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -34,49 +38,67 @@ import (
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
// Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter. // NewContext return the Context with Input and Output
func NewContext() *Context {
return &Context{
Input: NewInput(),
Output: NewOutput(),
}
}
// Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter.
// BeegoInput and BeegoOutput provides some api to operate request and response more easily. // BeegoInput and BeegoOutput provides some api to operate request and response more easily.
type Context struct { type Context struct {
Input *BeegoInput Input *BeegoInput
Output *BeegoOutput Output *BeegoOutput
Request *http.Request Request *http.Request
ResponseWriter http.ResponseWriter ResponseWriter *Response
_xsrf_token string _xsrfToken string
}
// Reset init Context, BeegoInput and BeegoOutput
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
ctx.Request = r
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. // Redirect does redirection to localurl with http header status code.
// It sends http response header directly.
func (ctx *Context) Redirect(status int, localurl string) { func (ctx *Context) Redirect(status int, localurl string) {
ctx.Output.Header("Location", localurl) http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status)
ctx.ResponseWriter.WriteHeader(status)
} }
// Abort stops this request. // Abort stops this request.
// if beego.ErrorMaps exists, panic body. // if beego.ErrorMaps exists, panic body.
func (ctx *Context) Abort(status int, body string) { func (ctx *Context) Abort(status int, body string) {
ctx.ResponseWriter.WriteHeader(status) ctx.Output.SetStatus(status)
panic(body) panic(body)
} }
// Write string to response body. // WriteString Write string to response body.
// it sends response body. // it sends response body.
func (ctx *Context) WriteString(content string) { func (ctx *Context) WriteString(content string) {
ctx.ResponseWriter.Write([]byte(content)) ctx.ResponseWriter.Write([]byte(content))
} }
// Get cookie from request by a given key. // GetCookie Get cookie from request by a given key.
// It's alias of BeegoInput.Cookie. // It's alias of BeegoInput.Cookie.
func (ctx *Context) GetCookie(key string) string { func (ctx *Context) GetCookie(key string) string {
return ctx.Input.Cookie(key) return ctx.Input.Cookie(key)
} }
// Set cookie for response. // SetCookie Set cookie for response.
// It's alias of BeegoOutput.Cookie. // It's alias of BeegoOutput.Cookie.
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
ctx.Output.Cookie(name, value, others...) ctx.Output.Cookie(name, value, others...)
} }
// Get secure cookie from request by a given key. // GetSecureCookie Get secure cookie from request by a given key.
func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) { func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
val := ctx.Input.Cookie(key) val := ctx.Input.Cookie(key)
if val == "" { if val == "" {
@ -103,7 +125,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
return string(res), true return string(res), true
} }
// Set Secure cookie for response. // SetSecureCookie Set Secure cookie for response.
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) { func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value)) vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10) timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
@ -114,23 +136,23 @@ func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interf
ctx.Output.Cookie(name, cookie, others...) ctx.Output.Cookie(name, cookie, others...)
} }
// XsrfToken creates a xsrf token string and returns. // XSRFToken creates a xsrf token string and returns.
func (ctx *Context) XsrfToken(key string, expire int64) string { func (ctx *Context) XSRFToken(key string, expire int64) string {
if ctx._xsrf_token == "" { if ctx._xsrfToken == "" {
token, ok := ctx.GetSecureCookie(key, "_xsrf") token, ok := ctx.GetSecureCookie(key, "_xsrf")
if !ok { if !ok {
token = string(utils.RandomCreateBytes(32)) token = string(utils.RandomCreateBytes(32))
ctx.SetSecureCookie(key, "_xsrf", token, expire) ctx.SetSecureCookie(key, "_xsrf", token, expire)
} }
ctx._xsrf_token = token ctx._xsrfToken = token
} }
return ctx._xsrf_token return ctx._xsrfToken
} }
// CheckXsrfCookie checks xsrf token in this request is valid or not. // CheckXSRFCookie checks xsrf token in this request is valid or not.
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" // the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
// or in form field value named as "_xsrf". // or in form field value named as "_xsrf".
func (ctx *Context) CheckXsrfCookie() bool { func (ctx *Context) CheckXSRFCookie() bool {
token := ctx.Input.Query("_xsrf") token := ctx.Input.Query("_xsrf")
if token == "" { if token == "" {
token = ctx.Request.Header.Get("X-Xsrftoken") token = ctx.Request.Header.Get("X-Xsrftoken")
@ -142,9 +164,67 @@ func (ctx *Context) CheckXsrfCookie() bool {
ctx.Abort(403, "'_xsrf' argument missing from POST") ctx.Abort(403, "'_xsrf' argument missing from POST")
return false return false
} }
if ctx._xsrf_token != token { if ctx._xsrfToken != token {
ctx.Abort(403, "XSRF cookie does not match POST argument") ctx.Abort(403, "XSRF cookie does not match POST argument")
return false return false
} }
return true return true
} }
//Response is a wrapper for the http.ResponseWriter
//started set to true if response was written to then don't execute other handler
type Response struct {
http.ResponseWriter
Started bool
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 (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 (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 (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")
}
return hj.Hijack()
}
// Flush http.Flusher
func (r *Response) Flush() {
if f, ok := r.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}
// CloseNotify http.CloseNotifier
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

@ -17,50 +17,71 @@ package context
import ( import (
"bytes" "bytes"
"errors" "errors"
"io"
"io/ioutil" "io/ioutil"
"net/http"
"net/url" "net/url"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
) )
// Regexes for checking the accept headers
// TODO make sure these are correct
var (
acceptsHTMLRegex = regexp.MustCompile(`(text/html|application/xhtml\+xml)(?:,|$)`)
acceptsXMLRegex = regexp.MustCompile(`(application/xml|text/xml)(?:,|$)`)
acceptsJSONRegex = regexp.MustCompile(`(application/json)(?:,|$)`)
maxParam = 50
)
// BeegoInput operates the http request header, data, cookie and body. // BeegoInput operates the http request header, data, cookie and body.
// it also contains router params and current session. // it also contains router params and current session.
type BeegoInput struct { type BeegoInput struct {
CruSession session.SessionStore Context *Context
Params map[string]string CruSession session.Store
Data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. pnames []string
Request *http.Request pvalues []string
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
RequestBody []byte RequestBody []byte
RunController reflect.Type
RunMethod string RunMethod string
RunController reflect.Type
} }
// NewInput return BeegoInput generated by http.Request. // NewInput return BeegoInput generated by Context.
func NewInput(req *http.Request) *BeegoInput { func NewInput() *BeegoInput {
return &BeegoInput{ return &BeegoInput{
Params: make(map[string]string), pnames: make([]string, 0, maxParam),
Data: make(map[interface{}]interface{}), pvalues: make([]string, 0, maxParam),
Request: req, data: make(map[interface{}]interface{}),
} }
} }
// Reset init the BeegoInput
func (input *BeegoInput) Reset(ctx *Context) {
input.Context = ctx
input.CruSession = nil
input.pnames = input.pnames[:0]
input.pvalues = input.pvalues[:0]
input.data = nil
input.RequestBody = []byte{}
}
// Protocol returns request protocol name, such as HTTP/1.1 . // Protocol returns request protocol name, such as HTTP/1.1 .
func (input *BeegoInput) Protocol() string { func (input *BeegoInput) Protocol() string {
return input.Request.Proto return input.Context.Request.Proto
} }
// Uri returns full request url with query string, fragment. // URI returns full request url with query string, fragment.
func (input *BeegoInput) Uri() string { func (input *BeegoInput) URI() string {
return input.Request.RequestURI return input.Context.Request.RequestURI
} }
// Url returns request url path (without query string, fragment). // URL returns request url path (without query string, fragment).
func (input *BeegoInput) Url() string { func (input *BeegoInput) URL() string {
return input.Request.URL.Path return input.Context.Request.URL.Path
} }
// Site returns base site url as scheme://domain type. // Site returns base site url as scheme://domain type.
@ -70,10 +91,13 @@ func (input *BeegoInput) Site() string {
// Scheme returns request scheme as "http" or "https". // Scheme returns request scheme as "http" or "https".
func (input *BeegoInput) Scheme() string { func (input *BeegoInput) Scheme() string {
if input.Request.URL.Scheme != "" { if scheme := input.Header("X-Forwarded-Proto"); scheme != "" {
return input.Request.URL.Scheme return scheme
} }
if input.Request.TLS == nil { if input.Context.Request.URL.Scheme != "" {
return input.Context.Request.URL.Scheme
}
if input.Context.Request.TLS == nil {
return "http" return "http"
} }
return "https" return "https"
@ -88,19 +112,19 @@ func (input *BeegoInput) Domain() string {
// Host returns host name. // Host returns host name.
// if no host info in request, return localhost. // if no host info in request, return localhost.
func (input *BeegoInput) Host() string { func (input *BeegoInput) Host() string {
if input.Request.Host != "" { if input.Context.Request.Host != "" {
hostParts := strings.Split(input.Request.Host, ":") hostParts := strings.Split(input.Context.Request.Host, ":")
if len(hostParts) > 0 { if len(hostParts) > 0 {
return hostParts[0] return hostParts[0]
} }
return input.Request.Host return input.Context.Request.Host
} }
return "localhost" return "localhost"
} }
// Method returns http request method. // Method returns http request method.
func (input *BeegoInput) Method() string { func (input *BeegoInput) Method() string {
return input.Request.Method return input.Context.Request.Method
} }
// Is returns boolean of this request is on given method, such as Is("POST"). // Is returns boolean of this request is on given method, such as Is("POST").
@ -108,37 +132,37 @@ func (input *BeegoInput) Is(method string) bool {
return input.Method() == method return input.Method() == method
} }
// Is this a GET method request? // IsGet Is this a GET method request?
func (input *BeegoInput) IsGet() bool { func (input *BeegoInput) IsGet() bool {
return input.Is("GET") return input.Is("GET")
} }
// Is this a POST method request? // IsPost Is this a POST method request?
func (input *BeegoInput) IsPost() bool { func (input *BeegoInput) IsPost() bool {
return input.Is("POST") return input.Is("POST")
} }
// Is this a Head method request? // IsHead Is this a Head method request?
func (input *BeegoInput) IsHead() bool { func (input *BeegoInput) IsHead() bool {
return input.Is("HEAD") return input.Is("HEAD")
} }
// Is this a OPTIONS method request? // IsOptions Is this a OPTIONS method request?
func (input *BeegoInput) IsOptions() bool { func (input *BeegoInput) IsOptions() bool {
return input.Is("OPTIONS") return input.Is("OPTIONS")
} }
// Is this a PUT method request? // IsPut Is this a PUT method request?
func (input *BeegoInput) IsPut() bool { func (input *BeegoInput) IsPut() bool {
return input.Is("PUT") return input.Is("PUT")
} }
// Is this a DELETE method request? // IsDelete Is this a DELETE method request?
func (input *BeegoInput) IsDelete() bool { func (input *BeegoInput) IsDelete() bool {
return input.Is("DELETE") return input.Is("DELETE")
} }
// Is this a PATCH method request? // IsPatch Is this a PATCH method request?
func (input *BeegoInput) IsPatch() bool { func (input *BeegoInput) IsPatch() bool {
return input.Is("PATCH") return input.Is("PATCH")
} }
@ -163,6 +187,21 @@ func (input *BeegoInput) IsUpload() bool {
return strings.Contains(input.Header("Content-Type"), "multipart/form-data") return strings.Contains(input.Header("Content-Type"), "multipart/form-data")
} }
// AcceptsHTML Checks if request accepts html response
func (input *BeegoInput) AcceptsHTML() bool {
return acceptsHTMLRegex.MatchString(input.Header("Accept"))
}
// AcceptsXML Checks if request accepts xml response
func (input *BeegoInput) AcceptsXML() bool {
return acceptsXMLRegex.MatchString(input.Header("Accept"))
}
// AcceptsJSON Checks if request accepts json response
func (input *BeegoInput) AcceptsJSON() bool {
return acceptsJSONRegex.MatchString(input.Header("Accept"))
}
// IP returns request client ip. // IP returns request client ip.
// if in proxy, return first proxy id. // if in proxy, return first proxy id.
// if error, return 127.0.0.1. // if error, return 127.0.0.1.
@ -172,7 +211,7 @@ func (input *BeegoInput) IP() string {
rip := strings.Split(ips[0], ":") rip := strings.Split(ips[0], ":")
return rip[0] return rip[0]
} }
ip := strings.Split(input.Request.RemoteAddr, ":") ip := strings.Split(input.Context.Request.RemoteAddr, ":")
if len(ip) > 0 { if len(ip) > 0 {
if ip[0] != "[" { if ip[0] != "[" {
return ip[0] return ip[0]
@ -212,7 +251,7 @@ func (input *BeegoInput) SubDomains() string {
// Port returns request client port. // Port returns request client port.
// when error or empty, return 80. // when error or empty, return 80.
func (input *BeegoInput) Port() int { func (input *BeegoInput) Port() int {
parts := strings.Split(input.Request.Host, ":") parts := strings.Split(input.Context.Request.Host, ":")
if len(parts) == 2 { if len(parts) == 2 {
port, _ := strconv.Atoi(parts[1]) port, _ := strconv.Atoi(parts[1])
return port return port
@ -225,35 +264,74 @@ func (input *BeegoInput) UserAgent() string {
return input.Header("User-Agent") return input.Header("User-Agent")
} }
// ParamsLen return the length of the params
func (input *BeegoInput) ParamsLen() int {
return len(input.pnames)
}
// Param returns router param by a given key. // Param returns router param by a given key.
func (input *BeegoInput) Param(key string) string { func (input *BeegoInput) Param(key string) string {
if v, ok := input.Params[key]; ok { for i, v := range input.pnames {
return v if v == key && i <= len(input.pvalues) {
return input.pvalues[i]
}
} }
return "" return ""
} }
// Params returns the map[key]value.
func (input *BeegoInput) Params() map[string]string {
m := make(map[string]string)
for i, v := range input.pnames {
if i <= len(input.pvalues) {
m[v] = input.pvalues[i]
}
}
return m
}
// 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. // Query returns input data item string by a given string.
func (input *BeegoInput) Query(key string) string { func (input *BeegoInput) Query(key string) string {
if val := input.Param(key); val != "" { if val := input.Param(key); val != "" {
return val return val
} }
if input.Request.Form == nil { if input.Context.Request.Form == nil {
input.Request.ParseForm() input.Context.Request.ParseForm()
} }
return input.Request.Form.Get(key) return input.Context.Request.Form.Get(key)
} }
// Header returns request header item string by a given string. // Header returns request header item string by a given string.
// if non-existed, return empty string. // if non-existed, return empty string.
func (input *BeegoInput) Header(key string) string { func (input *BeegoInput) Header(key string) string {
return input.Request.Header.Get(key) return input.Context.Request.Header.Get(key)
} }
// Cookie returns request cookie item string by a given key. // Cookie returns request cookie item string by a given key.
// if non-existed, return empty string. // if non-existed, return empty string.
func (input *BeegoInput) Cookie(key string) string { func (input *BeegoInput) Cookie(key string) string {
ck, err := input.Request.Cookie(key) ck, err := input.Context.Request.Cookie(key)
if err != nil { if err != nil {
return "" return ""
} }
@ -261,24 +339,36 @@ func (input *BeegoInput) Cookie(key string) string {
} }
// Session returns current session item value by a given key. // 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{} { func (input *BeegoInput) Session(key interface{}) interface{} {
return input.CruSession.Get(key) return input.CruSession.Get(key)
} }
// CopyBody returns the raw request body data as bytes. // CopyBody returns the raw request body data as bytes.
func (input *BeegoInput) CopyBody() []byte { func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
requestbody, _ := ioutil.ReadAll(input.Request.Body) if input.Context.Request.Body == nil {
input.Request.Body.Close() return []byte{}
}
safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory}
requestbody, _ := ioutil.ReadAll(safe)
input.Context.Request.Body.Close()
bf := bytes.NewBuffer(requestbody) bf := bytes.NewBuffer(requestbody)
input.Request.Body = ioutil.NopCloser(bf) input.Context.Request.Body = ioutil.NopCloser(bf)
input.RequestBody = requestbody input.RequestBody = requestbody
return requestbody return requestbody
} }
// Data return the implicit data in the input
func (input *BeegoInput) Data() map[interface{}]interface{} {
if input.data == nil {
input.data = make(map[interface{}]interface{})
}
return input.data
}
// GetData returns the stored data in this context. // GetData returns the stored data in this context.
func (input *BeegoInput) GetData(key interface{}) interface{} { func (input *BeegoInput) GetData(key interface{}) interface{} {
if v, ok := input.Data[key]; ok { if v, ok := input.data[key]; ok {
return v return v
} }
return nil return nil
@ -287,17 +377,20 @@ func (input *BeegoInput) GetData(key interface{}) interface{} {
// SetData stores data with given key in this context. // SetData stores data with given key in this context.
// This data are only available in this context. // This data are only available in this context.
func (input *BeegoInput) SetData(key, val interface{}) { func (input *BeegoInput) SetData(key, val interface{}) {
input.Data[key] = val if input.data == nil {
input.data = make(map[interface{}]interface{})
}
input.data[key] = val
} }
// parseForm or parseMultiForm based on Content-type // ParseFormOrMulitForm parseForm or parseMultiForm based on Content-type
func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error {
// Parse the body depending on the content type. // Parse the body depending on the content type.
if strings.Contains(input.Header("Content-Type"), "multipart/form-data") { if strings.Contains(input.Header("Content-Type"), "multipart/form-data") {
if err := input.Request.ParseMultipartForm(maxMemory); err != nil { if err := input.Context.Request.ParseMultipartForm(maxMemory); err != nil {
return errors.New("Error parsing request body:" + err.Error()) return errors.New("Error parsing request body:" + err.Error())
} }
} else if err := input.Request.ParseForm(); err != nil { } else if err := input.Context.Request.ParseForm(); err != nil {
return errors.New("Error parsing request body:" + err.Error()) return errors.New("Error parsing request body:" + err.Error())
} }
return nil return nil
@ -306,7 +399,7 @@ func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error {
// Bind data from request.Form[key] to dest // Bind data from request.Form[key] to dest
// like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie // like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie
// var id int beegoInput.Bind(&id, "id") id ==123 // var id int beegoInput.Bind(&id, "id") id ==123
// var isok bool beegoInput.Bind(&isok, "isok") id ==true // var isok bool beegoInput.Bind(&isok, "isok") isok ==true
// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2 // var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2
// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2] // ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2]
// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array] // ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array]
@ -320,7 +413,13 @@ func (input *BeegoInput) Bind(dest interface{}, key string) error {
if !value.CanSet() { if !value.CanSet() {
return errors.New("beego: non-settable variable passed to Bind: " + key) return errors.New("beego: non-settable variable passed to Bind: " + key)
} }
rv := input.bind(key, value.Type()) typ := value.Type()
// Get real type if dest define with interface{}.
// e.g var dest interface{} dest=1.0
if value.Kind() == reflect.Interface {
typ = value.Elem().Type()
}
rv := input.bind(key, typ)
if !rv.IsValid() { if !rv.IsValid() {
return errors.New("beego: reflect value is empty") return errors.New("beego: reflect value is empty")
} }
@ -329,7 +428,10 @@ func (input *BeegoInput) Bind(dest interface{}, key string) error {
} }
func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value { func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value {
rv := reflect.Zero(reflect.TypeOf(0)) if input.Context.Request.Form == nil {
input.Context.Request.ParseForm()
}
rv := reflect.Zero(typ)
switch typ.Kind() { switch typ.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val := input.Query(key) val := input.Query(key)
@ -362,19 +464,19 @@ func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value {
} }
rv = input.bindBool(val, typ) rv = input.bindBool(val, typ)
case reflect.Slice: case reflect.Slice:
rv = input.bindSlice(&input.Request.Form, key, typ) rv = input.bindSlice(&input.Context.Request.Form, key, typ)
case reflect.Struct: case reflect.Struct:
rv = input.bindStruct(&input.Request.Form, key, typ) rv = input.bindStruct(&input.Context.Request.Form, key, typ)
case reflect.Ptr: case reflect.Ptr:
rv = input.bindPoint(key, typ) rv = input.bindPoint(key, typ)
case reflect.Map: case reflect.Map:
rv = input.bindMap(&input.Request.Form, key, typ) rv = input.bindMap(&input.Context.Request.Form, key, typ)
} }
return rv return rv
} }
func (input *BeegoInput) bindValue(val string, typ reflect.Type) reflect.Value { func (input *BeegoInput) bindValue(val string, typ reflect.Type) reflect.Value {
rv := reflect.Zero(reflect.TypeOf(0)) rv := reflect.Zero(typ)
switch typ.Kind() { switch typ.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
rv = input.bindInt(val, typ) rv = input.bindInt(val, typ)
@ -499,12 +601,15 @@ func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.
result := reflect.New(typ).Elem() result := reflect.New(typ).Elem()
fieldValues := make(map[string]reflect.Value) fieldValues := make(map[string]reflect.Value)
for reqKey, val := range *params { 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 continue
} }
fieldName := reqKey[len(key)+1:]
if _, ok := fieldValues[fieldName]; !ok { if _, ok := fieldValues[fieldName]; !ok {
// Time to bind this field. Get it and make sure we can set it. // Time to bind this field. Get it and make sure we can set it.
fieldValue := result.FieldByName(fieldName) fieldValue := result.FieldByName(fieldName)

View File

@ -15,65 +15,105 @@
package context package context
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest"
"reflect"
"testing" "testing"
) )
func TestParse(t *testing.T) { func TestBind(t *testing.T) {
r, _ := http.NewRequest("GET", "/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil) type testItem struct {
beegoInput := NewInput(r) field string
beegoInput.ParseFormOrMulitForm(1 << 20) empty interface{}
want interface{}
}
type Human struct {
ID int
Nick string
Pwd string
Ms bool
}
var id int cases := []struct {
err := beegoInput.Bind(&id, "id") request string
if id != 123 || err != nil { valueGp []testItem
t.Fatal("id should has int value") }{
} {"/?p=str", []testItem{{"p", interface{}(""), interface{}("str")}}},
fmt.Println(id)
var isok bool {"/?p=", []testItem{{"p", "", ""}}},
err = beegoInput.Bind(&isok, "isok") {"/?p=str", []testItem{{"p", "", "str"}}},
if !isok || err != nil {
t.Fatal("isok should be true")
}
fmt.Println(isok)
var float float64 {"/?p=123", []testItem{{"p", 0, 123}}},
err = beegoInput.Bind(&float, "ft") {"/?p=123", []testItem{{"p", uint(0), uint(123)}}},
if float != 1.2 || err != nil {
t.Fatal("float should be equal to 1.2")
}
fmt.Println(float)
ol := make([]int, 0, 2) {"/?p=1.0", []testItem{{"p", 0.0, 1.0}}},
err = beegoInput.Bind(&ol, "ol") {"/?p=1", []testItem{{"p", false, true}}},
if len(ol) != 2 || err != nil || ol[0] != 1 || ol[1] != 2 {
t.Fatal("ol should has two elements")
}
fmt.Println(ol)
ul := make([]string, 0, 2) {"/?p=true", []testItem{{"p", false, true}}},
err = beegoInput.Bind(&ul, "ul") {"/?p=ON", []testItem{{"p", false, true}}},
if len(ul) != 2 || err != nil || ul[0] != "str" || ul[1] != "array" { {"/?p=on", []testItem{{"p", false, true}}},
t.Fatal("ul should has two elements") {"/?p=1", []testItem{{"p", false, true}}},
} {"/?p=2", []testItem{{"p", false, false}}},
fmt.Println(ul) {"/?p=false", []testItem{{"p", false, false}}},
type User struct { {"/?p[a]=1&p[b]=2&p[c]=3", []testItem{{"p", map[string]int{}, map[string]int{"a": 1, "b": 2, "c": 3}}}},
Name string {"/?p[a]=v1&p[b]=v2&p[c]=v3", []testItem{{"p", map[string]string{}, map[string]string{"a": "v1", "b": "v2", "c": "v3"}}}},
{"/?p[]=8&p[]=9&p[]=10", []testItem{{"p", []int{}, []int{8, 9, 10}}}},
{"/?p[0]=8&p[1]=9&p[2]=10", []testItem{{"p", []int{}, []int{8, 9, 10}}}},
{"/?p[0]=8&p[1]=9&p[2]=10&p[5]=14", []testItem{{"p", []int{}, []int{8, 9, 10, 0, 0, 14}}}},
{"/?p[0]=8.0&p[1]=9.0&p[2]=10.0", []testItem{{"p", []float64{}, []float64{8.0, 9.0, 10.0}}}},
{"/?p[]=10&p[]=9&p[]=8", []testItem{{"p", []string{}, []string{"10", "9", "8"}}}},
{"/?p[0]=8&p[1]=9&p[2]=10", []testItem{{"p", []string{}, []string{"8", "9", "10"}}}},
{"/?p[0]=true&p[1]=false&p[2]=true&p[5]=1&p[6]=ON&p[7]=other", []testItem{{"p", []bool{}, []bool{true, false, true, false, false, true, true, false}}}},
{"/?human.Nick=astaxie", []testItem{{"human", Human{}, Human{Nick: "astaxie"}}}},
{"/?human.ID=888&human.Nick=astaxie&human.Ms=true&human[Pwd]=pass", []testItem{{"human", Human{}, Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass"}}}},
{"/?human[0].ID=888&human[0].Nick=astaxie&human[0].Ms=true&human[0][Pwd]=pass01&human[1].ID=999&human[1].Nick=ysqi&human[1].Ms=On&human[1].Pwd=pass02",
[]testItem{{"human", []Human{}, []Human{
Human{ID: 888, Nick: "astaxie", Ms: true, Pwd: "pass01"},
Human{ID: 999, Nick: "ysqi", Ms: true, Pwd: "pass02"},
}}}},
{
"/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&human.Nick=astaxie",
[]testItem{
{"id", 0, 123},
{"isok", false, true},
{"ft", 0.0, 1.2},
{"ol", []int{}, []int{1, 2}},
{"ul", []string{}, []string{"str", "array"}},
{"human", Human{}, Human{Nick: "astaxie"}},
},
},
} }
user := User{} for _, c := range cases {
err = beegoInput.Bind(&user, "user") r, _ := http.NewRequest("GET", c.request, nil)
if err != nil || user.Name != "astaxie" { beegoInput := NewInput()
t.Fatal("user should has name") beegoInput.Context = NewContext()
beegoInput.Context.Reset(httptest.NewRecorder(), r)
for _, item := range c.valueGp {
got := item.empty
err := beegoInput.Bind(&got, item.field)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, item.want) {
t.Fatalf("Bind %q error,should be:\n%#v \ngot:\n%#v", item.field, item.want, got)
}
}
} }
fmt.Println(user)
} }
func TestSubDomain(t *testing.T) { 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) 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(r) beegoInput := NewInput()
beegoInput.Context = NewContext()
beegoInput.Context.Reset(httptest.NewRecorder(), r)
subdomain := beegoInput.SubDomains() subdomain := beegoInput.SubDomains()
if subdomain != "www" { if subdomain != "www" {
@ -81,34 +121,87 @@ func TestSubDomain(t *testing.T) {
} }
r, _ = http.NewRequest("GET", "http://localhost/", nil) r, _ = http.NewRequest("GET", "http://localhost/", nil)
beegoInput.Request = r beegoInput.Context.Request = r
if beegoInput.SubDomains() != "" { if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, should be empty, got " + beegoInput.SubDomains()) t.Fatal("Subdomain parse error, should be empty, got " + beegoInput.SubDomains())
} }
r, _ = http.NewRequest("GET", "http://aa.bb.example.com/", nil) r, _ = http.NewRequest("GET", "http://aa.bb.example.com/", nil)
beegoInput.Request = r beegoInput.Context.Request = r
if beegoInput.SubDomains() != "aa.bb" { if beegoInput.SubDomains() != "aa.bb" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
} }
/* TODO Fix this /* TODO Fix this
r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil) r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil)
beegoInput.Request = r beegoInput.Context.Request = r
if beegoInput.SubDomains() != "" { if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
} }
*/ */
r, _ = http.NewRequest("GET", "http://example.com/", nil) r, _ = http.NewRequest("GET", "http://example.com/", nil)
beegoInput.Request = r beegoInput.Context.Request = r
if beegoInput.SubDomains() != "" { if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
} }
r, _ = http.NewRequest("GET", "http://aa.bb.cc.dd.example.com/", nil) r, _ = http.NewRequest("GET", "http://aa.bb.cc.dd.example.com/", nil)
beegoInput.Request = r beegoInput.Context.Request = r
if beegoInput.SubDomains() != "aa.bb.cc.dd" { if beegoInput.SubDomains() != "aa.bb.cc.dd" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) 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

@ -16,8 +16,6 @@ package context
import ( import (
"bytes" "bytes"
"compress/flate"
"compress/gzip"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors" "errors"
@ -26,9 +24,12 @@ import (
"io" "io"
"mime" "mime"
"net/http" "net/http"
"net/url"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// BeegoOutput does work for sending response header. // BeegoOutput does work for sending response header.
@ -44,6 +45,12 @@ func NewOutput() *BeegoOutput {
return &BeegoOutput{} return &BeegoOutput{}
} }
// Reset init BeegoOutput
func (output *BeegoOutput) Reset(ctx *Context) {
output.Context = ctx
output.Status = 0
}
// Header sets response header item string via given key. // Header sets response header item string via given key.
func (output *BeegoOutput) Header(key, val string) { func (output *BeegoOutput) Header(key, val string) {
output.Context.ResponseWriter.Header().Set(key, val) output.Context.ResponseWriter.Header().Set(key, val)
@ -52,45 +59,28 @@ func (output *BeegoOutput) Header(key, val string) {
// Body sets response body content. // Body sets response body content.
// if EnableGzip, compress content string. // if EnableGzip, compress content string.
// it sends out response body directly. // it sends out response body directly.
func (output *BeegoOutput) Body(content []byte) { func (output *BeegoOutput) Body(content []byte) error {
output_writer := output.Context.ResponseWriter.(io.Writer) var encoding string
if output.EnableGzip == true && output.Context.Input.Header("Accept-Encoding") != "" { var buf = &bytes.Buffer{}
splitted := strings.SplitN(output.Context.Input.Header("Accept-Encoding"), ",", -1) if output.EnableGzip {
encodings := make([]string, len(splitted)) encoding = ParseEncoding(output.Context.Request)
}
for i, val := range splitted { if b, n, _ := WriteBody(encoding, buf, content); b {
encodings[i] = strings.TrimSpace(val) output.Header("Content-Encoding", n)
} output.Header("Content-Length", strconv.Itoa(buf.Len()))
for _, val := range encodings {
if val == "gzip" {
output.Header("Content-Encoding", "gzip")
output_writer, _ = gzip.NewWriterLevel(output.Context.ResponseWriter, gzip.BestSpeed)
break
} else if val == "deflate" {
output.Header("Content-Encoding", "deflate")
output_writer, _ = flate.NewWriter(output.Context.ResponseWriter, flate.BestSpeed)
break
}
}
} else { } else {
output.Header("Content-Length", strconv.Itoa(len(content))) output.Header("Content-Length", strconv.Itoa(len(content)))
} }
// Write status code if it has been set manually // Write status code if it has been set manually
// Set it to 0 afterwards to prevent "multiple response.WriteHeader calls" // Set it to 0 afterwards to prevent "multiple response.WriteHeader calls"
if output.Status != 0 { if output.Status != 0 {
output.Context.ResponseWriter.WriteHeader(output.Status) output.Context.ResponseWriter.WriteHeader(output.Status)
output.Status = 0 output.Status = 0
} else {
output.Context.ResponseWriter.Started = true
} }
io.Copy(output.Context.ResponseWriter, buf)
output_writer.Write(content) return nil
switch output_writer.(type) {
case *gzip.Writer:
output_writer.(*gzip.Writer).Close()
case *flate.Writer:
output_writer.(*flate.Writer).Close()
}
} }
// Cookie sets cookie value via given key. // Cookie sets cookie value via given key.
@ -98,26 +88,25 @@ func (output *BeegoOutput) Body(content []byte) {
func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) { func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
var b bytes.Buffer var b bytes.Buffer
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value)) fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
//fix cookie not work in IE
if len(others) > 0 { if len(others) > 0 {
var maxAge int64
switch v := others[0].(type) { switch v := others[0].(type) {
case int: case int:
if v > 0 { maxAge = int64(v)
fmt.Fprintf(&b, "; Max-Age=%d", v)
} else if v < 0 {
fmt.Fprintf(&b, "; Max-Age=0")
}
case int64:
if v > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", v)
} else if v < 0 {
fmt.Fprintf(&b, "; Max-Age=0")
}
case int32: case int32:
if v > 0 { maxAge = int64(v)
fmt.Fprintf(&b, "; Max-Age=%d", v) case int64:
} else if v < 0 { maxAge = v
fmt.Fprintf(&b, "; Max-Age=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)
case maxAge < 0:
fmt.Fprintf(&b, "; Max-Age=0")
} }
} }
@ -158,18 +147,12 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
} }
// default false. for session cookie default true // default false. for session cookie default true
httponly := false
if len(others) > 4 { if len(others) > 4 {
if v, ok := others[4].(bool); ok && v { if v, ok := others[4].(bool); ok && v {
// HttpOnly = true fmt.Fprintf(&b, "; HttpOnly")
httponly = true
} }
} }
if httponly {
fmt.Fprintf(&b, "; HttpOnly")
}
output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String()) output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
} }
@ -185,9 +168,9 @@ func sanitizeValue(v string) string {
return cookieValueSanitizer.Replace(v) return cookieValueSanitizer.Replace(v)
} }
// Json writes json to response body. // JSON writes json to response body.
// if coding is true, it converts utf-8 to \u0000 type. // if coding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) error { func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error {
output.Header("Content-Type", "application/json; charset=utf-8") output.Header("Content-Type", "application/json; charset=utf-8")
var content []byte var content []byte
var err error var err error
@ -201,14 +184,13 @@ func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) e
return err return err
} }
if coding { if coding {
content = []byte(stringsToJson(string(content))) content = []byte(stringsToJSON(string(content)))
} }
output.Body(content) return output.Body(content)
return nil
} }
// Jsonp writes jsonp to response body. // JSONP writes jsonp to response body.
func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error { func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/javascript; charset=utf-8") output.Header("Content-Type", "application/javascript; charset=utf-8")
var content []byte var content []byte
var err error var err error
@ -225,16 +207,16 @@ func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error {
if callback == "" { if callback == "" {
return errors.New(`"callback" parameter required`) return errors.New(`"callback" parameter required`)
} }
callback_content := bytes.NewBufferString(" " + template.JSEscapeString(callback)) callback = template.JSEscapeString(callback)
callback_content.WriteString("(") callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback)
callback_content.Write(content) callbackContent.WriteString("(")
callback_content.WriteString(");\r\n") callbackContent.Write(content)
output.Body(callback_content.Bytes()) callbackContent.WriteString(");\r\n")
return nil return output.Body(callbackContent.Bytes())
} }
// Xml writes xml string to response body. // XML writes xml string to response body.
func (output *BeegoOutput) Xml(data interface{}, hasIndent bool) error { func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/xml; charset=utf-8") output.Header("Content-Type", "application/xml; charset=utf-8")
var content []byte var content []byte
var err error var err error
@ -247,20 +229,27 @@ func (output *BeegoOutput) Xml(data interface{}, hasIndent bool) error {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err return err
} }
output.Body(content) return output.Body(content)
return nil
} }
// Download forces response for download file. // Download forces response for download file.
// it prepares the download response header automatically. // it prepares the download response header automatically.
func (output *BeegoOutput) Download(file string, filename ...string) { func (output *BeegoOutput) Download(file string, filename ...string) {
// 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-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream") 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("Content-Transfer-Encoding", "binary")
output.Header("Expires", "0") output.Header("Expires", "0")
output.Header("Cache-Control", "must-revalidate") output.Header("Cache-Control", "must-revalidate")
@ -288,73 +277,74 @@ func (output *BeegoOutput) SetStatus(status int) {
// IsCachable returns boolean of this request is cached. // IsCachable returns boolean of this request is cached.
// HTTP 304 means 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 return output.Status >= 200 && output.Status < 300 || output.Status == 304
} }
// IsEmpty returns boolean of this request is empty. // IsEmpty returns boolean of this request is empty.
// HTTP 201204 and 304 means 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 return output.Status == 201 || output.Status == 204 || output.Status == 304
} }
// IsOk returns boolean of this request runs well. // IsOk returns boolean of this request runs well.
// HTTP 200 means ok. // HTTP 200 means ok.
func (output *BeegoOutput) IsOk(status int) bool { func (output *BeegoOutput) IsOk() bool {
return output.Status == 200 return output.Status == 200
} }
// IsSuccessful returns boolean of this request runs successfully. // IsSuccessful returns boolean of this request runs successfully.
// HTTP 2xx means ok. // HTTP 2xx means ok.
func (output *BeegoOutput) IsSuccessful(status int) bool { func (output *BeegoOutput) IsSuccessful() bool {
return output.Status >= 200 && output.Status < 300 return output.Status >= 200 && output.Status < 300
} }
// IsRedirect returns boolean of this request is redirection header. // IsRedirect returns boolean of this request is redirection header.
// HTTP 301,302,307 means redirection. // 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 return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
} }
// IsForbidden returns boolean of this request is forbidden. // IsForbidden returns boolean of this request is forbidden.
// HTTP 403 means forbidden. // HTTP 403 means forbidden.
func (output *BeegoOutput) IsForbidden(status int) bool { func (output *BeegoOutput) IsForbidden() bool {
return output.Status == 403 return output.Status == 403
} }
// IsNotFound returns boolean of this request is not found. // IsNotFound returns boolean of this request is not found.
// HTTP 404 means forbidden. // HTTP 404 means forbidden.
func (output *BeegoOutput) IsNotFound(status int) bool { func (output *BeegoOutput) IsNotFound() bool {
return output.Status == 404 return output.Status == 404
} }
// IsClient returns boolean of this request client sends error data. // IsClientError returns boolean of this request client sends error data.
// HTTP 4xx means forbidden. // HTTP 4xx means forbidden.
func (output *BeegoOutput) IsClientError(status int) bool { func (output *BeegoOutput) IsClientError() bool {
return output.Status >= 400 && output.Status < 500 return output.Status >= 400 && output.Status < 500
} }
// IsServerError returns boolean of this server handler errors. // IsServerError returns boolean of this server handler errors.
// HTTP 5xx means server internal error. // 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 return output.Status >= 500 && output.Status < 600
} }
func stringsToJson(str string) string { func stringsToJSON(str string) string {
rs := []rune(str) rs := []rune(str)
jsons := "" var jsons bytes.Buffer
for _, r := range rs { for _, r := range rs {
rint := int(r) rint := int(r)
if rint < 128 { if rint < 128 {
jsons += string(r) jsons.WriteRune(r)
} else { } else {
jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json jsons.WriteString("\\u")
jsons.WriteString(strconv.FormatInt(int64(rint), 16))
} }
} }
return jsons return jsons.String()
} }
// Sessions sets session item value with given key. // Session sets session item value with given key.
func (output *BeegoOutput) Session(name interface{}, value interface{}) { func (output *BeegoOutput) Session(name interface{}, value interface{}) {
output.Context.Input.CruSession.Set(name, value) output.Context.Input.CruSession.Set(name, value)
} }

View File

@ -19,7 +19,6 @@ import (
"errors" "errors"
"html/template" "html/template"
"io" "io"
"io/ioutil"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
@ -34,18 +33,19 @@ import (
//commonly used mime-types //commonly used mime-types
const ( const (
applicationJson = "application/json" applicationJSON = "application/json"
applicationXml = "application/xml" applicationXML = "application/xml"
textXml = "text/xml" textXML = "text/xml"
) )
var ( var (
// custom error when user stop request handler manually. // ErrAbort custom error when user stop request handler manually.
USERSTOPRUN = errors.New("User stop run") ErrAbort = errors.New("User stop run")
GlobalControllerRouter map[string][]ControllerComments = make(map[string][]ControllerComments) //pkgpath+controller:comments // GlobalControllerRouter store comments with controller. pkgpath+controller:comments
GlobalControllerRouter = make(map[string][]ControllerComments)
) )
// store the comment for the controller method // ControllerComments store the comment for the controller method
type ControllerComments struct { type ControllerComments struct {
Method string Method string
Router string Router string
@ -56,22 +56,33 @@ type ControllerComments struct {
// Controller defines some basic http request handler operations, such as // Controller defines some basic http request handler operations, such as
// http context, template and view, session and xsrf. // http context, template and view, session and xsrf.
type Controller struct { type Controller struct {
Ctx *context.Context // context data
Data map[interface{}]interface{} Ctx *context.Context
Data map[interface{}]interface{}
// route controller info
controllerName string controllerName string
actionName string actionName string
TplNames string methodMapping map[string]func() //method:routertree
gotofunc string
AppController interface{}
// template data
TplName string
ViewPath string
Layout string Layout string
LayoutSections map[string]string // the key is the section name and the value is the template name LayoutSections map[string]string // the key is the section name and the value is the template name
TplPrefix string
TplExt string TplExt string
_xsrf_token string
gotofunc string
CruSession session.SessionStore
XSRFExpire int
AppController interface{}
EnableRender bool EnableRender bool
EnableXSRF bool
methodMapping map[string]func() //method:routertree // xsrf data
_xsrfToken string
XSRFExpire int
EnableXSRF bool
// session
CruSession session.Store
} }
// ControllerInterface is an interface to uniform all controller handler. // ControllerInterface is an interface to uniform all controller handler.
@ -87,8 +98,8 @@ type ControllerInterface interface {
Options() Options()
Finish() Finish()
Render() error Render() error
XsrfToken() string XSRFToken() string
CheckXsrfCookie() bool CheckXSRFCookie() bool
HandlerFunc(fn string) bool HandlerFunc(fn string) bool
URLMapping() URLMapping()
} }
@ -96,7 +107,7 @@ type ControllerInterface interface {
// Init generates default values of controller operations. // Init generates default values of controller operations.
func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) { func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) {
c.Layout = "" c.Layout = ""
c.TplNames = "" c.TplName = ""
c.controllerName = controllerName c.controllerName = controllerName
c.actionName = actionName c.actionName = actionName
c.Ctx = ctx c.Ctx = ctx
@ -104,19 +115,15 @@ func (c *Controller) Init(ctx *context.Context, controllerName, actionName strin
c.AppController = app c.AppController = app
c.EnableRender = true c.EnableRender = true
c.EnableXSRF = true c.EnableXSRF = true
c.Data = ctx.Input.Data c.Data = ctx.Input.Data()
c.methodMapping = make(map[string]func()) c.methodMapping = make(map[string]func())
} }
// Prepare runs after Init before request function execution. // Prepare runs after Init before request function execution.
func (c *Controller) Prepare() { func (c *Controller) Prepare() {}
}
// Finish runs after request function execution. // Finish runs after request function execution.
func (c *Controller) Finish() { func (c *Controller) Finish() {}
}
// Get adds a request function to handle GET request. // Get adds a request function to handle GET request.
func (c *Controller) Get() { func (c *Controller) Get() {
@ -153,20 +160,19 @@ func (c *Controller) Options() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405) http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
} }
// call function fn // HandlerFunc call function with the name
func (c *Controller) HandlerFunc(fnname string) bool { func (c *Controller) HandlerFunc(fnname string) bool {
if v, ok := c.methodMapping[fnname]; ok { if v, ok := c.methodMapping[fnname]; ok {
v() v()
return true return true
} else {
return false
} }
return false
} }
// URLMapping register the internal Controller router. // URLMapping register the internal Controller router.
func (c *Controller) URLMapping() { func (c *Controller) URLMapping() {}
}
// Mapping the method to function
func (c *Controller) Mapping(method string, fn func()) { func (c *Controller) Mapping(method string, fn func()) {
c.methodMapping[method] = fn c.methodMapping[method] = fn
} }
@ -177,14 +183,15 @@ func (c *Controller) Render() error {
return nil return nil
} }
rb, err := c.RenderBytes() rb, err := c.RenderBytes()
if err != nil { if err != nil {
return err return err
} else {
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
c.Ctx.Output.Body(rb)
} }
return nil
if c.Ctx.ResponseWriter.Header().Get("Content-Type") == "" {
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
}
return c.Ctx.Output.Body(rb)
} }
// RenderString returns the rendered template string. Do not send out response. // RenderString returns the rendered template string. Do not send out response.
@ -195,25 +202,10 @@ func (c *Controller) RenderString() (string, error) {
// RenderBytes returns the bytes of rendered template string. Do not send out response. // RenderBytes returns the bytes of rendered template string. Do not send out response.
func (c *Controller) RenderBytes() ([]byte, error) { 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 buf, err := c.renderTemplate()
if c.Layout != "" { //if the controller has set layout, then first get the tplName's content set the content to the layout
if c.TplNames == "" { if err == nil && c.Layout != "" {
c.TplNames = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt c.Data["LayoutContent"] = template.HTML(buf.String())
}
if RunMode == "dev" {
BuildTemplate(ViewsPath)
}
newbytes := bytes.NewBufferString("")
if _, ok := BeeTemplates[c.TplNames]; !ok {
panic("can't find templatefile in the path:" + c.TplNames)
}
err := BeeTemplates[c.TplNames].ExecuteTemplate(newbytes, c.TplNames, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
tplcontent, _ := ioutil.ReadAll(newbytes)
c.Data["LayoutContent"] = template.HTML(string(tplcontent))
if c.LayoutSections != nil { if c.LayoutSections != nil {
for sectionName, sectionTpl := range c.LayoutSections { for sectionName, sectionTpl := range c.LayoutSections {
@ -221,45 +213,52 @@ func (c *Controller) RenderBytes() ([]byte, error) {
c.Data[sectionName] = "" c.Data[sectionName] = ""
continue continue
} }
buf.Reset()
sectionBytes := bytes.NewBufferString("") err = ExecuteViewPathTemplate(&buf, sectionTpl, c.viewPath(), c.Data)
err = BeeTemplates[sectionTpl].ExecuteTemplate(sectionBytes, sectionTpl, c.Data)
if err != nil { if err != nil {
Trace("template Execute err:", err)
return nil, err return nil, err
} }
sectionContent, _ := ioutil.ReadAll(sectionBytes) c.Data[sectionName] = template.HTML(buf.String())
c.Data[sectionName] = template.HTML(string(sectionContent))
} }
} }
ibytes := bytes.NewBufferString("") buf.Reset()
err = BeeTemplates[c.Layout].ExecuteTemplate(ibytes, c.Layout, c.Data) ExecuteViewPathTemplate(&buf, c.Layout, c.viewPath() ,c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
icontent, _ := ioutil.ReadAll(ibytes)
return icontent, nil
} else {
if c.TplNames == "" {
c.TplNames = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
}
if RunMode == "dev" {
BuildTemplate(ViewsPath)
}
ibytes := bytes.NewBufferString("")
if _, ok := BeeTemplates[c.TplNames]; !ok {
panic("can't find templatefile in the path:" + c.TplNames)
}
err := BeeTemplates[c.TplNames].ExecuteTemplate(ibytes, c.TplNames, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
icontent, _ := ioutil.ReadAll(ibytes)
return icontent, nil
} }
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 {
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(c.viewPath() , buildFiles...)
}
return buf, ExecuteViewPathTemplate(&buf, c.TplName, c.viewPath(), c.Data)
}
func (c *Controller) viewPath() string {
if c.ViewPath == "" {
return BConfig.WebConfig.ViewsPath
}
return c.ViewPath
} }
// Redirect sends the redirection response to url with status code. // Redirect sends the redirection response to url with status code.
@ -267,7 +266,7 @@ func (c *Controller) Redirect(url string, code int) {
c.Ctx.Redirect(code, url) c.Ctx.Redirect(code, url)
} }
// Aborts stops controller handler and show the error data if code is defined in ErrorMap or code string. // Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
func (c *Controller) Abort(code string) { func (c *Controller) Abort(code string) {
status, err := strconv.Atoi(code) status, err := strconv.Atoi(code)
if err != nil { if err != nil {
@ -278,81 +277,77 @@ 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. // 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) { func (c *Controller) CustomAbort(status int, body string) {
c.Ctx.ResponseWriter.WriteHeader(status) // first panic from ErrorMaps, it is user defined error functions.
// first panic from ErrorMaps, is is user defined error functions.
if _, ok := ErrorMaps[body]; ok { if _, ok := ErrorMaps[body]; ok {
c.Ctx.Output.Status = status
panic(body) panic(body)
} }
// last panic user string // last panic user string
c.Ctx.ResponseWriter.WriteHeader(status)
c.Ctx.ResponseWriter.Write([]byte(body)) c.Ctx.ResponseWriter.Write([]byte(body))
panic(USERSTOPRUN) panic(ErrAbort)
} }
// StopRun makes panic of USERSTOPRUN error and go to recover function if defined. // StopRun makes panic of USERSTOPRUN error and go to recover function if defined.
func (c *Controller) StopRun() { func (c *Controller) StopRun() {
panic(USERSTOPRUN) panic(ErrAbort)
} }
// UrlFor does another controller handler in this request function. // URLFor does another controller handler in this request function.
// it goes to this controller method if endpoint is not clear. // it goes to this controller method if endpoint is not clear.
func (c *Controller) UrlFor(endpoint string, values ...interface{}) string { func (c *Controller) URLFor(endpoint string, values ...interface{}) string {
if len(endpoint) <= 0 { if len(endpoint) == 0 {
return "" return ""
} }
if endpoint[0] == '.' { if endpoint[0] == '.' {
return UrlFor(reflect.Indirect(reflect.ValueOf(c.AppController)).Type().Name()+endpoint, values...) return URLFor(reflect.Indirect(reflect.ValueOf(c.AppController)).Type().Name()+endpoint, values...)
} else {
return UrlFor(endpoint, values...)
} }
return URLFor(endpoint, values...)
} }
// ServeJson sends a json response with encoding charset. // ServeJSON sends a json response with encoding charset.
func (c *Controller) ServeJson(encoding ...bool) { func (c *Controller) ServeJSON(encoding ...bool) {
var hasIndent bool var (
var hasencoding bool hasIndent = true
if RunMode == "prod" { hasEncoding = false
)
if BConfig.RunMode == PROD {
hasIndent = false hasIndent = false
} else {
hasIndent = true
} }
if len(encoding) > 0 && encoding[0] == true { if len(encoding) > 0 && encoding[0] == true {
hasencoding = true hasEncoding = true
} }
c.Ctx.Output.Json(c.Data["json"], hasIndent, hasencoding) c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding)
} }
// ServeJsonp sends a jsonp response. // ServeJSONP sends a jsonp response.
func (c *Controller) ServeJsonp() { func (c *Controller) ServeJSONP() {
var hasIndent bool hasIndent := true
if RunMode == "prod" { if BConfig.RunMode == PROD {
hasIndent = false hasIndent = false
} else {
hasIndent = true
} }
c.Ctx.Output.Jsonp(c.Data["jsonp"], hasIndent) c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent)
} }
// ServeXml sends xml response. // ServeXML sends xml response.
func (c *Controller) ServeXml() { func (c *Controller) ServeXML() {
var hasIndent bool hasIndent := true
if RunMode == "prod" { if BConfig.RunMode == PROD {
hasIndent = false hasIndent = false
} else {
hasIndent = true
} }
c.Ctx.Output.Xml(c.Data["xml"], hasIndent) c.Ctx.Output.XML(c.Data["xml"], hasIndent)
} }
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header // ServeFormatted serve Xml OR Json, depending on the value of the Accept header
func (c *Controller) ServeFormatted() { func (c *Controller) ServeFormatted() {
accept := c.Ctx.Input.Header("Accept") accept := c.Ctx.Input.Header("Accept")
switch accept { switch accept {
case applicationJson: case applicationJSON:
c.ServeJson() c.ServeJSON()
case applicationXml, textXml: case applicationXML, textXML:
c.ServeXml() c.ServeXML()
default: default:
c.ServeJson() c.ServeJSON()
} }
} }
@ -371,16 +366,13 @@ func (c *Controller) ParseForm(obj interface{}) error {
// GetString returns the input value by key string or the default value while it's present and input is blank // GetString returns the input value by key string or the default value while it's present and input is blank
func (c *Controller) GetString(key string, def ...string) string { func (c *Controller) GetString(key string, def ...string) string {
var defv string
if len(def) > 0 {
defv = def[0]
}
if v := c.Ctx.Input.Query(key); v != "" { if v := c.Ctx.Input.Query(key); v != "" {
return v return v
} else {
return defv
} }
if len(def) > 0 {
return def[0]
}
return ""
} }
// GetStrings returns the input string slice by key string or the default value while it's present and input is blank // GetStrings returns the input string slice by key string or the default value while it's present and input is blank
@ -391,122 +383,118 @@ func (c *Controller) GetStrings(key string, def ...[]string) []string {
defv = def[0] defv = def[0]
} }
f := c.Input() if f := c.Input(); f == nil {
if f == nil {
return defv return defv
} else if vs := f[key]; len(vs) > 0 {
return vs
} }
vs := f[key] return defv
if len(vs) > 0 {
return vs
} else {
return defv
}
} }
// GetInt returns input as an int or the default value while it's present and input is blank // GetInt returns input as an int or the default value while it's present and input is blank
func (c *Controller) GetInt(key string, def ...int) (int, error) { func (c *Controller) GetInt(key string, def ...int) (int, error) {
var defv int strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.Atoi(strv)
} else {
return defv, nil
} }
return strconv.Atoi(strv)
} }
// GetInt8 return input as an int8 or the default value while it's present and input is blank // GetInt8 return input as an int8 or the default value while it's present and input is blank
func (c *Controller) GetInt8(key string, def ...int8) (int8, error) { func (c *Controller) GetInt8(key string, def ...int8) (int8, error) {
var defv int8 strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
} }
i64, err := strconv.ParseInt(strv, 10, 8)
return int8(i64), err
}
if strv := c.Ctx.Input.Query(key); strv != "" { // GetUint8 return input as an uint8 or the default value while it's present and input is blank
i64, err := strconv.ParseInt(strv, 10, 8) func (c *Controller) GetUint8(key string, def ...uint8) (uint8, error) {
i8 := int8(i64) strv := c.Ctx.Input.Query(key)
return i8, err if len(strv) == 0 && len(def) > 0 {
} else { return def[0], nil
return defv, 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 // 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) { func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
var defv int16 strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
} }
i64, err := strconv.ParseInt(strv, 10, 16)
return int16(i64), err
}
if strv := c.Ctx.Input.Query(key); strv != "" { // GetUint16 returns input as an uint16 or the default value while it's present and input is blank
i64, err := strconv.ParseInt(strv, 10, 16) func (c *Controller) GetUint16(key string, def ...uint16) (uint16, error) {
i16 := int16(i64) strv := c.Ctx.Input.Query(key)
if len(strv) == 0 && len(def) > 0 {
return i16, err return def[0], nil
} else {
return defv, 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 // 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) { func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
var defv int32 strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
} }
i64, err := strconv.ParseInt(strv, 10, 32)
return int32(i64), err
}
if strv := c.Ctx.Input.Query(key); strv != "" { // GetUint32 returns input as an uint32 or the default value while it's present and input is blank
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32) func (c *Controller) GetUint32(key string, def ...uint32) (uint32, error) {
i32 := int32(i64) strv := c.Ctx.Input.Query(key)
return i32, err if len(strv) == 0 && len(def) > 0 {
} else { return def[0], nil
return defv, 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. // 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) { func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
var defv int64 strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
} }
return strconv.ParseInt(strv, 10, 64)
}
if strv := c.Ctx.Input.Query(key); strv != "" { // GetUint64 returns input value as uint64 or the default value while it's present and input is blank.
return strconv.ParseInt(strv, 10, 64) func (c *Controller) GetUint64(key string, def ...uint64) (uint64, error) {
} else { strv := c.Ctx.Input.Query(key)
return defv, nil 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. // 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) { func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
var defv bool strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.ParseBool(strv)
} else {
return defv, nil
} }
return strconv.ParseBool(strv)
} }
// GetFloat returns input value as float64 or the default value while it's present and input is blank. // GetFloat returns input value as float64 or the default value while it's present and input is blank.
func (c *Controller) GetFloat(key string, def ...float64) (float64, error) { func (c *Controller) GetFloat(key string, def ...float64) (float64, error) {
var defv float64 strv := c.Ctx.Input.Query(key)
if len(def) > 0 { if len(strv) == 0 && len(def) > 0 {
defv = def[0] return def[0], nil
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.ParseFloat(c.Ctx.Input.Query(key), 64)
} else {
return defv, nil
} }
return strconv.ParseFloat(strv, 64)
} }
// GetFile returns the file data in file upload field named as key. // GetFile returns the file data in file upload field named as key.
@ -515,6 +503,40 @@ func (c *Controller) GetFile(key string) (multipart.File, *multipart.FileHeader,
return c.Ctx.Request.FormFile(key) return c.Ctx.Request.FormFile(key)
} }
// GetFiles return multi-upload files
// files, err:=c.GetFiles("myfiles")
// if err != nil {
// http.Error(w, err.Error(), http.StatusNoContent)
// return
// }
// for i, _ := range files {
// //for each fileheader, get a handle to the actual file
// file, err := files[i].Open()
// defer file.Close()
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// //create destination file making sure the path is writeable.
// dst, err := os.Create("upload/" + files[i].Filename)
// defer dst.Close()
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// //copy the uploaded file to the destination file
// if _, err := io.Copy(dst, file); err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// }
func (c *Controller) GetFiles(key string) ([]*multipart.FileHeader, error) {
if files, ok := c.Ctx.Request.MultipartForm.File[key]; ok {
return files, nil
}
return nil, http.ErrMissingFile
}
// SaveToFile saves uploaded file to new path. // SaveToFile saves uploaded file to new path.
// it only operates the first one of mutil-upload form file field. // it only operates the first one of mutil-upload form file field.
func (c *Controller) SaveToFile(fromfile, tofile string) error { func (c *Controller) SaveToFile(fromfile, tofile string) error {
@ -533,7 +555,7 @@ func (c *Controller) SaveToFile(fromfile, tofile string) error {
} }
// StartSession starts session and load old session data info this controller. // StartSession starts session and load old session data info this controller.
func (c *Controller) StartSession() session.SessionStore { func (c *Controller) StartSession() session.Store {
if c.CruSession == nil { if c.CruSession == nil {
c.CruSession = c.Ctx.Input.CruSession c.CruSession = c.Ctx.Input.CruSession
} }
@ -556,7 +578,7 @@ func (c *Controller) GetSession(name interface{}) interface{} {
return c.CruSession.Get(name) return c.CruSession.Get(name)
} }
// SetSession removes value from session. // DelSession removes value from session.
func (c *Controller) DelSession(name interface{}) { func (c *Controller) DelSession(name interface{}) {
if c.CruSession == nil { if c.CruSession == nil {
c.StartSession() c.StartSession()
@ -570,13 +592,14 @@ func (c *Controller) SessionRegenerateID() {
if c.CruSession != nil { if c.CruSession != nil {
c.CruSession.SessionRelease(c.Ctx.ResponseWriter) c.CruSession.SessionRelease(c.Ctx.ResponseWriter)
} }
c.CruSession = GlobalSessions.SessionRegenerateId(c.Ctx.ResponseWriter, c.Ctx.Request) c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request)
c.Ctx.Input.CruSession = c.CruSession c.Ctx.Input.CruSession = c.CruSession
} }
// DestroySession cleans session data and session cookie. // DestroySession cleans session data and session cookie.
func (c *Controller) DestroySession() { func (c *Controller) DestroySession() {
c.Ctx.Input.CruSession.Flush() c.Ctx.Input.CruSession.Flush()
c.Ctx.Input.CruSession = nil
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request) GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
} }
@ -595,37 +618,35 @@ func (c *Controller) SetSecureCookie(Secret, name, value string, others ...inter
c.Ctx.SetSecureCookie(Secret, name, value, others...) c.Ctx.SetSecureCookie(Secret, name, value, others...)
} }
// XsrfToken creates a xsrf token string and returns. // XSRFToken creates a CSRF token string and returns.
func (c *Controller) XsrfToken() string { func (c *Controller) XSRFToken() string {
if c._xsrf_token == "" { if c._xsrfToken == "" {
var expire int64 expire := int64(BConfig.WebConfig.XSRFExpire)
if c.XSRFExpire > 0 { if c.XSRFExpire > 0 {
expire = int64(c.XSRFExpire) expire = int64(c.XSRFExpire)
} else {
expire = int64(XSRFExpire)
} }
c._xsrf_token = c.Ctx.XsrfToken(XSRFKEY, expire) c._xsrfToken = c.Ctx.XSRFToken(BConfig.WebConfig.XSRFKey, expire)
} }
return c._xsrf_token return c._xsrfToken
} }
// CheckXsrfCookie checks xsrf token in this request is valid or not. // CheckXSRFCookie checks xsrf token in this request is valid or not.
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken" // the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
// or in form field value named as "_xsrf". // or in form field value named as "_xsrf".
func (c *Controller) CheckXsrfCookie() bool { func (c *Controller) CheckXSRFCookie() bool {
if !c.EnableXSRF { if !c.EnableXSRF {
return true return true
} }
return c.Ctx.CheckXsrfCookie() return c.Ctx.CheckXSRFCookie()
} }
// XsrfFormHtml writes an input field contains xsrf token value. // XSRFFormHTML writes an input field contains xsrf token value.
func (c *Controller) XsrfFormHtml() string { func (c *Controller) XSRFFormHTML() string {
return "<input type=\"hidden\" name=\"_xsrf\" value=\"" + return `<input type="hidden" name="_xsrf" value="` +
c._xsrf_token + "\"/>" c.XSRFToken() + `" />`
} }
// GetControllerAndAction gets the executing controller name and action name. // GetControllerAndAction gets the executing controller name and action name.
func (c *Controller) GetControllerAndAction() (controllerName, actionName string) { func (c *Controller) GetControllerAndAction() (string, string) {
return c.controllerName, c.actionName return c.controllerName, c.actionName
} }

View File

@ -15,61 +15,167 @@
package beego package beego
import ( import (
"fmt" "math"
"strconv"
"testing"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"os"
"path/filepath"
) )
func ExampleGetInt() { func TestGetInt(t *testing.T) {
i := context.NewInput()
i := &context.BeegoInput{Params: map[string]string{"age": "40"}} i.SetParam("age", "40")
ctx := &context.Context{Input: i} ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx} ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt("age") val, _ := ctrlr.GetInt("age")
fmt.Printf("%T", val) if val != 40 {
//Output: int t.Errorf("TestGetInt expect 40,get %T,%v", val, val)
}
} }
func ExampleGetInt8() { func TestGetInt8(t *testing.T) {
i := context.NewInput()
i := &context.BeegoInput{Params: map[string]string{"age": "40"}} i.SetParam("age", "40")
ctx := &context.Context{Input: i} ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx} ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt8("age") val, _ := ctrlr.GetInt8("age")
fmt.Printf("%T", val) if val != 40 {
t.Errorf("TestGetInt8 expect 40,get %T,%v", val, val)
}
//Output: int8 //Output: int8
} }
func ExampleGetInt16() { func TestGetInt16(t *testing.T) {
i := context.NewInput()
i := &context.BeegoInput{Params: map[string]string{"age": "40"}} i.SetParam("age", "40")
ctx := &context.Context{Input: i} ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx} ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt16("age") val, _ := ctrlr.GetInt16("age")
fmt.Printf("%T", val) if val != 40 {
//Output: int16 t.Errorf("TestGetInt16 expect 40,get %T,%v", val, val)
}
} }
func ExampleGetInt32() { func TestGetInt32(t *testing.T) {
i := context.NewInput()
i := &context.BeegoInput{Params: map[string]string{"age": "40"}} i.SetParam("age", "40")
ctx := &context.Context{Input: i} ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx} ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt32("age") val, _ := ctrlr.GetInt32("age")
fmt.Printf("%T", val) if val != 40 {
//Output: int32 t.Errorf("TestGetInt32 expect 40,get %T,%v", val, val)
}
} }
func ExampleGetInt64() { func TestGetInt64(t *testing.T) {
i := context.NewInput()
i := &context.BeegoInput{Params: map[string]string{"age": "40"}} i.SetParam("age", "40")
ctx := &context.Context{Input: i} ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx} ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt64("age") val, _ := ctrlr.GetInt64("age")
fmt.Printf("%T", val) if val != 40 {
//Output: int64 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)
}
}
func TestAdditionalViewPaths(t *testing.T) {
dir1 := "_beeTmp"
dir2 := "_beeTmp2"
defer os.RemoveAll(dir1)
defer os.RemoveAll(dir2)
dir1file := "file1.tpl"
dir2file := "file2.tpl"
genFile := func(dir string, name string, content string) {
os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777)
if f, err := os.Create(filepath.Join(dir, name)); err != nil {
t.Fatal(err)
} else {
defer f.Close()
f.WriteString(content)
f.Close()
}
}
genFile(dir1, dir1file, `<div>{{.Content}}</div>`)
genFile(dir2, dir2file, `<html>{{.Content}}</html>`)
AddViewPath(dir1)
AddViewPath(dir2)
ctrl := Controller{
TplName: "file1.tpl",
ViewPath: dir1,
}
ctrl.Data = map[interface{}]interface{}{
"Content": "value2",
}
if result, err := ctrl.RenderString(); err != nil {
t.Fatal(err)
} else {
if result != "<div>value2</div>" {
t.Fatalf("TestAdditionalViewPaths expect %s got %s", "<div>value2</div>", result)
}
}
func() {
ctrl.TplName = "file2.tpl"
defer func() {
if r := recover(); r == nil {
t.Fatal("TestAdditionalViewPaths expected error")
}
}()
ctrl.RenderString();
}()
ctrl.TplName = "file2.tpl"
ctrl.ViewPath = dir2
ctrl.RenderString();
} }

17
doc.go Normal file
View File

@ -0,0 +1,17 @@
/*
Package beego provide a MVC framework
beego: an open-source, high-performance, modular, full-stack web framework
It is used for rapid development of RESTful APIs, web apps and backend services in Go.
beego is inspired by Tornado, Sinatra and Flask with the added benefit of some Go-specific features such as interfaces and struct embedding.
package main
import "github.com/astaxie/beego"
func main() {
beego.Run()
}
more information: http://beego.me
*/
package beego

52
docs.go
View File

@ -1,52 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"encoding/json"
"github.com/astaxie/beego/context"
)
var GlobalDocApi map[string]interface{}
func init() {
if EnableDocs {
GlobalDocApi = make(map[string]interface{})
}
}
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 {
bt, err := json.Marshal(obj)
if err != nil {
ctx.Output.SetStatus(504)
return
}
ctx.Output.Header("Content-Type", "application/json;charset=UTF-8")
ctx.Output.Header("Access-Control-Allow-Origin", "*")
ctx.Output.Body(bt)
return
}
ctx.Output.SetStatus(404)
}

366
error.go
View File

@ -82,17 +82,22 @@ var tpl = `
` `
// render default application error page with error and stack string. // render default application error page with error and stack string.
func showErr(err interface{}, ctx *context.Context, Stack string) { func showErr(err interface{}, ctx *context.Context, stack string) {
t, _ := template.New("beegoerrortemp").Parse(tpl) t, _ := template.New("beegoerrortemp").Parse(tpl)
data := make(map[string]string) data := map[string]string{
data["AppError"] = AppName + ":" + fmt.Sprint(err) "AppError": fmt.Sprintf("%s:%v", BConfig.AppName, err),
data["RequestMethod"] = ctx.Input.Method() "RequestMethod": ctx.Input.Method(),
data["RequestURL"] = ctx.Input.Uri() "RequestURL": ctx.Input.URI(),
data["RemoteAddr"] = ctx.Input.IP() "RemoteAddr": ctx.Input.IP(),
data["Stack"] = Stack "Stack": stack,
data["BeegoVersion"] = VERSION "BeegoVersion": VERSION,
data["GoVersion"] = runtime.Version() "GoVersion": runtime.Version(),
ctx.Output.SetStatus(500) }
if ctx.Output.Status != 0 {
ctx.ResponseWriter.WriteHeader(ctx.Output.Status)
} else {
ctx.ResponseWriter.WriteHeader(500)
}
t.Execute(ctx.ResponseWriter, data) t.Execute(ctx.ResponseWriter, data)
} }
@ -203,260 +208,220 @@ type errorInfo struct {
errorType int errorType int
} }
// map of http handlers for each error string. // ErrorMaps holds map of http handlers for each error string.
var ErrorMaps map[string]*errorInfo // there is 10 kinds default error(40x and 50x)
var ErrorMaps = make(map[string]*errorInfo, 10)
func init() {
ErrorMaps = make(map[string]*errorInfo)
}
// show 401 unauthorized error. // show 401 unauthorized error.
func unauthorized(rw http.ResponseWriter, r *http.Request) { func unauthorized(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 401,
data["Title"] = "Unauthorized" "<br>The page you have requested can't be authorized."+
data["Content"] = template.HTML("<br>The page you have requested can't be authorized." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<br><br><ul>" + "<br>The credentials you supplied are incorrect"+
"<br>The credentials you supplied are incorrect" + "<br>There are errors in the website address"+
"<br>There are errors in the website address" + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 402 Payment Required // show 402 Payment Required
func paymentRequired(rw http.ResponseWriter, r *http.Request) { func paymentRequired(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 402,
data["Title"] = "Payment Required" "<br>The page you have requested Payment Required."+
data["Content"] = template.HTML("<br>The page you have requested Payment Required." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<br><br><ul>" + "<br>The credentials you supplied are incorrect"+
"<br>The credentials you supplied are incorrect" + "<br>There are errors in the website address"+
"<br>There are errors in the website address" + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 403 forbidden error. // show 403 forbidden error.
func forbidden(rw http.ResponseWriter, r *http.Request) { func forbidden(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 403,
data["Title"] = "Forbidden" "<br>The page you have requested is forbidden."+
data["Content"] = template.HTML("<br>The page you have requested is forbidden." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<br><br><ul>" + "<br>Your address may be blocked"+
"<br>Your address may be blocked" + "<br>The site may be disabled"+
"<br>The site may be disabled" + "<br>You need to log in"+
"<br>You need to log in" + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 404 notfound error. // show 404 not found error.
func notFound(rw http.ResponseWriter, r *http.Request) { func notFound(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 404,
data["Title"] = "Page Not Found" "<br>The page you have requested has flown the coop."+
data["Content"] = template.HTML("<br>The page you have requested has flown the coop." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<br><br><ul>" + "<br>The page has moved"+
"<br>The page has moved" + "<br>The page no longer exists"+
"<br>The page no longer exists" + "<br>You were looking for your puppy and got lost"+
"<br>You were looking for your puppy and got lost" + "<br>You like 404 pages"+
"<br>You like 404 pages" + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 405 Method Not Allowed // show 405 Method Not Allowed
func methodNotAllowed(rw http.ResponseWriter, r *http.Request) { func methodNotAllowed(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 405,
data["Title"] = "Method Not Allowed" "<br>The method you have requested Not Allowed."+
data["Content"] = template.HTML("<br>The method you have requested Not Allowed." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<br><br><ul>" + "<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI"+
"<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."+
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource." + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 500 internal server error. // show 500 internal server error.
func internalServerError(rw http.ResponseWriter, r *http.Request) { func internalServerError(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 500,
data["Title"] = "Internal Server Error" "<br>The page you have requested is down right now."+
data["Content"] = template.HTML("<br>The page you have requested is down right now." + "<br><br><ul>"+
"<br><br><ul>" + "<br>Please try again later and report the error to the website administrator"+
"<br>Please try again later and report the error to the website administrator" + "<br></ul>",
"<br></ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 501 Not Implemented. // show 501 Not Implemented.
func notImplemented(rw http.ResponseWriter, r *http.Request) { func notImplemented(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 501,
data["Title"] = "Not Implemented" "<br>The page you have requested is Not Implemented."+
data["Content"] = template.HTML("<br>The page you have requested is Not Implemented." + "<br><br><ul>"+
"<br><br><ul>" + "<br>Please try again later and report the error to the website administrator"+
"<br>Please try again later and report the error to the website administrator" + "<br></ul>",
"<br></ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 502 Bad Gateway. // show 502 Bad Gateway.
func badGateway(rw http.ResponseWriter, r *http.Request) { func badGateway(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 502,
data["Title"] = "Bad Gateway" "<br>The page you have requested is down right now."+
data["Content"] = template.HTML("<br>The page you have requested is down right now." + "<br><br><ul>"+
"<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>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>Please try again later and report the error to the website administrator" + "<br></ul>",
"<br></ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 503 service unavailable error. // show 503 service unavailable error.
func serviceUnavailable(rw http.ResponseWriter, r *http.Request) { func serviceUnavailable(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 503,
data["Title"] = "Service Unavailable" "<br>The page you have requested is unavailable."+
data["Content"] = template.HTML("<br>The page you have requested is unavailable." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<br><br><ul>" + "<br><br>The page is overloaded"+
"<br><br>The page is overloaded" + "<br>Please try again later."+
"<br>Please try again later." + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// show 504 Gateway Timeout. // show 504 Gateway Timeout.
func gatewayTimeout(rw http.ResponseWriter, r *http.Request) { func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl) responseError(rw, r,
data := make(map[string]interface{}) 504,
data["Title"] = "Gateway Timeout" "<br>The page you have requested is unavailable"+
data["Content"] = template.HTML("<br>The page you have requested is unavailable." + "<br>Perhaps you are here because:"+
"<br>Perhaps you are here because:" + "<br><br><ul>"+
"<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><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."+
"<br>Please try again later." + "</ul>",
"</ul>") )
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
} }
// register default error http handlers, 404,401,403,500 and 503. func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
func registerDefaultErrorHandler() { t, _ := template.New("beegoerrortemp").Parse(errtpl)
if _, ok := ErrorMaps["401"]; !ok { data := map[string]interface{}{
Errorhandler("401", unauthorized) "Title": http.StatusText(errCode),
} "BeegoVersion": VERSION,
"Content": template.HTML(errContent),
if _, ok := ErrorMaps["402"]; !ok {
Errorhandler("402", paymentRequired)
}
if _, ok := ErrorMaps["403"]; !ok {
Errorhandler("403", forbidden)
}
if _, ok := ErrorMaps["404"]; !ok {
Errorhandler("404", notFound)
}
if _, ok := ErrorMaps["405"]; !ok {
Errorhandler("405", methodNotAllowed)
}
if _, ok := ErrorMaps["500"]; !ok {
Errorhandler("500", internalServerError)
}
if _, ok := ErrorMaps["501"]; !ok {
Errorhandler("501", notImplemented)
}
if _, ok := ErrorMaps["502"]; !ok {
Errorhandler("502", badGateway)
}
if _, ok := ErrorMaps["503"]; !ok {
Errorhandler("503", serviceUnavailable)
}
if _, ok := ErrorMaps["504"]; !ok {
Errorhandler("504", gatewayTimeout)
} }
t.Execute(rw, data)
} }
// ErrorHandler registers http.HandlerFunc to each http err code string. // ErrorHandler registers http.HandlerFunc to each http err code string.
// usage: // usage:
// beego.ErrorHandler("404",NotFound) // beego.ErrorHandler("404",NotFound)
// beego.ErrorHandler("500",InternalServerError) // beego.ErrorHandler("500",InternalServerError)
func Errorhandler(code string, h http.HandlerFunc) *App { func ErrorHandler(code string, h http.HandlerFunc) *App {
errinfo := &errorInfo{} ErrorMaps[code] = &errorInfo{
errinfo.errorType = errorTypeHandler errorType: errorTypeHandler,
errinfo.handler = h handler: h,
errinfo.method = code method: code,
ErrorMaps[code] = errinfo }
return BeeApp return BeeApp
} }
// ErrorController registers ControllerInterface to each http err code string. // ErrorController registers ControllerInterface to each http err code string.
// usage: // usage:
// beego.ErrorHandler(&controllers.ErrorController{}) // beego.ErrorController(&controllers.ErrorController{})
func ErrorController(c ControllerInterface) *App { func ErrorController(c ControllerInterface) *App {
reflectVal := reflect.ValueOf(c) reflectVal := reflect.ValueOf(c)
rt := reflectVal.Type() rt := reflectVal.Type()
ct := reflect.Indirect(reflectVal).Type() ct := reflect.Indirect(reflectVal).Type()
for i := 0; i < rt.NumMethod(); i++ { for i := 0; i < rt.NumMethod(); i++ {
if !utils.InSlice(rt.Method(i).Name, exceptMethod) && strings.HasPrefix(rt.Method(i).Name, "Error") { methodName := rt.Method(i).Name
errinfo := &errorInfo{} if !utils.InSlice(methodName, exceptMethod) && strings.HasPrefix(methodName, "Error") {
errinfo.errorType = errorTypeController errName := strings.TrimPrefix(methodName, "Error")
errinfo.controllerType = ct ErrorMaps[errName] = &errorInfo{
errinfo.method = rt.Method(i).Name errorType: errorTypeController,
errname := strings.TrimPrefix(rt.Method(i).Name, "Error") controllerType: ct,
ErrorMaps[errname] = errinfo method: methodName,
}
} }
} }
return BeeApp return BeeApp
} }
// show error string as simple text message. // Exception Write HttpStatus with errCode and Exec error handler if exist.
// if error string is empty, show 500 error as default. func Exception(errCode uint64, ctx *context.Context) {
func exception(errcode string, ctx *context.Context) { exception(strconv.FormatUint(errCode, 10), ctx)
code, err := strconv.Atoi(errcode)
if err != nil {
code = 503
}
ctx.ResponseWriter.WriteHeader(code)
if h, ok := ErrorMaps[errcode]; ok {
executeError(h, ctx)
return
} else if h, ok := ErrorMaps["503"]; ok {
executeError(h, ctx)
return
} else {
ctx.WriteString(errcode)
}
} }
func executeError(err *errorInfo, ctx *context.Context) { // 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) {
atoi := func(code string) int {
v, err := strconv.Atoi(code)
if err == nil {
return v
}
if ctx.Output.Status == 0 {
return 503
}
return ctx.Output.Status
}
for _, ec := range []string{errCode, "503", "500"} {
if h, ok := ErrorMaps[ec]; ok {
executeError(h, ctx, atoi(ec))
return
}
}
//if 50x error has been removed from errorMap
ctx.ResponseWriter.WriteHeader(atoi(errCode))
ctx.WriteString(errCode)
}
func executeError(err *errorInfo, ctx *context.Context, code int) {
if err.errorType == errorTypeHandler { if err.errorType == errorTypeHandler {
ctx.ResponseWriter.WriteHeader(code)
err.handler(ctx.ResponseWriter, ctx.Request) err.handler(ctx.ResponseWriter, ctx.Request)
return return
} }
if err.errorType == errorTypeController { if err.errorType == errorTypeController {
ctx.Output.SetStatus(code)
//Invoke the request handler //Invoke the request handler
vc := reflect.New(err.controllerType) vc := reflect.New(err.controllerType)
execController, ok := vc.Interface().(ControllerInterface) execController, ok := vc.Interface().(ControllerInterface)
@ -471,16 +436,13 @@ func executeError(err *errorInfo, ctx *context.Context) {
execController.URLMapping() execController.URLMapping()
in := make([]reflect.Value, 0)
method := vc.MethodByName(err.method) method := vc.MethodByName(err.method)
method.Call(in) method.Call([]reflect.Value{})
//render template //render template
if ctx.Output.Status == 0 { if BConfig.WebConfig.AutoRender {
if AutoRender { if err := execController.Render(); err != nil {
if err := execController.Render(); err != nil { panic(err)
panic(err)
}
} }
} }

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

@ -1,5 +0,0 @@
appname = beeapi
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true

View File

@ -1,63 +0,0 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package controllers
import (
"encoding/json"
"github.com/astaxie/beego"
"github.com/astaxie/beego/example/beeapi/models"
)
type ObjectController struct {
beego.Controller
}
func (o *ObjectController) Post() {
var ob models.Object
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
objectid := models.AddOne(ob)
o.Data["json"] = map[string]string{"ObjectId": objectid}
o.ServeJson()
}
func (o *ObjectController) Get() {
objectId := o.Ctx.Input.Params[":objectId"]
if objectId != "" {
ob, err := models.GetOne(objectId)
if err != nil {
o.Data["json"] = err
} else {
o.Data["json"] = ob
}
} else {
obs := models.GetAll()
o.Data["json"] = obs
}
o.ServeJson()
}
func (o *ObjectController) Put() {
objectId := o.Ctx.Input.Params[":objectId"]
var ob models.Object
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
err := models.Update(objectId, ob.Score)
if err != nil {
o.Data["json"] = err
} else {
o.Data["json"] = "update success!"
}
o.ServeJson()
}
func (o *ObjectController) Delete() {
objectId := o.Ctx.Input.Params[":objectId"]
models.Delete(objectId)
o.Data["json"] = "delete success!"
o.ServeJson()
}

View File

@ -1,30 +0,0 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/example/beeapi/controllers"
)
// Objects
// URL HTTP Verb Functionality
// /object POST Creating Objects
// /object/<objectId> GET Retrieving Objects
// /object/<objectId> PUT Updating Objects
// /object GET Queries
// /object/<objectId> DELETE Deleting Objects
func main() {
beego.RESTRouter("/object", &controllers.ObjectController{})
beego.Run()
}

View File

@ -1,58 +0,0 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package models
import (
"errors"
"strconv"
"time"
)
var (
Objects map[string]*Object
)
type Object struct {
ObjectId string
Score int64
PlayerName string
}
func init() {
Objects = make(map[string]*Object)
Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
}
func AddOne(object Object) (ObjectId string) {
object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
Objects[object.ObjectId] = &object
return object.ObjectId
}
func GetOne(ObjectId string) (object *Object, err error) {
if v, ok := Objects[ObjectId]; ok {
return v, nil
}
return nil, errors.New("ObjectId Not Exist")
}
func GetAll() map[string]*Object {
return Objects
}
func Update(ObjectId string, Score int64) (err error) {
if v, ok := Objects[ObjectId]; ok {
v.Score = Score
return nil
}
return errors.New("ObjectId Not Exist")
}
func Delete(ObjectId string) {
delete(Objects, ObjectId)
}

View File

@ -1,3 +0,0 @@
appname = chat
httpport = 8080
runmode = dev

View File

@ -1,20 +0,0 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors Unknwon
package controllers
import (
"github.com/astaxie/beego"
)
type MainController struct {
beego.Controller
}
func (m *MainController) Get() {
m.Data["host"] = m.Ctx.Request.Host
m.TplNames = "index.tpl"
}

View File

@ -1,181 +0,0 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors Unknwon
package controllers
import (
"io/ioutil"
"math/rand"
"net/http"
"time"
"github.com/astaxie/beego"
"github.com/gorilla/websocket"
)
const (
// Time allowed to write a message to the client.
writeWait = 10 * time.Second
// Time allowed to read the next message from the client.
readWait = 60 * time.Second
// Send pings to client with this period. Must be less than readWait.
pingPeriod = (readWait * 9) / 10
// Maximum message size allowed from client.
maxMessageSize = 512
)
func init() {
rand.Seed(time.Now().UTC().UnixNano())
go h.run()
}
// connection is an middleman between the websocket connection and the hub.
type connection struct {
username string
// The websocket connection.
ws *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
}
// readPump pumps messages from the websocket connection to the hub.
func (c *connection) readPump() {
defer func() {
h.unregister <- c
c.ws.Close()
}()
c.ws.SetReadLimit(maxMessageSize)
c.ws.SetReadDeadline(time.Now().Add(readWait))
for {
op, r, err := c.ws.NextReader()
if err != nil {
break
}
switch op {
case websocket.PongMessage:
c.ws.SetReadDeadline(time.Now().Add(readWait))
case websocket.TextMessage:
message, err := ioutil.ReadAll(r)
if err != nil {
break
}
h.broadcast <- []byte(c.username + "_" + time.Now().Format("15:04:05") + ":" + string(message))
}
}
}
// write writes a message with the given opCode and payload.
func (c *connection) write(opCode int, payload []byte) error {
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
return c.ws.WriteMessage(opCode, payload)
}
// writePump pumps messages from the hub to the websocket connection.
func (c *connection) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.ws.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
c.write(websocket.CloseMessage, []byte{})
return
}
if err := c.write(websocket.TextMessage, message); err != nil {
return
}
case <-ticker.C:
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}
type hub struct {
// Registered connections.
connections map[*connection]bool
// Inbound messages from the connections.
broadcast chan []byte
// Register requests from the connections.
register chan *connection
// Unregister requests from connections.
unregister chan *connection
}
var h = &hub{
broadcast: make(chan []byte, maxMessageSize),
register: make(chan *connection, 1),
unregister: make(chan *connection, 1),
connections: make(map[*connection]bool),
}
func (h *hub) run() {
for {
select {
case c := <-h.register:
h.connections[c] = true
case c := <-h.unregister:
delete(h.connections, c)
close(c.send)
case m := <-h.broadcast:
for c := range h.connections {
select {
case c.send <- m:
default:
close(c.send)
delete(h.connections, c)
}
}
}
}
}
type WSController struct {
beego.Controller
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func (w *WSController) Get() {
ws, err := upgrader.Upgrade(w.Ctx.ResponseWriter, w.Ctx.Request, nil)
if _, ok := err.(websocket.HandshakeError); ok {
http.Error(w.Ctx.ResponseWriter, "Not a websocket handshake", 400)
return
} else if err != nil {
return
}
c := &connection{send: make(chan []byte, 256), ws: ws, username: randomString(10)}
h.register <- c
go c.writePump()
c.readPump()
}
func randomString(l int) string {
bytes := make([]byte, l)
for i := 0; i < l; i++ {
bytes[i] = byte(randInt(65, 90))
}
return string(bytes)
}
func randInt(min int, max int) int {
return min + rand.Intn(max-min)
}

View File

@ -1,17 +0,0 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors Unknwon
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/example/chat/controllers"
)
func main() {
beego.Router("/", &controllers.MainController{})
beego.Router("/ws", &controllers.WSController{})
beego.Run()
}

View File

@ -1,92 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Chat Example</title>
<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
<script type="text/javascript">
$(function() {
var conn;
var msg = $("#msg");
var log = $("#log");
function appendLog(msg) {
var d = log[0]
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
msg.appendTo(log)
if (doScroll) {
d.scrollTop = d.scrollHeight - d.clientHeight;
}
}
$("#form").submit(function() {
if (!conn) {
return false;
}
if (!msg.val()) {
return false;
}
conn.send(msg.val());
msg.val("");
return false
});
if (window["WebSocket"]) {
conn = new WebSocket("ws://{{.host}}/ws");
conn.onclose = function(evt) {
appendLog($("<div><b>Connection closed.</b></div>"))
}
conn.onmessage = function(evt) {
appendLog($("<div/>").text(evt.data))
}
} else {
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
}
});
</script>
<style type="text/css">
html {
overflow: hidden;
}
body {
overflow: hidden;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
background: gray;
}
#log {
background: white;
margin: 0;
padding: 0.5em 0.5em 0.5em 0.5em;
position: absolute;
top: 0.5em;
left: 0.5em;
right: 0.5em;
bottom: 3em;
overflow: auto;
}
#form {
padding: 0 0.5em 0 0.5em;
margin: 0;
position: absolute;
bottom: 1em;
left: 0px;
width: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="log"></div>
<form id="form">
<input type="submit" value="Send" />
<input type="text" id="msg" size="64"/>
</form>
</body>
</html>

View File

@ -16,28 +16,29 @@ package beego
import "github.com/astaxie/beego/context" import "github.com/astaxie/beego/context"
// FilterFunc defines filter function type. // FilterFunc defines a filter function which is invoked before the controller handler is executed.
type FilterFunc func(*context.Context) type FilterFunc func(*context.Context)
// FilterRouter defines filter operation before controller handler execution. // FilterRouter defines a filter operation which is invoked before the controller handler is executed.
// it can match patterned url and do filter function when action arrives. // It can match the URL against a pattern, and execute a filter function
// when a request with a matching URL arrives.
type FilterRouter struct { type FilterRouter struct {
filterFunc FilterFunc filterFunc FilterFunc
tree *Tree tree *Tree
pattern string pattern string
returnOnOutput bool returnOnOutput bool
resetParams bool
} }
// ValidRouter check current request is valid for this filter. // ValidRouter checks if the current request is matched by this filter.
// if matched, returns parsed params in this request by defined filter router pattern. // If the request is matched, the values of the URL parameters defined
func (f *FilterRouter) ValidRouter(router string) (bool, map[string]string) { // by the filter pattern are also returned.
isok, params := f.tree.Match(router) func (f *FilterRouter) ValidRouter(url string, ctx *context.Context) bool {
if isok == nil { isOk := f.tree.Match(url, ctx)
return false, nil if isOk != nil {
} if b, ok := isOk.(bool); ok {
if isok, ok := isok.(bool); ok { return b
return isok, params }
} else {
return false, nil
} }
return false
} }

View File

@ -23,7 +23,7 @@ import (
) )
var FilterUser = func(ctx *context.Context) { var FilterUser = func(ctx *context.Context) {
ctx.Output.Body([]byte("i am " + ctx.Input.Params[":last"] + ctx.Input.Params[":first"])) ctx.Output.Body([]byte("i am " + ctx.Input.Param(":last") + ctx.Input.Param(":first")))
} }
func TestFilter(t *testing.T) { func TestFilter(t *testing.T) {

View File

@ -83,27 +83,27 @@ func (fd *FlashData) Store(c *Controller) {
c.Data["flash"] = fd.Data c.Data["flash"] = fd.Data
var flashValue string var flashValue string
for key, value := range fd.Data { for key, value := range fd.Data {
flashValue += "\x00" + key + "\x23" + FlashSeperator + "\x23" + value + "\x00" flashValue += "\x00" + key + "\x23" + BConfig.WebConfig.FlashSeparator + "\x23" + value + "\x00"
} }
c.Ctx.SetCookie(FlashName, url.QueryEscape(flashValue), 0, "/") c.Ctx.SetCookie(BConfig.WebConfig.FlashName, url.QueryEscape(flashValue), 0, "/")
} }
// ReadFromRequest parsed flash data from encoded values in cookie. // ReadFromRequest parsed flash data from encoded values in cookie.
func ReadFromRequest(c *Controller) *FlashData { func ReadFromRequest(c *Controller) *FlashData {
flash := NewFlash() flash := NewFlash()
if cookie, err := c.Ctx.Request.Cookie(FlashName); err == nil { if cookie, err := c.Ctx.Request.Cookie(BConfig.WebConfig.FlashName); err == nil {
v, _ := url.QueryUnescape(cookie.Value) v, _ := url.QueryUnescape(cookie.Value)
vals := strings.Split(v, "\x00") vals := strings.Split(v, "\x00")
for _, v := range vals { for _, v := range vals {
if len(v) > 0 { if len(v) > 0 {
kv := strings.Split(v, "\x23"+FlashSeperator+"\x23") kv := strings.Split(v, "\x23"+BConfig.WebConfig.FlashSeparator+"\x23")
if len(kv) == 2 { if len(kv) == 2 {
flash.Data[kv[0]] = kv[1] flash.Data[kv[0]] = kv[1]
} }
} }
} }
//read one time then delete it //read one time then delete it
c.Ctx.SetCookie(FlashName, "", -1, "/") c.Ctx.SetCookie(BConfig.WebConfig.FlashName, "", -1, "/")
} }
c.Data["flash"] = flash.Data c.Data["flash"] = flash.Data
return flash return flash

View File

@ -30,7 +30,7 @@ func (t *TestFlashController) TestWriteFlash() {
flash.Notice("TestFlashString") flash.Notice("TestFlashString")
flash.Store(&t.Controller) flash.Store(&t.Controller)
// we choose to serve json because we don't want to load a template html file // we choose to serve json because we don't want to load a template html file
t.ServeJson(true) t.ServeJSON(true)
} }
func TestFlashHeader(t *testing.T) { func TestFlashHeader(t *testing.T) {

28
grace/conn.go Normal file
View File

@ -0,0 +1,28 @@
package grace
import (
"errors"
"net"
)
type graceConn struct {
net.Conn
server *Server
}
func (c graceConn) Close() (err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("Unknown panic")
}
}
}()
c.server.wg.Done()
return c.Conn.Close()
}

166
grace/grace.go Normal file
View File

@ -0,0 +1,166 @@
// 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 grace use to hot reload
// Description: http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/
//
// Usage:
//
// import(
// "log"
// "net/http"
// "os"
//
// "github.com/astaxie/beego/grace"
// )
//
// func handler(w http.ResponseWriter, r *http.Request) {
// w.Write([]byte("WORLD!"))
// }
//
// func main() {
// mux := http.NewServeMux()
// mux.HandleFunc("/hello", handler)
//
// err := grace.ListenAndServe("localhost:8080", mux)
// if err != nil {
// log.Println(err)
// }
// log.Println("Server on 8080 stopped")
// os.Exit(0)
// }
package grace
import (
"flag"
"net/http"
"os"
"strings"
"sync"
"syscall"
"time"
)
const (
// PreSignal is the position to add filter before signal
PreSignal = iota
// PostSignal is the position to add filter after signal
PostSignal
// StateInit represent the application inited
StateInit
// StateRunning represent the application is running
StateRunning
// StateShuttingDown represent the application is shutting down
StateShuttingDown
// StateTerminate represent the application is killed
StateTerminate
)
var (
regLock *sync.Mutex
runningServers map[string]*Server
runningServersOrder []string
socketPtrOffsetMap map[string]uint
runningServersForked bool
// DefaultReadTimeOut is the HTTP read timeout
DefaultReadTimeOut time.Duration
// DefaultWriteTimeOut is the HTTP Write timeout
DefaultWriteTimeOut time.Duration
// DefaultMaxHeaderBytes is the Max HTTP Herder size, default is 0, no limit
DefaultMaxHeaderBytes int
// DefaultTimeout is the shutdown server's timeout. default is 60s
DefaultTimeout = 60 * time.Second
isChild bool
socketOrder string
hookableSignals []os.Signal
)
func init() {
flag.BoolVar(&isChild, "graceful", false, "listen on open fd (after forking)")
flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
regLock = &sync.Mutex{}
runningServers = make(map[string]*Server)
runningServersOrder = []string{}
socketPtrOffsetMap = make(map[string]uint)
hookableSignals = []os.Signal{
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
}
}
// NewServer returns a new graceServer.
func NewServer(addr string, handler http.Handler) (srv *Server) {
regLock.Lock()
defer regLock.Unlock()
if !flag.Parsed() {
flag.Parse()
}
if len(socketOrder) > 0 {
for i, addr := range strings.Split(socketOrder, ",") {
socketPtrOffsetMap[addr] = uint(i)
}
} else {
socketPtrOffsetMap[addr] = uint(len(runningServersOrder))
}
srv = &Server{
wg: sync.WaitGroup{},
sigChan: make(chan os.Signal),
isChild: isChild,
SignalHooks: map[int]map[os.Signal][]func(){
PreSignal: {
syscall.SIGHUP: {},
syscall.SIGINT: {},
syscall.SIGTERM: {},
},
PostSignal: {
syscall.SIGHUP: {},
syscall.SIGINT: {},
syscall.SIGTERM: {},
},
},
state: StateInit,
Network: "tcp",
}
srv.Server = &http.Server{}
srv.Server.Addr = addr
srv.Server.ReadTimeout = DefaultReadTimeOut
srv.Server.WriteTimeout = DefaultWriteTimeOut
srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes
srv.Server.Handler = handler
runningServersOrder = append(runningServersOrder, addr)
runningServers[addr] = srv
return
}
// ListenAndServe refer http.ListenAndServe
func ListenAndServe(addr string, handler http.Handler) error {
server := NewServer(addr, handler)
return server.ListenAndServe()
}
// ListenAndServeTLS refer http.ListenAndServeTLS
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error {
server := NewServer(addr, handler)
return server.ListenAndServeTLS(certFile, keyFile)
}

62
grace/listener.go Normal file
View File

@ -0,0 +1,62 @@
package grace
import (
"net"
"os"
"syscall"
"time"
)
type graceListener struct {
net.Listener
stop chan error
stopped bool
server *Server
}
func newGraceListener(l net.Listener, srv *Server) (el *graceListener) {
el = &graceListener{
Listener: l,
stop: make(chan error),
server: srv,
}
go func() {
_ = <-el.stop
el.stopped = true
el.stop <- el.Listener.Close()
}()
return
}
func (gl *graceListener) Accept() (c net.Conn, err error) {
tc, err := gl.Listener.(*net.TCPListener).AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
c = graceConn{
Conn: tc,
server: gl.server,
}
gl.server.wg.Add(1)
return
}
func (gl *graceListener) Close() error {
if gl.stopped {
return syscall.EINVAL
}
gl.stop <- nil
return <-gl.stop
}
func (gl *graceListener) File() *os.File {
// returns a dup(2) - FD_CLOEXEC flag *not* set
tl := gl.Listener.(*net.TCPListener)
fl, _ := tl.File()
return fl
}

306
grace/server.go Normal file
View File

@ -0,0 +1,306 @@
package grace
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
// Server embedded http.Server
type Server struct {
*http.Server
GraceListener net.Listener
SignalHooks map[int]map[os.Signal][]func()
tlsInnerListener *graceListener
wg sync.WaitGroup
sigChan chan os.Signal
isChild bool
state uint8
Network string
}
// Serve accepts incoming connections on the Listener l,
// creating a new service goroutine for each.
// The service goroutines read requests and then call srv.Handler to reply to them.
func (srv *Server) Serve() (err error) {
srv.state = StateRunning
err = srv.Server.Serve(srv.GraceListener)
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
srv.wg.Wait()
srv.state = StateTerminate
return
}
// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
// to handle requests on incoming connections. If srv.Addr is blank, ":http" is
// used.
func (srv *Server) ListenAndServe() (err error) {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
go srv.handleSignals()
l, err := srv.getListener(addr)
if err != nil {
log.Println(err)
return err
}
srv.GraceListener = newGraceListener(l, srv)
if srv.isChild {
process, err := os.FindProcess(os.Getppid())
if err != nil {
log.Println(err)
return err
}
err = process.Kill()
if err != nil {
return err
}
}
log.Println(os.Getpid(), srv.Addr)
return srv.Serve()
}
// ListenAndServeTLS listens on the TCP network address srv.Addr and then calls
// Serve to handle requests on incoming TLS connections.
//
// Filenames containing a certificate and matching private key for the server must
// be provided. If the certificate is signed by a certificate authority, the
// certFile should be the concatenation of the server's certificate followed by the
// CA's certificate.
//
// If srv.Addr is blank, ":https" is used.
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
if srv.TLSConfig == nil {
srv.TLSConfig = &tls.Config{}
}
if srv.TLSConfig.NextProtos == nil {
srv.TLSConfig.NextProtos = []string{"http/1.1"}
}
srv.TLSConfig.Certificates = make([]tls.Certificate, 1)
srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return
}
go srv.handleSignals()
l, err := srv.getListener(addr)
if err != nil {
log.Println(err)
return err
}
srv.tlsInnerListener = newGraceListener(l, srv)
srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig)
if srv.isChild {
process, err := os.FindProcess(os.Getppid())
if err != nil {
log.Println(err)
return err
}
err = process.Kill()
if err != nil {
return err
}
}
log.Println(os.Getpid(), srv.Addr)
return srv.Serve()
}
// getListener either opens a new socket to listen on, or takes the acceptor socket
// it got passed when restarted.
func (srv *Server) getListener(laddr string) (l net.Listener, err error) {
if srv.isChild {
var ptrOffset uint
if len(socketPtrOffsetMap) > 0 {
ptrOffset = socketPtrOffsetMap[laddr]
log.Println("laddr", laddr, "ptr offset", socketPtrOffsetMap[laddr])
}
f := os.NewFile(uintptr(3+ptrOffset), "")
l, err = net.FileListener(f)
if err != nil {
err = fmt.Errorf("net.FileListener error: %v", err)
return
}
} else {
l, err = net.Listen(srv.Network, laddr)
if err != nil {
err = fmt.Errorf("net.Listen error: %v", err)
return
}
}
return
}
// handleSignals listens for os Signals and calls any hooked in function that the
// user had registered with the signal.
func (srv *Server) handleSignals() {
var sig os.Signal
signal.Notify(
srv.sigChan,
hookableSignals...,
)
pid := syscall.Getpid()
for {
sig = <-srv.sigChan
srv.signalHooks(PreSignal, sig)
switch sig {
case syscall.SIGHUP:
log.Println(pid, "Received SIGHUP. forking.")
err := srv.fork()
if err != nil {
log.Println("Fork err:", err)
}
case syscall.SIGINT:
log.Println(pid, "Received SIGINT.")
srv.shutdown()
case syscall.SIGTERM:
log.Println(pid, "Received SIGTERM.")
srv.shutdown()
default:
log.Printf("Received %v: nothing i care about...\n", sig)
}
srv.signalHooks(PostSignal, sig)
}
}
func (srv *Server) signalHooks(ppFlag int, sig os.Signal) {
if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet {
return
}
for _, f := range srv.SignalHooks[ppFlag][sig] {
f()
}
return
}
// shutdown closes the listener so that no new connections are accepted. it also
// starts a goroutine that will serverTimeout (stop all running requests) the server
// after DefaultTimeout.
func (srv *Server) shutdown() {
if srv.state != StateRunning {
return
}
srv.state = StateShuttingDown
if DefaultTimeout >= 0 {
go srv.serverTimeout(DefaultTimeout)
}
err := srv.GraceListener.Close()
if err != nil {
log.Println(syscall.Getpid(), "Listener.Close() error:", err)
} else {
log.Println(syscall.Getpid(), srv.GraceListener.Addr(), "Listener closed.")
}
}
// serverTimeout forces the server to shutdown in a given timeout - whether it
// finished outstanding requests or not. if Read/WriteTimeout are not set or the
// max header size is very big a connection could hang
func (srv *Server) serverTimeout(d time.Duration) {
defer func() {
if r := recover(); r != nil {
log.Println("WaitGroup at 0", r)
}
}()
if srv.state != StateShuttingDown {
return
}
time.Sleep(d)
log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
for {
if srv.state == StateTerminate {
break
}
srv.wg.Done()
}
}
func (srv *Server) fork() (err error) {
regLock.Lock()
defer regLock.Unlock()
if runningServersForked {
return
}
runningServersForked = true
var files = make([]*os.File, len(runningServers))
var orderArgs = make([]string, len(runningServers))
for _, srvPtr := range runningServers {
switch srvPtr.GraceListener.(type) {
case *graceListener:
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.GraceListener.(*graceListener).File()
default:
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
}
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
}
log.Println(files)
path := os.Args[0]
var args []string
if len(os.Args) > 1 {
for _, arg := range os.Args[1:] {
if arg == "-graceful" {
break
}
args = append(args, arg)
}
}
args = append(args, "-graceful")
if len(runningServers) > 1 {
args = append(args, fmt.Sprintf(`-socketorder=%s`, strings.Join(orderArgs, ",")))
log.Println(args)
}
cmd := exec.Command(path, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = files
err = cmd.Start()
if err != nil {
log.Fatalf("Restart: Failed to launch, error: %v", err)
}
return
}
// RegisterSignalHook registers a function to be run PreSignal or PostSignal for a given signal.
func (srv *Server) RegisterSignalHook(ppFlag int, sig os.Signal, f func()) (err error) {
if ppFlag != PreSignal && ppFlag != PostSignal {
err = fmt.Errorf("Invalid ppFlag argument. Must be either grace.PreSignal or grace.PostSignal.")
return
}
for _, s := range hookableSignals {
if s == sig {
srv.SignalHooks[ppFlag][sig] = append(srv.SignalHooks[ppFlag][sig], f)
return
}
}
err = fmt.Errorf("Signal '%v' is not supported.", sig)
return
}

101
hooks.go Normal file
View File

@ -0,0 +1,101 @@
package beego
import (
"encoding/json"
"mime"
"net/http"
"path/filepath"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/session"
)
//
func registerMime() error {
for k, v := range mimemaps {
mime.AddExtensionType(k, v)
}
return nil
}
// register default error http handlers, 404,401,403,500 and 503.
func registerDefaultErrorHandler() error {
m := map[string]func(http.ResponseWriter, *http.Request){
"401": unauthorized,
"402": paymentRequired,
"403": forbidden,
"404": notFound,
"405": methodNotAllowed,
"500": internalServerError,
"501": notImplemented,
"502": badGateway,
"503": serviceUnavailable,
"504": gatewayTimeout,
}
for e, h := range m {
if _, ok := ErrorMaps[e]; !ok {
ErrorHandler(e, h)
}
}
return nil
}
func registerSession() error {
if BConfig.WebConfig.Session.SessionOn {
var err error
sessionConfig := AppConfig.String("sessionConfig")
conf := new(session.ManagerConfig)
if sessionConfig == "" {
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
}
}
if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, conf); err != nil {
return err
}
go GlobalSessions.GC()
}
return nil
}
func registerTemplate() error {
defer lockViewPaths()
if err := AddViewPath(BConfig.WebConfig.ViewsPath); err != nil {
if BConfig.RunMode == DEV {
logs.Warn(err)
}
return err
}
return nil
}
func registerAdmin() error {
if BConfig.Listen.EnableAdmin {
go beeAdminApp.Run()
}
return nil
}
func registerGzip() error {
if BConfig.EnableGzip {
context.InitGzip(
AppConfig.DefaultInt("gzipMinLength", -1),
AppConfig.DefaultInt("gzipCompressLevel", -1),
AppConfig.DefaultStrings("includedMethods", []string{"GET"}),
)
}
return nil
}

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package httplib is used as http.Client
// Usage: // Usage:
// //
// import "github.com/astaxie/beego/httplib" // import "github.com/astaxie/beego/httplib"
@ -32,6 +33,7 @@ package httplib
import ( import (
"bytes" "bytes"
"compress/gzip"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
@ -50,7 +52,14 @@ import (
"time" "time"
) )
var defaultSetting = BeegoHttpSettings{false, "beegoServer", 60 * time.Second, 60 * time.Second, nil, nil, nil, false} var defaultSetting = BeegoHTTPSettings{
UserAgent: "beegoServer",
ConnectTimeout: 60 * time.Second,
ReadWriteTimeout: 60 * time.Second,
Gzip: true,
DumpBody: true,
}
var defaultCookieJar http.CookieJar var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex var settingMutex sync.Mutex
@ -61,132 +70,174 @@ func createDefaultCookie() {
defaultCookieJar, _ = cookiejar.New(nil) defaultCookieJar, _ = cookiejar.New(nil)
} }
// Overwrite default settings // SetDefaultSetting Overwrite default settings
func SetDefaultSetting(setting BeegoHttpSettings) { func SetDefaultSetting(setting BeegoHTTPSettings) {
settingMutex.Lock() settingMutex.Lock()
defer settingMutex.Unlock() defer settingMutex.Unlock()
defaultSetting = setting defaultSetting = setting
if defaultSetting.ConnectTimeout == 0 {
defaultSetting.ConnectTimeout = 60 * time.Second
}
if defaultSetting.ReadWriteTimeout == 0 {
defaultSetting.ReadWriteTimeout = 60 * time.Second
}
} }
// return *BeegoHttpRequest with specific method // NewBeegoRequest return *BeegoHttpRequest with specific method
func newBeegoRequest(url, method string) *BeegoHttpRequest { func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest {
var resp http.Response var resp http.Response
u, err := url.Parse(rawurl)
if err != nil {
log.Println("Httplib:", err)
}
req := http.Request{ req := http.Request{
URL: u,
Method: method, Method: method,
Header: make(http.Header), Header: make(http.Header),
Proto: "HTTP/1.1", Proto: "HTTP/1.1",
ProtoMajor: 1, ProtoMajor: 1,
ProtoMinor: 1, ProtoMinor: 1,
} }
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil} return &BeegoHTTPRequest{
url: rawurl,
req: &req,
params: map[string][]string{},
files: map[string]string{},
setting: defaultSetting,
resp: &resp,
}
} }
// Get returns *BeegoHttpRequest with GET method. // Get returns *BeegoHttpRequest with GET method.
func Get(url string) *BeegoHttpRequest { func Get(url string) *BeegoHTTPRequest {
return newBeegoRequest(url, "GET") return NewBeegoRequest(url, "GET")
} }
// Post returns *BeegoHttpRequest with POST method. // Post returns *BeegoHttpRequest with POST method.
func Post(url string) *BeegoHttpRequest { func Post(url string) *BeegoHTTPRequest {
return newBeegoRequest(url, "POST") return NewBeegoRequest(url, "POST")
} }
// Put returns *BeegoHttpRequest with PUT method. // Put returns *BeegoHttpRequest with PUT method.
func Put(url string) *BeegoHttpRequest { func Put(url string) *BeegoHTTPRequest {
return newBeegoRequest(url, "PUT") return NewBeegoRequest(url, "PUT")
} }
// Delete returns *BeegoHttpRequest DELETE method. // Delete returns *BeegoHttpRequest DELETE method.
func Delete(url string) *BeegoHttpRequest { func Delete(url string) *BeegoHTTPRequest {
return newBeegoRequest(url, "DELETE") return NewBeegoRequest(url, "DELETE")
} }
// Head returns *BeegoHttpRequest with HEAD method. // Head returns *BeegoHttpRequest with HEAD method.
func Head(url string) *BeegoHttpRequest { func Head(url string) *BeegoHTTPRequest {
return newBeegoRequest(url, "HEAD") return NewBeegoRequest(url, "HEAD")
} }
// BeegoHttpSettings // BeegoHTTPSettings is the http.Client setting
type BeegoHttpSettings struct { type BeegoHTTPSettings struct {
ShowDebug bool ShowDebug bool
UserAgent string UserAgent string
ConnectTimeout time.Duration ConnectTimeout time.Duration
ReadWriteTimeout time.Duration ReadWriteTimeout time.Duration
TlsClientConfig *tls.Config TLSClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error) Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper Transport http.RoundTripper
CheckRedirect func(req *http.Request, via []*http.Request) error
EnableCookie bool EnableCookie bool
Gzip bool
DumpBody bool
Retries int // if set to -1 means will retry forever
} }
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request. // BeegoHTTPRequest provides more useful methods for requesting one url than http.Request.
type BeegoHttpRequest struct { type BeegoHTTPRequest struct {
url string url string
req *http.Request req *http.Request
params map[string]string params map[string][]string
files map[string]string files map[string]string
setting BeegoHttpSettings setting BeegoHTTPSettings
resp *http.Response resp *http.Response
body []byte body []byte
dump []byte
} }
// Change request settings // GetRequest return the request object
func (b *BeegoHttpRequest) Setting(setting BeegoHttpSettings) *BeegoHttpRequest { func (b *BeegoHTTPRequest) GetRequest() *http.Request {
return b.req
}
// Setting Change request settings
func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest {
b.setting = setting b.setting = setting
return b return b
} }
// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. // SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
func (b *BeegoHttpRequest) SetBasicAuth(username, password string) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest {
b.req.SetBasicAuth(username, password) b.req.SetBasicAuth(username, password)
return b return b
} }
// SetEnableCookie sets enable/disable cookiejar // SetEnableCookie sets enable/disable cookiejar
func (b *BeegoHttpRequest) SetEnableCookie(enable bool) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest {
b.setting.EnableCookie = enable b.setting.EnableCookie = enable
return b return b
} }
// SetUserAgent sets User-Agent header field // SetUserAgent sets User-Agent header field
func (b *BeegoHttpRequest) SetUserAgent(useragent string) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest {
b.setting.UserAgent = useragent b.setting.UserAgent = useragent
return b return b
} }
// Debug sets show debug or not when executing request. // Debug sets show debug or not when executing request.
func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest { func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest {
b.setting.ShowDebug = isdebug b.setting.ShowDebug = isdebug
return b return b
} }
// Retries sets Retries times.
// default is 0 means no retried.
// -1 means retried forever.
// others means retried times.
func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest {
b.setting.Retries = times
return b
}
// DumpBody setting whether need to Dump the Body.
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
b.setting.DumpBody = isdump
return b
}
// DumpRequest return the DumpRequest
func (b *BeegoHTTPRequest) DumpRequest() []byte {
return b.dump
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest. // SetTimeout sets connect time out and read-write time out for BeegoRequest.
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest {
b.setting.ConnectTimeout = connectTimeout b.setting.ConnectTimeout = connectTimeout
b.setting.ReadWriteTimeout = readWriteTimeout b.setting.ReadWriteTimeout = readWriteTimeout
return b return b
} }
// SetTLSClientConfig sets tls connection configurations if visiting https url. // SetTLSClientConfig sets tls connection configurations if visiting https url.
func (b *BeegoHttpRequest) SetTLSClientConfig(config *tls.Config) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest {
b.setting.TlsClientConfig = config b.setting.TLSClientConfig = config
return b return b
} }
// Header add header item string in request. // Header add header item string in request.
func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest { func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest {
b.req.Header.Set(key, value) b.req.Header.Set(key, value)
return b return b
} }
// Set the protocol version for incoming requests. // SetHost set the request host
func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest {
b.req.Host = host
return b
}
// SetProtocolVersion Set the protocol version for incoming requests.
// Client requests always use HTTP/1.1. // Client requests always use HTTP/1.1.
func (b *BeegoHttpRequest) SetProtocolVersion(vers string) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest {
if len(vers) == 0 { if len(vers) == 0 {
vers = "HTTP/1.1" vers = "HTTP/1.1"
} }
@ -202,44 +253,58 @@ func (b *BeegoHttpRequest) SetProtocolVersion(vers string) *BeegoHttpRequest {
} }
// SetCookie add cookie into request. // SetCookie add cookie into request.
func (b *BeegoHttpRequest) SetCookie(cookie *http.Cookie) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest {
b.req.Header.Add("Cookie", cookie.String()) b.req.Header.Add("Cookie", cookie.String())
return b return b
} }
// Set transport to // SetTransport set the setting transport
func (b *BeegoHttpRequest) SetTransport(transport http.RoundTripper) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest {
b.setting.Transport = transport b.setting.Transport = transport
return b return b
} }
// Set http proxy // SetProxy set the http proxy
// example: // example:
// //
// func(req *http.Request) (*url.URL, error) { // func(req *http.Request) (*url.URL, error) {
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118") // u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
// return u, nil // return u, nil
// } // }
func (b *BeegoHttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHttpRequest { func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest {
b.setting.Proxy = proxy b.setting.Proxy = proxy
return b 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. // Param adds query param in to request.
// params build query string as ?key1=value1&key2=value2... // params build query string as ?key1=value1&key2=value2...
func (b *BeegoHttpRequest) Param(key, value string) *BeegoHttpRequest { func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest {
b.params[key] = value if param, ok := b.params[key]; ok {
b.params[key] = append(param, value)
} else {
b.params[key] = []string{value}
}
return b return b
} }
func (b *BeegoHttpRequest) PostFile(formname, filename string) *BeegoHttpRequest { // PostFile add a post file to the request
func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest {
b.files[formname] = filename b.files[formname] = filename
return b return b
} }
// Body adds request raw body. // Body adds request raw body.
// it supports string and []byte. // it supports string and []byte.
func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest { func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
switch t := data.(type) { switch t := data.(type) {
case string: case string:
bf := bytes.NewBufferString(t) bf := bytes.NewBufferString(t)
@ -253,7 +318,21 @@ func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
return b return b
} }
func (b *BeegoHttpRequest) buildUrl(paramBody string) { // JSONBody adds request raw body encoding by JSON.
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil {
byts, err := json.Marshal(obj)
if err != nil {
return b, err
}
b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/json")
}
return b, nil
}
func (b *BeegoHTTPRequest) buildURL(paramBody string) {
// build GET url with query string // build GET url with query string
if b.req.Method == "GET" && len(paramBody) > 0 { if b.req.Method == "GET" && len(paramBody) > 0 {
if strings.Index(b.url, "?") != -1 { if strings.Index(b.url, "?") != -1 {
@ -264,8 +343,8 @@ func (b *BeegoHttpRequest) buildUrl(paramBody string) {
return return
} }
// build POST url and body // build POST/PUT/PATCH url and body
if b.req.Method == "POST" && b.req.Body == nil { if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH") && b.req.Body == nil {
// with files // with files
if len(b.files) > 0 { if len(b.files) > 0 {
pr, pw := io.Pipe() pr, pw := io.Pipe()
@ -274,21 +353,23 @@ func (b *BeegoHttpRequest) buildUrl(paramBody string) {
for formname, filename := range b.files { for formname, filename := range b.files {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename) fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
if err != nil { if err != nil {
log.Fatal(err) log.Println("Httplib:", err)
} }
fh, err := os.Open(filename) fh, err := os.Open(filename)
if err != nil { if err != nil {
log.Fatal(err) log.Println("Httplib:", err)
} }
//iocopy //iocopy
_, err = io.Copy(fileWriter, fh) _, err = io.Copy(fileWriter, fh)
fh.Close() fh.Close()
if err != nil { if err != nil {
log.Fatal(err) log.Println("Httplib:", err)
} }
} }
for k, v := range b.params { for k, v := range b.params {
bodyWriter.WriteField(k, v) for _, vv := range v {
bodyWriter.WriteField(k, vv)
}
} }
bodyWriter.Close() bodyWriter.Close()
pw.Close() pw.Close()
@ -306,24 +387,36 @@ func (b *BeegoHttpRequest) buildUrl(paramBody string) {
} }
} }
func (b *BeegoHttpRequest) getResponse() (*http.Response, error) { func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) {
if b.resp.StatusCode != 0 { if b.resp.StatusCode != 0 {
return b.resp, nil return b.resp, nil
} }
resp, err := b.DoRequest()
if err != nil {
return nil, err
}
b.resp = resp
return resp, nil
}
// DoRequest will do the client.Do
func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
var paramBody string var paramBody string
if len(b.params) > 0 { if len(b.params) > 0 {
var buf bytes.Buffer var buf bytes.Buffer
for k, v := range b.params { for k, v := range b.params {
buf.WriteString(url.QueryEscape(k)) for _, vv := range v {
buf.WriteByte('=') buf.WriteString(url.QueryEscape(k))
buf.WriteString(url.QueryEscape(v)) buf.WriteByte('=')
buf.WriteByte('&') buf.WriteString(url.QueryEscape(vv))
buf.WriteByte('&')
}
} }
paramBody = buf.String() paramBody = buf.String()
paramBody = paramBody[0 : len(paramBody)-1] paramBody = paramBody[0 : len(paramBody)-1]
} }
b.buildUrl(paramBody) b.buildURL(paramBody)
url, err := url.Parse(b.url) url, err := url.Parse(b.url)
if err != nil { if err != nil {
return nil, err return nil, err
@ -336,15 +429,16 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
if trans == nil { if trans == nil {
// create default transport // create default transport
trans = &http.Transport{ trans = &http.Transport{
TLSClientConfig: b.setting.TlsClientConfig, TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy, Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: -1,
} }
} else { } else {
// if b.transport is *http.Transport then set the settings. // if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok { if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil { if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TlsClientConfig t.TLSClientConfig = b.setting.TLSClientConfig
} }
if t.Proxy == nil { if t.Proxy == nil {
t.Proxy = b.setting.Proxy t.Proxy = b.setting.Proxy
@ -355,7 +449,7 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
} }
} }
var jar http.CookieJar = nil var jar http.CookieJar
if b.setting.EnableCookie { if b.setting.EnableCookie {
if defaultCookieJar == nil { if defaultCookieJar == nil {
createDefaultCookie() createDefaultCookie()
@ -372,25 +466,32 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
b.req.Header.Set("User-Agent", b.setting.UserAgent) b.req.Header.Set("User-Agent", b.setting.UserAgent)
} }
if b.setting.ShowDebug { if b.setting.CheckRedirect != nil {
dump, err := httputil.DumpRequest(b.req, true) client.CheckRedirect = b.setting.CheckRedirect
if err != nil {
println(err.Error())
}
println(string(dump))
} }
resp, err := client.Do(b.req) if b.setting.ShowDebug {
if err != nil { dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody)
return nil, err if err != nil {
log.Println(err.Error())
}
b.dump = dump
} }
b.resp = resp // retries default value is 0, it will run once.
return resp, nil // retries equal to -1, it will run forever until success
// retries is setted, it will retries fixed times.
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
resp, err = client.Do(b.req)
if err == nil {
break
}
}
return resp, err
} }
// String returns the body string in response. // String returns the body string in response.
// it calls Response inner. // it calls Response inner.
func (b *BeegoHttpRequest) String() (string, error) { func (b *BeegoHTTPRequest) String() (string, error) {
data, err := b.Bytes() data, err := b.Bytes()
if err != nil { if err != nil {
return "", err return "", err
@ -401,7 +502,7 @@ func (b *BeegoHttpRequest) String() (string, error) {
// Bytes returns the body []byte in response. // Bytes returns the body []byte in response.
// it calls Response inner. // it calls Response inner.
func (b *BeegoHttpRequest) Bytes() ([]byte, error) { func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
if b.body != nil { if b.body != nil {
return b.body, nil return b.body, nil
} }
@ -413,16 +514,21 @@ func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
return nil, nil return nil, nil
} }
defer resp.Body.Close() defer resp.Body.Close()
b.body, err = ioutil.ReadAll(resp.Body) if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" {
if err != nil { reader, err := gzip.NewReader(resp.Body)
return nil, err if err != nil {
return nil, err
}
b.body, err = ioutil.ReadAll(reader)
} else {
b.body, err = ioutil.ReadAll(resp.Body)
} }
return b.body, nil return b.body, err
} }
// ToFile saves the body data in response to one file. // ToFile saves the body data in response to one file.
// it calls Response inner. // it calls Response inner.
func (b *BeegoHttpRequest) ToFile(filename string) error { func (b *BeegoHTTPRequest) ToFile(filename string) error {
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
return err return err
@ -441,9 +547,9 @@ func (b *BeegoHttpRequest) ToFile(filename string) error {
return err return err
} }
// ToJson returns the map that marshals from the body bytes as json in response . // ToJSON returns the map that marshals from the body bytes as json in response .
// it calls Response inner. // it calls Response inner.
func (b *BeegoHttpRequest) ToJson(v interface{}) error { func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {
data, err := b.Bytes() data, err := b.Bytes()
if err != nil { if err != nil {
return err return err
@ -451,9 +557,9 @@ func (b *BeegoHttpRequest) ToJson(v interface{}) error {
return json.Unmarshal(data, v) return json.Unmarshal(data, v)
} }
// ToXml returns the map that marshals from the body bytes as xml in response . // ToXML returns the map that marshals from the body bytes as xml in response .
// it calls Response inner. // it calls Response inner.
func (b *BeegoHttpRequest) ToXml(v interface{}) error { func (b *BeegoHTTPRequest) ToXML(v interface{}) error {
data, err := b.Bytes() data, err := b.Bytes()
if err != nil { if err != nil {
return err return err
@ -462,7 +568,7 @@ func (b *BeegoHttpRequest) ToXml(v interface{}) error {
} }
// Response executes request client gets response mannually. // Response executes request client gets response mannually.
func (b *BeegoHttpRequest) Response() (*http.Response, error) { func (b *BeegoHTTPRequest) Response() (*http.Response, error) {
return b.getResponse() return b.getResponse()
} }
@ -473,7 +579,7 @@ func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, ad
if err != nil { if err != nil {
return nil, err return nil, err
} }
conn.SetDeadline(time.Now().Add(rwTimeout)) err = conn.SetDeadline(time.Now().Add(rwTimeout))
return conn, nil return conn, err
} }
} }

View File

@ -19,6 +19,7 @@ import (
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
) )
func TestResponse(t *testing.T) { func TestResponse(t *testing.T) {
@ -149,10 +150,11 @@ func TestWithUserAgent(t *testing.T) {
func TestWithSetting(t *testing.T) { func TestWithSetting(t *testing.T) {
v := "beego" v := "beego"
var setting BeegoHttpSettings var setting BeegoHTTPSettings
setting.EnableCookie = true setting.EnableCookie = true
setting.UserAgent = v setting.UserAgent = v
setting.Transport = nil setting.Transport = nil
setting.ReadWriteTimeout = 5 * time.Second
SetDefaultSetting(setting) SetDefaultSetting(setting)
str, err := Get("http://httpbin.org/get").String() str, err := Get("http://httpbin.org/get").String()
@ -176,11 +178,11 @@ func TestToJson(t *testing.T) {
t.Log(resp) t.Log(resp)
// httpbin will return http remote addr // httpbin will return http remote addr
type Ip struct { type IP struct {
Origin string `json:"origin"` Origin string `json:"origin"`
} }
var ip Ip var ip IP
err = req.ToJson(&ip) err = req.ToJSON(&ip)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

54
log.go
View File

@ -32,80 +32,78 @@ const (
LevelDebug LevelDebug
) )
// SetLogLevel sets the global log level used by the simple // BeeLogger references the used application logger.
// logger. var BeeLogger = logs.GetBeeLogger()
// SetLevel sets the global log level used by the simple logger.
func SetLevel(l int) { func SetLevel(l int) {
BeeLogger.SetLevel(l) logs.SetLevel(l)
} }
// SetLogFuncCall set the CallDepth, default is 3
func SetLogFuncCall(b bool) { func SetLogFuncCall(b bool) {
BeeLogger.EnableFuncCallDepth(b) logs.SetLogFuncCall(b)
BeeLogger.SetLogFuncCallDepth(3)
} }
// logger references the used application logger.
var BeeLogger *logs.BeeLogger
// SetLogger sets a new logger. // SetLogger sets a new logger.
func SetLogger(adaptername string, config string) error { func SetLogger(adaptername string, config string) error {
err := BeeLogger.SetLogger(adaptername, config) return logs.SetLogger(adaptername, config)
if err != nil {
return err
}
return nil
} }
// Emergency logs a message at emergency level.
func Emergency(v ...interface{}) { 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{}) { func Alert(v ...interface{}) {
BeeLogger.Alert(generateFmtStr(len(v)), v...) logs.Alert(generateFmtStr(len(v)), v...)
} }
// Critical logs a message at critical level. // Critical logs a message at critical level.
func Critical(v ...interface{}) { func Critical(v ...interface{}) {
BeeLogger.Critical(generateFmtStr(len(v)), v...) logs.Critical(generateFmtStr(len(v)), v...)
} }
// Error logs a message at error level. // Error logs a message at error level.
func Error(v ...interface{}) { func Error(v ...interface{}) {
BeeLogger.Error(generateFmtStr(len(v)), v...) logs.Error(generateFmtStr(len(v)), v...)
} }
// Warning logs a message at warning level. // Warning logs a message at warning level.
func Warning(v ...interface{}) { func Warning(v ...interface{}) {
BeeLogger.Warning(generateFmtStr(len(v)), v...) logs.Warning(generateFmtStr(len(v)), v...)
} }
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0. // Warn compatibility alias for Warning()
func Warn(v ...interface{}) { func Warn(v ...interface{}) {
Warning(v...) logs.Warn(generateFmtStr(len(v)), v...)
} }
// Notice logs a message at notice level.
func Notice(v ...interface{}) { func Notice(v ...interface{}) {
BeeLogger.Notice(generateFmtStr(len(v)), v...) logs.Notice(generateFmtStr(len(v)), v...)
} }
// Info logs a message at info level. // Informational logs a message at info level.
func Informational(v ...interface{}) { func Informational(v ...interface{}) {
BeeLogger.Informational(generateFmtStr(len(v)), v...) logs.Informational(generateFmtStr(len(v)), v...)
} }
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0. // Info compatibility alias for Warning()
func Info(v ...interface{}) { func Info(v ...interface{}) {
Informational(v...) logs.Info(generateFmtStr(len(v)), v...)
} }
// Debug logs a message at debug level. // Debug logs a message at debug level.
func Debug(v ...interface{}) { func Debug(v ...interface{}) {
BeeLogger.Debug(generateFmtStr(len(v)), v...) logs.Debug(generateFmtStr(len(v)), v...)
} }
// Trace logs a message at trace level. // Trace logs a message at trace level.
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0. // compatibility alias for Warning()
func Trace(v ...interface{}) { func Trace(v ...interface{}) {
BeeLogger.Trace(generateFmtStr(len(v)), v...) logs.Trace(generateFmtStr(len(v)), v...)
} }
func generateFmtStr(n int) string { func generateFmtStr(n int) string {

192
logs/alils/alils.go Normal file
View File

@ -0,0 +1,192 @@
package alils
import (
"encoding/json"
"github.com/astaxie/beego/logs"
"github.com/gogo/protobuf/proto"
"strings"
"sync"
"time"
)
const (
CacheSize int = 64
Delimiter string = "##"
)
type AliLSConfig struct {
Project string `json:"project"`
Endpoint string `json:"endpoint"`
KeyID string `json:"key_id"`
KeySecret string `json:"key_secret"`
LogStore string `json:"log_store"`
Topics []string `json:"topics"`
Source string `json:"source"`
Level int `json:"level"`
FlushWhen int `json:"flush_when"`
}
// aliLSWriter implements LoggerInterface.
// it writes messages in keep-live tcp connection.
type aliLSWriter struct {
store *LogStore
group []*LogGroup
withMap bool
groupMap map[string]*LogGroup
lock *sync.Mutex
AliLSConfig
}
// 创建提供Logger接口的日志服务
func NewAliLS() logs.Logger {
alils := new(aliLSWriter)
alils.Level = logs.LevelTrace
return alils
}
// 读取配置
// 初始化必要的数据结构
func (c *aliLSWriter) Init(jsonConfig string) (err error) {
json.Unmarshal([]byte(jsonConfig), c)
if c.FlushWhen > CacheSize {
c.FlushWhen = CacheSize
}
// 初始化Project
prj := &LogProject{
Name: c.Project,
Endpoint: c.Endpoint,
AccessKeyId: c.KeyID,
AccessKeySecret: c.KeySecret,
}
// 获取logstore
c.store, err = prj.GetLogStore(c.LogStore)
if err != nil {
return err
}
// 创建默认Log Group
c.group = append(c.group, &LogGroup{
Topic: proto.String(""),
Source: proto.String(c.Source),
Logs: make([]*Log, 0, c.FlushWhen),
})
// 创建其它Log Group
c.groupMap = make(map[string]*LogGroup)
for _, topic := range c.Topics {
lg := &LogGroup{
Topic: proto.String(topic),
Source: proto.String(c.Source),
Logs: make([]*Log, 0, c.FlushWhen),
}
c.group = append(c.group, lg)
c.groupMap[topic] = lg
}
if len(c.group) == 1 {
c.withMap = false
} else {
c.withMap = true
}
c.lock = &sync.Mutex{}
return nil
}
// WriteMsg write message in connection.
// if connection is down, try to re-connect.
func (c *aliLSWriter) WriteMsg(when time.Time, msg string, level int) (err error) {
if level > c.Level {
return nil
}
var topic string
var content string
var lg *LogGroup
if c.withMap {
// 解析出Topic并匹配LogGroup
strs := strings.SplitN(msg, Delimiter, 2)
if len(strs) == 2 {
pos := strings.LastIndex(strs[0], " ")
topic = strs[0][pos+1 : len(strs[0])]
content = strs[0][0:pos] + strs[1]
lg = c.groupMap[topic]
}
// 默认发到空Topic
if lg == nil {
topic = ""
content = msg
lg = c.group[0]
}
} else {
topic = ""
content = msg
lg = c.group[0]
}
// 生成日志
c1 := &Log_Content{
Key: proto.String("msg"),
Value: proto.String(content),
}
l := &Log{
Time: proto.Uint32(uint32(when.Unix())), // 填写日志时间
Contents: []*Log_Content{
c1,
},
}
c.lock.Lock()
lg.Logs = append(lg.Logs, l)
c.lock.Unlock()
// 满足条件则Flush
if len(lg.Logs) >= c.FlushWhen {
c.flush(lg)
}
return nil
}
// Flush implementing method. empty.
func (c *aliLSWriter) Flush() {
// flush所有group
for _, lg := range c.group {
c.flush(lg)
}
}
// Destroy destroy connection writer and close tcp listener.
func (c *aliLSWriter) Destroy() {
}
func (c *aliLSWriter) flush(lg *LogGroup) {
c.lock.Lock()
defer c.lock.Unlock()
// 把以上的LogGroup推送到SLS服务器
// SLS服务器会根据该logstore的shard个数自动进行负载均衡。
err := c.store.PutLogs(lg)
if err != nil {
return
}
lg.Logs = make([]*Log, 0, c.FlushWhen)
}
func init() {
logs.Register(logs.AdapterAliLS, NewAliLS)
}

13
logs/alils/config.go Executable file
View File

@ -0,0 +1,13 @@
package alils
const (
version = "0.5.0" // SDK version
signatureMethod = "hmac-sha1" // Signature method
// OffsetNewest stands for the log head offset, i.e. the offset that will be
// assigned to the next message that will be produced to the shard.
OffsetNewest = "end"
// OffsetOldest stands for the oldest offset available on the logstore for a
// shard.
OffsetOldest = "begin"
)

984
logs/alils/log.pb.go Executable file
View File

@ -0,0 +1,984 @@
package alils
import "github.com/gogo/protobuf/proto"
import "fmt"
import "math"
// discarding unused import gogoproto "."
import github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
import "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type Log struct {
Time *uint32 `protobuf:"varint,1,req,name=Time" json:"Time,omitempty"`
Contents []*Log_Content `protobuf:"bytes,2,rep,name=Contents" json:"Contents,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Log) Reset() { *m = Log{} }
func (m *Log) String() string { return proto.CompactTextString(m) }
func (*Log) ProtoMessage() {}
func (m *Log) GetTime() uint32 {
if m != nil && m.Time != nil {
return *m.Time
}
return 0
}
func (m *Log) GetContents() []*Log_Content {
if m != nil {
return m.Contents
}
return nil
}
type Log_Content struct {
Key *string `protobuf:"bytes,1,req,name=Key" json:"Key,omitempty"`
Value *string `protobuf:"bytes,2,req,name=Value" json:"Value,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Log_Content) Reset() { *m = Log_Content{} }
func (m *Log_Content) String() string { return proto.CompactTextString(m) }
func (*Log_Content) ProtoMessage() {}
func (m *Log_Content) GetKey() string {
if m != nil && m.Key != nil {
return *m.Key
}
return ""
}
func (m *Log_Content) GetValue() string {
if m != nil && m.Value != nil {
return *m.Value
}
return ""
}
type LogGroup struct {
Logs []*Log `protobuf:"bytes,1,rep,name=Logs" json:"Logs,omitempty"`
Reserved *string `protobuf:"bytes,2,opt,name=Reserved" json:"Reserved,omitempty"`
Topic *string `protobuf:"bytes,3,opt,name=Topic" json:"Topic,omitempty"`
Source *string `protobuf:"bytes,4,opt,name=Source" json:"Source,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *LogGroup) Reset() { *m = LogGroup{} }
func (m *LogGroup) String() string { return proto.CompactTextString(m) }
func (*LogGroup) ProtoMessage() {}
func (m *LogGroup) GetLogs() []*Log {
if m != nil {
return m.Logs
}
return nil
}
func (m *LogGroup) GetReserved() string {
if m != nil && m.Reserved != nil {
return *m.Reserved
}
return ""
}
func (m *LogGroup) GetTopic() string {
if m != nil && m.Topic != nil {
return *m.Topic
}
return ""
}
func (m *LogGroup) GetSource() string {
if m != nil && m.Source != nil {
return *m.Source
}
return ""
}
type LogGroupList struct {
LogGroups []*LogGroup `protobuf:"bytes,1,rep,name=logGroups" json:"logGroups,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *LogGroupList) Reset() { *m = LogGroupList{} }
func (m *LogGroupList) String() string { return proto.CompactTextString(m) }
func (*LogGroupList) ProtoMessage() {}
func (m *LogGroupList) GetLogGroups() []*LogGroup {
if m != nil {
return m.LogGroups
}
return nil
}
func (m *Log) Marshal() (data []byte, err error) {
size := m.Size()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *Log) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.Time == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Time")
} else {
data[i] = 0x8
i++
i = encodeVarintLog(data, i, uint64(*m.Time))
}
if len(m.Contents) > 0 {
for _, msg := range m.Contents {
data[i] = 0x12
i++
i = encodeVarintLog(data, i, uint64(msg.Size()))
n, err := msg.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n
}
}
if m.XXX_unrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized)
}
return i, nil
}
func (m *Log_Content) Marshal() (data []byte, err error) {
size := m.Size()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *Log_Content) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.Key == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Key")
} else {
data[i] = 0xa
i++
i = encodeVarintLog(data, i, uint64(len(*m.Key)))
i += copy(data[i:], *m.Key)
}
if m.Value == nil {
return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("Value")
} else {
data[i] = 0x12
i++
i = encodeVarintLog(data, i, uint64(len(*m.Value)))
i += copy(data[i:], *m.Value)
}
if m.XXX_unrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized)
}
return i, nil
}
func (m *LogGroup) Marshal() (data []byte, err error) {
size := m.Size()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *LogGroup) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Logs) > 0 {
for _, msg := range m.Logs {
data[i] = 0xa
i++
i = encodeVarintLog(data, i, uint64(msg.Size()))
n, err := msg.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n
}
}
if m.Reserved != nil {
data[i] = 0x12
i++
i = encodeVarintLog(data, i, uint64(len(*m.Reserved)))
i += copy(data[i:], *m.Reserved)
}
if m.Topic != nil {
data[i] = 0x1a
i++
i = encodeVarintLog(data, i, uint64(len(*m.Topic)))
i += copy(data[i:], *m.Topic)
}
if m.Source != nil {
data[i] = 0x22
i++
i = encodeVarintLog(data, i, uint64(len(*m.Source)))
i += copy(data[i:], *m.Source)
}
if m.XXX_unrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized)
}
return i, nil
}
func (m *LogGroupList) Marshal() (data []byte, err error) {
size := m.Size()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *LogGroupList) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.LogGroups) > 0 {
for _, msg := range m.LogGroups {
data[i] = 0xa
i++
i = encodeVarintLog(data, i, uint64(msg.Size()))
n, err := msg.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n
}
}
if m.XXX_unrecognized != nil {
i += copy(data[i:], m.XXX_unrecognized)
}
return i, nil
}
func encodeFixed64Log(data []byte, offset int, v uint64) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
data[offset+2] = uint8(v >> 16)
data[offset+3] = uint8(v >> 24)
data[offset+4] = uint8(v >> 32)
data[offset+5] = uint8(v >> 40)
data[offset+6] = uint8(v >> 48)
data[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Log(data []byte, offset int, v uint32) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
data[offset+2] = uint8(v >> 16)
data[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintLog(data []byte, offset int, v uint64) int {
for v >= 1<<7 {
data[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
data[offset] = uint8(v)
return offset + 1
}
func (m *Log) Size() (n int) {
var l int
_ = l
if m.Time != nil {
n += 1 + sovLog(uint64(*m.Time))
}
if len(m.Contents) > 0 {
for _, e := range m.Contents {
l = e.Size()
n += 1 + l + sovLog(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *Log_Content) Size() (n int) {
var l int
_ = l
if m.Key != nil {
l = len(*m.Key)
n += 1 + l + sovLog(uint64(l))
}
if m.Value != nil {
l = len(*m.Value)
n += 1 + l + sovLog(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *LogGroup) Size() (n int) {
var l int
_ = l
if len(m.Logs) > 0 {
for _, e := range m.Logs {
l = e.Size()
n += 1 + l + sovLog(uint64(l))
}
}
if m.Reserved != nil {
l = len(*m.Reserved)
n += 1 + l + sovLog(uint64(l))
}
if m.Topic != nil {
l = len(*m.Topic)
n += 1 + l + sovLog(uint64(l))
}
if m.Source != nil {
l = len(*m.Source)
n += 1 + l + sovLog(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func (m *LogGroupList) Size() (n int) {
var l int
_ = l
if len(m.LogGroups) > 0 {
for _, e := range m.LogGroups {
l = e.Size()
n += 1 + l + sovLog(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
return n
}
func sovLog(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozLog(x uint64) (n int) {
return sovLog(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Log) Unmarshal(data []byte) error {
var hasFields [1]uint64
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Log: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Log: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType)
}
var v uint32
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
v |= (uint32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.Time = &v
hasFields[0] |= uint64(0x00000001)
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Contents", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Contents = append(m.Contents, &Log_Content{})
if err := m.Contents[len(m.Contents)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipLog(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthLog
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if hasFields[0]&uint64(0x00000001) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Time")
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Log_Content) Unmarshal(data []byte) error {
var hasFields [1]uint64
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Content: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Content: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[iNdEx:postIndex])
m.Key = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000001)
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[iNdEx:postIndex])
m.Value = &s
iNdEx = postIndex
hasFields[0] |= uint64(0x00000002)
default:
iNdEx = preIndex
skippy, err := skipLog(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthLog
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if hasFields[0]&uint64(0x00000001) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Key")
}
if hasFields[0]&uint64(0x00000002) == 0 {
return github_com_gogo_protobuf_proto.NewRequiredNotSetError("Value")
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *LogGroup) Unmarshal(data []byte) error {
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: LogGroup: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: LogGroup: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Logs = append(m.Logs, &Log{})
if err := m.Logs[len(m.Logs)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Reserved", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[iNdEx:postIndex])
m.Reserved = &s
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Topic", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[iNdEx:postIndex])
m.Topic = &s
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Source", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := string(data[iNdEx:postIndex])
m.Source = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipLog(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthLog
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *LogGroupList) Unmarshal(data []byte) error {
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: LogGroupList: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: LogGroupList: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field LogGroups", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowLog
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthLog
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.LogGroups = append(m.LogGroups, &LogGroup{})
if err := m.LogGroups[len(m.LogGroups)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipLog(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthLog
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipLog(data []byte) (n int, err error) {
l := len(data)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLog
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLog
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if data[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLog
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthLog
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLog
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipLog(data[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthLog = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowLog = fmt.Errorf("proto: integer overflow")
)

39
logs/alils/log_config.go Executable file
View File

@ -0,0 +1,39 @@
package alils
type InputDetail struct {
LogType string `json:"logType"`
LogPath string `json:"logPath"`
FilePattern string `json:"filePattern"`
LocalStorage bool `json:"localStorage"`
TimeFormat string `json:"timeFormat"`
LogBeginRegex string `json:"logBeginRegex"`
Regex string `json:"regex"`
Keys []string `json:"key"`
FilterKeys []string `json:"filterKey"`
FilterRegex []string `json:"filterRegex"`
TopicFormat string `json:"topicFormat"`
}
type OutputDetail struct {
Endpoint string `json:"endpoint"`
LogStoreName string `json:"logstoreName"`
}
type LogConfig struct {
Name string `json:"configName"`
InputType string `json:"inputType"`
InputDetail InputDetail `json:"inputDetail"`
OutputType string `json:"outputType"`
OutputDetail OutputDetail `json:"outputDetail"`
CreateTime uint32
LastModifyTime uint32
project *LogProject
}
// GetAppliedMachineGroup returns applied machine group of this config.
func (c *LogConfig) GetAppliedMachineGroup(confName string) (groupNames []string, err error) {
groupNames, err = c.project.GetAppliedMachineGroups(c.Name)
return
}

818
logs/alils/log_project.go Executable file
View File

@ -0,0 +1,818 @@
/*
Package sls implements the SDK(v0.5.0) of Simple Log Service(abbr. SLS).
For more description about SLS, please read this article:
http://gitlab.alibaba-inc.com/sls/doc.
*/
package alils
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
)
// Error message in SLS HTTP response.
type errorMessage struct {
Code string `json:"errorCode"`
Message string `json:"errorMessage"`
}
type LogProject struct {
Name string // Project name
Endpoint string // IP or hostname of SLS endpoint
AccessKeyId string
AccessKeySecret string
}
// NewLogProject creates a new SLS project.
func NewLogProject(name, endpoint, accessKeyId, accessKeySecret string) (p *LogProject, err error) {
p = &LogProject{
Name: name,
Endpoint: endpoint,
AccessKeyId: accessKeyId,
AccessKeySecret: accessKeySecret,
}
return p, nil
}
// ListLogStore returns all logstore names of project p.
func (p *LogProject) ListLogStore() (storeNames []string, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/logstores")
r, err := request(p, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to list logstore")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
type Body struct {
Count int
LogStores []string
}
body := &Body{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
storeNames = body.LogStores
return
}
// GetLogStore returns logstore according by logstore name.
func (p *LogProject) GetLogStore(name string) (s *LogStore, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
r, err := request(p, "GET", "/logstores/"+name, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to get logstore")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
s = &LogStore{}
err = json.Unmarshal(buf, s)
if err != nil {
return
}
s.project = p
return
}
// CreateLogStore creates a new logstore in SLS,
// where name is logstore name,
// and ttl is time-to-live(in day) of logs,
// and shardCnt is the number of shards.
func (p *LogProject) CreateLogStore(name string, ttl, shardCnt int) (err error) {
type Body struct {
Name string `json:"logstoreName"`
TTL int `json:"ttl"`
ShardCount int `json:"shardCount"`
}
store := &Body{
Name: name,
TTL: ttl,
ShardCount: shardCnt,
}
body, err := json.Marshal(store)
if err != nil {
return
}
h := map[string]string{
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/json",
"Accept-Encoding": "deflate", // TODO: support lz4
}
r, err := request(p, "POST", "/logstores", h, body)
if err != nil {
return
}
body, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to create logstore")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// DeleteLogStore deletes a logstore according by logstore name.
func (p *LogProject) DeleteLogStore(name string) (err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
r, err := request(p, "DELETE", "/logstores/"+name, h, nil)
if err != nil {
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to delete logstore")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// UpdateLogStore updates a logstore according by logstore name,
// obviously we can't modify the logstore name itself.
func (p *LogProject) UpdateLogStore(name string, ttl, shardCnt int) (err error) {
type Body struct {
Name string `json:"logstoreName"`
TTL int `json:"ttl"`
ShardCount int `json:"shardCount"`
}
store := &Body{
Name: name,
TTL: ttl,
ShardCount: shardCnt,
}
body, err := json.Marshal(store)
if err != nil {
return
}
h := map[string]string{
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/json",
"Accept-Encoding": "deflate", // TODO: support lz4
}
r, err := request(p, "PUT", "/logstores", h, body)
if err != nil {
return
}
body, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to update logstore")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// ListMachineGroup returns machine group name list and the total number of machine groups.
// The offset starts from 0 and the size is the max number of machine groups could be returned.
func (p *LogProject) ListMachineGroup(offset, size int) (m []string, total int, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
if size <= 0 {
size = 500
}
uri := fmt.Sprintf("/machinegroups?offset=%v&size=%v", offset, size)
r, err := request(p, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to list machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
type Body struct {
MachineGroups []string
Count int
Total int
}
body := &Body{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
m = body.MachineGroups
total = body.Total
return
}
// GetMachineGroup retruns machine group according by machine group name.
func (p *LogProject) GetMachineGroup(name string) (m *MachineGroup, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
r, err := request(p, "GET", "/machinegroups/"+name, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to get machine group:%v", name)
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
m = &MachineGroup{}
err = json.Unmarshal(buf, m)
if err != nil {
return
}
m.project = p
return
}
// CreateMachineGroup creates a new machine group in SLS.
func (p *LogProject) CreateMachineGroup(m *MachineGroup) (err error) {
body, err := json.Marshal(m)
if err != nil {
return
}
h := map[string]string{
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/json",
"Accept-Encoding": "deflate", // TODO: support lz4
}
r, err := request(p, "POST", "/machinegroups", h, body)
if err != nil {
return
}
body, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to create machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// UpdateMachineGroup updates a machine group.
func (p *LogProject) UpdateMachineGroup(m *MachineGroup) (err error) {
body, err := json.Marshal(m)
if err != nil {
return
}
h := map[string]string{
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/json",
"Accept-Encoding": "deflate", // TODO: support lz4
}
r, err := request(p, "PUT", "/machinegroups/"+m.Name, h, body)
if err != nil {
return
}
body, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to update machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// DeleteMachineGroup deletes machine group according machine group name.
func (p *LogProject) DeleteMachineGroup(name string) (err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
r, err := request(p, "DELETE", "/machinegroups/"+name, h, nil)
if err != nil {
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to delete machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// ListConfig returns config names list and the total number of configs.
// The offset starts from 0 and the size is the max number of configs could be returned.
func (p *LogProject) ListConfig(offset, size int) (cfgNames []string, total int, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
if size <= 0 {
size = 100
}
uri := fmt.Sprintf("/configs?offset=%v&size=%v", offset, size)
r, err := request(p, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to delete machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
type Body struct {
Total int
Configs []string
}
body := &Body{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
cfgNames = body.Configs
total = body.Total
return
}
// GetConfig returns config according by config name.
func (p *LogProject) GetConfig(name string) (c *LogConfig, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
r, err := request(p, "GET", "/configs/"+name, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to delete config")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
c = &LogConfig{}
err = json.Unmarshal(buf, c)
if err != nil {
return
}
c.project = p
return
}
// UpdateConfig updates a config.
func (p *LogProject) UpdateConfig(c *LogConfig) (err error) {
body, err := json.Marshal(c)
if err != nil {
return
}
h := map[string]string{
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/json",
"Accept-Encoding": "deflate", // TODO: support lz4
}
r, err := request(p, "PUT", "/configs/"+c.Name, h, body)
if err != nil {
return
}
body, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to update config")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// CreateConfig creates a new config in SLS.
func (p *LogProject) CreateConfig(c *LogConfig) (err error) {
body, err := json.Marshal(c)
if err != nil {
return
}
h := map[string]string{
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/json",
"Accept-Encoding": "deflate", // TODO: support lz4
}
r, err := request(p, "POST", "/configs", h, body)
if err != nil {
return
}
body, err = ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to update config")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// DeleteConfig deletes a config according by config name.
func (p *LogProject) DeleteConfig(name string) (err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
r, err := request(p, "DELETE", "/configs/"+name, h, nil)
if err != nil {
return
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(body, errMsg)
if err != nil {
err = fmt.Errorf("failed to delete config")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// GetAppliedMachineGroups returns applied machine group names list according config name.
func (p *LogProject) GetAppliedMachineGroups(confName string) (groupNames []string, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/configs/%v/machinegroups", confName)
r, err := request(p, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to get applied machine groups")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
type Body struct {
Count int
Machinegroups []string
}
body := &Body{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
groupNames = body.Machinegroups
return
}
// GetAppliedConfigs returns applied config names list according machine group name groupName.
func (p *LogProject) GetAppliedConfigs(groupName string) (confNames []string, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/machinegroups/%v/configs", groupName)
r, err := request(p, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to applied configs")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
type Cfg struct {
Count int `json:"count"`
Configs []string `json:"configs"`
}
body := &Cfg{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
confNames = body.Configs
return
}
// ApplyConfigToMachineGroup applies config to machine group.
func (p *LogProject) ApplyConfigToMachineGroup(confName, groupName string) (err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
r, err := request(p, "PUT", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to apply config to machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// RemoveConfigFromMachineGroup removes config from machine group.
func (p *LogProject) RemoveConfigFromMachineGroup(confName, groupName string) (err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/machinegroups/%v/configs/%v", groupName, confName)
r, err := request(p, "DELETE", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to remove config from machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Printf("%s\n", dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}

269
logs/alils/log_store.go Executable file
View File

@ -0,0 +1,269 @@
package alils
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
"strconv"
lz4 "github.com/cloudflare/golz4"
"github.com/gogo/protobuf/proto"
)
type LogStore struct {
Name string `json:"logstoreName"`
TTL int
ShardCount int
CreateTime uint32
LastModifyTime uint32
project *LogProject
}
type Shard struct {
ShardID int `json:"shardID"`
}
// ListShards returns shard id list of this logstore.
func (s *LogStore) ListShards() (shardIDs []int, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/logstores/%v/shards", s.Name)
r, err := request(s.project, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to list logstore")
dump, _ := httputil.DumpResponse(r, true)
fmt.Println(dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
var shards []*Shard
err = json.Unmarshal(buf, &shards)
if err != nil {
return
}
for _, v := range shards {
shardIDs = append(shardIDs, v.ShardID)
}
return
}
// PutLogs put logs into logstore.
// The callers should transform user logs into LogGroup.
func (s *LogStore) PutLogs(lg *LogGroup) (err error) {
body, err := proto.Marshal(lg)
if err != nil {
return
}
// Compresse body with lz4
out := make([]byte, lz4.CompressBound(body))
n, err := lz4.Compress(body, out)
if err != nil {
return
}
h := map[string]string{
"x-sls-compresstype": "lz4",
"x-sls-bodyrawsize": fmt.Sprintf("%v", len(body)),
"Content-Type": "application/x-protobuf",
}
uri := fmt.Sprintf("/logstores/%v", s.Name)
r, err := request(s.project, "POST", uri, h, out[:n])
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to put logs")
dump, _ := httputil.DumpResponse(r, true)
fmt.Println(dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
return
}
// GetCursor gets log cursor of one shard specified by shardId.
// The from can be in three form: a) unix timestamp in seccond, b) "begin", c) "end".
// For more detail please read: http://gitlab.alibaba-inc.com/sls/doc/blob/master/api/shard.md#logstore
func (s *LogStore) GetCursor(shardId int, from string) (cursor string, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=cursor&from=%v",
s.Name, shardId, from)
r, err := request(s.project, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to get cursor")
dump, _ := httputil.DumpResponse(r, true)
fmt.Println(dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
type Body struct {
Cursor string
}
body := &Body{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
cursor = body.Cursor
return
}
// GetLogsBytes gets logs binary data from shard specified by shardId according cursor.
// The logGroupMaxCount is the max number of logGroup could be returned.
// The nextCursor is the next curosr can be used to read logs at next time.
func (s *LogStore) GetLogsBytes(shardId int, cursor string,
logGroupMaxCount int) (out []byte, nextCursor string, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
"Accept": "application/x-protobuf",
"Accept-Encoding": "lz4",
}
uri := fmt.Sprintf("/logstores/%v/shards/%v?type=logs&cursor=%v&count=%v",
s.Name, shardId, cursor, logGroupMaxCount)
r, err := request(s.project, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to get cursor")
dump, _ := httputil.DumpResponse(r, true)
fmt.Println(dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
v, ok := r.Header["X-Sls-Compresstype"]
if !ok || len(v) == 0 {
err = fmt.Errorf("can't find 'x-sls-compresstype' header")
return
}
if v[0] != "lz4" {
err = fmt.Errorf("unexpected compress type:%v", v[0])
return
}
v, ok = r.Header["X-Sls-Cursor"]
if !ok || len(v) == 0 {
err = fmt.Errorf("can't find 'x-sls-cursor' header")
return
}
nextCursor = v[0]
v, ok = r.Header["X-Sls-Bodyrawsize"]
if !ok || len(v) == 0 {
err = fmt.Errorf("can't find 'x-sls-bodyrawsize' header")
return
}
bodyRawSize, err := strconv.Atoi(v[0])
if err != nil {
return
}
out = make([]byte, bodyRawSize)
err = lz4.Uncompress(buf, out)
if err != nil {
return
}
return
}
// LogsBytesDecode decodes logs binary data retruned by GetLogsBytes API
func LogsBytesDecode(data []byte) (gl *LogGroupList, err error) {
gl = &LogGroupList{}
err = proto.Unmarshal(data, gl)
if err != nil {
return
}
return
}
// GetLogs gets logs from shard specified by shardId according cursor.
// The logGroupMaxCount is the max number of logGroup could be returned.
// The nextCursor is the next curosr can be used to read logs at next time.
func (s *LogStore) GetLogs(shardId int, cursor string,
logGroupMaxCount int) (gl *LogGroupList, nextCursor string, err error) {
out, nextCursor, err := s.GetLogsBytes(shardId, cursor, logGroupMaxCount)
if err != nil {
return
}
gl, err = LogsBytesDecode(out)
if err != nil {
return
}
return
}

87
logs/alils/machine_group.go Executable file
View File

@ -0,0 +1,87 @@
package alils
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
)
type MachinGroupAttribute struct {
ExternalName string `json:"externalName"`
TopicName string `json:"groupTopic"`
}
type MachineGroup struct {
Name string `json:"groupName"`
Type string `json:"groupType"`
MachineIdType string `json:"machineIdentifyType"`
MachineIdList []string `json:"machineList"`
Attribute MachinGroupAttribute `json:"groupAttribute"`
CreateTime uint32
LastModifyTime uint32
project *LogProject
}
type Machine struct {
IP string
UniqueId string `json:"machine-uniqueid"`
UserdefinedId string `json:"userdefined-id"`
}
type MachineList struct {
Total int
Machines []*Machine
}
// ListMachines returns machine list of this machine group.
func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) {
h := map[string]string{
"x-sls-bodyrawsize": "0",
}
uri := fmt.Sprintf("/machinegroups/%v/machines", m.Name)
r, err := request(m.project, "GET", uri, h, nil)
if err != nil {
return
}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}
if r.StatusCode != http.StatusOK {
errMsg := &errorMessage{}
err = json.Unmarshal(buf, errMsg)
if err != nil {
err = fmt.Errorf("failed to remove config from machine group")
dump, _ := httputil.DumpResponse(r, true)
fmt.Println(dump)
return
}
err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message)
return
}
body := &MachineList{}
err = json.Unmarshal(buf, body)
if err != nil {
return
}
ms = body.Machines
total = body.Total
return
}
// GetAppliedConfigs returns applied configs of this machine group.
func (m *MachineGroup) GetAppliedConfigs() (confNames []string, err error) {
confNames, err = m.project.GetAppliedConfigs(m.Name)
return
}

62
logs/alils/request.go Executable file
View File

@ -0,0 +1,62 @@
package alils
import (
"bytes"
"crypto/md5"
"fmt"
"net/http"
)
// request sends a request to SLS.
func request(project *LogProject, method, uri string, headers map[string]string,
body []byte) (resp *http.Response, err error) {
// The caller should provide 'x-sls-bodyrawsize' header
if _, ok := headers["x-sls-bodyrawsize"]; !ok {
err = fmt.Errorf("Can't find 'x-sls-bodyrawsize' header")
return
}
// SLS public request headers
headers["Host"] = project.Name + "." + project.Endpoint
headers["Date"] = nowRFC1123()
headers["x-sls-apiversion"] = version
headers["x-sls-signaturemethod"] = signatureMethod
if body != nil {
bodyMD5 := fmt.Sprintf("%X", md5.Sum(body))
headers["Content-MD5"] = bodyMD5
if _, ok := headers["Content-Type"]; !ok {
err = fmt.Errorf("Can't find 'Content-Type' header")
return
}
}
// Calc Authorization
// Authorization = "SLS <AccessKeyId>:<Signature>"
digest, err := signature(project, method, uri, headers)
if err != nil {
return
}
auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyId, digest)
headers["Authorization"] = auth
// Initialize http request
reader := bytes.NewReader(body)
urlStr := fmt.Sprintf("http://%v.%v%v", project.Name, project.Endpoint, uri)
req, err := http.NewRequest(method, urlStr, reader)
if err != nil {
return
}
for k, v := range headers {
req.Header.Add(k, v)
}
// Get ready to do request
resp, err = http.DefaultClient.Do(req)
if err != nil {
return
}
return
}

112
logs/alils/signature.go Executable file
View File

@ -0,0 +1,112 @@
package alils
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/url"
"sort"
"strings"
"time"
)
// GMT location
var gmtLoc = time.FixedZone("GMT", 0)
// NowRFC1123 returns now time in RFC1123 format with GMT timezone,
// eg. "Mon, 02 Jan 2006 15:04:05 GMT".
func nowRFC1123() string {
return time.Now().In(gmtLoc).Format(time.RFC1123)
}
// signature calculates a request's signature digest.
func signature(project *LogProject, method, uri string,
headers map[string]string) (digest string, err error) {
var contentMD5, contentType, date, canoHeaders, canoResource string
var slsHeaderKeys sort.StringSlice
// SignString = VERB + "\n"
// + CONTENT-MD5 + "\n"
// + CONTENT-TYPE + "\n"
// + DATE + "\n"
// + CanonicalizedSLSHeaders + "\n"
// + CanonicalizedResource
if val, ok := headers["Content-MD5"]; ok {
contentMD5 = val
}
if val, ok := headers["Content-Type"]; ok {
contentType = val
}
date, ok := headers["Date"]
if !ok {
err = fmt.Errorf("Can't find 'Date' header")
return
}
// Calc CanonicalizedSLSHeaders
slsHeaders := make(map[string]string, len(headers))
for k, v := range headers {
l := strings.TrimSpace(strings.ToLower(k))
if strings.HasPrefix(l, "x-sls-") {
slsHeaders[l] = strings.TrimSpace(v)
slsHeaderKeys = append(slsHeaderKeys, l)
}
}
sort.Sort(slsHeaderKeys)
for i, k := range slsHeaderKeys {
canoHeaders += k + ":" + slsHeaders[k]
if i+1 < len(slsHeaderKeys) {
canoHeaders += "\n"
}
}
// Calc CanonicalizedResource
u, err := url.Parse(uri)
if err != nil {
return
}
canoResource += url.QueryEscape(u.Path)
if u.RawQuery != "" {
var keys sort.StringSlice
vals := u.Query()
for k, _ := range vals {
keys = append(keys, k)
}
sort.Sort(keys)
canoResource += "?"
for i, k := range keys {
if i > 0 {
canoResource += "&"
}
for _, v := range vals[k] {
canoResource += k + "=" + v
}
}
}
signStr := method + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
date + "\n" +
canoHeaders + "\n" +
canoResource
// Signature = base64(hmac-sha1(UTF8-Encoding-Of(SignString)AccessKeySecret))
mac := hmac.New(sha1.New, []byte(project.AccessKeySecret))
_, err = mac.Write([]byte(signStr))
if err != nil {
return
}
digest = base64.StdEncoding.EncodeToString(mac.Sum(nil))
return
}

28
logs/color.go Normal file
View File

@ -0,0 +1,28 @@
// 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 "io"
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 ( import (
"encoding/json" "encoding/json"
"io" "io"
"log"
"net" "net"
"time"
) )
// ConnWriter implements LoggerInterface. // connWriter implements LoggerInterface.
// it writes messages in keep-live tcp connection. // it writes messages in keep-live tcp connection.
type ConnWriter struct { type connWriter struct {
lg *log.Logger lg *logWriter
innerWriter io.WriteCloser innerWriter io.WriteCloser
ReconnectOnMsg bool `json:"reconnectOnMsg"` ReconnectOnMsg bool `json:"reconnectOnMsg"`
Reconnect bool `json:"reconnect"` Reconnect bool `json:"reconnect"`
@ -33,26 +33,26 @@ type ConnWriter struct {
Level int `json:"level"` Level int `json:"level"`
} }
// create new ConnWrite returning as LoggerInterface. // NewConn create new ConnWrite returning as LoggerInterface.
func NewConn() LoggerInterface { func NewConn() Logger {
conn := new(ConnWriter) conn := new(connWriter)
conn.Level = LevelTrace conn.Level = LevelTrace
return conn return conn
} }
// init connection writer with json config. // Init init connection writer with json config.
// json config only need key "level". // json config only need key "level".
func (c *ConnWriter) Init(jsonconfig string) error { func (c *connWriter) Init(jsonConfig string) error {
return json.Unmarshal([]byte(jsonconfig), c) return json.Unmarshal([]byte(jsonConfig), c)
} }
// write message in connection. // WriteMsg write message in connection.
// if connection is down, try to re-connect. // 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 { if level > c.Level {
return nil return nil
} }
if c.neddedConnectOnMsg() { if c.needToConnectOnMsg() {
err := c.connect() err := c.connect()
if err != nil { if err != nil {
return err return err
@ -62,23 +62,24 @@ func (c *ConnWriter) WriteMsg(msg string, level int) error {
if c.ReconnectOnMsg { if c.ReconnectOnMsg {
defer c.innerWriter.Close() defer c.innerWriter.Close()
} }
c.lg.Println(msg)
c.lg.println(when, msg)
return nil return nil
} }
// implementing method. empty. // Flush implementing method. empty.
func (c *ConnWriter) Flush() { func (c *connWriter) Flush() {
} }
// destroy connection writer and close tcp listener. // Destroy destroy connection writer and close tcp listener.
func (c *ConnWriter) Destroy() { func (c *connWriter) Destroy() {
if c.innerWriter != nil { if c.innerWriter != nil {
c.innerWriter.Close() c.innerWriter.Close()
} }
} }
func (c *ConnWriter) connect() error { func (c *connWriter) connect() error {
if c.innerWriter != nil { if c.innerWriter != nil {
c.innerWriter.Close() c.innerWriter.Close()
c.innerWriter = nil c.innerWriter = nil
@ -94,11 +95,11 @@ func (c *ConnWriter) connect() error {
} }
c.innerWriter = conn c.innerWriter = conn
c.lg = log.New(conn, "", log.Ldate|log.Ltime) c.lg = newLogWriter(conn)
return nil return nil
} }
func (c *ConnWriter) neddedConnectOnMsg() bool { func (c *connWriter) needToConnectOnMsg() bool {
if c.Reconnect { if c.Reconnect {
c.Reconnect = false c.Reconnect = false
return true return true
@ -112,5 +113,5 @@ func (c *ConnWriter) neddedConnectOnMsg() bool {
} }
func init() { func init() {
Register("conn", NewConn) Register(AdapterConn, NewConn)
} }

View File

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

View File

@ -43,11 +43,9 @@ func TestConsole(t *testing.T) {
testConsoleCalls(log2) testConsoleCalls(log2)
} }
func BenchmarkConsole(b *testing.B) { // Test console without color
log := NewLogger(10000) func TestConsoleNoColor(t *testing.T) {
log.EnableFuncCallDepth(true) log := NewLogger(100)
log.SetLogger("console", "") log.SetLogger("console", `{"color":false}`)
for i := 0; i < b.N; i++ { testConsoleCalls(log)
log.Debug("debug")
}
} }

80
logs/es/es.go Normal file
View File

@ -0,0 +1,80 @@
package es
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"time"
"github.com/astaxie/beego/logs"
"github.com/belogik/goes"
)
// NewES return a LoggerInterface
func NewES() logs.Logger {
cw := &esLogger{
Level: logs.LevelDebug,
}
return cw
}
type esLogger struct {
*goes.Connection
DSN string `json:"dsn"`
Level int `json:"level"`
}
// {"dsn":"http://localhost:9200/","level":1}
func (el *esLogger) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), el)
if err != nil {
return err
}
if el.DSN == "" {
return errors.New("empty dsn")
} else if u, err := url.Parse(el.DSN); err != nil {
return err
} else if u.Path == "" {
return errors.New("missing prefix")
} else if host, port, err := net.SplitHostPort(u.Host); err != nil {
return err
} else {
conn := goes.NewConnection(host, port)
el.Connection = conn
}
return nil
}
// WriteMsg will write the msg and level into es
func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
if level > el.Level {
return nil
}
vals := make(map[string]interface{})
vals["@timestamp"] = when.Format(time.RFC3339)
vals["@msg"] = msg
d := goes.Document{
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
Type: "logs",
Fields: vals,
}
_, err := el.Index(d, nil)
return err
}
// Destroy is a empty method
func (el *esLogger) Destroy() {
}
// Flush is a empty method
func (el *esLogger) Flush() {
}
func init() {
logs.Register(logs.AdapterEs, NewES)
}

View File

@ -20,164 +20,192 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
) )
// FileLogWriter implements LoggerInterface. // fileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency. // It writes messages by lines limit, file size limit, or time frequency.
type FileLogWriter struct { type fileLogWriter struct {
*log.Logger sync.RWMutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize
mw *MuxWriter
// The opened file // The opened file
Filename string `json:"filename"` Filename string `json:"filename"`
fileWriter *os.File
Maxlines int `json:"maxlines"` // Rotate at line
maxlines_curlines int MaxLines int `json:"maxlines"`
maxLinesCurLines int
// Rotate at size // Rotate at size
Maxsize int `json:"maxsize"` MaxSize int `json:"maxsize"`
maxsize_cursize int maxSizeCurSize int
// Rotate daily // Rotate daily
Daily bool `json:"daily"` Daily bool `json:"daily"`
Maxdays int64 `json:"maxdays"` MaxDays int64 `json:"maxdays"`
daily_opendate int dailyOpenDate int
dailyOpenTime time.Time
Rotate bool `json:"rotate"` Rotate bool `json:"rotate"`
startLock sync.Mutex // Only one log can write to the file
Level int `json:"level"` Level int `json:"level"`
Perm string `json:"perm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
} }
// an *os.File writer with locker. // newFileWriter create a FileLogWriter returning as LoggerInterface.
type MuxWriter struct { func newFileWriter() Logger {
sync.Mutex w := &fileLogWriter{
fd *os.File Daily: true,
} MaxDays: 7,
Rotate: true,
// write to os.File. Level: LevelTrace,
func (l *MuxWriter) Write(b []byte) (int, error) { Perm: "0660",
l.Lock()
defer l.Unlock()
return l.fd.Write(b)
}
// set os.File in writer.
func (l *MuxWriter) SetFd(fd *os.File) {
if l.fd != nil {
l.fd.Close()
} }
l.fd = fd
}
// create a FileLogWriter returning as LoggerInterface.
func NewFileWriter() LoggerInterface {
w := &FileLogWriter{
Filename: "",
Maxlines: 1000000,
Maxsize: 1 << 28, //256 MB
Daily: true,
Maxdays: 7,
Rotate: true,
Level: LevelTrace,
}
// use MuxWriter instead direct use os.File for lock write when rotate
w.mw = new(MuxWriter)
// set MuxWriter as Logger's io.Writer
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime)
return w return w
} }
// Init file logger with json config. // Init file logger with json config.
// jsonconfig like: // jsonConfig like:
// { // {
// "filename":"logs/beego.log", // "filename":"logs/beego.log",
// "maxlines":10000, // "maxLines":10000,
// "maxsize":1<<30, // "maxsize":1024,
// "daily":true, // "daily":true,
// "maxdays":15, // "maxDays":15,
// "rotate":true // "rotate":true,
// "perm":"0600"
// } // }
func (w *FileLogWriter) Init(jsonconfig string) error { func (w *fileLogWriter) Init(jsonConfig string) error {
err := json.Unmarshal([]byte(jsonconfig), w) err := json.Unmarshal([]byte(jsonConfig), w)
if err != nil { if err != nil {
return err return err
} }
if len(w.Filename) == 0 { if len(w.Filename) == 0 {
return errors.New("jsonconfig must have filename") 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() err = w.startLogger()
return err return err
} }
// start file logger. create log file and set to locker-inside file writer. // start file logger. create log file and set to locker-inside file writer.
func (w *FileLogWriter) startLogger() error { func (w *fileLogWriter) startLogger() error {
fd, err := w.createLogFile() file, err := w.createLogFile()
if err != nil { if err != nil {
return err return err
} }
w.mw.SetFd(fd) if w.fileWriter != nil {
w.fileWriter.Close()
}
w.fileWriter = file
return w.initFd() return w.initFd()
} }
func (w *FileLogWriter) docheck(size int) { func (w *fileLogWriter) needRotate(size int, day int) bool {
w.startLock.Lock() return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) ||
defer w.startLock.Unlock() (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) ||
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) || (w.Daily && day != w.dailyOpenDate)
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
(w.Daily && time.Now().Day() != w.daily_opendate)) {
if err := w.DoRotate(); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
return
}
}
w.maxlines_curlines++
w.maxsize_cursize += size
} }
// write logger message into file. // 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 { if level > w.Level {
return nil return nil
} }
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] " h, d := formatTimeHeader(when)
w.docheck(n) msg = string(h) + msg + "\n"
w.Logger.Println(msg) if w.Rotate {
return nil w.RLock()
if w.needRotate(len(msg), d) {
w.RUnlock()
w.Lock()
if w.needRotate(len(msg), d) {
if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
}
}
w.Unlock()
} else {
w.RUnlock()
}
}
w.Lock()
_, err := w.fileWriter.Write([]byte(msg))
if err == nil {
w.maxLinesCurLines++
w.maxSizeCurSize += len(msg)
}
w.Unlock()
return err
} }
func (w *FileLogWriter) createLogFile() (*os.File, error) { func (w *fileLogWriter) createLogFile() (*os.File, error) {
// Open the log file // Open the log file
fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) 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 return fd, err
} }
func (w *FileLogWriter) initFd() error { func (w *fileLogWriter) initFd() error {
fd := w.mw.fd fd := w.fileWriter
finfo, err := fd.Stat() fInfo, err := fd.Stat()
if err != nil { if err != nil {
return fmt.Errorf("get stat err: %s\n", err) return fmt.Errorf("get stat err: %s\n", err)
} }
w.maxsize_cursize = int(finfo.Size()) w.maxSizeCurSize = int(fInfo.Size())
w.daily_opendate = time.Now().Day() w.dailyOpenTime = time.Now()
w.maxlines_curlines = 0 w.dailyOpenDate = w.dailyOpenTime.Day()
if finfo.Size() > 0 { w.maxLinesCurLines = 0
if w.Daily {
go w.dailyRotate(w.dailyOpenTime)
}
if fInfo.Size() > 0 {
count, err := w.lines() count, err := w.lines()
if err != nil { if err != nil {
return err return err
} }
w.maxlines_curlines = count w.maxLinesCurLines = count
} }
return nil return nil
} }
func (w *FileLogWriter) lines() (int, error) { 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) fd, err := os.Open(w.Filename)
if err != nil { if err != nil {
return 0, err return 0, err
@ -205,60 +233,76 @@ func (w *FileLogWriter) lines() (int, error) {
} }
// DoRotate means it need to write file in new file. // DoRotate means it need to write file in new file.
// new file name like xx.log.2013-01-01.2 // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
func (w *FileLogWriter) DoRotate() error { func (w *fileLogWriter) doRotate(logTime time.Time) error {
// file exists
// Find the next available number
num := 1
fName := ""
_, err := os.Lstat(w.Filename) _, err := os.Lstat(w.Filename)
if err == nil { // file exists if err != nil {
// Find the next available number //even if the file is not exist or other ,we should RESTART the logger
num := 1 goto RESTART_LOGGER
fname := ""
for ; err == nil && num <= 999; num++ {
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num)
_, err = os.Lstat(fname)
}
// return error if the last file checked still existed
if err == nil {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
}
// block Logger's io.Writer
w.mw.Lock()
defer w.mw.Unlock()
fd := w.mw.fd
fd.Close()
// close fd before rename
// Rename the file to its newfound home
err = os.Rename(w.Filename, fname)
if err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
// re-start logger
err = w.startLogger()
if err != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", err)
}
go w.deleteOldLog()
} }
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 {
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename)
}
// close fileWriter before rename
w.fileWriter.Close()
// Rename the file to its new found name
// even if occurs error,we MUST guarantee to restart new logger
err = os.Rename(w.Filename, fName)
err = os.Chmod(fName, os.FileMode(440))
// re-start logger
RESTART_LOGGER:
startLoggerErr := w.startLogger()
go w.deleteOldLog()
if startLoggerErr != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr)
}
if err != nil {
return fmt.Errorf("Rotate: %s\n", err)
}
return nil return nil
} }
func (w *FileLogWriter) deleteOldLog() { func (w *fileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename) dir := filepath.Dir(w.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r)
fmt.Println(returnErr)
} }
}() }()
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) { if info == nil {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { 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) os.Remove(path)
} }
} }
@ -266,18 +310,18 @@ func (w *FileLogWriter) deleteOldLog() {
}) })
} }
// destroy file logger, close file writer. // Destroy close the file description, close file writer.
func (w *FileLogWriter) Destroy() { func (w *fileLogWriter) Destroy() {
w.mw.fd.Close() w.fileWriter.Close()
} }
// flush file logger. // Flush flush file logger.
// there are no buffering messages in file logger in memory. // there are no buffering messages in file logger in memory.
// flush file means sync file from disk. // flush file means sync file from disk.
func (w *FileLogWriter) Flush() { func (w *fileLogWriter) Flush() {
w.mw.fd.Sync() w.fileWriter.Sync()
} }
func init() { func init() {
Register("file", NewFileWriter) Register(AdapterFile, newFileWriter)
} }

View File

@ -17,13 +17,36 @@ package logs
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strconv" "strconv"
"testing" "testing"
"time" "time"
) )
func TestFile(t *testing.T) { 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 := NewLogger(10000)
log.SetLogger("file", `{"filename":"test.log"}`) log.SetLogger("file", `{"filename":"test.log"}`)
log.Debug("debug") log.Debug("debug")
@ -34,25 +57,24 @@ func TestFile(t *testing.T) {
log.Alert("alert") log.Alert("alert")
log.Critical("critical") log.Critical("critical")
log.Emergency("emergency") log.Emergency("emergency")
time.Sleep(time.Second * 4)
f, err := os.Open("test.log") f, err := os.Open("test.log")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
b := bufio.NewReader(f) b := bufio.NewReader(f)
linenum := 0 lineNum := 0
for { for {
line, _, err := b.ReadLine() line, _, err := b.ReadLine()
if err != nil { if err != nil {
break break
} }
if len(line) > 0 { if len(line) > 0 {
linenum++ lineNum++
} }
} }
var expected = LevelDebug + 1 var expected = LevelDebug + 1
if linenum != expected { if lineNum != expected {
t.Fatal(linenum, "not "+strconv.Itoa(expected)+" lines") t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
} }
os.Remove("test.log") os.Remove("test.log")
} }
@ -68,30 +90,29 @@ func TestFile2(t *testing.T) {
log.Alert("alert") log.Alert("alert")
log.Critical("critical") log.Critical("critical")
log.Emergency("emergency") log.Emergency("emergency")
time.Sleep(time.Second * 4)
f, err := os.Open("test2.log") f, err := os.Open("test2.log")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
b := bufio.NewReader(f) b := bufio.NewReader(f)
linenum := 0 lineNum := 0
for { for {
line, _, err := b.ReadLine() line, _, err := b.ReadLine()
if err != nil { if err != nil {
break break
} }
if len(line) > 0 { if len(line) > 0 {
linenum++ lineNum++
} }
} }
var expected = LevelError + 1 var expected = LevelError + 1
if linenum != expected { if lineNum != expected {
t.Fatal(linenum, "not "+strconv.Itoa(expected)+" lines") t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines")
} }
os.Remove("test2.log") os.Remove("test2.log")
} }
func TestFileRotate(t *testing.T) { func TestFileRotate_01(t *testing.T) {
log := NewLogger(10000) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`) log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
log.Debug("debug") log.Debug("debug")
@ -102,16 +123,100 @@ func TestFileRotate(t *testing.T) {
log.Alert("alert") log.Alert("alert")
log.Critical("critical") log.Critical("critical")
log.Emergency("emergency") log.Emergency("emergency")
time.Sleep(time.Second * 4) rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log"
rotatename := "test3.log" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) b, err := exists(rotateName)
b, err := exists(rotatename)
if !b || err != nil { if !b || err != nil {
os.Remove("test3.log")
t.Fatal("rotate not generated") t.Fatal("rotate not generated")
} }
os.Remove(rotatename) os.Remove(rotateName)
os.Remove("test3.log") 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) { func exists(path string) (bool, error) {
_, err := os.Stat(path) _, err := os.Stat(path)
if err == nil { if err == nil {
@ -131,3 +236,45 @@ func BenchmarkFile(b *testing.B) {
} }
os.Remove("test4.log") os.Remove("test4.log")
} }
func BenchmarkFileAsynchronous(b *testing.B) {
log := NewLogger(100000)
log.SetLogger("file", `{"filename":"test4.log"}`)
log.Async()
for i := 0; i < b.N; i++ {
log.Debug("debug")
}
os.Remove("test4.log")
}
func BenchmarkFileCallDepth(b *testing.B) {
log := NewLogger(100000)
log.SetLogger("file", `{"filename":"test4.log"}`)
log.EnableFuncCallDepth(true)
log.SetLogFuncCallDepth(2)
for i := 0; i < b.N; i++ {
log.Debug("debug")
}
os.Remove("test4.log")
}
func BenchmarkFileAsynchronousCallDepth(b *testing.B) {
log := NewLogger(100000)
log.SetLogger("file", `{"filename":"test4.log"}`)
log.EnableFuncCallDepth(true)
log.SetLogFuncCallDepth(2)
log.Async()
for i := 0; i < b.N; i++ {
log.Debug("debug")
}
os.Remove("test4.log")
}
func BenchmarkFileOnGoroutine(b *testing.B) {
log := NewLogger(100000)
log.SetLogger("file", `{"filename":"test4.log"}`)
for i := 0; i < b.N; i++ {
go log.Debug("debug")
}
os.Remove("test4.log")
}

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

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package logs provide a general log interface
// Usage: // Usage:
// //
// import "github.com/astaxie/beego/logs" // import "github.com/astaxie/beego/logs"
@ -34,9 +35,14 @@ package logs
import ( import (
"fmt" "fmt"
"log"
"os"
"path" "path"
"runtime" "runtime"
"strconv"
"strings"
"sync" "sync"
"time"
) )
// RFC5424 log message levels. // RFC5424 log message levels.
@ -51,31 +57,47 @@ const (
LevelDebug LevelDebug
) )
// Legacy loglevel constants to ensure backwards compatibility. // levelLogLogger is defined to implement log.Logger
// // the real log level will be LevelEmergency
// Deprecated: will be removed in 1.5.0. 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"
AdapterAliLS = "alils"
)
// Legacy log level constants to ensure backwards compatibility.
const ( const (
LevelInfo = LevelInformational LevelInfo = LevelInformational
LevelTrace = LevelDebug LevelTrace = LevelDebug
LevelWarn = LevelWarning LevelWarn = LevelWarning
) )
type loggerType func() LoggerInterface type newLoggerFunc func() Logger
// LoggerInterface defines the behavior of a log provider. // Logger defines the behavior of a log provider.
type LoggerInterface interface { type Logger interface {
Init(config string) error Init(config string) error
WriteMsg(msg string, level int) error WriteMsg(when time.Time, msg string, level int) error
Destroy() Destroy()
Flush() 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. // Register makes a log provide available by the provided name.
// If Register is called twice with the same name or if driver is nil, // If Register is called twice with the same name or if driver is nil,
// it panics. // it panics.
func Register(name string, log loggerType) { func Register(name string, log newLoggerFunc) {
if log == nil { if log == nil {
panic("logs: Register provide is nil") panic("logs: Register provide is nil")
} }
@ -90,101 +112,210 @@ func Register(name string, log loggerType) {
type BeeLogger struct { type BeeLogger struct {
lock sync.Mutex lock sync.Mutex
level int level int
init bool
enableFuncCallDepth bool enableFuncCallDepth bool
loggerFuncCallDepth int loggerFuncCallDepth int
msg chan *logMsg asynchronous bool
outputs map[string]LoggerInterface msgChanLen int64
msgChan chan *logMsg
signalChan chan string
wg sync.WaitGroup
outputs []*nameLogger
}
const defaultAsyncMsgLen = 1e3
type nameLogger struct {
Logger
name string
} }
type logMsg struct { type logMsg struct {
level int level int
msg string msg string
when time.Time
} }
var logMsgPool *sync.Pool
// NewLogger returns a new BeeLogger. // NewLogger returns a new BeeLogger.
// channellen means the number of messages in chan. // 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. // 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 := new(BeeLogger)
bl.level = LevelDebug bl.level = LevelDebug
bl.loggerFuncCallDepth = 2 bl.loggerFuncCallDepth = 2
bl.msg = make(chan *logMsg, channellen) bl.msgChanLen = append(channelLens, 0)[0]
bl.outputs = make(map[string]LoggerInterface) if bl.msgChanLen <= 0 {
//bl.SetLogger("console", "") // default output to console 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(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() go bl.startLogger()
return bl return bl
} }
// SetLogger provides a given logger adapter into BeeLogger with config string. // SetLogger provides a given logger adapter into BeeLogger with config string.
// config need to be correct JSON as string: {"interval":360}. // config need to be correct JSON as string: {"interval":360}.
func (bl *BeeLogger) SetLogger(adaptername string, config string) error { func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
bl.lock.Lock() config := append(configs, "{}")[0]
defer bl.lock.Unlock() for _, l := range bl.outputs {
if log, ok := adapters[adaptername]; ok { if l.name == adapterName {
lg := log() return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
err := lg.Init(config)
bl.outputs[adaptername] = lg
if err != nil {
fmt.Println("logs.BeeLogger.SetLogger: " + err.Error())
return err
} }
} else {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
} }
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 return nil
} }
// remove a logger adapter in BeeLogger. // SetLogger provides a given logger adapter into BeeLogger with config string.
func (bl *BeeLogger) DelLogger(adaptername string) error { // config need to be correct JSON as string: {"interval":360}.
func (bl *BeeLogger) SetLogger(adapterName string, configs ...string) error {
bl.lock.Lock() bl.lock.Lock()
defer bl.lock.Unlock() defer bl.lock.Unlock()
if lg, ok := bl.outputs[adaptername]; ok { if !bl.init {
lg.Destroy() bl.outputs = []*nameLogger{}
delete(bl.outputs, adaptername) bl.init = true
return nil }
} else { return bl.setLogger(adapterName, configs...)
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername) }
// DelLogger remove a logger adapter in BeeLogger.
func (bl *BeeLogger) DelLogger(adapterName string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
outputs := []*nameLogger{}
for _, lg := range bl.outputs {
if lg.name == adapterName {
lg.Destroy()
} else {
outputs = append(outputs, lg)
}
}
if len(outputs) == len(bl.outputs) {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
}
bl.outputs = outputs
return nil
}
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
for _, l := range bl.outputs {
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) writerMsg(loglevel int, msg string) error { func (bl *BeeLogger) Write(p []byte) (n int, err error) {
if loglevel > bl.level { if len(p) == 0 {
return nil return 0, nil
} }
lm := new(logMsg) // writeMsg will always add a '\n' character
lm.level = loglevel 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 { if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if _, filename := path.Split(file); filename == "log.go" && (line == 97 || line == 83) { if !ok {
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth + 1) file = "???"
line = 0
} }
if ok { _, filename := path.Split(file)
_, filename := path.Split(file) msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg) }
} else {
lm.msg = msg //set level info in front of filename info
} if logLevel == levelLoggerImpl {
} else { // set to emergency to ensure all log will be print out correctly
lm.msg = msg 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(when, msg, logLevel)
} }
bl.msg <- lm
return nil return nil
} }
// Set log message level. // SetLevel Set log message level.
//
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), // If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
// log providers will not even be sent the message. // log providers will not even be sent the message.
func (bl *BeeLogger) SetLevel(l int) { func (bl *BeeLogger) SetLevel(l int) {
bl.level = l bl.level = l
} }
// set log funcCallDepth // SetLogFuncCallDepth set log funcCallDepth
func (bl *BeeLogger) SetLogFuncCallDepth(d int) { func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
bl.loggerFuncCallDepth = d bl.loggerFuncCallDepth = d
} }
// enable log funcCallDepth // GetLogFuncCallDepth return log funcCallDepth for wrapper
func (bl *BeeLogger) GetLogFuncCallDepth() int {
return bl.loggerFuncCallDepth
}
// EnableFuncCallDepth enable log funcCallDepth
func (bl *BeeLogger) EnableFuncCallDepth(b bool) { func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
bl.enableFuncCallDepth = b bl.enableFuncCallDepth = b
} }
@ -192,112 +323,327 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
// start logger chan reading. // start logger chan reading.
// when chan is not empty, write logs. // when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() { func (bl *BeeLogger) startLogger() {
gameOver := false
for { for {
select { select {
case bm := <-bl.msg: case bm := <-bl.msgChan:
for _, l := range bl.outputs { bl.writeToLoggers(bm.when, bm.msg, bm.level)
err := l.WriteMsg(bm.msg, bm.level) logMsgPool.Put(bm)
if err != nil { case sg := <-bl.signalChan:
fmt.Println("ERROR, unable to WriteMsg:", err) // 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
} }
} }
} }
// Log EMERGENCY level message. // Emergency Log EMERGENCY level message.
func (bl *BeeLogger) Emergency(format string, v ...interface{}) { func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
msg := fmt.Sprintf("[M] "+format, v...) if LevelEmergency > bl.level {
bl.writerMsg(LevelEmergency, msg) return
}
bl.writeMsg(LevelEmergency, format, v...)
} }
// Log ALERT level message. // Alert Log ALERT level message.
func (bl *BeeLogger) Alert(format string, v ...interface{}) { func (bl *BeeLogger) Alert(format string, v ...interface{}) {
msg := fmt.Sprintf("[A] "+format, v...) if LevelAlert > bl.level {
bl.writerMsg(LevelAlert, msg) return
}
bl.writeMsg(LevelAlert, format, v...)
} }
// Log CRITICAL level message. // Critical Log CRITICAL level message.
func (bl *BeeLogger) Critical(format string, v ...interface{}) { func (bl *BeeLogger) Critical(format string, v ...interface{}) {
msg := fmt.Sprintf("[C] "+format, v...) if LevelCritical > bl.level {
bl.writerMsg(LevelCritical, msg) return
}
bl.writeMsg(LevelCritical, format, v...)
} }
// Log ERROR level message. // Error Log ERROR level message.
func (bl *BeeLogger) Error(format string, v ...interface{}) { func (bl *BeeLogger) Error(format string, v ...interface{}) {
msg := fmt.Sprintf("[E] "+format, v...) if LevelError > bl.level {
bl.writerMsg(LevelError, msg) return
}
bl.writeMsg(LevelError, format, v...)
} }
// Log WARNING level message. // Warning Log WARNING level message.
func (bl *BeeLogger) Warning(format string, v ...interface{}) { func (bl *BeeLogger) Warning(format string, v ...interface{}) {
msg := fmt.Sprintf("[W] "+format, v...) if LevelWarn > bl.level {
bl.writerMsg(LevelWarning, msg) return
}
bl.writeMsg(LevelWarn, format, v...)
} }
// Log NOTICE level message. // Notice Log NOTICE level message.
func (bl *BeeLogger) Notice(format string, v ...interface{}) { func (bl *BeeLogger) Notice(format string, v ...interface{}) {
msg := fmt.Sprintf("[N] "+format, v...) if LevelNotice > bl.level {
bl.writerMsg(LevelNotice, msg) return
}
bl.writeMsg(LevelNotice, format, v...)
} }
// Log INFORMATIONAL level message. // Informational Log INFORMATIONAL level message.
func (bl *BeeLogger) Informational(format string, v ...interface{}) { func (bl *BeeLogger) Informational(format string, v ...interface{}) {
msg := fmt.Sprintf("[I] "+format, v...) if LevelInfo > bl.level {
bl.writerMsg(LevelInformational, msg) return
}
bl.writeMsg(LevelInfo, format, v...)
} }
// Log DEBUG level message. // Debug Log DEBUG level message.
func (bl *BeeLogger) Debug(format string, v ...interface{}) { func (bl *BeeLogger) Debug(format string, v ...interface{}) {
msg := fmt.Sprintf("[D] "+format, v...) if LevelDebug > bl.level {
bl.writerMsg(LevelDebug, msg) return
}
bl.writeMsg(LevelDebug, format, v...)
} }
// Log WARN level message. // Warn Log WARN level message.
// // compatibility alias for Warning()
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
func (bl *BeeLogger) Warn(format string, v ...interface{}) { func (bl *BeeLogger) Warn(format string, v ...interface{}) {
bl.Warning(format, v...) if LevelWarn > bl.level {
return
}
bl.writeMsg(LevelWarn, format, v...)
} }
// Log INFO level message. // Info Log INFO level message.
// // compatibility alias for Informational()
// Deprecated: compatibility alias for Informational(), Will be removed in 1.5.0.
func (bl *BeeLogger) Info(format string, v ...interface{}) { func (bl *BeeLogger) Info(format string, v ...interface{}) {
bl.Informational(format, v...) if LevelInfo > bl.level {
return
}
bl.writeMsg(LevelInfo, format, v...)
} }
// Log TRACE level message. // Trace Log TRACE level message.
// // compatibility alias for Debug()
// Deprecated: compatibility alias for Debug(), Will be removed in 1.5.0.
func (bl *BeeLogger) Trace(format string, v ...interface{}) { func (bl *BeeLogger) Trace(format string, v ...interface{}) {
bl.Debug(format, v...) if LevelDebug > bl.level {
return
}
bl.writeMsg(LevelDebug, format, v...)
} }
// flush all chan data. // Flush flush all chan data.
func (bl *BeeLogger) Flush() { func (bl *BeeLogger) Flush() {
for _, l := range bl.outputs { if bl.asynchronous {
l.Flush() bl.signalChan <- "flush"
bl.wg.Wait()
bl.wg.Add(1)
return
} }
bl.flush()
} }
// close logger, flush all chan data and destroy all adapters in BeeLogger. // Close close logger, flush all chan data and destroy all adapters in BeeLogger.
func (bl *BeeLogger) Close() { func (bl *BeeLogger) Close() {
for { if bl.asynchronous {
if len(bl.msg) > 0 { bl.signalChan <- "close"
bm := <-bl.msg bl.wg.Wait()
for _, l := range bl.outputs { close(bl.msgChan)
err := l.WriteMsg(bm.msg, bm.level) } else {
if err != nil { bl.flush()
fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err) for _, l := range bl.outputs {
} l.Destroy()
}
continue
} }
break 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 { for _, l := range bl.outputs {
l.Flush()
l.Destroy() 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()
}
}
// 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

@ -24,31 +24,26 @@ import (
"time" "time"
) )
const ( // SMTPWriter implements LoggerInterface and is used to send emails via given SMTP-server.
// no usage type SMTPWriter struct {
// subjectPhrase = "Diagnostic message from server" Username string `json:"username"`
)
// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server.
type SmtpWriter struct {
Username string `json:"Username"`
Password string `json:"password"` Password string `json:"password"`
Host string `json:"Host"` Host string `json:"host"`
Subject string `json:"subject"` Subject string `json:"subject"`
FromAddress string `json:"fromAddress"` FromAddress string `json:"fromAddress"`
RecipientAddresses []string `json:"sendTos"` RecipientAddresses []string `json:"sendTos"`
Level int `json:"level"` Level int `json:"level"`
} }
// create smtp writer. // NewSMTPWriter create smtp writer.
func NewSmtpWriter() LoggerInterface { func newSMTPWriter() Logger {
return &SmtpWriter{Level: LevelTrace} return &SMTPWriter{Level: LevelTrace}
} }
// init smtp writer with json config. // Init smtp writer with json config.
// config like: // config like:
// { // {
// "Username":"example@gmail.com", // "username":"example@gmail.com",
// "password:"password", // "password:"password",
// "host":"smtp.gmail.com:465", // "host":"smtp.gmail.com:465",
// "subject":"email title", // "subject":"email title",
@ -56,7 +51,7 @@ func NewSmtpWriter() LoggerInterface {
// "sendTos":["email1","email2"], // "sendTos":["email1","email2"],
// "level":LevelError // "level":LevelError
// } // }
func (s *SmtpWriter) Init(jsonconfig string) error { func (s *SMTPWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s) err := json.Unmarshal([]byte(jsonconfig), s)
if err != nil { if err != nil {
return err return err
@ -64,7 +59,7 @@ func (s *SmtpWriter) Init(jsonconfig string) error {
return nil return nil
} }
func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth { func (s *SMTPWriter) getSMTPAuth(host string) smtp.Auth {
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 { if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
return nil return nil
} }
@ -76,7 +71,7 @@ func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth {
) )
} }
func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error { func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
client, err := smtp.Dial(hostAddressWithPort) client, err := smtp.Dial(hostAddressWithPort)
if err != nil { if err != nil {
return err return err
@ -129,9 +124,9 @@ func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
return nil return nil
} }
// write message in smtp writer. // WriteMsg write message in smtp writer.
// it will send an email with subject and only this message. // 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 { if level > s.Level {
return nil return nil
} }
@ -139,27 +134,27 @@ func (s *SmtpWriter) WriteMsg(msg string, level int) error {
hp := strings.Split(s.Host, ":") hp := strings.Split(s.Host, ":")
// Set up authentication information. // Set up authentication information.
auth := s.GetSmtpAuth(hp[0]) auth := s.getSMTPAuth(hp[0])
// Connect to the server, authenticate, set the sender and recipient, // Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step. // and send the email all in one step.
content_type := "Content-Type: text/plain" + "; charset=UTF-8" contentType := "Content-Type: text/plain" + "; charset=UTF-8"
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\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) return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
} }
// implementing method. empty. // Flush implementing method. empty.
func (s *SmtpWriter) Flush() { func (s *SMTPWriter) Flush() {
return return
} }
// implementing method. empty. // Destroy implementing method. empty.
func (s *SmtpWriter) Destroy() { func (s *SMTPWriter) Destroy() {
return return
} }
func init() { func init() {
Register("smtp", NewSmtpWriter) Register(AdapterMail, newSMTPWriter)
} }

View File

@ -1,212 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"bytes"
"compress/flate"
"compress/gzip"
"errors"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"sync"
"time"
)
var gmfim map[string]*memFileInfo = make(map[string]*memFileInfo)
var lock sync.RWMutex
// OpenMemZipFile returns MemFile object with a compressed static file.
// it's used for serve static file if gzip enable.
func openMemZipFile(path string, zip string) (*memFile, error) {
osfile, e := os.Open(path)
if e != nil {
return nil, e
}
defer osfile.Close()
osfileinfo, e := osfile.Stat()
if e != nil {
return nil, e
}
modtime := osfileinfo.ModTime()
fileSize := osfileinfo.Size()
lock.RLock()
cfi, ok := gmfim[zip+":"+path]
lock.RUnlock()
if !(ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize) {
var content []byte
if zip == "gzip" {
var zipbuf bytes.Buffer
gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression)
if e != nil {
return nil, e
}
_, e = io.Copy(gzipwriter, osfile)
gzipwriter.Close()
if e != nil {
return nil, e
}
content, e = ioutil.ReadAll(&zipbuf)
if e != nil {
return nil, e
}
} else if zip == "deflate" {
var zipbuf bytes.Buffer
deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression)
if e != nil {
return nil, e
}
_, e = io.Copy(deflatewriter, osfile)
deflatewriter.Close()
if e != nil {
return nil, e
}
content, e = ioutil.ReadAll(&zipbuf)
if e != nil {
return nil, e
}
} else {
content, e = ioutil.ReadAll(osfile)
if e != nil {
return nil, e
}
}
cfi = &memFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
lock.Lock()
defer lock.Unlock()
gmfim[zip+":"+path] = cfi
}
return &memFile{fi: cfi, offset: 0}, nil
}
// MemFileInfo contains a compressed file bytes and file information.
// it implements os.FileInfo interface.
type memFileInfo struct {
os.FileInfo
modTime time.Time
content []byte
contentSize int64
fileSize int64
}
// Name returns the compressed filename.
func (fi *memFileInfo) Name() string {
return fi.Name()
}
// Size returns the raw file content size, not compressed size.
func (fi *memFileInfo) Size() int64 {
return fi.contentSize
}
// Mode returns file mode.
func (fi *memFileInfo) Mode() os.FileMode {
return fi.Mode()
}
// ModTime returns the last modified time of raw file.
func (fi *memFileInfo) ModTime() time.Time {
return fi.modTime
}
// IsDir returns the compressing file is a directory or not.
func (fi *memFileInfo) IsDir() bool {
return fi.IsDir()
}
// return nil. implement the os.FileInfo interface method.
func (fi *memFileInfo) Sys() interface{} {
return nil
}
// MemFile contains MemFileInfo and bytes offset when reading.
// it implements io.Reader,io.ReadCloser and io.Seeker.
type memFile struct {
fi *memFileInfo
offset int64
}
// Close memfile.
func (f *memFile) Close() error {
return nil
}
// Get os.FileInfo of memfile.
func (f *memFile) Stat() (os.FileInfo, error) {
return f.fi, nil
}
// read os.FileInfo of files in directory of memfile.
// it returns empty slice.
func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
infos := []os.FileInfo{}
return infos, nil
}
// Read bytes from the compressed file bytes.
func (f *memFile) Read(p []byte) (n int, err error) {
if len(f.fi.content)-int(f.offset) >= len(p) {
n = len(p)
} else {
n = len(f.fi.content) - int(f.offset)
err = io.EOF
}
copy(p, f.fi.content[f.offset:f.offset+int64(n)])
f.offset += int64(n)
return
}
var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")
// Read bytes from the compressed file bytes by seeker.
func (f *memFile) Seek(offset int64, whence int) (ret int64, err error) {
switch whence {
default:
return 0, errWhence
case os.SEEK_SET:
case os.SEEK_CUR:
offset += f.offset
case os.SEEK_END:
offset += int64(len(f.fi.content))
}
if offset < 0 || int(offset) > len(f.fi.content) {
return 0, errOffset
}
f.offset = offset
return f.offset, nil
}
// GetAcceptEncodingZip returns accept encoding format in http header.
// zip is first, then deflate if both accepted.
// If no accepted, return empty string.
func getAcceptEncodingZip(r *http.Request) string {
ss := r.Header.Get("Accept-Encoding")
ss = strings.ToLower(ss)
if strings.Contains(ss, "gzip") {
return "gzip"
} else if strings.Contains(ss, "deflate") {
return "deflate"
} else {
return ""
}
}

View File

@ -1,71 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
//
// import "github.com/astaxie/beego/middleware"
//
// I18N = middleware.NewLocale("conf/i18n.conf", beego.AppConfig.String("language"))
//
// more docs: http://beego.me/docs/module/i18n.md
package middleware
import (
"encoding/json"
"io/ioutil"
"os"
)
type Translation struct {
filepath string
CurrentLocal string
Locales map[string]map[string]string
}
func NewLocale(filepath string, defaultlocal string) *Translation {
file, err := os.Open(filepath)
if err != nil {
panic("open " + filepath + " err :" + err.Error())
}
data, err := ioutil.ReadAll(file)
if err != nil {
panic("read " + filepath + " err :" + err.Error())
}
i18n := make(map[string]map[string]string)
if err = json.Unmarshal(data, &i18n); err != nil {
panic("json.Unmarshal " + filepath + " err :" + err.Error())
}
return &Translation{
filepath: filepath,
CurrentLocal: defaultlocal,
Locales: i18n,
}
}
func (t *Translation) SetLocale(local string) {
t.CurrentLocal = local
}
func (t *Translation) Translate(key string, local string) string {
if local == "" {
local = t.CurrentLocal
}
if ct, ok := t.Locales[key]; ok {
if v, o := ct[local]; o {
return v
}
}
return key
}

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