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

382 Commits

Author SHA1 Message Date
3b6d634f55 Merge pull request #4 from flycash/fix-bug-1.x
Fix BUG: /abc.html/aaa match /abc/aaa
2021-01-25 23:58:18 +08:00
d0847e866e Fix BUG: /abc.html/aaa match /abc/aaa 2021-01-25 23:56:52 +08:00
19e6ba8e7c Merge pull request #3 from flycash/develop-1
Remove duration from prometheus label
2020-12-26 21:56:49 +08:00
5fddd26069 Remove duration from prometheus label 2020-12-26 21:54:57 +08:00
89d4c9a3df Merge pull request #2 from flycash/develop-1
Fix generated code 4384
2020-12-26 21:32:13 +08:00
db6af39bcd Using commentRouter.go as generated file name 2020-12-22 21:29:23 +08:00
9418b243d0 Fix generated code 4384 2020-12-22 21:19:13 +08:00
3d93903f9e Merge pull request #4324 from flycash/fix4321
Expose error from SessionRegenerateID
2020-11-26 18:06:40 +08:00
2781f88173 Expose error from SessionRegenerateID 2020-11-25 21:13:04 +08:00
cbbb6bfb08 Merge pull request #4284 from flycash/develop
Upgrade version
2020-11-03 22:49:59 +08:00
b2a96234ab Upgrade version 2020-11-03 22:21:32 +08:00
db3defa76a Merge pull request #4261 from flycash/reduceCache
make stmt cache smaller
2020-10-10 22:10:43 +08:00
1dffa20435 make stmt cache smaller 2020-10-10 21:35:58 +08:00
8e37fe3b78 Merge pull request #4251 from sc0Vu/fix-typo
Fix typo
2020-10-06 19:44:27 +08:00
91e18996bd Fix typo 2020-10-06 18:20:06 +08:00
b8c1e133bf Merge pull request #4226 from vinicio/feature/same-site
session: adds CookieSameSite to ManagerConfig
2020-10-05 23:05:30 +08:00
d66321fe4e session: adds CookieSameSite config to hooks.go#registerSession 2020-10-05 11:39:20 -03:00
0b7ece44cf Merge pull request #4244 from AllenX2018/develop
testing: fix temporary create failed on Windows
2020-10-04 18:55:37 +08:00
6ffbc0a2b8 testing: fix temporary create failed on Windows
We are creating temporary files on the root directory of beego now
This PR using system temporary directory for testing.

Fixes #4243
2020-09-30 15:50:40 +08:00
26208a53e6 session: adds CookieSameSite to ManagerConfig 2020-09-15 18:05:33 -03:00
f6519b29a8 Merge pull request #4198 from CadenGuo/enhancement/orm_mysql_operator_consolidate
Enhancement/Add a new MySQL operator for strict case sensitive query
2020-09-01 21:33:41 +08:00
91410be722 orm_test:use predefined variable to check db driver 2020-08-31 15:47:37 +00:00
ff53e12191 skip strictexact operator test for db drivers other than mysql 2020-08-31 15:29:48 +00:00
60bb057783 add a new mysql operator for force case sensitie query 2020-08-31 03:40:18 +00:00
8736ffaf6f use 'BINARY' key word for exact operator for mysql db 2020-08-30 23:38:52 +08:00
f946a35acd Merge pull request #4191 from mchtech/rawseter_fielder
fix issue #3776
2020-08-26 10:49:38 +08:00
9bd3a27e80 fix #3776 2020-08-25 23:36:15 +08:00
310161f9d4 Merge pull request #4126 from flycash/fix/secureFlagV1
XSRF add secure and http only flag
2020-08-03 21:24:23 +08:00
a0d1c42dac XSRF add secure and http only flag 2020-08-03 21:04:33 +08:00
87b40ee9e7 Merge pull request #4116 from livelyRyan/patch-2
添加 BConfig.Listen.ClientAuth 字段及处理逻辑
2020-07-30 16:07:00 +08:00
7831638f37 移除多余的条件判断 2020-07-30 14:48:46 +08:00
5203804165 调整默认配置中的 ClientAuth 值,使之与原来的行为保持一致 2020-07-30 14:46:17 +08:00
0815e77f9a 修复笔误产生的拼写错误 2020-07-30 11:20:22 +08:00
c46ba86215 修复笔误产生的拼写错误 2020-07-30 11:18:14 +08:00
9d23e5a3fb 简化代码写法 2020-07-30 11:03:32 +08:00
513a4afff1 对 Listen 结构体增加 ClientAuth 字段
对 Listen 结构体增加 ClientAuth 字段,赋予默认配置对象该字段值为 tls.VerifyClientCertIfGiven,与原代码逻辑的默认值保持一致
2020-07-30 10:59:32 +08:00
15e11931fc 添加对 BConfig.Listen.ClientAuth 字段的逻辑处理。当指定了该配置时,使用配置的值来作为验证客户端的方式。如果没指定,使用默认值 tls.RequireAndVerifyClientCert 2020-07-30 10:53:30 +08:00
7d561607d8 Merge pull request #4111 from liuhaogui/develop
添加配置文件路径环境变量
2020-07-30 09:14:12 +08:00
15f04b8da4 add env BEEGO_CONFIG_PATH 2020-07-29 21:57:16 +08:00
7a48fbb698 Merge pull request #4066 from playHing/self-dev
Fix concurrent issue of context/input Query method
2020-07-16 13:52:11 +08:00
3e2c795410 Rlock for form query 2020-07-15 20:44:59 +08:00
55e6298f29 Fix concurrent form parsing and getting 2020-07-15 20:44:59 +08:00
b50fb44950 Add bench test on context input query 2020-07-15 20:44:59 +08:00
1c0714405a Merge pull request #4068 from jianzhiyao/fix_3898
fix `index out of range` when sid len = 1
2020-07-15 00:03:59 +08:00
678b90385b add log 2020-07-14 09:57:13 +08:00
5940ae33c2 fix index out of range when sid len = 1
add unit test for sess_file.go
2020-07-13 19:14:53 +08:00
3db31385cf Merge pull request #4065 from Acmefocus/develop
update README.md
2020-07-11 23:16:36 +08:00
2b9aaa5b0d update README.md
Signed-off-by: Acmefocus <107723772@qq.com>
2020-07-10 09:58:06 +08:00
dced745d55 Merge branch 'develop' of https://github.com/Acmefocus/beego into develop 2020-07-09 18:15:25 +08:00
25ba78ea72 update README.md
Signed-off-by: Acmefocus <107723772@qq.com>
2020-07-09 18:14:31 +08:00
863b5bd0f4 Merge branch 'develop' of https://github.com/Acmefocus/beego into develop
Signed-off-by: Acmefocus <107723772@qq.com>
2020-07-09 17:59:07 +08:00
ba3153621a Merge branch 'develop' of https://github.com/Acmefocus/beego into develop 2020-07-09 17:47:46 +08:00
40cdc877b6 Update README.md
Signed-off-by: Acmefocus <107723772@qq.com>
2020-07-09 17:38:08 +08:00
76debb1899 Update README.md 2020-07-09 17:18:01 +08:00
35dcc3df7c Merge pull request #4058 from a631807682/fix/issues/4054
Fix response payload too large
2020-07-09 16:41:11 +08:00
c3f14a0ad6 refactor: log error when payload too large 2020-07-09 09:45:40 +08:00
8ee167bc7b Merge pull request #4051 from gmelodie/sleep-on-reconnect-3972
Add sleep on reconnect
2020-07-09 09:41:04 +08:00
2eccb23461 Add sleep on reconnect functionality 2020-07-08 17:30:34 +01:00
28d3f624a3 Merge pull request #4055 from tayoogunbiyi/develop
Allow Healthcheck endpoint return JSON for Kubernetes
2020-07-08 23:51:03 +08:00
926b80d1d8 Merge pull request #4061 from flycash/race-condition
Fix 4059
2020-07-08 23:49:54 +08:00
c08b27111c Fix 4059 2020-07-08 23:32:18 +08:00
03f78b2e4a fix: add error code support 2020-07-08 18:09:01 +08:00
946a42c021 fix: response http 413 when body size larger then MaxMemory. 2020-07-08 17:14:52 +08:00
d7b0d55357 added extra check for same response lengths 2020-07-07 17:23:52 +01:00
728bf34006 refacted cache health check from toolbox 2020-07-07 16:46:59 +01:00
e0f8c6832d added test for buildingHealthCheckResponse 2020-07-07 16:28:16 +01:00
469dc7bea9 refactored the building of healthcheck response map 2020-07-07 16:09:22 +01:00
ca0c64b69e refactored tests for health check endpoint 2020-07-07 15:21:38 +01:00
5a4a082af0 renamed functions for clarity 2020-07-07 14:54:21 +01:00
9dc660c1da Merge pull request #4056 from gmelodie/reconn-bug-3971
Fix reconnection bug in logs/conn.go
2020-07-07 20:25:44 +08:00
d8724cb122 Add error returning to writeln 2020-07-06 21:38:47 +02:00
fc56c562db Fix logger reconnection 2020-07-06 21:38:47 +02:00
8d1a9bc92e added tests for health check endpoints 2020-07-06 19:34:48 +01:00
db547a7c84 added test for execJson 2020-07-06 16:04:29 +01:00
7c575585e9 added conditional json flag when trying to view healthchecks 2020-07-06 15:27:12 +01:00
289f86247e Merge pull request #4053 from flycash/ftr/test-docker
Add docker-compose to support running unit test locally
2020-07-05 22:08:20 +08:00
ca9b21bb30 Add docker-compose to support running test 2020-07-04 13:42:20 +00:00
d6ef33feee Merge pull request #4045 from flycash/preprare-1.12.2
Remove tidb dependency
2020-06-30 23:16:41 +08:00
2f5ed5b433 Remove tidb dependency 2020-06-30 21:04:37 +08:00
9c18dbe7dc Merge pull request #4044 from flycash/upgrade_version
upgrade version
2020-06-30 20:58:14 +08:00
01219944a4 upgrade version 2020-06-30 20:55:31 +08:00
4fac6ceb3f Merge pull request #4039 from flycash/fixbug
Fix ES bug
2020-06-30 13:18:13 +08:00
1c4085e7ea Update es.go 2020-06-30 10:12:10 +08:00
2117562113 Update es.go 2020-06-30 10:09:36 +08:00
1f9da8d75b Fix ES bug 2020-06-29 22:25:22 +08:00
9dea9f9ae7 Merge pull request #3994 from HarryWang29/develop
update support bit operation
2020-06-29 20:27:36 +08:00
0f6735e20d Merge branch 'develop' of https://github.com/astaxie/beego into develop
 Conflicts:
	go.mod
	go.sum
2020-06-29 16:48:03 +08:00
ad68e8d866 Merge pull request #4033 from jianzhiyao/develop
make redis client idle timeout configurable
2020-06-28 23:14:28 +08:00
26beef756f Merge pull request #4038 from flycash/fixbug
Fix ES index problem
2020-06-28 23:14:07 +08:00
5c9cc805a1 make redis client idle timeout configurable 2020-06-28 22:20:46 +08:00
de51bd28d7 Fix ES index problem 2020-06-28 21:12:12 +08:00
6b6a0e8a56 Merge pull request #4032 from jianzhiyao/develop
fix strings.Repeat panic
2020-06-26 17:51:47 +08:00
8039cc8e59 Merge pull request #4017 from guhan121/fix#4000
Prohibit multiple calls SetDefaultMessage.
2020-06-26 17:49:36 +08:00
b88b7d2899 fix strings.Repeat panic 2020-06-25 23:48:07 +08:00
e725192072 fix #4000 2020-06-25 22:11:32 +08:00
469f2c226d Merge pull request #3998 from guhan121/go_modules_route_compatibility
for go modules, generate route by `GO111MODULE=on`
2020-06-25 20:30:27 +08:00
828b20163d Merge pull request #4029 from flycash/feature/buildInfo
Add build info for prometheus
2020-06-25 18:37:11 +08:00
9c5eab4834 Add build info for prometheus 2020-06-25 17:44:40 +08:00
1813d414ae Merge pull request #4027 from flycash/develop
Move many PR's change here since the original authors are responseless
2020-06-25 02:17:49 +08:00
6052524a28 Merge pull request #4028 from jianzhiyao/develop
acquire() in Lock
2020-06-23 23:58:42 +08:00
3ce68d6a30 Move many PR's change here since the original authors are responseless 2020-06-23 23:14:51 +08:00
89420eacd0 go destroy 2020-06-23 23:05:58 +08:00
6d9862b924 acquire() in Lock 2020-06-23 22:29:41 +08:00
96658121dc Merge pull request #3818 from dbt4516/develop
Get the real location of the log directory before using filepath.Walk function when remove old logs
2020-06-23 21:17:14 +08:00
43a3623f51 Merge pull request #3803 from yinggaozhen/fix-statistics
GetMapData change to read lock
2020-06-23 21:08:32 +08:00
089dfbdbee Merge pull request #4016 from huija/develop
Update redis.go
2020-06-23 20:41:42 +08:00
526a5463d6 Merge pull request #4025 from jianzhiyao/develop
Fix: Too Many Prepare Statement
2020-06-23 20:33:04 +08:00
a28d294a83 upgrade acquire method return *sql.Stmt 2020-06-23 13:46:19 +08:00
f70fd5babf add comments on Scan func 2020-06-23 12:32:26 +08:00
a764e17fbf check ci 2020-06-23 10:43:51 +08:00
f5c580a403 Merge pull request #3739 from nuczzz/develop
fix graceful bug: old process didn't exist when graceful restart
2020-06-23 00:10:53 +08:00
7210dc24b6 Merge pull request #3743 from Colstuwjx/fix/orm-end-printstack
Fix/orm end printstack
2020-06-22 23:59:16 +08:00
1060dc7df8 Merge pull request #4026 from flycash/develop
fix UT
2020-06-22 23:36:25 +08:00
3ab1e48217 fix UT 2020-06-22 23:24:15 +08:00
a5e8344a0a add stmt garbage recycle 2020-06-22 23:23:52 +08:00
e1386c448c Merge pull request #4022 from jianzhiyao/develop
stmt: fix bug of non concurrent security & add double check
2020-06-21 18:01:14 +08:00
6e4d0917b2 Merge pull request #4023 from flycash/feature/prometheus
Change prometheus patter
2020-06-21 17:39:02 +08:00
9b43a90138 Change prometheus patter 2020-06-21 17:36:13 +08:00
b7833fb297 Merge pull request #4021 from flycash/feature/prometheus
Add prometheus support
2020-06-21 17:11:00 +08:00
16b81d09a7 Add prometheus support 2020-06-21 17:10:34 +08:00
0056b92d0b Merge pull request #4007 from flycash/fix3949
Store RouterPattern before filter execute
2020-06-21 17:05:04 +08:00
decc75e025 fix bug of non concurrent security & add double check 2020-06-21 16:25:32 +08:00
f2bae3e367 add UT for Scan function 2020-06-19 22:55:40 +08:00
6eeea141d8 Merge pull request #4018 from flycash/fix/xss
Using HTMLEscapeString in adminui.go to avoid XSS attack
2020-06-19 22:21:51 +08:00
6c0db4db3d Using HTMLEscapeString in adminui.go to avoid XSS attack 2020-06-19 21:49:17 +08:00
b9fbcbd906 Merge pull request #4014 from jianzhiyao/develop
over size file do not run openFile
2020-06-19 20:19:16 +08:00
40181c1042 Update redis.go
use `scan` instead of `keys`
2020-06-19 14:21:12 +08:00
3879fd9c34 修改测试用例 2020-06-18 17:25:48 +08:00
af238ee047 Merge remote-tracking branch 'origin/develop' into go_modules_route_compatibility
# Conflicts:
#	go.mod
#	go.sum
2020-06-18 16:41:03 +08:00
86935ada01 Merge pull request #3943 from zhlicen/master
#3942 fix encoded url(with slash) router match problem
2020-06-18 16:04:16 +08:00
8160059182 big size file lead to memory leak 2020-06-16 14:56:58 +08:00
dc70e9fc47 update go test 2020-06-15 18:16:40 +08:00
bac2b31afe Merge pull request #3975 from nicowaisman/patch-2
Change permission mask
2020-06-13 23:40:13 +08:00
a134cb8d17 update go test 2020-06-11 23:45:06 +08:00
4ad699b7b8 Merge pull request #3981 from zwei0526/develop
Update parser.go
2020-06-10 16:38:15 +08:00
14fc935cb6 Merge pull request #4005 from keminar/develop
fix graceful bug
2020-06-10 16:34:32 +08:00
d9f262e277 update go test 2020-06-10 12:31:38 +08:00
131748bb3d update go.mod&go.sum 2020-06-10 11:34:58 +08:00
2c3c66ccea Merge remote-tracking branch 'origin/develop' into develop 2020-06-10 11:16:17 +08:00
71cb1379b4 update support bit operation 2020-06-10 11:15:23 +08:00
b351f02525 Merge branch 'develop' of https://github.com/astaxie/beego into develop
 Conflicts:
	go.sum
2020-06-10 11:05:57 +08:00
8e29300f85 fix graceful bug 2020-06-09 12:20:08 +08:00
5f31bf45d4 Fix #3949 : store RouterPattern before filter execute so that filter can use the pattern 2020-06-07 23:16:26 +08:00
3e30f37172 Merge pull request #4006 from flycash/develop
fix CI and UT
2020-06-06 21:16:08 +08:00
ce4ce74c8d Fix validation test 2020-06-06 20:50:48 +08:00
ce0e7525ad Fix ES client 2020-06-06 20:14:01 +08:00
d0b7244d57 Fix ledis 2020-06-06 19:21:57 +08:00
5100a8c396 fix CI and UT 2020-06-06 18:16:36 +08:00
b8efb3ef45 Merge pull request #3984 from jianzhiyao/develop
fix bug:static can not real hit cache & memory leak
2020-06-06 13:10:58 +08:00
ae15b54f83 Merge pull request #3992 from maxshine/fix-3991
[Fix-Issue-3991] Fix Read with SQLite not supporting SELECT FOR UPDAT…
2020-06-06 13:10:08 +08:00
0dab959c95 Merge pull request #4004 from gokangaroo/develop
avoid `panic: send on closed channel` after closing logger.
2020-06-06 13:09:36 +08:00
ee615dd08f Merge pull request #3985 from leapsea/develop
修复多线程下数据库连接异常问题
2020-06-06 12:40:50 +08:00
b879a07b3a avoid panic: send on closed channel after closing logger. 2020-06-04 14:52:54 +08:00
c265f6e49c add comment 2020-06-02 20:02:58 +08:00
690e91e1b6 static file module:make cache file size and cache file numbers configurable 2020-06-02 18:22:47 +08:00
50f71a8a21 fix bug of getting int error 2020-06-02 18:10:17 +08:00
677d010d86 [Fix-Issue-3991] Add warn for SQLite Read is invoked isForUpdate=true 2020-06-01 23:12:12 +08:00
db2a1134ce Merge pull request #3993 from skanger/develop
fix httplib PostFile method
2020-06-01 22:05:01 +08:00
c2771397be delete vendor 2020-06-01 15:06:49 +08:00
70733d9810 fix label == `` #4001 2020-06-01 15:06:33 +08:00
11740cede6 Merge pull request #3996 from bharrat/3995-fix-middleware-not-working-with-graceful
Fixes #3995 Use handlers with middleware when starting Graceful server
2020-05-31 22:23:05 +08:00
4ffe26a1d2 for go modules, generate route by GO111MODULE=on 2020-05-22 11:35:45 +08:00
075db4773b Fixes #3995 Use handlers with middleware when starting Graceful server 2020-05-18 10:00:14 -07:00
2fd9dfca7b update support bit operation 2020-05-18 19:18:35 +08:00
eea20f6ceb fix httplib PostFile method 2020-05-16 11:00:15 +08:00
8055357576 [Fix-Issue-3991] Fix Read with SQLite not supporting SELECT FOR UPDATE syntax 2020-05-15 17:36:01 +08:00
9fda81b7f3 modify static cache total size about 100m 2020-05-09 18:24:14 +08:00
4b12e053b7 fix 2020-05-09 17:58:10 +08:00
0307c8b110 set static file cahce limit:file size & file count 2020-05-09 17:57:00 +08:00
867f83de34 修复数据库连接异常问题 2020-05-09 16:31:30 +08:00
af19822293 not modify go.mod 2020-05-09 14:06:51 +08:00
2449aad105 add cache-hit test example 2020-05-09 10:44:37 +08:00
d31975a752 complete test example 2020-05-08 17:35:02 +08:00
b28d5e2716 fix bug:static canot real hit cache
opt:reduce risk of memory leak with lru cache
2020-05-08 17:10:19 +08:00
ab33d683ea Update parser.go
修复genRouterCode方法解析  router 注释(// @router) 单双引号引起的bug
2020-04-26 19:26:10 +08:00
f99cbe0fa4 Change permission mask 2020-04-22 08:42:54 -07:00
0aa82d875a Update input.go 2020-03-05 14:46:17 +00:00
8f3d1c5f42 Merge pull request #3922 from BurtonQin/bug-1-2-3-inconsistent-field-protection
cache, context, session: add lock to fix inconsistent field protection
2020-02-22 15:09:25 +08:00
2410b364af Merge pull request #3923 from timchenxiaoyu/develop
fix exist typo
2020-02-22 11:25:13 +08:00
713503e43d fix exist typo 2020-02-14 16:47:47 +08:00
cfdd1cd5be cache, context, session: add lock to fix inconsistent field protection 2020-02-10 21:49:46 +08:00
de5650b723 version 1.12.1 2020-02-07 16:23:57 +08:00
e5e4a3bea7 Merge pull request #3868 from holtyuzhuyanbo/fix_session_destory
fix: session destory
2020-02-07 11:49:54 +08:00
90d0b43f34 Merge pull request #3888 from gavin2014/develop
[Fix] Fix create table with SQLite not supporting COMMENT syntax
2020-02-07 11:46:52 +08:00
1b7f5ba2c4 Merge pull request #3900 from aixiaoxiang/develop
httplib:fixes network request failed to create an invalid file and automatically created file directory
2020-02-07 11:10:56 +08:00
96f01079cb Merge pull request #3905 from ywk253100/200110_context
Send the request from context rather than the original one to handlers
2020-02-07 11:05:01 +08:00
9d4b5b313f Merge pull request #3911 from liuzhang/develop
验证调整,增加label, xx不能为空
2020-02-07 11:04:11 +08:00
b882009979 Merge pull request #3919 from wy65701436/develop-sha256
update hash algorithm for signing the cookie for xsrf token
2020-02-07 11:01:10 +08:00
a768bf8f00 update hash algorithm for signing the cookie for xsrf token
Due to the chosen-prefix collision in SHA-1(details at https://sha-mbles.github.io/), SHA-1 hash functions should to be deprecated and SHA-2/SHA-3 should be used instead.

Signed-off-by: wang yan <wangyan@vmware.com>
2020-02-06 17:31:24 +08:00
034599ca1d 验证调整,增加label, xx不能为空 2020-01-17 16:47:19 +08:00
5a02c556b2 Send the request from context rather than the original one to handlers
The filters may do some changes to the request, such as putting values in the request's context

Signed-off-by: Wenkai Yin <yinw@vmware.com>
2020-01-10 17:58:00 +08:00
axx
dc5c42e981 httplib:fixes network request failed to create an invalid file and automatically created file directory 2019-12-30 11:24:55 +08:00
92a4119258 Update cmd_utils.go
[Fix] Fix create table with SQLite not supporting COMMENT syntax
2019-12-11 16:50:08 +08:00
aa90c67a75 Merge pull request #3867 from ywk253100/191119_xsrf
Abort with the pre-defined status code when handling XSRF error
2019-11-29 18:43:02 +08:00
38a144c68f fix: session destory 2019-11-19 21:25:30 +08:00
793047097c Abort with the pre-defined status code when handling XSRF error
As the status codes(422 and 417) are set in the error map, abort with them directly to active the pre-defined error handlers

Signed-off-by: Wenkai Yin <yinw@vmware.com>
2019-11-19 18:55:54 +08:00
1923b8c767 Merge pull request #3841 from HKail/develop
添加16开头手机号验证
2019-10-23 22:22:52 +08:00
fb640f0075 更新16开头手机号的正则测试 2019-10-22 17:07:22 +08:00
241f10b429 添加16开头手机号验证,162电信,165移动,166/167联通。 2019-10-22 16:58:05 +08:00
b8d626bbea 添加16开头手机号验证,162电信,165移动,166/167联通。 2019-10-22 16:28:19 +08:00
2a6ceca861 Update README.md 2019-10-10 11:23:19 +08:00
10236b9f2d Merge pull request #3814 from cloudzhou/patch-2
leak opened file
2019-10-10 00:48:24 +08:00
b3ae5d4ac6 Update file.go
According to issue#4759 (https://github.com/golang/go/issues/4759) filepath.Walk function in golang cannot handle symbolic path, meanwhile symbolic path for log directory is pretty common used. In such scenario this deleteOldLog function will fail without any error log. Get the real location of the log directory before using walk function can fix this.
2019-10-09 15:53:22 +08:00
77fc8e4e38 According to issue#4759 (https://github.com/golang/go/issues/4759) filepath.Walk function in golang cannot handle symbolic path, meanwhile symbolic path for log directory is pretty common used. In such scenario this deleteOldLog function will fail without any error log. Get the real location of the log directory before using walk function can fix this. 2019-10-09 15:33:38 +08:00
5a5482c77f leak opened file
should defer file.Close()
2019-09-27 19:27:44 +08:00
11774c87a5 update version 1.13 2019-09-19 00:19:33 +08:00
6d47b4a2e0 GetMapData change to read lock 2019-09-09 17:44:10 +08:00
8395a26061 Merge pull request #3801 from DennisMao/develop
fix annotation on orm/utils #3777
2019-09-09 00:46:36 +08:00
4348356d0a fix annotation on orm/utils 2019-09-09 00:47:20 +08:00
b551949a2b Add PrintStack() while orm abnormally exit. 2019-07-31 14:08:58 +08:00
32cd76396d fix graceful bug 2019-07-27 18:54:13 +08:00
5620608418 Update README.md 2019-07-21 22:58:28 +08:00
32ee728078 Merge pull request #3586 from astaxie/develop
V1.12.0
2019-07-05 12:26:39 +08:00
de7ce2f9b0 v1.12.0 2019-07-05 11:58:41 +08:00
d6c2a9fd4b Merge pull request #3703 from jkylin/develop
email的Attach和AttachFile 的参数检查逻辑有误。len(args) < 1 && len(args) > 2 改为 l…
2019-07-05 11:35:47 +08:00
582a4fa34b Merge pull request #3699 from orgmatileg/develop
router.go: add comment func LogAccess
2019-07-05 11:34:59 +08:00
99647986de Merge pull request #3689 from GeorgeXc/addStmt
Add stmt
2019-07-05 11:34:40 +08:00
5bcde306ea Revert "update"
This reverts commit 2909ff3366.
2019-06-28 23:37:32 +08:00
2909ff3366 update 2019-06-28 23:23:01 +08:00
40078cba2c update 2019-06-28 23:13:18 +08:00
5d0c0a03d7 update 2019-06-28 22:56:32 +08:00
8cfd7f5c19 email的Attach和AttachFile 的参数检查逻辑有误。len(args) < 1 && len(args) > 2 改为 len(args) < 1 || len(args) > 2 2019-06-28 20:09:23 +08:00
62d96c2e93 router.go: add comment func LogAccess 2019-06-25 08:04:21 +07:00
e844058aed Merge pull request #3632 from Wusuluren/develop
fix concurrent map access problem on BeegoInput.data
2019-06-18 12:09:34 +08:00
1eab6bb32a Merge pull request #3669 from xfwduke/develop
fix bugs of ParseForm about time in RFC3339 format
2019-06-18 12:08:55 +08:00
c265d32c36 Merge pull request #3685 from Anderson-Lu/fix_orm_datarace
fix orm datarace
2019-06-18 12:05:54 +08:00
06692c3e27 update 2019-06-17 23:38:07 +08:00
394a73c75f fix orm datarace 2019-06-14 14:43:02 +08:00
cbcde8bd1f Merge branch 'develop' of https://github.com/Wusuluren/beego into develop 2019-06-09 22:10:22 +08:00
b17e49e6aa fix concurrent map access problem on BeegoInput.data 2019-06-09 22:09:38 +08:00
873f62edff update 2019-06-09 01:19:17 +08:00
cc0eacbe02 update 2019-06-08 23:53:42 +08:00
649c5c861d fix bugs of ParseForm about time in RFC3339 format 2019-05-31 15:52:19 +08:00
206a7ed1fc Merge pull request #3662 from npu21/develop
fix typos
2019-05-27 11:07:52 +02:00
3c046a4dbf fix two typos 2019-05-27 16:38:30 +08:00
f2be6af2ca Merge pull request #3658 from priyesh-lb/patch-1
Beego skipping some migrations
2019-05-18 09:38:09 +08:00
804b9769e0 Merge pull request #3642 from GeorgeXc/fixBatchUpdate
Fix BatchUpdate not update the auto_now field
2019-05-18 09:28:22 +08:00
585df01899 Merge pull request #3635 from edwardhey/develop
Incr和Decr应该改成排它锁,否则在并发的时候会出现非期望的结果值
2019-05-18 09:27:38 +08:00
1a529c061c Merge pull request #3633 from MarxGo/develop
fix:utils.GetGOPATHs() when go version equal or after go1.10
2019-05-18 09:27:08 +08:00
fcacfc08e3 Beego skipping some migrations
Beego skipping some migrations #3657
2019-05-17 16:19:26 +05:30
a0ca3d61d6 update 2019-05-08 23:11:57 +08:00
39bd40e512 Incr和Decr应该改成排它锁,否则在并发的时候会出现非期望的结果值 2019-05-01 13:10:21 +08:00
Guo
8748de95c7 fix:utils.GetGOPATHs() when go version equal or after go1.10 func does not return defaultGoPATH() 2019-04-30 06:43:25 +08:00
0939e8e493 fix concurrent map access problem on BeegoInput.data 2019-04-30 00:15:24 +08:00
6a33feee46 Merge pull request #1 from astaxie/develop
update
2019-04-29 23:19:27 +08:00
58b2ac702c Merge pull request #3623 from Martinho0330/develop
make routers configurable for beego multi-instance in the same repo
2019-04-29 18:19:22 +08:00
175714f69a Merge pull request #3627 from haleyly/develop
route request put amendment
2019-04-29 18:18:19 +08:00
d5b70118a3 Merge branch 'develop' of https://github.com/haleyLy/beego into dev 2019-04-28 08:51:34 +08:00
a9629f707e route request put amendment 2019-04-28 08:50:30 +08:00
6123c72752 Merge pull request #3621 from guanle/guanle-patch-1
Update templatefunc.go  for default value tag
2019-04-27 23:28:35 +08:00
56afa5c2bf Merge pull request #3625 from hsluoyz/develop
remove the 1000-row limit for ORM result set
2019-04-27 23:27:58 +08:00
0c576dac82 Merge pull request #3599 from Wusuluren/cache_file
fix bug on cache/file
2019-04-27 23:27:27 +08:00
8462372c03 Merge pull request #3598 from Wusuluren/develop
fix race problem on toolbox/task
2019-04-27 23:26:29 +08:00
20ff97d53d Merge pull request #3595 from JessonChan/log_revet
Log revet
2019-04-27 23:26:06 +08:00
8ce5b6cc52 Merge pull request #3593 from JessonChan/trace_method
Trace method
2019-04-27 23:25:44 +08:00
afb787d49d remove the 1000-row limit for ORM result set 2019-04-27 16:58:00 +08:00
0b165b78a1 make routers configurable for beego multi-instance in the same repo 2019-04-22 22:18:37 +08:00
fa97488bdc Update templatefunc.go 2019-04-21 10:27:35 +08:00
3086081ec0 v1.11.2 2019-04-06 13:50:14 +08:00
dfab44c24a fix bug on cache/file 2019-04-05 22:32:28 +08:00
1900246054 fix bug on cache/file 2019-04-05 22:17:56 +08:00
e980f92c63 fix race problem on toolbox/task 2019-04-05 20:28:24 +08:00
ce3800e3ef // Deprecated: use github.com/astaxie/beego/logs instead. 2019-04-03 14:13:38 +08:00
e3d668f450 revet to log.go
https://github.com/astaxie/beego/issues/3591
https://github.com/astaxie/beego/issues/3588
2019-04-03 14:08:42 +08:00
75b4bc5896 it's no need to override Trace method. 2019-04-03 10:19:09 +08:00
c0ecf32d17 update travis 2019-04-02 21:53:01 +08:00
3155f07ccd no need to override Trace method. 2019-03-27 13:43:27 +08:00
02bead5097 handle trace request must NOT CACHE
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
2019-03-27 13:40:34 +08:00
e8b29c9fd1 handle trace request 2019-03-27 13:34:46 +08:00
610f27d684 Merge pull request #3530 from GeorgeXc/develop
[orm] add ignore auto_now_add field when update
2019-03-27 00:14:07 +08:00
59466f6678 Merge pull request #3554 from grahamjamesaddis/travis-ci-build
Allow forked beego project to pass travis ci builds
2019-03-26 19:24:06 +08:00
3ddd8f860e Merge pull request #3561 from JessonChan/develop
refactor color logger
2019-03-26 19:23:14 +08:00
8535ec0819 Merge pull request #3567 from bsdelf/feature/ensure-custom-error-handler
Ensure custom error handler is called
2019-03-26 19:22:54 +08:00
f294121ab7 Merge pull request #3583 from maxshine/develop
[Fix] Fix the issue that genRouterCode results in @Import annotations getting overwritten
2019-03-26 19:13:44 +08:00
0b8ebaf387 Update db.go 2019-03-26 01:00:04 +08:00
3b00cfccec [Fix] Fix the issue that genRouterCode incorrect logic results in @Import annotations getting overwritten 2019-03-24 14:41:28 +08:00
005391be81 update 2019-03-23 00:33:26 +08:00
bc8fffe347 Merge pull request #3571 from dxas90/patch-1
Fixes #3570
2019-03-14 19:16:15 +01:00
914bbfd710 Update apiauth.go fixed infinite recursive call 2019-03-14 09:14:12 -04:00
be31bd2bbd Ensure custom error handler is called 2019-03-13 16:24:04 +08:00
95ff817019 undefined: beego.BeeLogger fixed 2019-03-13 09:41:13 +08:00
ea91e7638c move log function to log package 2019-03-12 17:01:23 +08:00
4564e9810c logger_test imported and not used: "bytes" 2019-03-12 16:36:00 +08:00
44a1a8f6be println is builtin function 2019-03-12 15:51:43 +08:00
9cecb22170 NewAnsiColorWriter remove 2019-03-12 15:13:54 +08:00
7693502aaa logAdapter is more readable 2019-03-12 13:20:13 +08:00
c0fae547e9 remove ansicolor code,import ansicolor package 2019-03-12 12:14:09 +08:00
0ba77a0d87 colorful is the switch to level label 2019-03-12 12:12:59 +08:00
661dcbb6ca router logger modify 2019-03-12 12:11:25 +08:00
578440a18d add ansicolor to beego 2019-03-12 12:09:41 +08:00
93485df3d2 color should be false by default otherwise the http logger's color would be wrong 2019-03-08 14:42:06 +08:00
121fab61f1 ResetColor function 2019-03-08 12:18:45 +08:00
915eec7943 Merge branch 'develop' of github.com:astaxie/beego into develop 2019-03-08 12:16:00 +08:00
8432a1c758 better comment for color map 2019-03-08 12:10:57 +08:00
4a5e108527 logger color function refactor,easy to read and run more quickly 2019-03-08 11:50:30 +08:00
6dd5171fdf logger color function refactor,easy to read and run more quickly 2019-03-08 11:08:39 +08:00
c2b6cb5c3a Merge pull request #3560 from JessonChan/develop
TestToJson bug fixed
2019-03-08 00:18:39 +08:00
1f93040af6 Merge remote-tracking branch 'upstream/develop' into develop 2019-03-07 18:07:37 +08:00
52f8ccd06c TestToJson bug fixed 2019-03-07 17:15:30 +08:00
3b86feab8a Merge pull request #3555 from hellomrleeus/develop
spelling mistake of word "Header"
2019-03-06 09:58:54 +08:00
aba51d99a1 spelling mistake of word "Header" 2019-03-04 11:05:29 +08:00
8454e8417e Allow forked beego project to pass travis ci builds 2019-03-01 14:00:20 +00:00
422e8285b5 Merge pull request #3494 from nuczzz/develop
simplify beego grace with http.Shutdown
2019-02-26 16:31:40 +08:00
bb6ca6b100 Merge pull request #3522 from saromanov/check-input-data
SessionRead: check of the length for input sid variable
2019-02-25 23:17:57 +08:00
1483c1f545 Merge pull request #3524 from Quasilyte/quasilyte/bytesreader
config/yaml: s/bytes.NewBuffer/bytes.NewReader/
2019-02-25 23:16:39 +08:00
3d6a68de77 Merge pull request #3535 from gadelkareem/develop
Make LogAccess() function public
2019-02-25 23:11:14 +08:00
387d387080 Merge pull request #3547 from snedzad/patch-1
Register .gohtml extension
2019-02-25 11:43:37 +08:00
94ed35e781 Register .gohtml extension
Goland as of 2018.3 seems to support only '.tpl.gohtml" and ".gohtml" templates. The regular ".tpl" templates will be rendered as plain text files which is not acceptable.
2019-02-23 22:49:32 +01:00
8995b291a9 Make LogAccess public 2019-02-14 16:30:25 +01:00
1942438b22 Merge pull request #3 from astaxie/develop
develop
2019-02-14 16:23:35 +01:00
40d653e659 Merge pull request #3533 from tvanriper/patch-1
APIBaiscAuth is misspelling of APIBasicAuth
2019-02-13 02:10:25 +08:00
ddcd28e67f APIBaiscAuth is misspelling of APIBasicAuth
Corrected APIBaiscAuth to APIBasicAuth.  Maintained old name (forwarding to new one) for those still using the previous API function name.
2019-02-12 13:00:11 -05:00
65f587d5e9 fix ineffectual assignment 2019-02-12 19:05:22 +08:00
2fefd8cbbf update len 2019-02-12 18:53:34 +08:00
ba17bdd366 add ignore auto_now_add field when update 2019-02-12 18:05:29 +08:00
c998e52cc0 config/yaml: s/bytes.NewBuffer/bytes.NewReader/
When io.Reader is required out of []byte,
it's better to use bytes.NewReader than bytes.NewBuffer.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2019-02-10 20:37:43 +03:00
3224369ac9 Merge pull request #3523 from Quasilyte/quasilyte/boolExprSimplify
Simplify boolean expressions
2019-02-09 16:16:31 +01:00
67666dbe0f all: simplify boolean expressions
- !(a == b) => a != b
- !(a != b) => a == b

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2019-02-09 17:18:59 +03:00
d7430eb921 SessionRead: check of the length for input sid variable 2019-02-04 11:03:27 +05:00
26a6b426f1 Merge pull request #3487 from duyazhe/patch-1
Update orm_log.go
2019-02-04 11:26:42 +08:00
6f35ce67f7 Merge pull request #3493 from bharat-p/3492-add-db-stats-to-ormer
Add DBStats method wrapper to provide sql.DBStats when using ormer
2019-02-04 11:24:13 +08:00
3406d58797 Merge pull request #3503 from wtospit/develop
fix: when parse post form it didnt parse fields correctly
2019-02-04 11:23:23 +08:00
7925458fc0 Merge pull request #3502 from Quasilyte/patch-1
cache: remove excessive type assertions
2019-02-02 16:43:04 +08:00
b6854aaf9f Merge pull request #3506 from DennisMao/hotfixFileCachePanic
fix panic cause by the map
2019-02-02 16:38:11 +08:00
656f595226 Merge pull request #3508 from Quasilyte/patch-2
replace unchecked Compile calls with MustCompile
2019-02-02 16:36:15 +08:00
280aaf9d3b Merge pull request #3519 from zav8/add_support_for_pointer_fields
add support for pointer fields of structs to method QueryRows()
2019-02-02 16:35:41 +08:00
7abdb05f91 little fix 2019-02-01 15:39:40 +08:00
af4464ce58 add support for pointer fields of structs to method QueryRows() 2019-02-01 15:27:10 +08:00
6ca0978777 replace unchecked Compile calls with MustCompile
For constant patterns and especially when errors are ignored,
`regexp.MustCompile` is a better choice than `regexp.Compile`.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2019-01-26 14:13:53 +03:00
8506194d2c fix panic cause by the map 2019-01-25 19:08:39 +08:00
3bd7614ade refactoring code after discussion 2019-01-25 11:00:24 +07:00
bd1b421491 fix: adding test for issue due to testing is not reflect changed 2019-01-25 09:04:01 +07:00
920207f72c add testing for ParseForm when form post has a slice in body 2019-01-25 00:38:14 +07:00
12fdc04f1b fix: when parse post form it didnt parse fields which have same name but the first index is empty but another is not 2019-01-25 00:15:40 +07:00
d0c744ae6a Merge pull request #3499 from JessonChan/develop
fix download filename(chinese) bug
2019-01-24 17:27:29 +08:00
dc07fa7085 cache: remove excessive type assertions
Assign type switch variable to get properly-typed value
inside case clauses.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>
2019-01-24 08:52:30 +03:00
1c893996c0 improve the download func code 2019-01-23 12:36:14 +08:00
a9ffc2a078 https://github.com/astaxie/beego/issues/3446
Use UTF-8 as the encoding of the "filename*" parameter, when
                           present, because at least one existing implementation only
                           implements that encoding.
2019-01-23 12:30:57 +08:00
712bbfe575 Merge pull request #3498 from JessonChan/develop
change gosimple to staticcheck
2019-01-23 11:31:37 +08:00
0145fe3486 remove SA1024 staticchek 2019-01-22 20:52:06 +08:00
2a579eb27c staticcheck checks -ST1003 remove 2019-01-22 20:41:07 +08:00
5b42afa324 modiyf staticcheck checks 2019-01-22 20:30:05 +08:00
97713849a1 delete stackcheck config file and ignore some staticcheck checks 2019-01-22 20:21:00 +08:00
abc9c38224 ignore some staticcheck checks 2019-01-22 20:04:30 +08:00
f237ff049a redis_sentinel test ignore 2019-01-22 19:45:32 +08:00
00264650b5 modify travis and redis_sentinel test 2019-01-22 19:29:53 +08:00
2956d33bab no need gosimple 2019-01-22 19:12:25 +08:00
c3eca637fb no need gosimple 2019-01-22 19:10:23 +08:00
0d54bbff02 make staticcheck happy 2019-01-22 19:09:57 +08:00
e65a9cbc00 Gosimple has been deprecated. Please use staticcheck instead. 2019-01-22 18:01:14 +08:00
3ed82c0882 Gosimple has been deprecated. Please use staticcheck instead. 2019-01-22 17:43:55 +08:00
475feb7e24 camel name style 2019-01-22 16:25:17 +08:00
30b80cba92 errors is better style 2019-01-22 16:23:10 +08:00
fe519bd2a0 update tls KeepAlive setting 2019-01-20 11:17:10 +08:00
f508f8d959 Nil check 2019-01-18 16:51:40 -08:00
e295c3c7c3 add shutdown log 2019-01-18 19:50:22 +08:00
313be996cd call cancel after shutdown 2019-01-18 19:33:45 +08:00
2ae480556d Add DBStats method wrapper to provide sql.DBStats when using ormer 2019-01-17 09:56:42 -08:00
7173fd7490 modify http graceful 2019-01-17 20:17:57 +08:00
d792536c23 Update orm_log.go
orm log支持用户自定义函数处理
2019-01-14 15:14:11 +08:00
80aabdd372 Merge pull request #3472 from sangheee/develop
fix: negative WaitGroup counter has not been resolved yet.
2019-01-08 23:54:40 +08:00
6892369cc6 Merge pull request #3464 from zhl11b/develop
手机号起始三位补全
2019-01-08 23:54:00 +08:00
5b80a56c36 Merge pull request #3468 from DennisMao/hotfix/UpdateDependencyGoes
update dependency
2019-01-08 23:52:39 +08:00
bf15535a5b fix panic: sync: negative WaitGroup counter 2019-01-03 22:44:32 +09:00
edb1c52dee fix function changes 2019-01-02 17:01:46 +08:00
6e16b8cdcf fix ci losing dependency 2019-01-02 16:46:27 +08:00
24215fb3eb update dependency 2019-01-02 15:37:41 +08:00
d02699a189 add new test case for china mobile phone 2018-12-30 20:51:21 +08:00
2034d1b101 联通171,175 电信173 2018-12-29 17:41:59 +08:00
5fe19d639f Merge pull request #3456 from japettyjohn/master
Fix bug in memcache sessions
2018-12-28 22:35:11 +08:00
1dea80d4ea Fixed error handling in memcache sessions 2018-12-27 16:20:26 -08:00
28f0008075 Merge pull request #3434 from zav8/add_sql_null_support
Add support for field of type sql.NullXxx in rawSet.setFieldValue()
2018-12-19 22:26:23 +08:00
ffe1b00baf Merge branch 'develop' of https://github.com/astaxie/beego into add_sql_null_support 2018-12-18 10:41:17 +08:00
d2c289193a add test case for QueryRow and QueryRows 2018-12-18 10:37:41 +08:00
f867583256 Merge pull request #3433 from DennisMao/FixOrmDescriptionTag
Fix Issue #3337
2018-12-18 09:20:36 +08:00
98dfb92e17 Merge pull request #3443 from zhiquan911/develop
add GetProvider
2018-12-18 09:10:17 +08:00
c8f22be675 Add redis sentinel session support (#3427)
Add Redis Sentinel session support
2018-12-17 14:47:47 +01:00
f03a7d1128 add GetProvider 2018-12-13 15:37:19 +08:00
6da4a66c20 merge switch cases 2018-12-06 16:09:39 +08:00
5e4241fc87 add support for field of type sql.NullXxx in rawSet.setFieldValue() 2018-12-06 16:07:07 +08:00
bf468c8d0c fix format 2018-12-06 10:57:32 +08:00
0d77a3f8d2 FixOrmDescrptionTag 2018-12-06 10:49:50 +08:00
143 changed files with 3324 additions and 14879 deletions

View File

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

View File

@ -1,17 +1,26 @@
language: go
go:
- "1.10.x"
- "1.11.x"
- "1.13.x"
services:
- redis-server
- mysql
- postgresql
- memcached
env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
global:
- GO_REPO_FULLNAME="github.com/astaxie/beego"
matrix:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
before_install:
# link the local repo with ${GOPATH}/src/<namespace>/<repo>
- GO_REPO_NAMESPACE=${GO_REPO_FULLNAME%/*}
# relies on GOPATH to contain only one directory...
- mkdir -p ${GOPATH}/src/${GO_REPO_NAMESPACE}
- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH}/src/${GO_REPO_FULLNAME}
- cd ${GOPATH}/src/${GO_REPO_FULLNAME}
# get and build ssdb
- git clone git://github.com/ideawu/ssdb.git
- cd ssdb
- make
@ -27,18 +36,19 @@ install:
- go get github.com/beego/goyaml2
- go get gopkg.in/yaml.v2
- go get github.com/belogik/goes
- go get github.com/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis
- go get github.com/ledisdb/ledisdb
- go get github.com/ssdb/gossdb/ssdb
- go get github.com/cloudflare/golz4
- go get github.com/gogo/protobuf/proto
- go get github.com/Knetic/govaluate
- go get github.com/casbin/casbin
- go get github.com/elazarl/go-bindata-assetfs
- go get -u honnef.co/go/tools/cmd/gosimple
- go get github.com/OwnLocal/goes
- go get github.com/shiena/ansicolor
- go get -u honnef.co/go/tools/cmd/staticcheck
- go get -u github.com/mdempsky/unconvert
- go get -u github.com/gordonklaus/ineffassign
- go get -u github.com/golang/lint/golint
- go get -u golang.org/x/lint/golint
- go get -u github.com/go-redis/redis
before_script:
- psql --version
@ -54,7 +64,7 @@ after_script:
- rm -rf ./res/var/*
script:
- go test -v ./...
- gosimple -ignore "$(cat .gosimpleignore)" $(go list ./... | grep -v /vendor/)
- staticcheck -show-ignored -checks "-ST1017,-U1000,-ST1005,-S1034,-S1012,-SA4006,-SA6005,-SA1019,-SA1024"
- unconvert $(go list ./... | grep -v /vendor/)
- ineffassign .
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s

View File

@ -4,12 +4,19 @@
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.
Response time ranking: [web-frameworks](https://github.com/the-benchmarker/web-frameworks).
###### More info at [beego.me](http://beego.me).
## Quick Start
#### Create `hello` directory, cd `hello` directory
mkdir hello
cd hello
#### Init module
go mod init
#### Download and install
go get github.com/astaxie/beego
@ -35,6 +42,8 @@ Congratulations! You've just built your first **beego** app.
###### Please see [Documentation](http://beego.me/docs) for more.
###### [beego-example](https://github.com/beego-dev/beego-example)
## Features
* RESTful support
@ -56,6 +65,7 @@ Congratulations! You've just built your first **beego** app.
* [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)
* QQ Group Group ID:523992905
## License

115
admin.go
View File

@ -21,9 +21,12 @@ import (
"net/http"
"os"
"reflect"
"strconv"
"text/template"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/toolbox"
@ -55,19 +58,21 @@ func init() {
beeAdminApp = &adminApp{
routers: make(map[string]http.HandlerFunc),
}
// keep in mind that all data should be html escaped to avoid XSS attack
beeAdminApp.Route("/", adminIndex)
beeAdminApp.Route("/qps", qpsIndex)
beeAdminApp.Route("/prof", profIndex)
beeAdminApp.Route("/healthcheck", healthcheck)
beeAdminApp.Route("/task", taskStatus)
beeAdminApp.Route("/listconf", listConf)
beeAdminApp.Route("/metrics", promhttp.Handler().ServeHTTP)
FilterMonitorFunc = func(string, string, time.Duration, string, int) bool { return true }
}
// AdminIndex is the default http.Handler for admin module.
// it matches url pattern "/".
func adminIndex(rw http.ResponseWriter, _ *http.Request) {
execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
writeTemplate(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
}
// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
@ -87,7 +92,7 @@ func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
}
}
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
writeTemplate(rw, data, qpsTpl, defaultScriptsTpl)
}
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
@ -105,8 +110,8 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
case "conf":
m := make(M)
list("BConfig", BConfig, m)
m["AppConfigPath"] = appConfigPath
m["AppConfigProvider"] = appConfigProvider
m["AppConfigPath"] = template.HTMLEscapeString(appConfigPath)
m["AppConfigProvider"] = template.HTMLEscapeString(appConfigProvider)
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(configTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
@ -124,7 +129,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
}
data["Content"] = content
data["Title"] = "Routers"
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
case "filter":
var (
content = M{
@ -151,8 +156,9 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
resultList := new([][]string)
for _, f := range bf {
var result = []string{
f.pattern,
utils.GetFuncName(f.filterFunc),
// void xss
template.HTMLEscapeString(f.pattern),
template.HTMLEscapeString(utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
@ -166,7 +172,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
data["Content"] = content
data["Title"] = "Filters"
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
default:
rw.Write([]byte("command not support"))
}
@ -207,8 +213,8 @@ func PrintTree() M {
printTree(resultList, t)
methods = append(methods, method)
methodsData[method] = resultList
methods = append(methods, template.HTMLEscapeString(method))
methodsData[template.HTMLEscapeString(method)] = resultList
}
content["Data"] = methodsData
@ -227,21 +233,21 @@ func printTree(resultList *[][]string, t *Tree) {
if v, ok := l.runObject.(*ControllerInfo); ok {
if v.routerType == routerTypeBeego {
var result = []string{
v.pattern,
fmt.Sprintf("%s", v.methods),
v.controllerType.String(),
template.HTMLEscapeString(v.pattern),
template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)),
template.HTMLEscapeString(v.controllerType.String()),
}
*resultList = append(*resultList, result)
} else if v.routerType == routerTypeRESTFul {
var result = []string{
v.pattern,
fmt.Sprintf("%s", v.methods),
template.HTMLEscapeString(v.pattern),
template.HTMLEscapeString(fmt.Sprintf("%s", v.methods)),
"",
}
*resultList = append(*resultList, result)
} else if v.routerType == routerTypeHandler {
var result = []string{
v.pattern,
template.HTMLEscapeString(v.pattern),
"",
"",
}
@ -266,7 +272,7 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
result bytes.Buffer
)
toolbox.ProcessInput(command, &result)
data["Content"] = result.String()
data["Content"] = template.HTMLEscapeString(result.String())
if format == "json" && command == "gc summary" {
dataJSON, err := json.Marshal(data)
@ -274,23 +280,21 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.Write(dataJSON)
writeJSON(rw, dataJSON)
return
}
data["Title"] = command
data["Title"] = template.HTMLEscapeString(command)
defaultTpl := defaultScriptsTpl
if command == "gc summary" {
defaultTpl = gcAjaxTpl
}
execTpl(rw, data, profillingTpl, defaultTpl)
writeTemplate(rw, data, profillingTpl, defaultTpl)
}
// Healthcheck is a http.Handler calling health checking and showing the result.
// it's in "/healthcheck" pattern in admin module.
func healthcheck(rw http.ResponseWriter, _ *http.Request) {
func healthcheck(rw http.ResponseWriter, r *http.Request) {
var (
result []string
data = make(map[interface{}]interface{})
@ -304,23 +308,62 @@ func healthcheck(rw http.ResponseWriter, _ *http.Request) {
if err := h.Check(); err != nil {
result = []string{
"error",
name,
err.Error(),
template.HTMLEscapeString(name),
template.HTMLEscapeString(err.Error()),
}
} else {
result = []string{
"success",
name,
template.HTMLEscapeString(name),
"OK",
}
}
*resultList = append(*resultList, result)
}
queryParams := r.URL.Query()
jsonFlag := queryParams.Get("json")
shouldReturnJSON, _ := strconv.ParseBool(jsonFlag)
if shouldReturnJSON {
response := buildHealthCheckResponseList(resultList)
jsonResponse, err := json.Marshal(response)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
} else {
writeJSON(rw, jsonResponse)
}
return
}
content["Data"] = resultList
data["Content"] = content
data["Title"] = "Health Check"
execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl)
}
func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} {
response := make([]map[string]interface{}, len(*healthCheckResults))
for i, healthCheckResult := range *healthCheckResults {
currentResultMap := make(map[string]interface{})
currentResultMap["name"] = healthCheckResult[0]
currentResultMap["message"] = healthCheckResult[1]
currentResultMap["status"] = healthCheckResult[2]
response[i] = currentResultMap
}
return response
}
func writeJSON(rw http.ResponseWriter, jsonData []byte) {
rw.Header().Set("Content-Type", "application/json")
rw.Write(jsonData)
}
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
@ -334,11 +377,11 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
if taskname != "" {
if t, ok := toolbox.AdminTaskList[taskname]; ok {
if err := t.Run(); err != nil {
data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
data["Message"] = []string{"error", template.HTMLEscapeString(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", template.HTMLEscapeString(fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus()))}
} else {
data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)}
data["Message"] = []string{"warning", template.HTMLEscapeString(fmt.Sprintf("there's no task which named: %s", taskname))}
}
}
@ -354,10 +397,10 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
}
for tname, tk := range toolbox.AdminTaskList {
result := []string{
tname,
tk.GetSpec(),
tk.GetStatus(),
tk.GetPrev().String(),
template.HTMLEscapeString(tname),
template.HTMLEscapeString(tk.GetSpec()),
template.HTMLEscapeString(tk.GetStatus()),
template.HTMLEscapeString(tk.GetPrev().String()),
}
*resultList = append(*resultList, result)
}
@ -366,10 +409,10 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
content["Data"] = resultList
data["Content"] = content
data["Title"] = "Tasks"
execTpl(rw, data, tasksTpl, defaultScriptsTpl)
writeTemplate(rw, data, tasksTpl, defaultScriptsTpl)
}
func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
for _, tpl := range tpls {
tmpl = template.Must(tmpl.Parse(tpl))

View File

@ -1,10 +1,32 @@
package beego
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"github.com/astaxie/beego/toolbox"
)
type SampleDatabaseCheck struct {
}
type SampleCacheCheck struct {
}
func (dc *SampleDatabaseCheck) Check() error {
return nil
}
func (cc *SampleCacheCheck) Check() error {
return errors.New("no cache detected")
}
func TestList_01(t *testing.T) {
m := make(M)
list("BConfig", BConfig, m)
@ -52,6 +74,8 @@ func oldMap() M {
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize
m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
@ -73,3 +97,143 @@ func oldMap() M {
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
return m
}
func TestWriteJSON(t *testing.T) {
t.Log("Testing the adding of JSON to the response")
w := httptest.NewRecorder()
originalBody := []int{1, 2, 3}
res, _ := json.Marshal(originalBody)
writeJSON(w, res)
decodedBody := []int{}
err := json.NewDecoder(w.Body).Decode(&decodedBody)
if err != nil {
t.Fatal("Could not decode response body into slice.")
}
for i := range decodedBody {
if decodedBody[i] != originalBody[i] {
t.Fatalf("Expected %d but got %d in decoded body slice", originalBody[i], decodedBody[i])
}
}
}
func TestHealthCheckHandlerDefault(t *testing.T) {
endpointPath := "/healthcheck"
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
req, err := http.NewRequest("GET", endpointPath, nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(healthcheck)
handler.ServeHTTP(w, req)
if status := w.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
if !strings.Contains(w.Body.String(), "database") {
t.Errorf("Expected 'database' in generated template.")
}
}
func TestBuildHealthCheckResponseList(t *testing.T) {
healthCheckResults := [][]string{
[]string{
"error",
"Database",
"Error occurred while starting the db",
},
[]string{
"success",
"Cache",
"Cache started successfully",
},
}
responseList := buildHealthCheckResponseList(&healthCheckResults)
if len(responseList) != len(healthCheckResults) {
t.Errorf("invalid response map length: got %d want %d",
len(responseList), len(healthCheckResults))
}
responseFields := []string{"name", "message", "status"}
for _, response := range responseList {
for _, field := range responseFields {
_, ok := response[field]
if !ok {
t.Errorf("expected %s to be in the response %v", field, response)
}
}
}
}
func TestHealthCheckHandlerReturnsJSON(t *testing.T) {
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
req, err := http.NewRequest("GET", "/healthcheck?json=true", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(healthcheck)
handler.ServeHTTP(w, req)
if status := w.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
decodedResponseBody := []map[string]interface{}{}
expectedResponseBody := []map[string]interface{}{}
expectedJSONString := []byte(`
[
{
"message":"database",
"name":"success",
"status":"OK"
},
{
"message":"cache",
"name":"error",
"status":"no cache detected"
}
]
`)
json.Unmarshal(expectedJSONString, &expectedResponseBody)
json.Unmarshal(w.Body.Bytes(), &decodedResponseBody)
if len(expectedResponseBody) != len(decodedResponseBody) {
t.Errorf("invalid response map length: got %d want %d",
len(decodedResponseBody), len(expectedResponseBody))
}
if !reflect.DeepEqual(decodedResponseBody, expectedResponseBody) {
t.Errorf("handler returned unexpected body: got %v want %v",
decodedResponseBody, expectedResponseBody)
}
}

15
app.go
View File

@ -123,14 +123,13 @@ func (app *App) Run(mws ...MiddleWare) {
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
app.Server.Addr = httpsAddr
}
server := grace.NewServer(httpsAddr, app.Handlers)
server := grace.NewServer(httpsAddr, app.Server.Handler)
server.Server.ReadTimeout = app.Server.ReadTimeout
server.Server.WriteTimeout = app.Server.WriteTimeout
if BConfig.Listen.EnableMutualHTTPS {
if err := server.ListenAndServeMutualTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile, BConfig.Listen.TrustCaFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
} else {
if BConfig.Listen.AutoTLS {
@ -145,14 +144,14 @@ func (app *App) Run(mws ...MiddleWare) {
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}
endRunning <- true
}()
}
if BConfig.Listen.EnableHTTP {
go func() {
server := grace.NewServer(addr, app.Handlers)
server := grace.NewServer(addr, app.Server.Handler)
server.Server.ReadTimeout = app.Server.ReadTimeout
server.Server.WriteTimeout = app.Server.WriteTimeout
if BConfig.Listen.ListenTCP4 {
@ -161,8 +160,8 @@ func (app *App) Run(mws ...MiddleWare) {
if err := server.ListenAndServe(); err != nil {
logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
endRunning <- true
}()
}
<-endRunning
@ -176,7 +175,7 @@ func (app *App) Run(mws ...MiddleWare) {
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, conflict with http. Please reset https port")
logs.Info("Start https server error, conflict with http. Please reset https port")
return
}
logs.Info("https server Running on https://%s", app.Server.Addr)
@ -192,13 +191,13 @@ func (app *App) Run(mws ...MiddleWare) {
pool := x509.NewCertPool()
data, err := ioutil.ReadFile(BConfig.Listen.TrustCaFile)
if err != nil {
BeeLogger.Info("MutualHTTPS should provide TrustCaFile")
logs.Info("MutualHTTPS should provide TrustCaFile")
return
}
pool.AppendCertsFromPEM(data)
app.Server.TLSConfig = &tls.Config{
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientAuth: BConfig.Listen.ClientAuth,
}
}
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {

View File

@ -23,7 +23,7 @@ import (
const (
// VERSION represent beego web framework version.
VERSION = "1.11.1"
VERSION = "1.12.3"
// DEV is for develop
DEV = "dev"

View File

@ -1,10 +1,10 @@
// Copyright 2014 beego Author. All Rights Reserved.
// Copyright 2020 astaxie
//
// 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
// 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,
@ -12,17 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !windows
package beego
package logs
var (
BuildVersion string
BuildGitRevision string
BuildStatus string
BuildTag string
BuildTime string
import "io"
GoVersion string
type ansiColorWriter struct {
w io.Writer
mode outputMode
}
func (cw *ansiColorWriter) Write(p []byte) (int, error) {
return cw.w.Write(p)
}
GitBranch string
)

25
cache/cache_test.go vendored
View File

@ -16,10 +16,33 @@ package cache
import (
"os"
"sync"
"testing"
"time"
)
func TestCacheIncr(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
t.Error("init err")
}
//timeoutDuration := 10 * time.Second
bm.Put("edwardhey", 0, time.Second*20)
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
bm.Incr("edwardhey")
}()
}
wg.Wait()
if bm.Get("edwardhey").(int) != 10 {
t.Error("Incr err")
}
}
func TestCache(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
@ -98,7 +121,7 @@ func TestCache(t *testing.T) {
}
func TestFileCache(t *testing.T) {
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
if err != nil {
t.Error("init err")
}

17
cache/file.go vendored
View File

@ -62,11 +62,14 @@ func NewFileCache() 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 {
var cfg map[string]string
json.Unmarshal([]byte(config), &cfg)
cfg := make(map[string]string)
err := json.Unmarshal([]byte(config), &cfg)
if err != nil {
return err
}
if _, ok := cfg["CachePath"]; !ok {
cfg["CachePath"] = FileCachePath
}
@ -142,12 +145,12 @@ func (fc *FileCache) GetMulti(keys []string) []interface{} {
// Put value into file cache.
// 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 fc.EmbedExpiry(default is 0), cache this item forever.
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
gob.Register(val)
item := FileCacheItem{Data: val}
if timeout == FileCacheEmbedExpiry {
if timeout == time.Duration(fc.EmbedExpiry) {
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
} else {
item.Expired = time.Now().Add(timeout)
@ -179,7 +182,7 @@ func (fc *FileCache) Incr(key string) error {
} else {
incr = data.(int) + 1
}
fc.Put(key, incr, FileCacheEmbedExpiry)
fc.Put(key, incr, time.Duration(fc.EmbedExpiry))
return nil
}
@ -192,7 +195,7 @@ func (fc *FileCache) Decr(key string) error {
} else {
decr = data.(int) - 1
}
fc.Put(key, decr, FileCacheEmbedExpiry)
fc.Put(key, decr, time.Duration(fc.EmbedExpiry))
return nil
}

View File

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

45
cache/memory.go vendored
View File

@ -110,25 +110,25 @@ func (bc *MemoryCache) Delete(name string) error {
// Incr increase cache counter in memory.
// it supports int,int32,int64,uint,uint32,uint64.
func (bc *MemoryCache) Incr(key string) error {
bc.RLock()
defer bc.RUnlock()
bc.Lock()
defer bc.Unlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
switch val := itm.val.(type) {
case int:
itm.val = itm.val.(int) + 1
itm.val = val + 1
case int32:
itm.val = itm.val.(int32) + 1
itm.val = val + 1
case int64:
itm.val = itm.val.(int64) + 1
itm.val = val + 1
case uint:
itm.val = itm.val.(uint) + 1
itm.val = val + 1
case uint32:
itm.val = itm.val.(uint32) + 1
itm.val = val + 1
case uint64:
itm.val = itm.val.(uint64) + 1
itm.val = val + 1
default:
return errors.New("item val is not (u)int (u)int32 (u)int64")
}
@ -137,34 +137,34 @@ func (bc *MemoryCache) Incr(key string) error {
// Decr decrease counter in memory.
func (bc *MemoryCache) Decr(key string) error {
bc.RLock()
defer bc.RUnlock()
bc.Lock()
defer bc.Unlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
switch val := itm.val.(type) {
case int:
itm.val = itm.val.(int) - 1
itm.val = val - 1
case int64:
itm.val = itm.val.(int64) - 1
itm.val = val - 1
case int32:
itm.val = itm.val.(int32) - 1
itm.val = val - 1
case uint:
if itm.val.(uint) > 0 {
itm.val = itm.val.(uint) - 1
if val > 0 {
itm.val = val - 1
} else {
return errors.New("item val is less than 0")
}
case uint32:
if itm.val.(uint32) > 0 {
itm.val = itm.val.(uint32) - 1
if val > 0 {
itm.val = val - 1
} else {
return errors.New("item val is less than 0")
}
case uint64:
if itm.val.(uint64) > 0 {
itm.val = itm.val.(uint64) - 1
if val > 0 {
itm.val = val - 1
} else {
return errors.New("item val is less than 0")
}
@ -218,9 +218,12 @@ func (bc *MemoryCache) vacuum() {
}
for {
<-time.After(bc.dur)
bc.RLock()
if bc.items == nil {
bc.RUnlock()
return
}
bc.RUnlock()
if keys := bc.expiredKeys(); len(keys) != 0 {
bc.clearItems(keys)
}

52
cache/redis/redis.go vendored
View File

@ -38,8 +38,9 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/astaxie/beego/cache"
"strings"
"github.com/astaxie/beego/cache"
)
var (
@ -55,6 +56,9 @@ type Cache struct {
key string
password string
maxIdle int
//the timeout to a value less than the redis server's timeout.
timeout time.Duration
}
// NewRedisCache create new redis cache with default collection name.
@ -137,12 +141,12 @@ func (rc *Cache) Decr(key string) error {
// ClearAll clean all cache in redis. delete this redis collection.
func (rc *Cache) ClearAll() error {
c := rc.p.Get()
defer c.Close()
cachedKeys, err := redis.Strings(c.Do("KEYS", rc.key+":*"))
cachedKeys, err := rc.Scan(rc.key + ":*")
if err != nil {
return err
}
c := rc.p.Get()
defer c.Close()
for _, str := range cachedKeys {
if _, err = c.Do("DEL", str); err != nil {
return err
@ -151,6 +155,35 @@ func (rc *Cache) ClearAll() error {
return err
}
// Scan scan all keys matching the pattern. a better choice than `keys`
func (rc *Cache) Scan(pattern string) (keys []string, err error) {
c := rc.p.Get()
defer c.Close()
var (
cursor uint64 = 0 // start
result []interface{}
list []string
)
for {
result, err = redis.Values(c.Do("SCAN", cursor, "MATCH", pattern, "COUNT", 1024))
if err != nil {
return
}
list, err = redis.Strings(result[1], nil)
if err != nil {
return
}
keys = append(keys, list...)
cursor, err = redis.Uint64(result[0], nil)
if err != nil {
return
}
if cursor == 0 { // over
return
}
}
}
// StartAndGC start redis cache adapter.
// config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
// the cache item in redis are stored forever,
@ -182,12 +215,21 @@ func (rc *Cache) StartAndGC(config string) error {
if _, ok := cf["maxIdle"]; !ok {
cf["maxIdle"] = "3"
}
if _, ok := cf["timeout"]; !ok {
cf["timeout"] = "180s"
}
rc.key = cf["key"]
rc.conninfo = cf["conn"]
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
rc.password = cf["password"]
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
if v, err := time.ParseDuration(cf["timeout"]); err == nil {
rc.timeout = v
} else {
rc.timeout = 180 * time.Second
}
rc.connectInit()
c := rc.p.Get()
@ -221,7 +263,7 @@ func (rc *Cache) connectInit() {
// initialize a new pool
rc.p = &redis.Pool{
MaxIdle: rc.maxIdle,
IdleTimeout: 180 * time.Second,
IdleTimeout: rc.timeout,
Dial: dialFunc,
}
}

View File

@ -15,6 +15,7 @@
package redis
import (
"fmt"
"testing"
"time"
@ -104,3 +105,40 @@ func TestRedisCache(t *testing.T) {
t.Error("clear all err")
}
}
func TestCache_Scan(t *testing.T) {
timeoutDuration := 10 * time.Second
// init
bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
if err != nil {
t.Error("init err")
}
// insert all
for i := 0; i < 10000; i++ {
if err = bm.Put(fmt.Sprintf("astaxie%d", i), fmt.Sprintf("author%d", i), timeoutDuration); err != nil {
t.Error("set Error", err)
}
}
// scan all for the first time
keys, err := bm.(*Cache).Scan(DefaultKey + ":*")
if err != nil {
t.Error("scan Error", err)
}
if len(keys) != 10000 {
t.Error("scan all err")
}
// clear all
if err = bm.ClearAll(); err != nil {
t.Error("clear all err")
}
// scan all for the second time
keys, err = bm.(*Cache).Scan(DefaultKey + ":*")
if err != nil {
t.Error("scan Error", err)
}
if len(keys) != 0 {
t.Error("scan all err")
}
}

View File

@ -15,7 +15,9 @@
package beego
import (
"crypto/tls"
"fmt"
"net/http"
"os"
"path/filepath"
"reflect"
@ -65,6 +67,7 @@ type Listen struct {
HTTPSCertFile string
HTTPSKeyFile string
TrustCaFile string
ClientAuth tls.ClientAuthType
EnableAdmin bool
AdminAddr string
AdminPort int
@ -81,6 +84,8 @@ type WebConfig struct {
DirectoryIndex bool
StaticDir map[string]string
StaticExtensionsToGzip []string
StaticCacheFileSize int
StaticCacheFileNum int
TemplateLeft string
TemplateRight string
ViewsPath string
@ -104,6 +109,7 @@ type SessionConfig struct {
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader string
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
SessionCookieSameSite http.SameSite
}
// LogConfig holds Log related config
@ -129,6 +135,8 @@ var (
appConfigPath string
// appConfigProvider is the provider for the config, default is ini
appConfigProvider = "ini"
// WorkPath is the absolute path to project root directory
WorkPath string
)
func init() {
@ -137,7 +145,7 @@ func init() {
if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
panic(err)
}
workPath, err := os.Getwd()
WorkPath, err = os.Getwd()
if err != nil {
panic(err)
}
@ -145,7 +153,10 @@ func init() {
if os.Getenv("BEEGO_RUNMODE") != "" {
filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
}
appConfigPath = filepath.Join(workPath, "conf", filename)
appConfigPath = filepath.Join(WorkPath, "conf", filename)
if configPath := os.Getenv("BEEGO_CONFIG_PATH"); configPath != "" {
appConfigPath = configPath
}
if !utils.FileExists(appConfigPath) {
appConfigPath = filepath.Join(AppPath, "conf", filename)
if !utils.FileExists(appConfigPath) {
@ -227,6 +238,7 @@ func newBConfig() *Config {
AdminPort: 8088,
EnableFcgi: false,
EnableStdIo: false,
ClientAuth: tls.RequireAndVerifyClientCert,
},
WebConfig: WebConfig{
AutoRender: true,
@ -236,6 +248,8 @@ func newBConfig() *Config {
DirectoryIndex: false,
StaticDir: map[string]string{"/static": "static"},
StaticExtensionsToGzip: []string{".css", ".js"},
StaticCacheFileSize: 1024 * 100,
StaticCacheFileNum: 1000,
TemplateLeft: "{{",
TemplateRight: "}}",
ViewsPath: "views",
@ -255,6 +269,7 @@ func newBConfig() *Config {
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader: "Beegosessionid",
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
SessionCookieSameSite: http.SameSiteDefaultMode,
},
},
Log: LogConfig{
@ -317,6 +332,14 @@ func assignConfig(ac config.Configer) error {
}
}
if sfs, err := ac.Int("StaticCacheFileSize"); err == nil {
BConfig.WebConfig.StaticCacheFileSize = sfs
}
if sfn, err := ac.Int("StaticCacheFileNum"); err == nil {
BConfig.WebConfig.StaticCacheFileNum = sfn
}
if lo := ac.String("LogOutputs"); lo != "" {
// if lo is not nil or empty
// means user has set his own LogOutputs
@ -408,9 +431,9 @@ func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, err
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)
return nil
}
func (b *beegoAppConfig) String(key string) string {

View File

@ -145,7 +145,7 @@ httpport = 8080
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
# support mysql,sqlserver
name = mysql
`
@ -161,7 +161,7 @@ httpport=8080
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
# support mysql,sqlserver
name=mysql
`
)

View File

@ -20,6 +20,7 @@ import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
)
@ -94,8 +95,10 @@ func (c *JSONConfigContainer) Int(key string) (int, error) {
if val != nil {
if v, ok := val.(float64); ok {
return int(v), nil
} else if v, ok := val.(string); ok {
return strconv.Atoi(v)
}
return 0, errors.New("not int value")
return 0, errors.New("not valid value")
}
return 0, errors.New("not exist key:" + key)
}

View File

@ -97,7 +97,7 @@ func parseYML(buf []byte) (cnf map[string]interface{}, err error) {
}
}
data, err := goyaml2.Read(bytes.NewBuffer(buf))
data, err := goyaml2.Read(bytes.NewReader(buf))
if err != nil {
log.Println("Goyaml2 ERR>", string(buf), err)
return
@ -296,7 +296,7 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
case map[string]interface{}:
{
tmpData = v.(map[string]interface{})
if idx == len(keys) - 1 {
if idx == len(keys)-1 {
return tmpData, nil
}
}

View File

@ -115,6 +115,8 @@ func TestAssignConfig_03(t *testing.T) {
ac.Set("RunMode", "online")
ac.Set("StaticDir", "download:down download2:down2")
ac.Set("StaticExtensionsToGzip", ".css,.js,.html,.jpg,.png")
ac.Set("StaticCacheFileSize", "87456")
ac.Set("StaticCacheFileNum", "1254")
assignConfig(ac)
t.Logf("%#v", BConfig)
@ -132,6 +134,12 @@ func TestAssignConfig_03(t *testing.T) {
if BConfig.WebConfig.StaticDir["/download2"] != "down2" {
t.FailNow()
}
if BConfig.WebConfig.StaticCacheFileSize != 87456 {
t.FailNow()
}
if BConfig.WebConfig.StaticCacheFileNum != 1254 {
t.FailNow()
}
if len(BConfig.WebConfig.StaticExtensionsToGzip) != 5 {
t.FailNow()
}

View File

@ -25,7 +25,7 @@ package context
import (
"bufio"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
@ -123,7 +123,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
h := hmac.New(sha256.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
@ -137,7 +137,7 @@ func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
h := hmac.New(sha256.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
@ -150,7 +150,7 @@ func (ctx *Context) XSRFToken(key string, expire int64) string {
token, ok := ctx.GetSecureCookie(key, "_xsrf")
if !ok {
token = string(utils.RandomCreateBytes(32))
ctx.SetSecureCookie(key, "_xsrf", token, expire)
ctx.SetSecureCookie(key, "_xsrf", token, expire, "", "", true, true)
}
ctx._xsrfToken = token
}
@ -169,11 +169,11 @@ func (ctx *Context) CheckXSRFCookie() bool {
token = ctx.Request.Header.Get("X-Csrftoken")
}
if token == "" {
ctx.Abort(403, "'_xsrf' argument missing from POST")
ctx.Abort(422, "422")
return false
}
if ctx._xsrfToken != token {
ctx.Abort(403, "XSRF cookie does not match POST argument")
ctx.Abort(417, "417")
return false
}
return true

View File

@ -17,7 +17,10 @@ package context
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestXsrfReset_01(t *testing.T) {
@ -44,4 +47,8 @@ func TestXsrfReset_01(t *testing.T) {
if token == c._xsrfToken {
t.FailNow()
}
ck := c.ResponseWriter.Header().Get("Set-Cookie")
assert.True(t, strings.Contains(ck, "Secure"))
assert.True(t, strings.Contains(ck, "HttpOnly"))
}

View File

@ -27,6 +27,7 @@ import (
"regexp"
"strconv"
"strings"
"sync"
"github.com/astaxie/beego/session"
)
@ -49,6 +50,7 @@ type BeegoInput struct {
pnames []string
pvalues []string
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
dataLock sync.RWMutex
RequestBody []byte
RunMethod string
RunController reflect.Type
@ -69,7 +71,9 @@ func (input *BeegoInput) Reset(ctx *Context) {
input.CruSession = nil
input.pnames = input.pnames[:0]
input.pvalues = input.pvalues[:0]
input.dataLock.Lock()
input.data = nil
input.dataLock.Unlock()
input.RequestBody = []byte{}
}
@ -85,7 +89,7 @@ func (input *BeegoInput) URI() string {
// URL returns request url path (without query string, fragment).
func (input *BeegoInput) URL() string {
return input.Context.Request.URL.Path
return input.Context.Request.URL.EscapedPath()
}
// Site returns base site url as scheme://domain type.
@ -204,6 +208,7 @@ func (input *BeegoInput) AcceptsXML() bool {
func (input *BeegoInput) AcceptsJSON() bool {
return acceptsJSONRegex.MatchString(input.Header("Accept"))
}
// AcceptsYAML Checks if request accepts json response
func (input *BeegoInput) AcceptsYAML() bool {
return acceptsYAMLRegex.MatchString(input.Header("Accept"))
@ -279,6 +284,11 @@ func (input *BeegoInput) ParamsLen() int {
func (input *BeegoInput) Param(key string) string {
for i, v := range input.pnames {
if v == key && i <= len(input.pvalues) {
// we cannot use url.PathEscape(input.pvalues[i])
// for example, if the value is /a/b
// after url.PathEscape(input.pvalues[i]), the value is %2Fa%2Fb
// However, the value is used in ControllerRegister.ServeHTTP
// and split by "/", so function crash...
return input.pvalues[i]
}
}
@ -323,8 +333,14 @@ func (input *BeegoInput) Query(key string) string {
return val
}
if input.Context.Request.Form == nil {
input.Context.Request.ParseForm()
input.dataLock.Lock()
if input.Context.Request.Form == nil {
input.Context.Request.ParseForm()
}
input.dataLock.Unlock()
}
input.dataLock.RLock()
defer input.dataLock.RUnlock()
return input.Context.Request.Form.Get(key)
}
@ -377,6 +393,8 @@ func (input *BeegoInput) CopyBody(MaxMemory int64) []byte {
// Data return the implicit data in the input
func (input *BeegoInput) Data() map[interface{}]interface{} {
input.dataLock.Lock()
defer input.dataLock.Unlock()
if input.data == nil {
input.data = make(map[interface{}]interface{})
}
@ -385,6 +403,8 @@ func (input *BeegoInput) Data() map[interface{}]interface{} {
// GetData returns the stored data in this context.
func (input *BeegoInput) GetData(key interface{}) interface{} {
input.dataLock.Lock()
defer input.dataLock.Unlock()
if v, ok := input.data[key]; ok {
return v
}
@ -394,6 +414,8 @@ func (input *BeegoInput) GetData(key interface{}) interface{} {
// SetData stores data with given key in this context.
// This data are only available in this context.
func (input *BeegoInput) SetData(key, val interface{}) {
input.dataLock.Lock()
defer input.dataLock.Unlock()
if input.data == nil {
input.data = make(map[interface{}]interface{})
}

View File

@ -205,3 +205,13 @@ func TestParams(t *testing.T) {
}
}
func BenchmarkQuery(b *testing.B) {
beegoInput := NewInput()
beegoInput.Context = NewContext()
beegoInput.Context.Request, _ = http.NewRequest("POST", "http://www.example.com/?q=foo", nil)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
beegoInput.Query("q")
}
})
}

View File

@ -30,7 +30,8 @@ import (
"strconv"
"strings"
"time"
"gopkg.in/yaml.v2"
yaml "gopkg.in/yaml.v2"
)
// BeegoOutput does work for sending response header.
@ -203,7 +204,6 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool)
return output.Body(content)
}
// YAML writes yaml to response body.
func (output *BeegoOutput) YAML(data interface{}) error {
output.Header("Content-Type", "application/x-yaml; charset=utf-8")
@ -288,7 +288,20 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
} else {
fName = filepath.Base(file)
}
output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
//https://tools.ietf.org/html/rfc6266#section-4.3
fn := url.PathEscape(fName)
if fName == fn {
fn = "filename=" + fn
} else {
/**
The parameters "filename" and "filename*" differ only in that
"filename*" uses the encoding defined in [RFC5987], allowing the use
of characters not present in the ISO-8859-1 character set
([ISO-8859-1]).
*/
fn = "filename=" + fName + "; filename*=utf-8''" + fn
}
output.Header("Content-Disposition", "attachment; "+fn)
output.Header("Content-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream")
output.Header("Content-Transfer-Encoding", "binary")

View File

@ -1,8 +1,10 @@
package param
import "testing"
import "reflect"
import "time"
import (
"reflect"
"testing"
"time"
)
type testDefinition struct {
strValue string

View File

@ -17,6 +17,7 @@ package beego
import (
"bytes"
"errors"
"fmt"
"html/template"
"io"
"mime/multipart"
@ -34,7 +35,7 @@ import (
var (
// ErrAbort custom error when user stop request handler manually.
ErrAbort = errors.New("User stop run")
ErrAbort = errors.New("user stop run")
// GlobalControllerRouter store comments with controller. pkgpath+controller:comments
GlobalControllerRouter = make(map[string][]ControllerComments)
)
@ -93,7 +94,6 @@ type Controller struct {
controllerName string
actionName string
methodMapping map[string]func() //method:routertree
gotofunc string
AppController interface{}
// template data
@ -125,6 +125,7 @@ type ControllerInterface interface {
Head()
Patch()
Options()
Trace()
Finish()
Render() error
XSRFToken() string
@ -156,37 +157,59 @@ func (c *Controller) Finish() {}
// Get adds a request function to handle GET request.
func (c *Controller) Get() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Post adds a request function to handle POST request.
func (c *Controller) Post() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Delete adds a request function to handle DELETE request.
func (c *Controller) Delete() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Put adds a request function to handle PUT request.
func (c *Controller) Put() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Head adds a request function to handle HEAD request.
func (c *Controller) Head() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Patch adds a request function to handle PATCH request.
func (c *Controller) Patch() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Options adds a request function to handle OPTIONS request.
func (c *Controller) Options() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", http.StatusMethodNotAllowed)
}
// Trace adds a request function to handle Trace request.
// this method SHOULD NOT be overridden.
// https://tools.ietf.org/html/rfc7231#section-4.3.8
// The TRACE method requests a remote, application-level loop-back of
// the request message. The final recipient of the request SHOULD
// reflect the message received, excluding some fields described below,
// back to the client as the message body of a 200 (OK) response with a
// Content-Type of "message/http" (Section 8.3.1 of [RFC7230]).
func (c *Controller) Trace() {
ts := func(h http.Header) (hs string) {
for k, v := range h {
hs += fmt.Sprintf("\r\n%s: %s", k, v)
}
return
}
hs := fmt.Sprintf("\r\nTRACE %s %s%s\r\n", c.Ctx.Request.RequestURI, c.Ctx.Request.Proto, ts(c.Ctx.Request.Header))
c.Ctx.Output.Header("Content-Type", "message/http")
c.Ctx.Output.Header("Content-Length", fmt.Sprint(len(hs)))
c.Ctx.Output.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Ctx.WriteString(hs)
}
// HandlerFunc call function with the name
@ -232,7 +255,7 @@ func (c *Controller) RenderString() (string, error) {
// RenderBytes returns the bytes of rendered template string. Do not send out response.
func (c *Controller) RenderBytes() ([]byte, error) {
buf, err := c.renderTemplate()
//if the controller has set layout, then first get the tplName's content set the content to the layout
// if the controller has set layout, then first get the tplName's content set the content to the layout
if err == nil && c.Layout != "" {
c.Data["LayoutContent"] = template.HTML(buf.String())
@ -292,7 +315,7 @@ func (c *Controller) viewPath() string {
// Redirect sends the redirection response to url with status code.
func (c *Controller) Redirect(url string, code int) {
logAccess(c.Ctx, nil, code)
LogAccess(c.Ctx, nil, code)
c.Ctx.Redirect(code, url)
}
@ -619,12 +642,13 @@ func (c *Controller) DelSession(name interface{}) {
// SessionRegenerateID regenerates session id for this session.
// the session data have no changes.
func (c *Controller) SessionRegenerateID() {
func (c *Controller) SessionRegenerateID() (err error) {
if c.CruSession != nil {
c.CruSession.SessionRelease(c.Ctx.ResponseWriter)
}
c.CruSession = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request)
c.CruSession, err = GlobalSessions.SessionRegenerateID(c.Ctx.ResponseWriter, c.Ctx.Request)
c.Ctx.Input.CruSession = c.CruSession
return
}
// DestroySession cleans session data and session cookie.

View File

@ -19,9 +19,10 @@ import (
"strconv"
"testing"
"github.com/astaxie/beego/context"
"os"
"path/filepath"
"github.com/astaxie/beego/context"
)
func TestGetInt(t *testing.T) {
@ -125,8 +126,8 @@ func TestGetUint64(t *testing.T) {
}
func TestAdditionalViewPaths(t *testing.T) {
dir1 := "_beeTmp"
dir2 := "_beeTmp2"
dir1 := tmpDir("TestAdditionalViewPaths1")
dir2 := tmpDir("TestAdditionalViewPaths2")
defer os.RemoveAll(dir1)
defer os.RemoveAll(dir2)

View File

@ -28,7 +28,7 @@ import (
)
const (
errorTypeHandler = iota
errorTypeHandler = iota
errorTypeController
)
@ -359,6 +359,20 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
)
}
// show 413 Payload Too Large
func payloadTooLarge(rw http.ResponseWriter, r *http.Request) {
responseError(rw, r,
413,
`<br>The page you have requested is unavailable.
<br>Perhaps you are here because:<br><br>
<ul>
<br>The request entity is larger than limits defined by server.
<br>Please change the request entity and try again.
</ul>
`,
)
}
func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := M{
@ -435,7 +449,7 @@ func exception(errCode string, ctx *context.Context) {
func executeError(err *errorInfo, ctx *context.Context, code int) {
//make sure to log the error in the access log
logAccess(ctx, nil, code)
LogAccess(ctx, nil, code)
if err.errorType == errorTypeHandler {
ctx.ResponseWriter.WriteHeader(code)

2
fs.go
View File

@ -42,13 +42,13 @@ func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.Wal
}
dir, err := fs.Open(path)
defer dir.Close()
if err != nil {
if err1 := walkFn(path, info, err); err1 != nil {
return err1
}
return err
}
defer dir.Close()
dirs, err := dir.Readdir(-1)
err1 := walkFn(path, info, err)
// If err != nil, walk can't walk into this directory.

30
go.mod
View File

@ -4,36 +4,38 @@ require (
github.com/Knetic/govaluate v3.0.0+incompatible // indirect
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
github.com/casbin/casbin v1.7.0
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c // indirect
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 // indirect
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
github.com/elastic/go-elasticsearch/v6 v6.8.5
github.com/elazarl/go-bindata-assetfs v1.0.0
github.com/go-redis/redis v6.14.2+incompatible
github.com/go-sql-driver/mysql v1.4.1
github.com/go-sql-driver/mysql v1.5.0
github.com/gogo/protobuf v1.1.1
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/gomodule/redigo v2.0.0+incompatible
github.com/hashicorp/golang-lru v0.5.4
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6
github.com/lib/pq v1.0.0
github.com/mattn/go-sqlite3 v1.10.0
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pkg/errors v0.8.0 // indirect
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
github.com/stretchr/testify v1.4.0
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect
gopkg.in/yaml.v2 v2.2.1
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
gopkg.in/yaml.v2 v2.2.8
)
replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85
replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d
go 1.13

189
go.sum
View File

@ -1,67 +1,210 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff h1:/kO0p2RTGLB8R5gub7ps0GmYpB2O8LXEoPq8tzFDCUI=
github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb h1:w3RapLhkA5+km9Z8vUkC6VCaskduJXvXwJg5neKnfDU=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c h1:K4FIibkr4//ziZKOKmt4RL0YImuTjLLBtwElf+F2lSQ=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d h1:OMrhQqj1QCyDT2sxHCDjE+k8aMdn2ngTCGG7g4wrdLo=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 h1:8s2l8TVUwMXl6tZMe3+hPCRJ25nQXiA3d1x622JtOqc=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+niDAlj5M8TK8=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d h1:xy93KVe+KrIIwWDEAfQBdIfsiHJkepbYsDr+VY3g9/o=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:B7ZbAFz7NOmvpUE5RGtu3u0WIizy5GdvbNpEf4RPnWs=
github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:uZvAcrsnNaCxlh1HorK5dUQHGmEKPh2H/Rl1kehswPo=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 h1:wxyqOzKxsRJ6vVRL9sXQ64Z45wmBuQ+OTH9sLsC5rKc=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 h1:p6IxqQMjab30l4lb9mmkIkkcE1yv6o0SKbPhW5pxqHI=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c h1:3eGShk3EQf5gJCYW+WzA0TEJQd37HLOmlYF7N0YJwv0=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -1,39 +0,0 @@
package grace
import (
"errors"
"net"
"sync"
)
type graceConn struct {
net.Conn
server *Server
m sync.Mutex
closed bool
}
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.m.Lock()
if c.closed {
c.m.Unlock()
return
}
c.server.wg.Done()
c.closed = true
c.m.Unlock()
return c.Conn.Close()
}

View File

@ -78,7 +78,7 @@ var (
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 is the Max HTTP Header size, default is 0, no limit
DefaultMaxHeaderBytes int
// DefaultTimeout is the shutdown server's timeout. default is 60s
DefaultTimeout = 60 * time.Second
@ -122,7 +122,6 @@ func NewServer(addr string, handler http.Handler) (srv *Server) {
}
srv = &Server{
wg: sync.WaitGroup{},
sigChan: make(chan os.Signal),
isChild: isChild,
SignalHooks: map[int]map[os.Signal][]func(){
@ -137,20 +136,21 @@ func NewServer(addr string, handler http.Handler) (srv *Server) {
syscall.SIGTERM: {},
},
},
state: StateInit,
Network: "tcp",
state: StateInit,
Network: "tcp",
terminalChan: make(chan error), //no cache channel
}
srv.Server = &http.Server{
Addr: addr,
ReadTimeout: DefaultReadTimeOut,
WriteTimeout: DefaultWriteTimeOut,
MaxHeaderBytes: DefaultMaxHeaderBytes,
Handler: handler,
}
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
return srv
}
// ListenAndServe refer http.ListenAndServe

View File

@ -1,62 +0,0 @@
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
}

View File

@ -1,6 +1,7 @@
package grace
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
@ -12,7 +13,6 @@ import (
"os/exec"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
@ -20,14 +20,13 @@ import (
// 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
ln net.Listener
SignalHooks map[int]map[os.Signal][]func()
sigChan chan os.Signal
isChild bool
state uint8
Network string
terminalChan chan error
}
// Serve accepts incoming connections on the Listener l,
@ -35,10 +34,21 @@ type Server struct {
// 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
defer func() { srv.state = StateTerminate }()
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
// immediately return ErrServerClosed. Make sure the program doesn't exit
// and waits instead for Shutdown to return.
if err = srv.Server.Serve(srv.ln); err != nil && err != http.ErrServerClosed {
log.Println(syscall.Getpid(), "Server.Serve() error:", err)
return err
}
log.Println(syscall.Getpid(), srv.ln.Addr(), "Listener closed.")
// wait for Shutdown to return
if shutdownErr := <-srv.terminalChan; shutdownErr != nil {
return shutdownErr
}
return
}
@ -53,14 +63,12 @@ func (srv *Server) ListenAndServe() (err error) {
go srv.handleSignals()
l, err := srv.getListener(addr)
srv.ln, 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 {
@ -107,14 +115,12 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
go srv.handleSignals()
l, err := srv.getListener(addr)
ln, 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)
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
if srv.isChild {
process, err := os.FindProcess(os.Getppid())
@ -127,6 +133,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) {
return err
}
}
log.Println(os.Getpid(), srv.Addr)
return srv.Serve()
}
@ -163,14 +170,12 @@ func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string)
log.Println("Mutual HTTPS")
go srv.handleSignals()
l, err := srv.getListener(addr)
ln, 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)
srv.ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig)
if srv.isChild {
process, err := os.FindProcess(os.Getppid())
@ -178,11 +183,12 @@ func (srv *Server) ListenAndServeMutualTLS(certFile, keyFile, trustFile string)
log.Println(err)
return err
}
err = process.Kill()
err = process.Signal(syscall.SIGTERM)
if err != nil {
return err
}
}
log.Println(os.Getpid(), srv.Addr)
return srv.Serve()
}
@ -213,6 +219,20 @@ func (srv *Server) getListener(laddr string) (l net.Listener, err error) {
return
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
// handleSignals listens for os Signals and calls any hooked in function that the
// user had registered with the signal.
func (srv *Server) handleSignals() {
@ -265,37 +285,14 @@ func (srv *Server) shutdown() {
}
srv.state = StateShuttingDown
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
ctx := context.Background()
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()
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
}
srv.terminalChan <- srv.Server.Shutdown(ctx)
}
func (srv *Server) fork() (err error) {
@ -309,12 +306,8 @@ func (srv *Server) fork() (err error) {
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()
}
f, _ := srvPtr.ln.(*net.TCPListener).File()
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = f
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
}

View File

@ -34,6 +34,7 @@ func registerDefaultErrorHandler() error {
"504": gatewayTimeout,
"417": invalidxsrf,
"422": missingxsrf,
"413": payloadTooLarge,
}
for e, h := range m {
if _, ok := ErrorMaps[e]; !ok {
@ -60,6 +61,7 @@ func registerSession() error {
conf.EnableSidInHTTPHeader = BConfig.WebConfig.Session.SessionEnableSidInHTTPHeader
conf.SessionNameInHTTPHeader = BConfig.WebConfig.Session.SessionNameInHTTPHeader
conf.EnableSidInURLQuery = BConfig.WebConfig.Session.SessionEnableSidInURLQuery
conf.CookieSameSite = BConfig.WebConfig.Session.SessionCookieSameSite
} else {
if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil {
return err

View File

@ -47,9 +47,11 @@ import (
"net/http/httputil"
"net/url"
"os"
"path"
"strings"
"sync"
"time"
"gopkg.in/yaml.v2"
)
@ -142,6 +144,7 @@ type BeegoHTTPSettings struct {
Gzip bool
DumpBody bool
Retries int // if set to -1 means will retry forever
RetryDelay time.Duration
}
// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request.
@ -200,6 +203,11 @@ func (b *BeegoHTTPRequest) Retries(times int) *BeegoHTTPRequest {
return b
}
func (b *BeegoHTTPRequest) RetryDelay(delay time.Duration) *BeegoHTTPRequest {
b.setting.RetryDelay = delay
return b
}
// DumpBody setting whether need to Dump the Body.
func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest {
b.setting.DumpBody = isdump
@ -405,6 +413,7 @@ func (b *BeegoHTTPRequest) buildURL(paramBody string) {
}()
b.Header("Content-Type", bodyWriter.FormDataContentType())
b.req.Body = ioutil.NopCloser(pr)
b.Header("Transfer-Encoding", "chunked")
return
}
@ -509,11 +518,13 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
// retries default value is 0, it will run once.
// retries equal to -1, it will run forever until success
// retries is setted, it will retries fixed times.
// Sleeps for a 400ms in between calls to reduce spam
for i := 0; b.setting.Retries == -1 || i <= b.setting.Retries; i++ {
resp, err = client.Do(b.req)
if err == nil {
break
}
time.Sleep(b.setting.RetryDelay)
}
return resp, err
}
@ -558,12 +569,6 @@ func (b *BeegoHTTPRequest) Bytes() ([]byte, error) {
// ToFile saves the body data in response to one file.
// it calls Response inner.
func (b *BeegoHTTPRequest) ToFile(filename string) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
resp, err := b.getResponse()
if err != nil {
return err
@ -572,10 +577,35 @@ func (b *BeegoHTTPRequest) ToFile(filename string) error {
return nil
}
defer resp.Body.Close()
err = pathExistAndMkdir(filename)
if err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
//Check that the file directory exists, there is no automatically created
func pathExistAndMkdir(filename string) (err error) {
filename = path.Dir(filename)
_, err = os.Stat(filename)
if err == nil {
return nil
}
if os.IsNotExist(err) {
err = os.MkdirAll(filename, os.ModePerm)
if err == nil {
return nil
}
}
return err
}
// ToJSON returns the map that marshals from the body bytes as json in response .
// it calls Response inner.
func (b *BeegoHTTPRequest) ToJSON(v interface{}) error {

View File

@ -15,6 +15,7 @@
package httplib
import (
"errors"
"io/ioutil"
"net"
"net/http"
@ -33,6 +34,34 @@ func TestResponse(t *testing.T) {
t.Log(resp)
}
func TestDoRequest(t *testing.T) {
req := Get("https://goolnk.com/33BD2j")
retryAmount := 1
req.Retries(1)
req.RetryDelay(1400 * time.Millisecond)
retryDelay := 1400 * time.Millisecond
req.setting.CheckRedirect = func(redirectReq *http.Request, redirectVia []*http.Request) error {
return errors.New("Redirect triggered")
}
startTime := time.Now().UnixNano() / int64(time.Millisecond)
_, err := req.Response()
if err == nil {
t.Fatal("Response should have yielded an error")
}
endTime := time.Now().UnixNano() / int64(time.Millisecond)
elapsedTime := endTime - startTime
delayedTime := int64(retryAmount) * retryDelay.Milliseconds()
if elapsedTime < delayedTime {
t.Errorf("Not enough retries. Took %dms. Delay was meant to take %dms", elapsedTime, delayedTime)
}
}
func TestGet(t *testing.T) {
req := Get("http://httpbin.org/get")
b, err := req.Bytes()
@ -206,10 +235,16 @@ func TestToJson(t *testing.T) {
t.Fatal(err)
}
t.Log(ip.Origin)
if n := strings.Count(ip.Origin, "."); n != 3 {
ips := strings.Split(ip.Origin, ",")
if len(ips) == 0 {
t.Fatal("response is not valid ip")
}
for i := range ips {
if net.ParseIP(strings.TrimSpace(ips[i])).To4() == nil {
t.Fatal("response is not valid ip")
}
}
}
func TestToFile(t *testing.T) {
@ -226,6 +261,20 @@ func TestToFile(t *testing.T) {
}
}
func TestToFileDir(t *testing.T) {
f := "./files/beego_testfile"
req := Get("http://httpbin.org/ip")
err := req.ToFile(f)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll("./files")
b, err := ioutil.ReadFile(f)
if n := strings.Index(string(b), "origin"); n == -1 {
t.Fatal(err)
}
}
func TestHeader(t *testing.T) {
req := Get("http://httpbin.org/headers")
req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36")

16
log.go
View File

@ -21,6 +21,7 @@ import (
)
// Log levels to control the logging output.
// Deprecated: use github.com/astaxie/beego/logs instead.
const (
LevelEmergency = iota
LevelAlert
@ -33,75 +34,90 @@ const (
)
// BeeLogger references the used application logger.
// Deprecated: use github.com/astaxie/beego/logs instead.
var BeeLogger = logs.GetBeeLogger()
// SetLevel sets the global log level used by the simple logger.
// Deprecated: use github.com/astaxie/beego/logs instead.
func SetLevel(l int) {
logs.SetLevel(l)
}
// SetLogFuncCall set the CallDepth, default is 3
// Deprecated: use github.com/astaxie/beego/logs instead.
func SetLogFuncCall(b bool) {
logs.SetLogFuncCall(b)
}
// SetLogger sets a new logger.
// Deprecated: use github.com/astaxie/beego/logs instead.
func SetLogger(adaptername string, config string) error {
return logs.SetLogger(adaptername, config)
}
// Emergency logs a message at emergency level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Emergency(v ...interface{}) {
logs.Emergency(generateFmtStr(len(v)), v...)
}
// Alert logs a message at alert level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Alert(v ...interface{}) {
logs.Alert(generateFmtStr(len(v)), v...)
}
// Critical logs a message at critical level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Critical(v ...interface{}) {
logs.Critical(generateFmtStr(len(v)), v...)
}
// Error logs a message at error level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Error(v ...interface{}) {
logs.Error(generateFmtStr(len(v)), v...)
}
// Warning logs a message at warning level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Warning(v ...interface{}) {
logs.Warning(generateFmtStr(len(v)), v...)
}
// Warn compatibility alias for Warning()
// Deprecated: use github.com/astaxie/beego/logs instead.
func Warn(v ...interface{}) {
logs.Warn(generateFmtStr(len(v)), v...)
}
// Notice logs a message at notice level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Notice(v ...interface{}) {
logs.Notice(generateFmtStr(len(v)), v...)
}
// Informational logs a message at info level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Informational(v ...interface{}) {
logs.Informational(generateFmtStr(len(v)), v...)
}
// Info compatibility alias for Warning()
// Deprecated: use github.com/astaxie/beego/logs instead.
func Info(v ...interface{}) {
logs.Info(generateFmtStr(len(v)), v...)
}
// Debug logs a message at debug level.
// Deprecated: use github.com/astaxie/beego/logs instead.
func Debug(v ...interface{}) {
logs.Debug(generateFmtStr(len(v)), v...)
}
// Trace logs a message at trace level.
// compatibility alias for Warning()
// Deprecated: use github.com/astaxie/beego/logs instead.
func Trace(v ...interface{}) {
logs.Trace(generateFmtStr(len(v)), v...)
}

View File

@ -16,9 +16,9 @@ package logs
import (
"bytes"
"strings"
"encoding/json"
"fmt"
"strings"
"time"
)

View File

@ -1,428 +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.
// +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) {
var r, nw, first, last int
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
}

View File

@ -1,294 +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.
// +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

@ -63,7 +63,10 @@ func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
defer c.innerWriter.Close()
}
c.lg.println(when, msg)
_, err := c.lg.writeln(when, msg)
if err != nil {
return err
}
return nil
}
@ -101,7 +104,6 @@ func (c *connWriter) connect() error {
func (c *connWriter) needToConnectOnMsg() bool {
if c.Reconnect {
c.Reconnect = false
return true
}

View File

@ -15,11 +15,65 @@
package logs
import (
"net"
"os"
"testing"
)
// ConnTCPListener takes a TCP listener and accepts n TCP connections
// Returns connections using connChan
func connTCPListener(t *testing.T, n int, ln net.Listener, connChan chan<- net.Conn) {
// Listen and accept n incoming connections
for i := 0; i < n; i++ {
conn, err := ln.Accept()
if err != nil {
t.Log("Error accepting connection: ", err.Error())
os.Exit(1)
}
// Send accepted connection to channel
connChan <- conn
}
ln.Close()
close(connChan)
}
func TestConn(t *testing.T) {
log := NewLogger(1000)
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Informational("informational")
}
func TestReconnect(t *testing.T) {
// Setup connection listener
newConns := make(chan net.Conn)
connNum := 2
ln, err := net.Listen("tcp", ":6002")
if err != nil {
t.Log("Error listening:", err.Error())
os.Exit(1)
}
go connTCPListener(t, connNum, ln, newConns)
// Setup logger
log := NewLogger(1000)
log.SetPrefix("test")
log.SetLogger(AdapterConn, `{"net":"tcp","reconnect":true,"level":6,"addr":":6002"}`)
log.Informational("informational 1")
// Refuse first connection
first := <-newConns
first.Close()
// Send another log after conn closed
log.Informational("informational 2")
// Check if there was a second connection attempt
select {
case second := <-newConns:
second.Close()
default:
t.Error("Did not reconnect")
}
}

View File

@ -17,8 +17,10 @@ package logs
import (
"encoding/json"
"os"
"runtime"
"strings"
"time"
"github.com/shiena/ansicolor"
)
// brush is a color join function
@ -54,9 +56,9 @@ type consoleWriter struct {
// NewConsole create ConsoleWriter returning as LoggerInterface.
func NewConsole() Logger {
cw := &consoleWriter{
lg: newLogWriter(os.Stdout),
lg: newLogWriter(ansicolor.NewAnsiColorWriter(os.Stdout)),
Level: LevelDebug,
Colorful: runtime.GOOS != "windows",
Colorful: true,
}
return cw
}
@ -67,11 +69,7 @@ func (c *consoleWriter) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
return nil
}
err := json.Unmarshal([]byte(jsonConfig), c)
if runtime.GOOS == "windows" {
c.Colorful = false
}
return err
return json.Unmarshal([]byte(jsonConfig), c)
}
// WriteMsg write message in console.
@ -80,9 +78,9 @@ func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
return nil
}
if c.Colorful {
msg = colors[level](msg)
msg = strings.Replace(msg, levelPrefix[level], colors[level](levelPrefix[level]), 1)
}
c.lg.println(when, msg)
c.lg.writeln(when, msg)
return nil
}

View File

@ -16,6 +16,7 @@ package logs
import (
"testing"
"time"
)
// Try each log level in decreasing order of priority.
@ -49,3 +50,15 @@ func TestConsoleNoColor(t *testing.T) {
log.SetLogger("console", `{"color":false}`)
testConsoleCalls(log)
}
// Test console async
func TestConsoleAsync(t *testing.T) {
log := NewLogger(100)
log.SetLogger("console")
log.Async()
//log.Close()
testConsoleCalls(log)
for len(log.msgChan) != 0 {
time.Sleep(1 * time.Millisecond)
}
}

View File

@ -1,15 +1,18 @@
package es
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"strings"
"time"
"github.com/elastic/go-elasticsearch/v6"
"github.com/elastic/go-elasticsearch/v6/esapi"
"github.com/astaxie/beego/logs"
"github.com/belogik/goes"
)
// NewES return a LoggerInterface
@ -20,8 +23,14 @@ func NewES() logs.Logger {
return cw
}
// esLogger will log msg into ES
// before you using this implementation,
// please import this package
// usually means that you can import this package in your main package
// for example, anonymous:
// import _ "github.com/astaxie/beego/logs/es"
type esLogger struct {
*goes.Connection
*elasticsearch.Client
DSN string `json:"dsn"`
Level int `json:"level"`
}
@ -38,11 +47,14 @@ func (el *esLogger) Init(jsonconfig string) error {
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
conn, err := elasticsearch.NewClient(elasticsearch.Config{
Addresses: []string{el.DSN},
})
if err != nil {
return err
}
el.Client = conn
}
return nil
}
@ -53,21 +65,26 @@ func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
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,
idx := LogDocument{
Timestamp: when.Format(time.RFC3339),
Msg: msg,
}
_, err := el.Index(d, nil)
body, err := json.Marshal(idx)
if err != nil {
return err
}
req := esapi.IndexRequest{
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
DocumentType: "logs",
Body: strings.NewReader(string(body)),
}
_, err = req.Do(context.Background(), el.Client)
return err
}
// Destroy is a empty method
func (el *esLogger) Destroy() {
}
// Flush is a empty method
@ -75,6 +92,11 @@ func (el *esLogger) Flush() {
}
type LogDocument struct {
Timestamp string `json:"timestamp"`
Msg string `json:"msg"`
}
func init() {
logs.Register(logs.AdapterEs, NewES)
}

View File

@ -359,6 +359,10 @@ RESTART_LOGGER:
func (w *fileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
absolutePath, err := filepath.EvalSymlinks(w.Filename)
if err == nil {
dir = filepath.Dir(absolutePath)
}
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
@ -369,21 +373,21 @@ func (w *fileLogWriter) deleteOldLog() {
if info == nil {
return
}
if w.Hourly {
if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
} else if w.Daily {
if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
}
if w.Hourly {
if !info.IsDir() && info.ModTime().Add(1*time.Hour*time.Duration(w.MaxHours)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
} else if w.Daily {
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path)
}
}
}
return
})
}

View File

@ -186,7 +186,7 @@ func TestFileDailyRotate_06(t *testing.T) { //test file mode
func TestFileHourlyRotate_01(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
log.SetLogger("file", `{"filename":"test3.log","hourly":true,"maxlines":4}`)
log.Debug("debug")
log.Info("info")
log.Notice("notice")
@ -237,7 +237,7 @@ func TestFileHourlyRotate_05(t *testing.T) {
func TestFileHourlyRotate_06(t *testing.T) { //test file mode
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
log.SetLogger("file", `{"filename":"test3.log", "hourly":true, "maxlines":4}`)
log.Debug("debug")
log.Info("info")
log.Notice("notice")
@ -269,19 +269,19 @@ func testFileRotate(t *testing.T, fn1, fn2 string, daily, hourly bool) {
RotatePerm: "0440",
}
if daily {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day()
}
if daily {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxdays":1}`, fn1))
fw.dailyOpenTime = time.Now().Add(-24 * time.Hour)
fw.dailyOpenDate = fw.dailyOpenTime.Day()
}
if hourly {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
}
if hourly {
fw.Init(fmt.Sprintf(`{"filename":"%v","maxhours":1}`, fn1))
fw.hourlyOpenTime = time.Now().Add(-1 * time.Hour)
fw.hourlyOpenDate = fw.hourlyOpenTime.Day()
}
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
fw.WriteMsg(time.Now(), "this is a msg for test", LevelDebug)
for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file)
@ -328,8 +328,8 @@ func testFileDailyRotate(t *testing.T, fn1, fn2 string) {
func testFileHourlyRotate(t *testing.T, fn1, fn2 string) {
fw := &fileLogWriter{
Hourly: true,
MaxHours: 168,
Hourly: true,
MaxHours: 168,
Rotate: true,
Level: LevelTrace,
Perm: "0660",

View File

@ -47,7 +47,7 @@ import (
// RFC5424 log message levels.
const (
LevelEmergency = iota
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
@ -92,7 +92,7 @@ type Logger interface {
}
var adapters = make(map[string]newLoggerFunc)
var levelPrefix = [LevelDebug + 1]string{"[M] ", "[A] ", "[C] ", "[E] ", "[W] ", "[N] ", "[I] ", "[D] "}
var levelPrefix = [LevelDebug + 1]string{"[M]", "[A]", "[C]", "[E]", "[W]", "[N]", "[I]", "[D]"}
// Register makes a log provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
@ -187,12 +187,12 @@ func (bl *BeeLogger) setLogger(adapterName string, configs ...string) error {
}
}
log, ok := adapters[adapterName]
logAdapter, ok := adapters[adapterName]
if !ok {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
}
lg := log()
lg := logAdapter()
err := lg.Init(config)
if err != nil {
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
@ -248,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
}
// writeMsg will always add a '\n' character
if p[len(p)-1] == '\n' {
p = p[0: len(p)-1]
p = p[0 : len(p)-1]
}
// set levelLoggerImpl to ensure all log message will be write out
err = bl.writeMsg(levelLoggerImpl, string(p))
@ -287,7 +287,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
// set to emergency to ensure all log will be print out correctly
logLevel = LevelEmergency
} else {
msg = levelPrefix[logLevel] + msg
msg = levelPrefix[logLevel] + " " + msg
}
if bl.asynchronous {
@ -295,7 +295,11 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
lm.level = logLevel
lm.msg = msg
lm.when = when
bl.msgChan <- lm
if bl.outputs != nil {
bl.msgChan <- lm
} else {
logMsgPool.Put(lm)
}
} else {
bl.writeToLoggers(when, msg, logLevel)
}

View File

@ -15,9 +15,8 @@
package logs
import (
"fmt"
"io"
"os"
"runtime"
"sync"
"time"
)
@ -31,45 +30,12 @@ func newLogWriter(wr io.Writer) *logWriter {
return &logWriter{writer: wr}
}
func (lg *logWriter) println(when time.Time, msg string) {
func (lg *logWriter) writeln(when time.Time, msg string) (int, error) {
lg.Lock()
h, _, _:= formatTimeHeader(when)
lg.writer.Write(append(append(h, msg...), '\n'))
h, _, _ := formatTimeHeader(when)
n, err := 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
return n, err
}
const (
@ -146,63 +112,65 @@ var (
reset = string([]byte{27, 91, 48, 109})
)
var once sync.Once
var colorMap map[string]string
func initColor() {
if runtime.GOOS == "windows" {
green = w32Green
white = w32White
yellow = w32Yellow
red = w32Red
blue = w32Blue
magenta = w32Magenta
cyan = w32Cyan
}
colorMap = map[string]string{
//by color
"green": green,
"white": white,
"yellow": yellow,
"red": red,
//by method
"GET": blue,
"POST": cyan,
"PUT": yellow,
"DELETE": red,
"PATCH": green,
"HEAD": magenta,
"OPTIONS": white,
}
}
// ColorByStatus return color by http code
// 2xx return Green
// 3xx return White
// 4xx return Yellow
// 5xx return Red
func ColorByStatus(cond bool, code int) string {
func ColorByStatus(code int) string {
once.Do(initColor)
switch {
case code >= 200 && code < 300:
return map[bool]string{true: green, false: w32Green}[cond]
return colorMap["green"]
case code >= 300 && code < 400:
return map[bool]string{true: white, false: w32White}[cond]
return colorMap["white"]
case code >= 400 && code < 500:
return map[bool]string{true: yellow, false: w32Yellow}[cond]
return colorMap["yellow"]
default:
return map[bool]string{true: red, false: w32Red}[cond]
return colorMap["red"]
}
}
// ColorByMethod return color by http code
// GET return Blue
// POST return Cyan
// PUT return Yellow
// DELETE return Red
// PATCH return Green
// HEAD return Magenta
// OPTIONS return WHITE
func ColorByMethod(cond bool, method string) string {
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
func ColorByMethod(method string) string {
once.Do(initColor)
if c := colorMap[method]; c != "" {
return c
}
return reset
}
// Guard Mutex to guarantee atomic of W32Debug(string) function
var mu sync.Mutex
// W32Debug 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)
// ResetColor return reset color
func ResetColor() string {
return reset
}

View File

@ -15,7 +15,6 @@
package logs
import (
"bytes"
"testing"
"time"
)
@ -56,20 +55,3 @@ func TestFormatHeader_1(t *testing.T) {
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)
}
}

99
metric/prometheus.go Normal file
View File

@ -0,0 +1,99 @@
// Copyright 2020 astaxie
//
// 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 metric
import (
"net/http"
"reflect"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
)
func PrometheusMiddleWare(next http.Handler) http.Handler {
summaryVec := prometheus.NewSummaryVec(prometheus.SummaryOpts{
Name: "beego",
Subsystem: "http_request",
ConstLabels: map[string]string{
"server": beego.BConfig.ServerName,
"env": beego.BConfig.RunMode,
"appname": beego.BConfig.AppName,
},
Help: "The statics info for http request",
}, []string{"pattern", "method", "status"})
prometheus.MustRegister(summaryVec)
registerBuildInfo()
return http.HandlerFunc(func(writer http.ResponseWriter, q *http.Request) {
start := time.Now()
next.ServeHTTP(writer, q)
end := time.Now()
go report(end.Sub(start), writer, q, summaryVec)
})
}
func registerBuildInfo() {
buildInfo := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "beego",
Subsystem: "build_info",
Help: "The building information",
ConstLabels: map[string]string{
"appname": beego.BConfig.AppName,
"build_version": beego.BuildVersion,
"build_revision": beego.BuildGitRevision,
"build_status": beego.BuildStatus,
"build_tag": beego.BuildTag,
"build_time": strings.Replace(beego.BuildTime, "--", " ", 1),
"go_version": beego.GoVersion,
"git_branch": beego.GitBranch,
"start_time": time.Now().Format("2006-01-02 15:04:05"),
},
}, []string{})
prometheus.MustRegister(buildInfo)
buildInfo.WithLabelValues().Set(1)
}
func report(dur time.Duration, writer http.ResponseWriter, q *http.Request, vec *prometheus.SummaryVec) {
ctrl := beego.BeeApp.Handlers
ctx := ctrl.GetContext()
ctx.Reset(writer, q)
defer ctrl.GiveBackContext(ctx)
// We cannot read the status code from q.Response.StatusCode
// since the http server does not set q.Response. So q.Response is nil
// Thus, we use reflection to read the status from writer whose concrete type is http.response
responseVal := reflect.ValueOf(writer).Elem()
field := responseVal.FieldByName("status")
status := -1
if field.IsValid() && field.Kind() == reflect.Int {
status = int(field.Int())
}
ptn := "UNKNOWN"
if rt, found := ctrl.FindRouter(ctx); found {
ptn = rt.GetPattern()
} else {
logs.Warn("we can not find the router info for this request, so request will be recorded as UNKNOWN: " + q.URL.String())
}
ms := dur / time.Millisecond
vec.WithLabelValues(ptn, q.Method, strconv.Itoa(status)).Observe(float64(ms))
}

42
metric/prometheus_test.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2020 astaxie
//
// 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 metric
import (
"net/http"
"net/url"
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/astaxie/beego/context"
)
func TestPrometheusMiddleWare(t *testing.T) {
middleware := PrometheusMiddleWare(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
writer := &context.Response{}
request := &http.Request{
URL: &url.URL{
Host: "localhost",
RawPath: "/a/b/c",
},
Method: "POST",
}
vec := prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{"pattern", "method", "status"})
report(time.Second, writer, request, vec)
middleware.ServeHTTP(writer, request)
}

View File

@ -17,7 +17,7 @@ package migration
import (
"fmt"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
)
// Index struct defines the structure of Index Columns
@ -316,7 +316,7 @@ func (m *Migration) GetSQL() (sql string) {
sql += fmt.Sprintf("ALTER TABLE `%s` ", m.TableName)
for index, column := range m.Columns {
if !column.remove {
beego.BeeLogger.Info("col")
logs.Info("col")
sql += fmt.Sprintf("\n ADD `%s` %s %s %s %s %s", column.Name, column.DataType, column.Unsign, column.Null, column.Inc, column.Default)
} else {
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)

View File

@ -176,8 +176,9 @@ func Register(name string, m Migrationer) error {
func Upgrade(lasttime int64) error {
sm := sortMap(migrationMap)
i := 0
migs, _ := getAllMigrations()
for _, v := range sm {
if v.created > lasttime {
if _, ok := migs[v.name]; !ok {
logs.Info("start upgrade", v.name)
v.m.Reset()
v.m.Up()
@ -310,3 +311,20 @@ func isRollBack(name string) bool {
}
return false
}
func getAllMigrations() (map[string]string, error) {
o := orm.NewOrm()
var maps []orm.Params
migs := make(map[string]string)
num, err := o.Raw("select * from migrations order by id_migration desc").Values(&maps)
if err != nil {
logs.Info("get name has error", err)
return migs, err
}
if num > 0 {
for _, v := range maps {
name := v["name"].(string)
migs[name] = v["status"].(string)
}
}
return migs, nil
}

View File

@ -207,11 +207,11 @@ func (n *Namespace) Include(cList ...ControllerInterface) *Namespace {
func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
for _, ni := range ns {
for k, v := range ni.handlers.routers {
if t, ok := n.handlers.routers[k]; ok {
if _, ok := n.handlers.routers[k]; ok {
addPrefix(v, ni.prefix)
n.handlers.routers[k].AddTree(ni.prefix, v)
} else {
t = NewTree()
t := NewTree()
t.AddTree(ni.prefix, v)
addPrefix(t, ni.prefix)
n.handlers.routers[k] = t
@ -236,11 +236,11 @@ func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
func AddNamespace(nl ...*Namespace) {
for _, n := range nl {
for k, v := range n.handlers.routers {
if t, ok := BeeApp.Handlers.routers[k]; ok {
if _, ok := BeeApp.Handlers.routers[k]; ok {
addPrefix(v, n.prefix)
BeeApp.Handlers.routers[k].AddTree(n.prefix, v)
} else {
t = NewTree()
t := NewTree()
t.AddTree(n.prefix, v)
addPrefix(t, n.prefix)
BeeApp.Handlers.routers[k] = t

View File

@ -197,9 +197,9 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
if strings.Contains(column, "%COL%") {
column = strings.Replace(column, "%COL%", fi.column, -1)
}
if fi.description != "" {
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
if fi.description != "" && al.Driver != DRSqlite {
column += " " + fmt.Sprintf("COMMENT '%s'", fi.description)
}
columns = append(columns, column)

View File

@ -36,10 +36,11 @@ var (
var (
operators = map[string]bool{
"exact": true,
"iexact": true,
"contains": true,
"icontains": true,
"exact": true,
"iexact": true,
"strictexact": true,
"contains": true,
"icontains": true,
// "regex": true,
// "iregex": true,
"gt": true,
@ -470,7 +471,7 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s
multi := len(values) / len(names)
if isMulti {
if isMulti && multi > 1 {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
@ -621,6 +622,31 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
return 0, err
}
var findAutoNowAdd, findAutoNow bool
var index int
for i, col := range setNames {
if mi.fields.GetByColumn(col).autoNowAdd {
index = i
findAutoNowAdd = true
}
if mi.fields.GetByColumn(col).autoNow {
findAutoNow = true
}
}
if findAutoNowAdd {
setNames = append(setNames[0:index], setNames[index+1:]...)
setValues = append(setValues[0:index], setValues[index+1:]...)
}
if !findAutoNow {
for col, info := range mi.fields.columns {
if info.autoNow {
setNames = append(setNames, col)
setValues = append(setValues, time.Now())
}
}
}
setValues = append(setValues, pkValue)
Q := d.ins.TableQuote()
@ -745,6 +771,16 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
cols = append(cols, col+" = "+col+" * ?")
case ColExcept:
cols = append(cols, col+" = "+col+" / ?")
case ColBitAnd:
cols = append(cols, col+" = "+col+" & ?")
case ColBitRShift:
cols = append(cols, col+" = "+col+" >> ?")
case ColBitLShift:
cols = append(cols, col+" = "+col+" << ?")
case ColBitXOR:
cols = append(cols, col+" = "+col+" ^ ?")
case ColBitOr:
cols = append(cols, col+" = "+col+" | ?")
}
values[i] = c.value
} else {
@ -1167,7 +1203,7 @@ func (d *dbBase) GenerateOperatorSQL(mi *modelInfo, fi *fieldInfo, operator stri
}
sql = d.ins.OperatorSQL(operator)
switch operator {
case "exact":
case "exact", "strictexact":
if arg == nil {
params[0] = "IS NULL"
}

View File

@ -15,11 +15,14 @@
package orm
import (
"context"
"database/sql"
"fmt"
"reflect"
"sync"
"time"
lru "github.com/hashicorp/golang-lru"
)
// DriverType database driver constant int.
@ -103,6 +106,121 @@ func (ac *_dbCache) getDefault() (al *alias) {
return
}
type DB struct {
*sync.RWMutex
DB *sql.DB
stmtDecorators *lru.Cache
}
func (d *DB) Begin() (*sql.Tx, error) {
return d.DB.Begin()
}
func (d *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
return d.DB.BeginTx(ctx, opts)
}
//su must call release to release *sql.Stmt after using
func (d *DB) getStmtDecorator(query string) (*stmtDecorator, error) {
d.RLock()
c, ok := d.stmtDecorators.Get(query)
if ok {
c.(*stmtDecorator).acquire()
d.RUnlock()
return c.(*stmtDecorator), nil
}
d.RUnlock()
d.Lock()
c, ok = d.stmtDecorators.Get(query)
if ok {
c.(*stmtDecorator).acquire()
d.Unlock()
return c.(*stmtDecorator), nil
}
stmt, err := d.Prepare(query)
if err != nil {
d.Unlock()
return nil, err
}
sd := newStmtDecorator(stmt)
sd.acquire()
d.stmtDecorators.Add(query, sd)
d.Unlock()
return sd, nil
}
func (d *DB) Prepare(query string) (*sql.Stmt, error) {
return d.DB.Prepare(query)
}
func (d *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
return d.DB.PrepareContext(ctx, query)
}
func (d *DB) Exec(query string, args ...interface{}) (sql.Result, error) {
sd, err := d.getStmtDecorator(query)
if err != nil {
return nil, err
}
stmt := sd.getStmt()
defer sd.release()
return stmt.Exec(args...)
}
func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
sd, err := d.getStmtDecorator(query)
if err != nil {
return nil, err
}
stmt := sd.getStmt()
defer sd.release()
return stmt.ExecContext(ctx, args...)
}
func (d *DB) Query(query string, args ...interface{}) (*sql.Rows, error) {
sd, err := d.getStmtDecorator(query)
if err != nil {
return nil, err
}
stmt := sd.getStmt()
defer sd.release()
return stmt.Query(args...)
}
func (d *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
sd, err := d.getStmtDecorator(query)
if err != nil {
return nil, err
}
stmt := sd.getStmt()
defer sd.release()
return stmt.QueryContext(ctx, args...)
}
func (d *DB) QueryRow(query string, args ...interface{}) *sql.Row {
sd, err := d.getStmtDecorator(query)
if err != nil {
panic(err)
}
stmt := sd.getStmt()
defer sd.release()
return stmt.QueryRow(args...)
}
func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
sd, err := d.getStmtDecorator(query)
if err != nil {
panic(err)
}
stmt := sd.getStmt()
defer sd.release()
return stmt.QueryRowContext(ctx, args)
}
type alias struct {
Name string
Driver DriverType
@ -110,7 +228,7 @@ type alias struct {
DataSource string
MaxIdleConns int
MaxOpenConns int
DB *sql.DB
DB *DB
DbBaser dbBaser
TZ *time.Location
Engine string
@ -176,7 +294,11 @@ func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
al := new(alias)
al.Name = aliasName
al.DriverName = driverName
al.DB = db
al.DB = &DB{
RWMutex: new(sync.RWMutex),
DB: db,
stmtDecorators: newStmtDecoratorLruWithEvict(),
}
if dr, ok := drivers[driverName]; ok {
al.DbBaser = dbBasers[dr]
@ -272,13 +394,14 @@ func SetDataBaseTZ(aliasName string, tz *time.Location) error {
func SetMaxIdleConns(aliasName string, maxIdleConns int) {
al := getDbAlias(aliasName)
al.MaxIdleConns = maxIdleConns
al.DB.SetMaxIdleConns(maxIdleConns)
al.DB.DB.SetMaxIdleConns(maxIdleConns)
}
// SetMaxOpenConns Change the max open conns for *sql.DB, use specify database alias name
func SetMaxOpenConns(aliasName string, maxOpenConns int) {
al := getDbAlias(aliasName)
al.MaxOpenConns = maxOpenConns
al.DB.DB.SetMaxOpenConns(maxOpenConns)
// for tip go 1.2
if fun := reflect.ValueOf(al.DB).MethodByName("SetMaxOpenConns"); fun.IsValid() {
fun.Call([]reflect.Value{reflect.ValueOf(maxOpenConns)})
@ -296,7 +419,51 @@ func GetDB(aliasNames ...string) (*sql.DB, error) {
}
al, ok := dataBaseCache.get(name)
if ok {
return al.DB, nil
return al.DB.DB, nil
}
return nil, fmt.Errorf("DataBase of alias name `%s` not found", name)
}
type stmtDecorator struct {
wg sync.WaitGroup
stmt *sql.Stmt
}
func (s *stmtDecorator) getStmt() *sql.Stmt {
return s.stmt
}
// acquire will add one
// since this method will be used inside read lock scope,
// so we can not do more things here
// we should think about refactor this
func (s *stmtDecorator) acquire() {
s.wg.Add(1)
}
func (s *stmtDecorator) release() {
s.wg.Done()
}
//garbage recycle for stmt
func (s *stmtDecorator) destroy() {
go func() {
s.wg.Wait()
_ = s.stmt.Close()
}()
}
func newStmtDecorator(sqlStmt *sql.Stmt) *stmtDecorator {
return &stmtDecorator{
stmt: sqlStmt,
}
}
func newStmtDecoratorLruWithEvict() *lru.Cache {
// temporarily solution
// we fixed this problem in v2.x
cache, _ := lru.NewWithEvict(50, func(key interface{}, value interface{}) {
value.(*stmtDecorator).destroy()
})
return cache
}

View File

@ -22,10 +22,11 @@ import (
// mysql operators.
var mysqlOperators = map[string]string{
"exact": "= ?",
"iexact": "LIKE ?",
"contains": "LIKE BINARY ?",
"icontains": "LIKE ?",
"exact": "= ?",
"iexact": "LIKE ?",
"strictexact": "= BINARY ?",
"contains": "LIKE BINARY ?",
"icontains": "LIKE ?",
// "regex": "REGEXP BINARY ?",
// "iregex": "REGEXP ?",
"gt": "> ?",

View File

@ -17,6 +17,8 @@ package orm
import (
"database/sql"
"fmt"
"reflect"
"time"
)
// sqlite operators.
@ -66,6 +68,14 @@ type dbBaseSqlite struct {
var _ dbBaser = new(dbBaseSqlite)
// override base db read for update behavior as SQlite does not support syntax
func (d *dbBaseSqlite) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error {
if isForUpdate {
DebugLog.Println("[WARN] SQLite does not support SELECT FOR UPDATE query, isForUpdate param is ignored and always as false to do the work")
}
return d.dbBase.Read(q, mi, ind, tz, cols, false)
}
// get sqlite operator.
func (d *dbBaseSqlite) OperatorSQL(operator string) string {
return sqliteOperators[operator]

View File

@ -18,6 +18,7 @@ import (
"fmt"
"os"
"reflect"
"runtime/debug"
"strings"
)
@ -298,6 +299,7 @@ func bootStrap() {
end:
if err != nil {
fmt.Println(err)
debug.PrintStack()
os.Exit(2)
}
}
@ -335,11 +337,11 @@ func RegisterModelWithSuffix(suffix string, models ...interface{}) {
// BootStrap bootstrap models.
// make all model parsed and can not add more models
func BootStrap() {
modelCache.Lock()
defer modelCache.Unlock()
if modelCache.done {
return
}
modelCache.Lock()
defer modelCache.Unlock()
bootStrap()
modelCache.done = true
}

View File

@ -301,7 +301,7 @@ checkType:
fi.sf = sf
fi.fullName = mi.fullName + mName + "." + sf.Name
fi.description = sf.Tag.Get("description")
fi.description = tags["description"]
fi.null = attrs["null"]
fi.index = attrs["index"]
fi.auto = attrs["auto"]

View File

@ -53,18 +53,24 @@ func (e *SliceStringField) FieldType() int {
}
func (e *SliceStringField) SetRaw(value interface{}) error {
switch d := value.(type) {
case []string:
e.Set(d)
case string:
if len(d) > 0 {
parts := strings.Split(d, ",")
f := func(str string) {
if len(str) > 0 {
parts := strings.Split(str, ",")
v := make([]string, 0, len(parts))
for _, p := range parts {
v = append(v, strings.TrimSpace(p))
}
e.Set(v)
}
}
switch d := value.(type) {
case []string:
e.Set(d)
case string:
f(d)
case []byte:
f(string(d))
default:
return fmt.Errorf("<SliceStringField.SetRaw> unknown value `%v`", value)
}
@ -96,6 +102,8 @@ func (e *JSONFieldTest) SetRaw(value interface{}) error {
switch d := value.(type) {
case string:
return json.Unmarshal([]byte(d), e)
case []byte:
return json.Unmarshal(d, e)
default:
return fmt.Errorf("<JSONField.SetRaw> unknown value `%v`", value)
}

View File

@ -44,6 +44,7 @@ var supportTag = map[string]int{
"decimals": 2,
"on_delete": 2,
"type": 2,
"description": 2,
}
// get reflect.Type name with package path.
@ -65,7 +66,7 @@ func getTableName(val reflect.Value) string {
return snakeString(reflect.Indirect(val).Type().Name())
}
// get table engine, mysiam or innodb.
// get table engine, myisam or innodb.
func getTableEngine(val reflect.Value) string {
fun := val.MethodByName("TableEngine")
if fun.IsValid() {

View File

@ -60,6 +60,7 @@ import (
"fmt"
"os"
"reflect"
"sync"
"time"
)
@ -72,7 +73,7 @@ const (
var (
Debug = false
DebugLog = NewLog(os.Stdout)
DefaultRowsLimit = 1000
DefaultRowsLimit = -1
DefaultRelsDepth = 2
DefaultTimeLoc = time.Local
ErrTxHasBegan = errors.New("<Ormer.Begin> transaction already begin")
@ -522,6 +523,15 @@ func (o *orm) Driver() Driver {
return driver(o.alias.Name)
}
// return sql.DBStats for current database
func (o *orm) DBStats() *sql.DBStats {
if o.alias != nil && o.alias.DB != nil {
stats := o.alias.DB.DB.Stats()
return &stats
}
return nil
}
// NewOrm create new orm
func NewOrm() Ormer {
BootStrap() // execute only once
@ -548,7 +558,11 @@ func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) {
al.Name = aliasName
al.DriverName = driverName
al.DB = db
al.DB = &DB{
RWMutex: new(sync.RWMutex),
DB: db,
stmtDecorators: newStmtDecoratorLruWithEvict(),
}
detectTZ(al)

View File

@ -29,6 +29,9 @@ type Log struct {
*log.Logger
}
//costomer log func
var LogFunc func(query map[string]interface{})
// NewLog set io.Writer to create a Logger.
func NewLog(out io.Writer) *Log {
d := new(Log)
@ -37,12 +40,15 @@ func NewLog(out io.Writer) *Log {
}
func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error, args ...interface{}) {
var logMap = make(map[string]interface{})
sub := time.Now().Sub(t) / 1e5
elsp := float64(int(sub)) / 10.0
logMap["cost_time"] = elsp
flag := " OK"
if err != nil {
flag = "FAIL"
}
logMap["flag"] = flag
con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query)
cons := make([]string, 0, len(args))
for _, arg := range args {
@ -54,6 +60,10 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error
if err != nil {
con += " - " + err.Error()
}
logMap["sql"] = fmt.Sprintf("%s-`%s`", query, strings.Join(cons, "`, `"))
if LogFunc != nil {
LogFunc(logMap)
}
DebugLog.Println(con)
}

View File

@ -32,6 +32,11 @@ const (
ColMinus
ColMultiply
ColExcept
ColBitAnd
ColBitRShift
ColBitLShift
ColBitXOR
ColBitOr
)
// ColValue do the field raw changes. e.g Nums = Nums + 10. usage:
@ -40,7 +45,8 @@ const (
// }
func ColValue(opt operator, value interface{}) interface{} {
switch opt {
case ColAdd, ColMinus, ColMultiply, ColExcept:
case ColAdd, ColMinus, ColMultiply, ColExcept, ColBitAnd, ColBitRShift,
ColBitLShift, ColBitXOR, ColBitOr:
default:
panic(fmt.Errorf("orm.ColValue wrong operator"))
}

View File

@ -19,6 +19,8 @@ import (
"fmt"
"reflect"
"time"
"github.com/pkg/errors"
)
// raw sql string prepared statement
@ -150,8 +152,10 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
case reflect.Struct:
if value == nil {
ind.Set(reflect.Zero(ind.Type()))
} else if _, ok := ind.Interface().(time.Time); ok {
return
}
switch ind.Interface().(type) {
case time.Time:
var str string
switch d := value.(type) {
case time.Time:
@ -178,7 +182,25 @@ func (o *rawSet) setFieldValue(ind reflect.Value, value interface{}) {
}
}
}
case sql.NullString, sql.NullInt64, sql.NullFloat64, sql.NullBool:
indi := reflect.New(ind.Type()).Interface()
sc, ok := indi.(sql.Scanner)
if !ok {
return
}
err := sc.Scan(value)
if err == nil {
ind.Set(reflect.Indirect(reflect.ValueOf(sc)))
}
}
case reflect.Ptr:
if value == nil {
ind.Set(reflect.Zero(ind.Type()))
break
}
ind.Set(reflect.New(ind.Type().Elem()))
o.setFieldValue(reflect.Indirect(ind), value)
}
}
@ -348,7 +370,15 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
field.Set(mf)
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
}
o.setFieldValue(field, value)
if fi.isFielder {
fd := field.Addr().Interface().(Fielder)
err := fd.SetRaw(value)
if err != nil {
return errors.Errorf("set raw error:%s", err)
}
} else {
o.setFieldValue(field, value)
}
}
}
} else {
@ -489,7 +519,15 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
field.Set(mf)
field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
}
o.setFieldValue(field, value)
if fi.isFielder {
fd := field.Addr().Interface().(Fielder)
err := fd.SetRaw(value)
if err != nil {
return 0, errors.Errorf("set raw error:%s", err)
}
} else {
o.setFieldValue(field, value)
}
}
}
} else {

View File

@ -458,6 +458,15 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime)))
throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate)))
throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime)))
// test support for pointer fields using RawSeter.QueryRows()
var dnList []*DataNull
Q := dDbBaser.TableQuote()
num, err = dORM.Raw(fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q), 3).QueryRows(&dnList)
throwFailNow(t, err)
throwFailNow(t, AssertIs(num, 1))
equal := reflect.DeepEqual(*dnList[0], d)
throwFailNow(t, AssertIs(equal, true))
}
func TestDataCustomTypes(t *testing.T) {
@ -760,6 +769,20 @@ func TestCustomField(t *testing.T) {
throwFailNow(t, AssertIs(user.Extra.Name, "beego"))
throwFailNow(t, AssertIs(user.Extra.Data, "orm"))
var users []User
Q := dDbBaser.TableQuote()
n, err := dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRows(&users)
throwFailNow(t, err)
throwFailNow(t, AssertIs(n, 1))
throwFailNow(t, AssertIs(users[0].Extra.Name, "beego"))
throwFailNow(t, AssertIs(users[0].Extra.Data, "orm"))
user = User{}
err = dORM.Raw(fmt.Sprintf("SELECT * FROM %suser%s where id=?", Q, Q), 2).QueryRow(&user)
throwFailNow(t, err)
throwFailNow(t, AssertIs(user.Extra.Name, "beego"))
throwFailNow(t, AssertIs(user.Extra.Data, "orm"))
}
func TestExpr(t *testing.T) {
@ -799,6 +822,17 @@ func TestOperators(t *testing.T) {
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
if IsMysql {
// Now only mysql support `strictexact`
num, err = qs.Filter("user_name__strictexact", "Slene").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 0))
num, err = qs.Filter("user_name__strictexact", "slene").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
}
num, err = qs.Filter("user_name__contains", "e").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 2))
@ -1679,6 +1713,31 @@ func TestRawQueryRow(t *testing.T) {
throwFail(t, AssertIs(uid, 4))
throwFail(t, AssertIs(*status, 3))
throwFail(t, AssertIs(pid, nil))
// test for sql.Null* fields
nData := &DataNull{
NullString: sql.NullString{String: "test sql.null", Valid: true},
NullBool: sql.NullBool{Bool: true, Valid: true},
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
}
newId, err := dORM.Insert(nData)
throwFailNow(t, err)
var nd *DataNull
query = fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q)
err = dORM.Raw(query, newId).QueryRow(&nd)
throwFailNow(t, err)
throwFailNow(t, AssertNot(nd, nil))
throwFail(t, AssertIs(nd.NullBool.Valid, true))
throwFail(t, AssertIs(nd.NullBool.Bool, true))
throwFail(t, AssertIs(nd.NullString.Valid, true))
throwFail(t, AssertIs(nd.NullString.String, "test sql.null"))
throwFail(t, AssertIs(nd.NullInt64.Valid, true))
throwFail(t, AssertIs(nd.NullInt64.Int64, 42))
throwFail(t, AssertIs(nd.NullFloat64.Valid, true))
throwFail(t, AssertIs(nd.NullFloat64.Float64, 42.42))
}
// user_profile table
@ -1771,6 +1830,32 @@ func TestQueryRows(t *testing.T) {
throwFailNow(t, AssertIs(l[1].UserName, "astaxie"))
throwFailNow(t, AssertIs(l[1].Age, 30))
// test for sql.Null* fields
nData := &DataNull{
NullString: sql.NullString{String: "test sql.null", Valid: true},
NullBool: sql.NullBool{Bool: true, Valid: true},
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
}
newId, err := dORM.Insert(nData)
throwFailNow(t, err)
var nDataList []*DataNull
query = fmt.Sprintf("SELECT * FROM %sdata_null%s where id=?", Q, Q)
num, err = dORM.Raw(query, newId).QueryRows(&nDataList)
throwFailNow(t, err)
throwFailNow(t, AssertIs(num, 1))
nd := nDataList[0]
throwFailNow(t, AssertNot(nd, nil))
throwFail(t, AssertIs(nd.NullBool.Valid, true))
throwFail(t, AssertIs(nd.NullBool.Bool, true))
throwFail(t, AssertIs(nd.NullString.Valid, true))
throwFail(t, AssertIs(nd.NullString.String, "test sql.null"))
throwFail(t, AssertIs(nd.NullInt64.Valid, true))
throwFail(t, AssertIs(nd.NullInt64.Int64, 42))
throwFail(t, AssertIs(nd.NullFloat64.Valid, true))
throwFail(t, AssertIs(nd.NullFloat64.Float64, 42.42))
}
func TestRawValues(t *testing.T) {

View File

@ -55,7 +55,7 @@ type Ormer interface {
// for example:
// user := new(User)
// id, err = Ormer.Insert(user)
// user must a pointer and Insert will set user's pk field
// user must be a pointer and Insert will set user's pk field
Insert(interface{}) (int64, error)
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
@ -128,6 +128,7 @@ type Ormer interface {
// // update user testing's name to slene
Raw(query string, args ...interface{}) RawSeter
Driver() Driver
DBStats() *sql.DBStats
}
// Inserter insert prepared statement

View File

@ -129,7 +129,7 @@ func (f StrTo) Uint16() (uint16, error) {
return uint16(v), err
}
// Uint32 string to uint31
// Uint32 string to uint32
func (f StrTo) Uint32() (uint32, error) {
v, err := strconv.ParseUint(f.String(), 10, 32)
return uint32(v), err

View File

@ -35,7 +35,7 @@ import (
"github.com/astaxie/beego/utils"
)
var globalRouterTemplate = `package routers
var globalRouterTemplate = `package {{.routersDir}}
import (
"github.com/astaxie/beego"
@ -49,7 +49,6 @@ func init() {
var (
lastupdateFilename = "lastupdate.tmp"
commentFilename string
pkgLastupdate map[string]int64
genInfoList map[string][]ControllerComments
@ -70,16 +69,13 @@ var (
}
)
const commentPrefix = "commentsRouter_"
const commentFilename = "commentsRouter.go"
func init() {
pkgLastupdate = make(map[string]int64)
}
func parserPkg(pkgRealpath, pkgpath string) error {
rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go"
if !compareFile(pkgRealpath) {
logs.Info(pkgRealpath + " no changed")
return nil
@ -102,7 +98,10 @@ func parserPkg(pkgRealpath, pkgpath string) error {
if specDecl.Recv != nil {
exp, ok := specDecl.Recv.List[0].Type.(*ast.StarExpr) // Check that the type is correct first beforing throwing to parser
if ok {
parserComments(specDecl, fmt.Sprint(exp.X), pkgpath)
err = parserComments(specDecl, fmt.Sprint(exp.X), pkgpath)
if err != nil {
return err
}
}
}
}
@ -459,13 +458,17 @@ func genRouterCode(pkgRealpath string) {
imports := ""
if len(c.ImportComments) > 0 {
for _, i := range c.ImportComments {
var s string
if i.ImportAlias != "" {
imports += fmt.Sprintf(`
s = fmt.Sprintf(`
%s "%s"`, i.ImportAlias, i.ImportPath)
} else {
imports += fmt.Sprintf(`
s = fmt.Sprintf(`
"%s"`, i.ImportPath)
}
if !strings.Contains(globalimport, s) {
imports += s
}
}
}
@ -490,7 +493,7 @@ func genRouterCode(pkgRealpath string) {
}`, filters)
}
globalimport = imports
globalimport += imports
globalinfo = globalinfo + `
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
@ -512,7 +515,9 @@ func genRouterCode(pkgRealpath string) {
}
defer f.Close()
routersDir := AppConfig.DefaultString("routersdir", "routers")
content := strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1)
content = strings.Replace(content, "{{.routersDir}}", routersDir, -1)
content = strings.Replace(content, "{{.globalimport}}", globalimport, -1)
f.WriteString(content)
}
@ -570,7 +575,8 @@ func getpathTime(pkgRealpath string) (lastupdate int64, err error) {
func getRouterDir(pkgRealpath string) string {
dir := filepath.Dir(pkgRealpath)
for {
d := filepath.Join(dir, "routers")
routersDir := AppConfig.DefaultString("routersdir", "routers")
d := filepath.Join(dir, routersDir)
if utils.FileExists(d) {
return d
}

View File

@ -72,8 +72,8 @@ import (
// AppIDToAppSecret is used to get appsecret throw appid
type AppIDToAppSecret func(string) string
// APIBaiscAuth use the basic appid/appkey as the AppIdToAppSecret
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
// APIBasicAuth use the basic appid/appkey as the AppIdToAppSecret
func APIBasicAuth(appid, appkey string) beego.FilterFunc {
ft := func(aid string) string {
if aid == appid {
return appkey
@ -83,6 +83,11 @@ func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
return APISecretAuth(ft, 300)
}
// APIBaiscAuth calls APIBasicAuth for previous callers
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
return APIBasicAuth(appid, appkey)
}
// APISecretAuth use AppIdToAppSecret verify and
func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
return func(ctx *context.Context) {

View File

@ -40,10 +40,11 @@
package authz
import (
"net/http"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casbin/casbin"
"net/http"
)
// NewAuthorizer returns the authorizer.

View File

@ -15,13 +15,14 @@
package authz
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/plugins/auth"
"github.com/casbin/casbin"
"net/http"
"net/http/httptest"
"testing"
)
func testRequest(t *testing.T, handler *beego.ControllerRegister, user string, path string, method string, code int) {

210
router.go
View File

@ -15,12 +15,13 @@
package beego
import (
"errors"
"fmt"
"net/http"
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
@ -121,6 +122,10 @@ type ControllerInfo struct {
methodParams []*param.MethodParam
}
func (c *ControllerInfo) GetPattern() string {
return c.pattern
}
// ControllerRegister containers registered router rules, controller handlers and filters.
type ControllerRegister struct {
routers map[string]*Tree
@ -249,25 +254,39 @@ func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerIn
func (p *ControllerRegister) Include(cList ...ControllerInterface) {
if BConfig.RunMode == DEV {
skip := make(map[string]bool, 10)
wgopath := utils.GetGOPATHs()
go111module := os.Getenv(`GO111MODULE`)
for _, c := range cList {
reflectVal := reflect.ValueOf(c)
t := reflect.Indirect(reflectVal).Type()
wgopath := utils.GetGOPATHs()
if len(wgopath) == 0 {
panic("you are in dev mode. So please set gopath")
}
pkgpath := ""
for _, wg := range wgopath {
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", t.PkgPath()))
if utils.FileExists(wg) {
pkgpath = wg
break
// for go modules
if go111module == `on` {
pkgpath := filepath.Join(WorkPath, "..", t.PkgPath())
if utils.FileExists(pkgpath) {
if pkgpath != "" {
if _, ok := skip[pkgpath]; !ok {
skip[pkgpath] = true
parserPkg(pkgpath, t.PkgPath())
}
}
}
}
if pkgpath != "" {
if _, ok := skip[pkgpath]; !ok {
skip[pkgpath] = true
parserPkg(pkgpath, t.PkgPath())
} else {
if len(wgopath) == 0 {
panic("you are in dev mode. So please set gopath")
}
pkgpath := ""
for _, wg := range wgopath {
wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", t.PkgPath()))
if utils.FileExists(wg) {
pkgpath = wg
break
}
}
if pkgpath != "" {
if _, ok := skip[pkgpath]; !ok {
skip[pkgpath] = true
parserPkg(pkgpath, t.PkgPath())
}
}
}
}
@ -288,6 +307,21 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) {
}
}
// GetContext returns a context from pool, so usually you should remember to call Reset function to clean the context
// And don't forget to give back context to pool
// example:
// ctx := p.GetContext()
// ctx.Reset(w, q)
// defer p.GiveBackContext(ctx)
func (p *ControllerRegister) GetContext() *beecontext.Context {
return p.pool.Get().(*beecontext.Context)
}
// GiveBackContext put the ctx into pool so that it could be reuse
func (p *ControllerRegister) GiveBackContext(ctx *beecontext.Context) {
p.pool.Put(ctx)
}
// Get add get method
// usage:
// Get("/", func(ctx *context.Context){
@ -479,8 +513,7 @@ func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter Filter
// add Filter into
func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) {
if pos < BeforeStatic || pos > FinishRouter {
err = fmt.Errorf("can not find your filter position")
return
return errors.New("can not find your filter position")
}
p.enableFilter = true
p.filters[pos] = append(p.filters[pos], mr)
@ -510,10 +543,10 @@ func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) stri
}
}
}
controllName := strings.Join(paths[:len(paths)-1], "/")
controllerName := strings.Join(paths[:len(paths)-1], "/")
methodName := paths[len(paths)-1]
for m, t := range p.routers {
ok, url := p.geturl(t, "/", controllName, methodName, params, m)
ok, url := p.getURL(t, "/", controllerName, methodName, params, m)
if ok {
return url
}
@ -521,17 +554,17 @@ func (p *ControllerRegister) URLFor(endpoint string, values ...interface{}) stri
return ""
}
func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName string, params map[string]string, httpMethod string) (bool, string) {
func (p *ControllerRegister) getURL(t *Tree, url, controllerName, methodName string, params map[string]string, httpMethod string) (bool, string) {
for _, subtree := range t.fixrouters {
u := path.Join(url, subtree.prefix)
ok, u := p.geturl(subtree, u, controllName, methodName, params, httpMethod)
ok, u := p.getURL(subtree, u, controllerName, methodName, params, httpMethod)
if ok {
return ok, u
}
}
if t.wildcard != nil {
u := path.Join(url, urlPlaceholder)
ok, u := p.geturl(t.wildcard, u, controllName, methodName, params, httpMethod)
ok, u := p.getURL(t.wildcard, u, controllerName, methodName, params, httpMethod)
if ok {
return ok, u
}
@ -539,7 +572,7 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
for _, l := range t.leaves {
if c, ok := l.runObject.(*ControllerInfo); ok {
if c.routerType == routerTypeBeego &&
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllName) {
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllerName) {
find := false
if HTTPMETHOD[strings.ToUpper(methodName)] {
if len(c.methods) == 0 {
@ -578,18 +611,18 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
}
}
}
canskip := false
canSkip := false
for _, v := range l.wildcards {
if v == ":" {
canskip = true
canSkip = true
continue
}
if u, ok := params[v]; ok {
delete(params, v)
url = strings.Replace(url, urlPlaceholder, u, 1)
} else {
if canskip {
canskip = false
if canSkip {
canSkip = false
continue
}
return false, ""
@ -598,27 +631,27 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin
return true, url + toURL(params)
}
var i int
var startreg bool
regurl := ""
var startReg bool
regURL := ""
for _, v := range strings.Trim(l.regexps.String(), "^$") {
if v == '(' {
startreg = true
startReg = true
continue
} else if v == ')' {
startreg = false
startReg = false
if v, ok := params[l.wildcards[i]]; ok {
delete(params, l.wildcards[i])
regurl = regurl + v
regURL = regURL + v
i++
} else {
break
}
} else if !startreg {
regurl = string(append([]rune(regurl), v))
} else if !startReg {
regURL = string(append([]rune(regURL), v))
}
}
if l.regexps.MatchString(regurl) {
ps := strings.Split(regurl, "/")
if l.regexps.MatchString(regURL) {
ps := strings.Split(regURL, "/")
for _, p := range ps {
url = strings.Replace(url, urlPlaceholder, p, 1)
}
@ -668,10 +701,11 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
routerInfo *ControllerInfo
isRunnable bool
)
context := p.pool.Get().(*beecontext.Context)
context := p.GetContext()
context.Reset(rw, r)
defer p.pool.Put(context)
defer p.GiveBackContext(context)
if BConfig.RecoverFunc != nil {
defer BConfig.RecoverFunc(context)
}
@ -690,7 +724,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
// filter wrong http method
if !HTTPMETHOD[r.Method] {
http.Error(rw, "Method Not Allowed", 405)
exception("405", context)
goto Admin
}
@ -708,6 +742,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
if r.Method != http.MethodGet && r.Method != http.MethodHead {
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
// connection will close if the incoming data are larger (RFC 7231, 6.5.11)
if r.ContentLength > BConfig.MaxMemory {
logs.Error(errors.New("payload too large"))
exception("413", context)
goto Admin
}
context.Input.CopyBody(BConfig.MaxMemory)
}
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
@ -740,7 +780,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
routerInfo, findRouter = p.FindRouter(context)
}
//if no matches to url, throw a not found exception
// if no matches to url, throw a not found exception
if !findRouter {
exception("404", context)
goto Admin
@ -751,19 +791,22 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
}
//execute middleware filters
if routerInfo != nil {
// store router pattern into context
context.Input.SetData("RouterPattern", routerInfo.pattern)
}
// execute middleware filters
if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
goto Admin
}
//check policies
// check policies
if p.execPolicy(context, urlPath) {
goto Admin
}
if routerInfo != nil {
//store router pattern into context
context.Input.SetData("RouterPattern", routerInfo.pattern)
if routerInfo.routerType == routerTypeRESTFul {
if _, ok := routerInfo.methods[r.Method]; ok {
isRunnable = true
@ -774,12 +817,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
} else if routerInfo.routerType == routerTypeHandler {
isRunnable = true
routerInfo.handler.ServeHTTP(rw, r)
routerInfo.handler.ServeHTTP(context.ResponseWriter, context.Request)
} else {
runRouter = routerInfo.controllerType
methodParams = routerInfo.methodParams
method := r.Method
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPut {
method = http.MethodPut
}
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
@ -797,7 +840,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
// also defined runRouter & runMethod from filter
if !isRunnable {
//Invoke the request handler
// Invoke the request handler
var execController ControllerInterface
if routerInfo != nil && routerInfo.initialize != nil {
execController = routerInfo.initialize()
@ -810,13 +853,13 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
}
//call the controller init function
// call the controller init function
execController.Init(context, runRouter.Name(), runMethod, execController)
//call prepare function
// call prepare function
execController.Prepare()
//if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
// if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
if BConfig.WebConfig.EnableXSRF {
execController.XSRFToken()
if r.Method == http.MethodPost || r.Method == http.MethodDelete || r.Method == http.MethodPut ||
@ -828,7 +871,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
execController.URLMapping()
if !context.ResponseWriter.Started {
//exec main logic
// exec main logic
switch runMethod {
case http.MethodGet:
execController.Get()
@ -844,6 +887,8 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
execController.Patch()
case http.MethodOptions:
execController.Options()
case http.MethodTrace:
execController.Trace()
default:
if !execController.HandlerFunc(runMethod) {
vc := reflect.ValueOf(execController)
@ -851,14 +896,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
in := param.ConvertParams(methodParams, method.Type(), context)
out := method.Call(in)
//For backward compatibility we only handle response if we had incoming methodParams
// For backward compatibility we only handle response if we had incoming methodParams
if methodParams != nil {
p.handleParamResponse(context, execController, out)
}
}
}
//render template
// render template
if !context.ResponseWriter.Started && context.Output.Status == 0 {
if BConfig.WebConfig.AutoRender {
if err := execController.Render(); err != nil {
@ -872,7 +917,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
execController.Finish()
}
//execute middleware filters
// execute middleware filters
if len(p.filters[AfterExec]) > 0 && p.execFilter(context, urlPath, AfterExec) {
goto Admin
}
@ -882,14 +927,14 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
Admin:
//admin module record QPS
// admin module record QPS
statusCode := context.ResponseWriter.Status
if statusCode == 0 {
statusCode = 200
}
logAccess(context, &startTime, statusCode)
LogAccess(context, &startTime, statusCode)
timeDur := time.Since(startTime)
context.ResponseWriter.Elapsed = timeDur
@ -900,38 +945,28 @@ Admin:
}
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
routerName := ""
if runRouter != nil {
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
} else {
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, "", timeDur)
routerName = runRouter.Name()
}
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, routerName, timeDur)
}
}
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
var devInfo string
iswin := (runtime.GOOS == "windows")
statusColor := logs.ColorByStatus(iswin, statusCode)
methodColor := logs.ColorByMethod(iswin, r.Method)
resetColor := logs.ColorByMethod(iswin, "")
if findRouter {
if routerInfo != nil {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path,
routerInfo.pattern)
} else {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path)
}
} else {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path)
}
if iswin {
logs.W32Debug(devInfo)
} else {
logs.Debug(devInfo)
match := map[bool]string{true: "match", false: "nomatch"}
devInfo := fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s",
context.Input.IP(),
logs.ColorByStatus(statusCode), statusCode, logs.ResetColor(),
timeDur.String(),
match[findRouter],
logs.ColorByMethod(r.Method), r.Method, logs.ResetColor(),
r.URL.Path)
if routerInfo != nil {
devInfo += fmt.Sprintf(" r:%s", routerInfo.pattern)
}
logs.Debug(devInfo)
}
// Call WriteHeader if status code has been set changed
if context.Output.Status != 0 {
@ -940,7 +975,7 @@ Admin:
}
func (p *ControllerRegister) handleParamResponse(context *beecontext.Context, execController ControllerInterface, results []reflect.Value) {
//looping in reverse order for the case when both error and value are returned and error sets the response status code
// looping in reverse order for the case when both error and value are returned and error sets the response status code
for i := len(results) - 1; i >= 0; i-- {
result := results[i]
if result.Kind() != reflect.Interface || !result.IsNil() {
@ -980,12 +1015,13 @@ func toURL(params map[string]string) string {
return strings.TrimRight(u, "&")
}
func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
//Skip logging if AccessLogs config is false
// LogAccess logging info HTTP Access
func LogAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
// Skip logging if AccessLogs config is false
if !BConfig.Log.AccessLogs {
return
}
//Skip logging static requests unless EnableStaticLogs config is true
// Skip logging static requests unless EnableStaticLogs config is true
if !BConfig.Log.EnableStaticLogs && DefaultAccessLogFilter.Filter(ctx) {
return
}
@ -1010,7 +1046,7 @@ func logAccess(ctx *beecontext.Context, startTime *time.Time, statusCode int) {
HTTPReferrer: r.Header.Get("Referer"),
HTTPUserAgent: r.Header.Get("User-Agent"),
RemoteUser: r.Header.Get("Remote-User"),
BodyBytesSent: 0, //@todo this one is missing!
BodyBytesSent: 0, // @todo this one is missing!
}
logs.AccessLog(record, BConfig.Log.AccessLogsFormat)
}

View File

@ -15,6 +15,7 @@
package beego
import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
@ -71,11 +72,6 @@ func (tc *TestController) GetEmptyBody() {
tc.Ctx.Output.Body(res)
}
type ResStatus struct {
Code int
Msg string
}
type JSONController struct {
Controller
}
@ -475,7 +471,7 @@ func TestParamResetFilter(t *testing.T) {
// a response header of `Splat`. The expectation here is that that Header
// value should match what the _request's_ router set, not the filter's.
headers := rw.HeaderMap
headers := rw.Result().Header
if len(headers["Splat"]) != 1 {
t.Errorf(
"%s: There was an error in the test. Splat param not set in Header",
@ -660,26 +656,14 @@ func beegoBeforeRouter1(ctx *context.Context) {
ctx.WriteString("|BeforeRouter1")
}
func beegoBeforeRouter2(ctx *context.Context) {
ctx.WriteString("|BeforeRouter2")
}
func beegoBeforeExec1(ctx *context.Context) {
ctx.WriteString("|BeforeExec1")
}
func beegoBeforeExec2(ctx *context.Context) {
ctx.WriteString("|BeforeExec2")
}
func beegoAfterExec1(ctx *context.Context) {
ctx.WriteString("|AfterExec1")
}
func beegoAfterExec2(ctx *context.Context) {
ctx.WriteString("|AfterExec2")
}
func beegoFinishRouter1(ctx *context.Context) {
ctx.WriteString("|FinishRouter1")
}
@ -722,3 +706,27 @@ func TestYAMLPrepare(t *testing.T) {
t.Errorf(w.Body.String())
}
}
func TestRouterEntityTooLargeCopyBody(t *testing.T) {
_MaxMemory := BConfig.MaxMemory
_CopyRequestBody := BConfig.CopyRequestBody
BConfig.CopyRequestBody = true
BConfig.MaxMemory = 20
b := bytes.NewBuffer([]byte("barbarbarbarbarbarbarbarbarbar"))
r, _ := http.NewRequest("POST", "/user/123", b)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Post("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
handler.ServeHTTP(w, r)
BConfig.CopyRequestBody = _CopyRequestBody
BConfig.MaxMemory = _MaxMemory
if w.Code != http.StatusRequestEntityTooLarge {
t.Errorf("TestRouterRequestEntityTooLarge can't run")
}
}

112
scripts/gobuild.sh Executable file
View File

@ -0,0 +1,112 @@
#!/bin/bash
# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY
#
# The original version of this file is located in the https://github.com/istio/common-files repo.
# If you're looking at this file in a different repo and want to make a change, please go to the
# common-files repo, make the change there and check it in. Then come back to this repo and run
# "make update-common".
# Copyright Istio Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script builds and version stamps the output
# adatp to beego
VERBOSE=${VERBOSE:-"0"}
V=""
if [[ "${VERBOSE}" == "1" ]];then
V="-x"
set -x
fi
SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
OUT=${1:?"output path"}
shift
set -e
BUILD_GOOS=${GOOS:-linux}
BUILD_GOARCH=${GOARCH:-amd64}
GOBINARY=${GOBINARY:-go}
GOPKG="$GOPATH/pkg"
BUILDINFO=${BUILDINFO:-""}
STATIC=${STATIC:-1}
LDFLAGS=${LDFLAGS:--extldflags -static}
GOBUILDFLAGS=${GOBUILDFLAGS:-""}
# Split GOBUILDFLAGS by spaces into an array called GOBUILDFLAGS_ARRAY.
IFS=' ' read -r -a GOBUILDFLAGS_ARRAY <<< "$GOBUILDFLAGS"
GCFLAGS=${GCFLAGS:-}
export CGO_ENABLED=0
if [[ "${STATIC}" != "1" ]];then
LDFLAGS=""
fi
# gather buildinfo if not already provided
# For a release build BUILDINFO should be produced
# at the beginning of the build and used throughout
if [[ -z ${BUILDINFO} ]];then
BUILDINFO=$(mktemp)
"${SCRIPTPATH}/report_build_info.sh" > "${BUILDINFO}"
fi
# BUILD LD_EXTRAFLAGS
LD_EXTRAFLAGS=""
while read -r line; do
LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X ${line}"
done < "${BUILDINFO}"
# verify go version before build
# NB. this was copied verbatim from Kubernetes hack
minimum_go_version=go1.13 # supported patterns: go1.x, go1.x.x (x should be a number)
IFS=" " read -ra go_version <<< "$(${GOBINARY} version)"
if [[ "${minimum_go_version}" != $(echo -e "${minimum_go_version}\n${go_version[2]}" | sort -s -t. -k 1,1 -k 2,2n -k 3,3n | head -n1) && "${go_version[2]}" != "devel" ]]; then
echo "Warning: Detected that you are using an older version of the Go compiler. Beego requires ${minimum_go_version} or greater."
fi
CURRENT_BRANCH=$(git branch | grep '*')
CURRENT_BRANCH=${CURRENT_BRANCH:2}
BUILD_TIME=$(date +%Y-%m-%d--%T)
LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.GoVersion=${go_version[2]:2}"
LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.GitBranch=${CURRENT_BRANCH}"
LD_EXTRAFLAGS="${LD_EXTRAFLAGS} -X github.com/astaxie/beego.BuildTime=$BUILD_TIME"
OPTIMIZATION_FLAGS="-trimpath"
if [ "${DEBUG}" == "1" ]; then
OPTIMIZATION_FLAGS=""
fi
echo "BUILD_GOARCH: $BUILD_GOARCH"
echo "GOPKG: $GOPKG"
echo "LD_EXTRAFLAGS: $LD_EXTRAFLAGS"
echo "GO_VERSION: ${go_version[2]}"
echo "BRANCH: $CURRENT_BRANCH"
echo "BUILD_TIME: $BUILD_TIME"
time GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} ${GOBINARY} build \
${V} "${GOBUILDFLAGS_ARRAY[@]}" ${GCFLAGS:+-gcflags "${GCFLAGS}"} \
-o "${OUT}" \
${OPTIMIZATION_FLAGS} \
-pkgdir="${GOPKG}/${BUILD_GOOS}_${BUILD_GOARCH}" \
-ldflags "${LDFLAGS} ${LD_EXTRAFLAGS}" "${@}"

52
scripts/report_build_info.sh Executable file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# WARNING: DO NOT EDIT, THIS FILE IS PROBABLY A COPY
#
# The original version of this file is located in the https://github.com/istio/common-files repo.
# If you're looking at this file in a different repo and want to make a change, please go to the
# common-files repo, make the change there and check it in. Then come back to this repo and run
# "make update-common".
# Copyright Istio Authors
#
# 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.
# adapt to beego
if BUILD_GIT_REVISION=$(git rev-parse HEAD 2> /dev/null); then
if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then
BUILD_GIT_REVISION=${BUILD_GIT_REVISION}"-dirty"
fi
else
BUILD_GIT_REVISION=unknown
fi
# Check for local changes
if git diff-index --quiet HEAD --; then
tree_status="Clean"
else
tree_status="Modified"
fi
# security wanted VERSION='unknown'
VERSION="${BUILD_GIT_REVISION}"
if [[ -n ${BEEGO_VERSION} ]]; then
VERSION="${BEEGO_VERSION}"
fi
GIT_DESCRIBE_TAG=$(git describe --tags)
echo "github.com/astaxie/beego.BuildVersion=${VERSION}"
echo "github.com/astaxie/beego.BuildGitRevision=${BUILD_GIT_REVISION}"
echo "github.com/astaxie/beego.BuildStatus=${tree_status}"
echo "github.com/astaxie/beego.BuildTag=${GIT_DESCRIBE_TAG}"

View File

@ -7,9 +7,10 @@ import (
"strings"
"sync"
"github.com/ledisdb/ledisdb/config"
"github.com/ledisdb/ledisdb/ledis"
"github.com/astaxie/beego/session"
"github.com/siddontang/ledisdb/config"
"github.com/siddontang/ledisdb/ledis"
)
var (
@ -133,7 +134,7 @@ func (lp *Provider) SessionRead(sid string) (session.Store, error) {
// SessionExist check ledis session exist by sid
func (lp *Provider) SessionExist(sid string) bool {
count, _ := c.Exists([]byte(sid))
return !(count == 0)
return count != 0
}
// SessionRegenerate generate new sid for ledis session

View File

@ -128,9 +128,12 @@ func (rp *MemProvider) SessionRead(sid string) (session.Store, error) {
}
}
item, err := client.Get(sid)
if err != nil && err == memcache.ErrCacheMiss {
rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
return rs, nil
if err != nil {
if err == memcache.ErrCacheMiss {
rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime}
return rs, nil
}
return nil, err
}
var kv map[interface{}]interface{}
if len(item.Value) == 0 {

View File

@ -170,7 +170,7 @@ func (mp *Provider) SessionExist(sid string) bool {
row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid)
var sessiondata []byte
err := row.Scan(&sessiondata)
return !(err == sql.ErrNoRows)
return err != sql.ErrNoRows
}
// SessionRegenerate generate new sid for mysql session

View File

@ -184,7 +184,7 @@ func (mp *Provider) SessionExist(sid string) bool {
row := c.QueryRow("select session_data from session where session_key=$1", sid)
var sessiondata []byte
err := row.Scan(&sessiondata)
return !(err == sql.ErrNoRows)
return err != sql.ErrNoRows
}
// SessionRegenerate generate new sid for postgresql session

View File

@ -31,14 +31,16 @@
//
// more docs: http://beego.me/docs/module/session.md
package redis_cluster
import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/astaxie/beego/session"
rediss "github.com/go-redis/redis"
"time"
)
var redispder = &Provider{}
@ -101,7 +103,7 @@ func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
return
}
c := rs.p
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime) * time.Second)
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second)
}
// Provider redis_cluster session provider
@ -146,10 +148,10 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
} else {
rp.dbNum = 0
}
rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{
Addrs: strings.Split(rp.savePath, ";"),
Password: rp.password,
Password: rp.password,
PoolSize: rp.poolsize,
})
return rp.poollist.Ping().Err()
@ -186,15 +188,15 @@ func (rp *Provider) SessionExist(sid string) bool {
// SessionRegenerate generate new sid for redis_cluster session
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
c := rp.poollist
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
// oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error
// the existed value will be 0
c.Set(sid, "", time.Duration(rp.maxlifetime) * time.Second)
c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second)
} else {
c.Rename(oldsid, sid)
c.Expire(sid, time.Duration(rp.maxlifetime) * time.Second)
c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second)
}
return rp.SessionRead(sid)
}

View File

@ -0,0 +1,235 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package redis for session provider
//
// depend on github.com/go-redis/redis
//
// go install github.com/go-redis/redis
//
// Usage:
// import(
// _ "github.com/astaxie/beego/session/redis_sentinel"
// "github.com/astaxie/beego/session"
// )
//
// func init() {
// globalSessions, _ = session.NewManager("redis_sentinel", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:26379;127.0.0.2:26379"}``)
// go globalSessions.GC()
// }
//
// more detail about params: please check the notes on the function SessionInit in this package
package redis_sentinel
import (
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/astaxie/beego/session"
"github.com/go-redis/redis"
)
var redispder = &Provider{}
// DefaultPoolSize redis_sentinel default pool size
var DefaultPoolSize = 100
// SessionStore redis_sentinel session store
type SessionStore struct {
p *redis.Client
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}
// Set value in redis_sentinel session
func (rs *SessionStore) Set(key, value interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values[key] = value
return nil
}
// Get value in redis_sentinel session
func (rs *SessionStore) Get(key interface{}) interface{} {
rs.lock.RLock()
defer rs.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v
}
return nil
}
// Delete value in redis_sentinel session
func (rs *SessionStore) Delete(key interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
delete(rs.values, key)
return nil
}
// Flush clear all values in redis_sentinel session
func (rs *SessionStore) Flush() error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
}
// SessionID get redis_sentinel session id
func (rs *SessionStore) SessionID() string {
return rs.sid
}
// SessionRelease save session values to redis_sentinel
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(rs.values)
if err != nil {
return
}
c := rs.p
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime)*time.Second)
}
// Provider redis_sentinel session provider
type Provider struct {
maxlifetime int64
savePath string
poolsize int
password string
dbNum int
poollist *redis.Client
masterName string
}
// SessionInit init redis_sentinel session
// savepath like redis sentinel addr,pool size,password,dbnum,masterName
// e.g. 127.0.0.1:26379;127.0.0.2:26379,100,1qaz2wsx,0,mymaster
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
if len(configs) > 0 {
rp.savePath = configs[0]
}
if len(configs) > 1 {
poolsize, err := strconv.Atoi(configs[1])
if err != nil || poolsize < 0 {
rp.poolsize = DefaultPoolSize
} else {
rp.poolsize = poolsize
}
} else {
rp.poolsize = DefaultPoolSize
}
if len(configs) > 2 {
rp.password = configs[2]
}
if len(configs) > 3 {
dbnum, err := strconv.Atoi(configs[3])
if err != nil || dbnum < 0 {
rp.dbNum = 0
} else {
rp.dbNum = dbnum
}
} else {
rp.dbNum = 0
}
if len(configs) > 4 {
if configs[4] != "" {
rp.masterName = configs[4]
} else {
rp.masterName = "mymaster"
}
} else {
rp.masterName = "mymaster"
}
rp.poollist = redis.NewFailoverClient(&redis.FailoverOptions{
SentinelAddrs: strings.Split(rp.savePath, ";"),
Password: rp.password,
PoolSize: rp.poolsize,
DB: rp.dbNum,
MasterName: rp.masterName,
})
return rp.poollist.Ping().Err()
}
// SessionRead read redis_sentinel session by sid
func (rp *Provider) SessionRead(sid string) (session.Store, error) {
var kv map[interface{}]interface{}
kvs, err := rp.poollist.Get(sid).Result()
if err != nil && err != redis.Nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
if kv, err = session.DecodeGob([]byte(kvs)); err != nil {
return nil, err
}
}
rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}
// SessionExist check redis_sentinel session exist by sid
func (rp *Provider) SessionExist(sid string) bool {
c := rp.poollist
if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 {
return false
}
return true
}
// SessionRegenerate generate new sid for redis_sentinel session
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
c := rp.poollist
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
// oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error
// the existed value will be 0
c.Set(sid, "", time.Duration(rp.maxlifetime)*time.Second)
} else {
c.Rename(oldsid, sid)
c.Expire(sid, time.Duration(rp.maxlifetime)*time.Second)
}
return rp.SessionRead(sid)
}
// SessionDestroy delete redis session by id
func (rp *Provider) SessionDestroy(sid string) error {
c := rp.poollist
c.Del(sid)
return nil
}
// SessionGC Impelment method, no used.
func (rp *Provider) SessionGC() {
}
// SessionAll return all activeSession
func (rp *Provider) SessionAll() int {
return 0
}
func init() {
session.Register("redis_sentinel", redispder)
}

View File

@ -0,0 +1,90 @@
package redis_sentinel
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/astaxie/beego/session"
)
func TestRedisSentinel(t *testing.T) {
sessionConfig := &session.ManagerConfig{
CookieName: "gosessionid",
EnableSetCookie: true,
Gclifetime: 3600,
Maxlifetime: 3600,
Secure: false,
CookieLifeTime: 3600,
ProviderConfig: "127.0.0.1:6379,100,,0,master",
}
globalSessions, e := session.NewManager("redis_sentinel", sessionConfig)
if e != nil {
t.Log(e)
return
}
//todo test if e==nil
go globalSessions.GC()
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("session start failed:", err)
}
defer sess.SessionRelease(w)
// SET AND GET
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set username failed:", err)
}
username := sess.Get("username")
if username != "astaxie" {
t.Fatal("get username failed")
}
// DELETE
err = sess.Delete("username")
if err != nil {
t.Fatal("delete username failed:", err)
}
username = sess.Get("username")
if username != nil {
t.Fatal("delete username failed")
}
// FLUSH
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set failed:", err)
}
err = sess.Set("password", "1qaz2wsx")
if err != nil {
t.Fatal("set failed:", err)
}
username = sess.Get("username")
if username != "astaxie" {
t.Fatal("get username failed")
}
password := sess.Get("password")
if password != "1qaz2wsx" {
t.Fatal("get password failed")
}
err = sess.Flush()
if err != nil {
t.Fatal("flush failed:", err)
}
username = sess.Get("username")
if username != nil {
t.Fatal("flush failed")
}
password = sess.Get("password")
if password != nil {
t.Fatal("flush failed")
}
sess.SessionRelease(w)
}

View File

@ -74,7 +74,9 @@ func (st *CookieSessionStore) SessionID() string {
// SessionRelease Write cookie session to http response cookie
func (st *CookieSessionStore) SessionRelease(w http.ResponseWriter) {
st.lock.Lock()
encodedCookie, err := encodeCookie(cookiepder.block, cookiepder.config.SecurityKey, cookiepder.config.SecurityName, st.values)
st.lock.Unlock()
if err == nil {
cookie := &http.Cookie{Name: cookiepder.config.CookieName,
Value: url.QueryEscape(encodedCookie),

View File

@ -15,6 +15,7 @@
package session
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -128,13 +129,17 @@ func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error {
// if file is not exist, create it.
// the file path is generated from sid string.
func (fp *FileProvider) SessionRead(sid string) (Store, error) {
if strings.ContainsAny(sid, "./") {
return nil, nil
invalidChars := "./"
if strings.ContainsAny(sid, invalidChars) {
return nil, errors.New("the sid shouldn't have following characters: " + invalidChars)
}
if len(sid) < 2 {
return nil, errors.New("length of the sid is less than 2")
}
filepder.lock.Lock()
defer filepder.lock.Unlock()
err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0777)
err := os.MkdirAll(path.Join(fp.savePath, string(sid[0]), string(sid[1])), 0755)
if err != nil {
SLogger.Println(err.Error())
}
@ -175,6 +180,11 @@ func (fp *FileProvider) SessionExist(sid string) bool {
filepder.lock.Lock()
defer filepder.lock.Unlock()
if len(sid) < 2 {
SLogger.Println("min length of session id is 2", sid)
return false
}
_, err := os.Stat(path.Join(fp.savePath, string(sid[0]), string(sid[1]), sid))
return err == nil
}
@ -227,7 +237,7 @@ func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
return nil, fmt.Errorf("newsid %s exist", newSidFile)
}
err = os.MkdirAll(newPath, 0777)
err = os.MkdirAll(newPath, 0755)
if err != nil {
SLogger.Println(err.Error())
}

386
session/sess_file_test.go Normal file
View File

@ -0,0 +1,386 @@
// 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 session
import (
"fmt"
"os"
"sync"
"testing"
"time"
)
const sid = "Session_id"
const sidNew = "Session_id_new"
const sessionPath = "./_session_runtime"
var (
mutex sync.Mutex
)
func TestFileProvider_SessionInit(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
if fp.maxlifetime != 180 {
t.Error()
}
if fp.savePath != sessionPath {
t.Error()
}
}
func TestFileProvider_SessionExist(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
if fp.SessionExist(sid) {
t.Error()
}
_, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
if !fp.SessionExist(sid) {
t.Error()
}
}
func TestFileProvider_SessionExist2(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
if fp.SessionExist(sid) {
t.Error()
}
if fp.SessionExist("") {
t.Error()
}
if fp.SessionExist("1") {
t.Error()
}
}
func TestFileProvider_SessionRead(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
s, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
_ = s.Set("sessionValue", 18975)
v := s.Get("sessionValue")
if v.(int) != 18975 {
t.Error()
}
}
func TestFileProvider_SessionRead1(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
_, err := fp.SessionRead("")
if err == nil {
t.Error(err)
}
_, err = fp.SessionRead("1")
if err == nil {
t.Error(err)
}
}
func TestFileProvider_SessionAll(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 546
for i := 1; i <= sessionCount; i++ {
_, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
}
if fp.SessionAll() != sessionCount {
t.Error()
}
}
func TestFileProvider_SessionRegenerate(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
_, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
if !fp.SessionExist(sid) {
t.Error()
}
_, err = fp.SessionRegenerate(sid, sidNew)
if err != nil {
t.Error(err)
}
if fp.SessionExist(sid) {
t.Error()
}
if !fp.SessionExist(sidNew) {
t.Error()
}
}
func TestFileProvider_SessionDestroy(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
_, err := fp.SessionRead(sid)
if err != nil {
t.Error(err)
}
if !fp.SessionExist(sid) {
t.Error()
}
err = fp.SessionDestroy(sid)
if err != nil {
t.Error(err)
}
if fp.SessionExist(sid) {
t.Error()
}
}
func TestFileProvider_SessionGC(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(1, sessionPath)
sessionCount := 412
for i := 1; i <= sessionCount; i++ {
_, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
}
time.Sleep(2 * time.Second)
fp.SessionGC()
if fp.SessionAll() != 0 {
t.Error()
}
}
func TestFileSessionStore_Set(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 100
s, _ := fp.SessionRead(sid)
for i := 1; i <= sessionCount; i++ {
err := s.Set(i, i)
if err != nil {
t.Error(err)
}
}
}
func TestFileSessionStore_Get(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 100
s, _ := fp.SessionRead(sid)
for i := 1; i <= sessionCount; i++ {
_ = s.Set(i, i)
v := s.Get(i)
if v.(int) != i {
t.Error()
}
}
}
func TestFileSessionStore_Delete(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
s, _ := fp.SessionRead(sid)
s.Set("1", 1)
if s.Get("1") == nil {
t.Error()
}
s.Delete("1")
if s.Get("1") != nil {
t.Error()
}
}
func TestFileSessionStore_Flush(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 100
s, _ := fp.SessionRead(sid)
for i := 1; i <= sessionCount; i++ {
_ = s.Set(i, i)
}
_ = s.Flush()
for i := 1; i <= sessionCount; i++ {
if s.Get(i) != nil {
t.Error()
}
}
}
func TestFileSessionStore_SessionID(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
sessionCount := 85
for i := 1; i <= sessionCount; i++ {
s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
if s.SessionID() != fmt.Sprintf("%s_%d", sid, i) {
t.Error(err)
}
}
}
func TestFileSessionStore_SessionRelease(t *testing.T) {
mutex.Lock()
defer mutex.Unlock()
os.RemoveAll(sessionPath)
defer os.RemoveAll(sessionPath)
fp := &FileProvider{}
_ = fp.SessionInit(180, sessionPath)
filepder.savePath = sessionPath
sessionCount := 85
for i := 1; i <= sessionCount; i++ {
s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
s.Set(i, i)
s.SessionRelease(nil)
}
for i := 1; i <= sessionCount; i++ {
s, err := fp.SessionRead(fmt.Sprintf("%s_%d", sid, i))
if err != nil {
t.Error(err)
}
if s.Get(i).(int) != i {
t.Error()
}
}
}

View File

@ -19,7 +19,7 @@ import (
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/gob"
@ -129,7 +129,7 @@ func encodeCookie(block cipher.Block, hashKey, name string, value map[interface{
b = encode(b)
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
b = []byte(fmt.Sprintf("%s|%d|%s|", name, time.Now().UTC().Unix(), b))
h := hmac.New(sha1.New, []byte(hashKey))
h := hmac.New(sha256.New, []byte(hashKey))
h.Write(b)
sig := h.Sum(nil)
// Append mac, remove name.
@ -153,7 +153,7 @@ func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime
}
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
h := hmac.New(sha1.New, []byte(hashKey))
h := hmac.New(sha256.New, []byte(hashKey))
h.Write(b)
sig := h.Sum(nil)
if len(sig) != len(parts[2]) || subtle.ConstantTimeCompare(sig, parts[2]) != 1 {

View File

@ -81,22 +81,32 @@ func Register(name string, provide Provider) {
provides[name] = provide
}
//GetProvider
func GetProvider(name string) (Provider, error) {
provider, ok := provides[name]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", name)
}
return provider, nil
}
// ManagerConfig define the session config
type ManagerConfig struct {
CookieName string `json:"cookieName"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"`
DisableHTTPOnly bool `json:"disableHTTPOnly"`
Secure bool `json:"secure"`
CookieLifeTime int `json:"cookieLifeTime"`
ProviderConfig string `json:"providerConfig"`
Domain string `json:"domain"`
SessionIDLength int64 `json:"sessionIDLength"`
EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"`
SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"`
EnableSidInURLQuery bool `json:"EnableSidInURLQuery"`
SessionIDPrefix string `json:"sessionIDPrefix"`
CookieName string `json:"cookieName"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"`
DisableHTTPOnly bool `json:"disableHTTPOnly"`
Secure bool `json:"secure"`
CookieLifeTime int `json:"cookieLifeTime"`
ProviderConfig string `json:"providerConfig"`
Domain string `json:"domain"`
SessionIDLength int64 `json:"sessionIDLength"`
EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"`
SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"`
EnableSidInURLQuery bool `json:"EnableSidInURLQuery"`
SessionIDPrefix string `json:"sessionIDPrefix"`
CookieSameSite http.SameSite `json:"cookieSameSite"`
}
// Manager contains Provider and its configuration.
@ -223,6 +233,7 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
HttpOnly: !manager.config.DisableHTTPOnly,
Secure: manager.isSecure(r),
Domain: manager.config.Domain,
SameSite: manager.config.CookieSameSite,
}
if manager.config.CookieLifeTime > 0 {
cookie.MaxAge = manager.config.CookieLifeTime
@ -261,7 +272,10 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
Path: "/",
HttpOnly: !manager.config.DisableHTTPOnly,
Expires: expiration,
MaxAge: -1}
MaxAge: -1,
Domain: manager.config.Domain,
SameSite: manager.config.CookieSameSite,
}
http.SetCookie(w, cookie)
}
@ -281,25 +295,36 @@ func (manager *Manager) GC() {
}
// SessionRegenerateID Regenerate a session id for this SessionStore who's id is saving in http request.
func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) (session Store) {
func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Request) (Store, error) {
sid, err := manager.sessionID()
if err != nil {
return
return nil, err
}
var session Store
cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" {
//delete old cookie
session, _ = manager.provider.SessionRead(sid)
// delete old cookie
session, err = manager.provider.SessionRead(sid)
if err != nil {
return nil, err
}
cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: "/",
HttpOnly: !manager.config.DisableHTTPOnly,
Secure: manager.isSecure(r),
Domain: manager.config.Domain,
SameSite: manager.config.CookieSameSite,
}
} else {
oldsid, _ := url.QueryUnescape(cookie.Value)
session, _ = manager.provider.SessionRegenerate(oldsid, sid)
oldsid, err := url.QueryUnescape(cookie.Value)
if err != nil {
return nil, err
}
session, err = manager.provider.SessionRegenerate(oldsid, sid)
if err != nil {
return nil, err
}
cookie.Value = url.QueryEscape(sid)
cookie.HttpOnly = true
cookie.Path = "/"
@ -318,7 +343,7 @@ func (manager *Manager) SessionRegenerateID(w http.ResponseWriter, r *http.Reque
w.Header().Set(manager.config.SessionNameInHTTPHeader, sid)
}
return
return session, nil
}
// GetActiveSession Get all active sessions count number.

View File

@ -28,6 +28,7 @@ import (
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs"
"github.com/hashicorp/golang-lru"
)
var errNotStaticRequest = errors.New("request not a static file request")
@ -67,6 +68,10 @@ func serverStaticRouter(ctx *context.Context) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, filePath)
}
return
} else if fileInfo.Size() > int64(BConfig.WebConfig.StaticCacheFileSize) {
//over size file serve with http module
http.ServeFile(ctx.ResponseWriter, ctx.Request, filePath)
return
}
var enableCompress = BConfig.EnableGzip && isStaticCompress(filePath)
@ -93,10 +98,11 @@ func serverStaticRouter(ctx *context.Context) {
}
type serveContentHolder struct {
data []byte
modTime time.Time
size int64
encoding string
data []byte
modTime time.Time
size int64
originSize int64 //original file size:to judge file changed
encoding string
}
type serveContentReader struct {
@ -104,22 +110,36 @@ type serveContentReader struct {
}
var (
staticFileMap = make(map[string]*serveContentHolder)
mapLock sync.RWMutex
staticFileLruCache *lru.Cache
lruLock sync.RWMutex
)
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) {
if staticFileLruCache == nil {
//avoid lru cache error
if BConfig.WebConfig.StaticCacheFileNum >= 1 {
staticFileLruCache, _ = lru.New(BConfig.WebConfig.StaticCacheFileNum)
} else {
staticFileLruCache, _ = lru.New(1)
}
}
mapKey := acceptEncoding + ":" + filePath
mapLock.RLock()
mapFile := staticFileMap[mapKey]
mapLock.RUnlock()
lruLock.RLock()
var mapFile *serveContentHolder
if cacheItem, ok := staticFileLruCache.Get(mapKey); ok {
mapFile = cacheItem.(*serveContentHolder)
}
lruLock.RUnlock()
if isOk(mapFile, fi) {
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
}
mapLock.Lock()
defer mapLock.Unlock()
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) {
lruLock.Lock()
defer lruLock.Unlock()
if cacheItem, ok := staticFileLruCache.Get(mapKey); ok {
mapFile = cacheItem.(*serveContentHolder)
}
if !isOk(mapFile, fi) {
file, err := os.Open(filePath)
if err != nil {
return false, "", nil, nil, err
@ -130,8 +150,10 @@ func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, str
if err != nil {
return false, "", nil, nil, err
}
mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
staticFileMap[mapKey] = mapFile
mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), originSize: fi.Size(), encoding: n}
if isOk(mapFile, fi) {
staticFileLruCache.Add(mapKey, mapFile)
}
}
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
@ -141,8 +163,10 @@ func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, str
func isOk(s *serveContentHolder, fi os.FileInfo) bool {
if s == nil {
return false
} else if s.size > int64(BConfig.WebConfig.StaticCacheFileSize) {
return false
}
return s.modTime == fi.ModTime() && s.size == fi.Size()
return s.modTime == fi.ModTime() && s.originSize == fi.Size()
}
// isStaticCompress detect static files
@ -178,7 +202,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) {
if !strings.Contains(requestPath, prefix) {
continue
}
if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' {
if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' {
continue
}
filePath := path.Join(staticDir, requestPath[len(prefix):])

View File

@ -4,6 +4,7 @@ import (
"bytes"
"compress/gzip"
"compress/zlib"
"fmt"
"io"
"io/ioutil"
"os"
@ -53,6 +54,31 @@ func TestOpenStaticFileDeflate_1(t *testing.T) {
testOpenFile("deflate", content, t)
}
func TestStaticCacheWork(t *testing.T) {
encodings := []string{"", "gzip", "deflate"}
fi, _ := os.Stat(licenseFile)
for _, encoding := range encodings {
_, _, first, _, err := openFile(licenseFile, fi, encoding)
if err != nil {
t.Error(err)
continue
}
_, _, second, _, err := openFile(licenseFile, fi, encoding)
if err != nil {
t.Error(err)
continue
}
address1 := fmt.Sprintf("%p", first)
address2 := fmt.Sprintf("%p", second)
if address1 != address2 {
t.Errorf("encoding '%v' can not hit cache", encoding)
}
}
}
func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) {
t.Log(sch.size, len(content))
if sch.size != int64(len(content)) {
@ -66,7 +92,7 @@ func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader
t.Fail()
}
}
if len(staticFileMap) == 0 {
if staticFileLruCache.Len() == 0 {
t.Log("men map is empty")
t.Fail()
}

View File

@ -38,7 +38,7 @@ var (
beeViewPathTemplates = make(map[string]map[string]*template.Template)
templatesLock sync.RWMutex
// beeTemplateExt stores the template extension which will build
beeTemplateExt = []string{"tpl", "html"}
beeTemplateExt = []string{"tpl", "html", "gohtml"}
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
beeTemplateEngines = map[string]templatePreProcessor{}
beeTemplateFS = defaultFSFunc
@ -240,7 +240,7 @@ func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *
var fileAbsPath string
var rParent string
var err error
if filepath.HasPrefix(file, "../") {
if strings.HasPrefix(file, "../") {
rParent = filepath.Join(filepath.Dir(parent), file)
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
} else {
@ -248,10 +248,10 @@ func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *
fileAbsPath = filepath.Join(root, file)
}
f, err := fs.Open(fileAbsPath)
defer f.Close()
if err != nil {
panic("can't find template file:" + file)
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, [][]string{}, err

View File

@ -16,12 +16,13 @@ package beego
import (
"bytes"
"github.com/astaxie/beego/testdata"
"github.com/elazarl/go-bindata-assetfs"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/astaxie/beego/testdata"
"github.com/elazarl/go-bindata-assetfs"
)
var header = `{{define "header"}}
@ -45,8 +46,12 @@ var block = `{{define "block"}}
<h1>Hello, blocks!</h1>
{{end}}`
func tmpDir(s string) string {
return filepath.Join(os.TempDir(), s)
}
func TestTemplate(t *testing.T) {
dir := "_beeTmp"
dir := tmpDir("TestTemplate")
files := []string{
"header.tpl",
"index.tpl",
@ -107,7 +112,7 @@ var user = `<!DOCTYPE html>
`
func TestRelativeTemplate(t *testing.T) {
dir := "_beeTmp"
dir := tmpDir("TestRelativeTemplate")
//Just add dir to known viewPaths
if err := AddViewPath(dir); err != nil {
@ -218,7 +223,7 @@ var output = `<!DOCTYPE html>
`
func TestTemplateLayout(t *testing.T) {
dir := "_beeTmp"
dir := tmpDir("TestTemplateLayout")
files := []string{
"add.tpl",
"layout_blog.tpl",

View File

@ -55,21 +55,21 @@ func Substr(s string, start, length int) string {
// HTML2str returns escaping text convert from html.
func HTML2str(html string) string {
re, _ := regexp.Compile(`\<[\S\s]+?\>`)
re := regexp.MustCompile(`\<[\S\s]+?\>`)
html = re.ReplaceAllStringFunc(html, strings.ToLower)
//remove STYLE
re, _ = regexp.Compile(`\<style[\S\s]+?\</style\>`)
re = regexp.MustCompile(`\<style[\S\s]+?\</style\>`)
html = re.ReplaceAllString(html, "")
//remove SCRIPT
re, _ = regexp.Compile(`\<script[\S\s]+?\</script\>`)
re = regexp.MustCompile(`\<script[\S\s]+?\</script\>`)
html = re.ReplaceAllString(html, "")
re, _ = regexp.Compile(`\<[\S\s]+?\>`)
re = regexp.MustCompile(`\<[\S\s]+?\>`)
html = re.ReplaceAllString(html, "\n")
re, _ = regexp.Compile(`\s{2,}`)
re = regexp.MustCompile(`\s{2,}`)
html = re.ReplaceAllString(html, "\n")
return strings.TrimSpace(html)
@ -85,24 +85,24 @@ func DateFormat(t time.Time, layout string) (datestring string) {
var datePatterns = []string{
// year
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
"y", "06", //A two digit representation of a year Examples: 99 or 03
"y", "06", //A two digit representation of a year Examples: 99 or 03
// month
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec
"F", "January", // A full textual representation of a month, such as January or March January through December
// day
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
"j", "2", // Day of the month without leading zeros 1 to 31
"j", "2", // Day of the month without leading zeros 1 to 31
// week
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
"D", "Mon", // A textual representation of a day, three letters Mon through Sun
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
// time
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23
@ -172,7 +172,7 @@ func GetConfig(returnType, key string, defaultVal interface{}) (value interface{
case "DIY":
value, err = AppConfig.DIY(key)
default:
err = errors.New("Config keys must be of type String, Bool, Int, Int64, Float, or DIY")
err = errors.New("config keys must be of type String, Bool, Int, Int64, Float, or DIY")
}
if err != nil {
@ -297,9 +297,21 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
tag = tags[0]
}
value := form.Get(tag)
if len(value) == 0 {
continue
formValues := form[tag]
var value string
if len(formValues) == 0 {
defaultValue := fieldT.Tag.Get("default")
if defaultValue != "" {
value = defaultValue
} else {
continue
}
}
if len(formValues) == 1 {
value = formValues[0]
if value == "" {
continue
}
}
switch fieldT.Type.Kind() {
@ -349,6 +361,8 @@ func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) e
if len(value) >= 25 {
value = value[:25]
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
} else if strings.HasSuffix(strings.ToUpper(value), "Z") {
t, err = time.ParseInLocation(time.RFC3339, value, time.Local)
} else if len(value) >= 19 {
if strings.Contains(value, "T") {
value = value[:19]

View File

@ -111,7 +111,7 @@ func TestHtmlunquote(t *testing.T) {
func TestParseForm(t *testing.T) {
type ExtendInfo struct {
Hobby string `form:"hobby"`
Hobby []string `form:"hobby"`
Memo string
}
@ -146,7 +146,7 @@ func TestParseForm(t *testing.T) {
"date": []string{"2014-11-12"},
"organization": []string{"beego"},
"title": []string{"CXO"},
"hobby": []string{"Basketball"},
"hobby": []string{"", "Basketball", "Football"},
"memo": []string{"nothing"},
}
if err := ParseForm(form, u); err == nil {
@ -186,8 +186,14 @@ func TestParseForm(t *testing.T) {
if u.Title != "CXO" {
t.Errorf("Title should equal `CXO`, but got `%v`", u.Title)
}
if u.Hobby != "Basketball" {
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby)
if u.Hobby[0] != "" {
t.Errorf("Hobby should equal ``, but got `%v`", u.Hobby[0])
}
if u.Hobby[1] != "Basketball" {
t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby[1])
}
if u.Hobby[2] != "Football" {
t.Errorf("Hobby should equal `Football`, but got `%v`", u.Hobby[2])
}
if len(u.Memo) != 0 {
t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo))
@ -197,7 +203,6 @@ func TestParseForm(t *testing.T) {
func TestRenderForm(t *testing.T) {
type user struct {
ID int `form:"-"`
tag string `form:"tag"`
Name interface{} `form:"username"`
Age int `form:"age,text,年龄:"`
Sex string

14
test.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
docker-compose -f test_docker_compose.yaml up -d
export ORM_DRIVER=mysql
export TZ=UTC
export ORM_SOURCE="beego:test@tcp(localhost:13306)/orm_test?charset=utf8"
go test ./...
# clear all container
docker-compose -f test_docker_compose.yaml down

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