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

262 Commits

Author SHA1 Message Date
1c9898dee5 Merge branch 'develop' 2015-02-27 22:52:42 +08:00
2cf7c6a58a change the jQuery URL 2015-02-27 22:51:20 +08:00
2cee46ab2b change the jQuery URL 2015-02-27 22:50:25 +08:00
020bfbcc9c Merge branch 'develop' 2015-02-27 22:47:59 +08:00
3f8252bffd change the version from 1.4.2 to 1.4.3 2015-02-27 22:47:21 +08:00
6d313aa15f fix #985 2015-02-27 22:37:41 +08:00
2a4e2d4a71 delete the group route, because we already has namespace 2015-02-27 22:37:07 +08:00
f96a6285bf fix #978 2015-02-27 22:21:58 +08:00
e938876c4a fix the cycle import 2015-02-27 00:12:10 +08:00
6e9d2dc965 add more error functions 2015-02-26 23:49:24 +08:00
3aceaf8838 error support controller 2015-02-26 23:34:43 +08:00
71b9854f48 Merge pull request #1044 from fuxiaohei/develop
code style simplify
2015-02-23 22:25:34 +08:00
f59ccd3a35 Merge pull request #1043 from Hepri/develop
Added support to parse slices of ints and strings in ParseForm func
2015-02-23 21:58:32 +08:00
181a7c35fe code simplify for package middleware 2015-02-23 11:50:45 +08:00
2ed272aeb2 code simplify for package middleware 2015-02-23 11:50:13 +08:00
77c1109134 code simplify for package logs 2015-02-23 11:42:46 +08:00
29d4823866 code simplify for package httplib 2015-02-23 11:30:59 +08:00
24cf06d288 code style simplify for context package 2015-02-23 11:15:55 +08:00
0c31c2d689 Added support to parse slices of ints and strings in ParseForm func 2015-02-22 22:13:06 +05:00
f988f035e5 redis provider for session and cache support select db 2015-02-16 21:56:32 +08:00
1b4158c15b Merge pull request #1039 from astaxie/revert-1000-group_by_queryseter
Revert "Add GroupBy to QuerySeter"
2015-02-14 20:40:52 +08:00
433e8f2ce3 Revert "Add GroupBy to QuerySeter" 2015-02-14 20:40:43 +08:00
22ba7fdce4 Merge pull request #1000 from pdf/group_by_queryseter
Add GroupBy to QuerySeter
2015-02-14 20:33:06 +08:00
2a0f87e810 Merge pull request #1010 from BlackLee/master
add compare_not/not_nil methods for template
2015-02-14 20:31:18 +08:00
19db4b67f6 Merge pull request #1025 from kongjian/develop
Update task tpl
2015-02-14 20:29:28 +08:00
b1baf4503d beego task list update for task spec list and task run url error 2015-02-04 18:07:31 +08:00
d536f5b8dc Merge pull request #1021 from kmulvey/readme-typo
typos in the readme
2015-02-03 09:07:00 +08:00
1cc1d57f55 development 2015-02-02 09:33:59 -05:00
73370ade90 modular 2015-02-02 09:33:27 -05:00
0d3b7dcd07 Merge pull request #911 from supar/add-column-default-attribute
Add attribute default to the column on create or alter commands. Skip co...
2015-01-16 10:40:11 -08:00
d7fe5ef435 Merge pull request #1004 from fugr/patch-2
Transaction
2015-01-13 11:19:03 -08:00
8bd902814f Transaction
err处理写反了
2015-01-13 11:09:43 +08:00
378356a65e Merge pull request #1001 from johndeng/develop
Fixed the status code issue at error handler.
2015-01-12 09:54:26 -08:00
30871e2617 Fixed the status issue at error handler. 2015-01-10 17:35:35 +08:00
3731088b4a Add GroupBy to QuerySeter
Adds support for GroupBy to QuerySeter SELECT operations.
2015-01-10 15:26:41 +11:00
d46833c6d8 Merge pull request #997 from dockercn/master
增加session模块中的ledisdb的动态配置
2015-01-09 13:58:13 +08:00
18659e16ba add compare_not/not_nil methods for template 2015-01-05 16:38:57 +08:00
5d8187d005 Merge pull request #977 from athurg/patch-1
Fix RequestURI nil caused template parse failed
2014-12-25 11:40:26 +08:00
d961ae4cd8 Fix RequestURI nil caused template parse failed
Sometime RequestURI is not set, e.g. running after a front proxy server.

We should always follow the document's directive, to use Request.URL instead of RequestURI.

Refer: http://golang.org/pkg/net/http/#Request
2014-12-25 11:23:04 +08:00
0e1a0049d1 Merge pull request #971 from athurg/get_request_params_with_default
Support default value for controller’s params get
2014-12-19 16:03:10 +08:00
0c933643e2 improve the empty router 2014-12-19 15:33:51 +08:00
d2c5daa5ee Update comments for controller's GetXXX functions 2014-12-19 15:28:18 +08:00
d3ab157915 fix the cache test 2014-12-19 14:40:16 +08:00
75d28d49c5 Merge pull request #965 from shuoli84/develop
Fix subdomain, add test, space and comment fix
2014-12-19 13:22:36 +08:00
76bb4827d0 Merge pull request #953 from kristen1980/patch-2
Allow absolute path for filesystem cache
2014-12-18 21:24:17 +08:00
3caba06189 Merge pull request #967 from athurg/support_all_type_on_urlfor
Add all type support for UrlFor’s params
2014-12-18 21:14:44 +08:00
572508ddd8 Clean json config. Fix DefaultStrings 2014-12-17 17:02:46 +08:00
e34f8479bb Add all type support for UrlFor’s params 2014-12-17 15:52:48 +08:00
daf85f06f8 Support default value for controller’s params get 2014-12-17 15:23:11 +08:00
22671c524e Fix subdomain, add test, space and comment fix 2014-12-17 12:06:53 +08:00
ab99d5f1e2 Merge pull request #957 from athurg/patch-2
Fix paginator attributes cannot be modified bug
2014-12-11 19:59:50 +08:00
c52f634d9c Fix paginator attributes cannot be modified bug
We can only use SetPaginator to create a pagination.

After that, we always need to modify something, like the totalNum, perPageNum.

These change should be seen in the view.

So we should give the view a pointer than a object.
2014-12-11 16:42:50 +08:00
9c665afc04 improve the error tips 2014-12-08 14:57:45 +08:00
77ed151243 Allow absolute path for filesystem cache
Gives more flexibility by making it an absolute path. A relative path can easily be created by the user.
2014-12-07 10:00:35 -07:00
29d98731c6 add sess_ledis select db config 2014-11-25 14:41:51 -08:00
934dd2e8d2 Merge branch 'master' of https://github.com/astaxie/beego 2014-11-25 14:27:13 -08:00
e65d87974a Merge pull request #940 from hilyjiang/develop
make Content-Type header more human-readable
2014-11-24 23:26:15 +08:00
db04c3cbb4 make Content-Type header more human-readable 2014-11-24 23:12:09 +08:00
802aa16136 Merge pull request #935 from mnhkahn/master
beego1.4.2,beego.AppConfig.Strings与老版本代码不兼容问题
2014-11-24 21:47:12 +08:00
f2df07f630 Merge pull request #933 from rbastic/develop
Reword message about reloading packages..
2014-11-24 13:18:05 +08:00
dc89f844f3 Reword message about reloading packages.. 2014-11-23 18:22:45 +01:00
b80cdef20f Merge pull request #932 from DeanThompson/master
count log file lines
2014-11-23 23:57:27 +08:00
98dcee0643 Merge pull request #926 from xuewuhen/master
SubDomains function bugfixed
2014-11-23 22:57:40 +08:00
0ad75cb5fa Merge pull request #928 from lei-cao/develop
Return the response directly if it's a options PreflightHeader request
2014-11-21 23:22:59 +08:00
6a9d04c269 count log file lines 2014-11-21 18:12:39 +08:00
93ca11f83d Return the response directly if it's a options PreflightHeader request 2014-11-21 01:35:30 +08:00
git
d0b43ef4f5 beego.AppConfig.Strings bug 2014-11-20 16:35:04 +08:00
c9bb9d6a09 SubDomains function bugfixed 2014-11-18 22:54:48 +08:00
f96245786a fix #912 2014-11-08 15:10:47 +08:00
1a1b0c14b9 Add attribute default to the column on create or alter commands. Skip columns which are keys and date or time data type 2014-11-06 17:43:53 +03:00
07c628c7e9 fix the commentsRouter init sequence 2014-11-06 17:30:50 +08:00
1e92d17605 fix the repeat commentsRouters 2014-11-06 16:25:47 +08:00
bb795847da fix the not exist config file application 2014-11-06 11:12:00 +08:00
54ba307f7f change this to short name 2014-11-05 22:40:31 +08:00
950ff91d87 hotfix for parsefiel 2014-11-05 22:23:54 +08:00
b43401b9f6 Merge pull request #907 from astaxie/develop
1.4.1 released
2014-11-04 22:39:03 +08:00
fe50269b3f change 1.4.1 to 1.4.2 2014-11-04 22:38:40 +08:00
000033e2a7 update the test case 2014-11-04 22:07:38 +08:00
15242d89ce simple the session init 2014-11-04 19:08:06 +08:00
76522d43af simple the session 2014-11-04 19:07:49 +08:00
52df1234bd Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-11-04 19:04:43 +08:00
fc6b9ce009 fix #620 simple the sessionID generate 2014-11-04 19:04:26 +08:00
b9fdd67519 add test case fot date & stringbool 2014-11-04 16:39:17 +08:00
7743eecfd4 support #761
type Test struct {
    Date    time.Time    `form:"Date, 2006-01-02"`
    Save    bool            `form:"Save"`
}
2014-11-04 16:19:46 +08:00
c4d8e4a244 fix #759 2014-11-04 15:29:33 +08:00
9d4ec508bb parse for github.com replace the . to _ 2014-11-04 10:19:30 +08:00
8b747f54bc fix #770 2014-11-03 23:33:11 +08:00
a2428af8a7 compatibility for warn & info function add one more depth 2014-11-03 16:48:45 +08:00
90a7ce5c6a split the file for logs 2014-11-03 16:45:42 +08:00
ef3c7c127b fix the variable 2014-11-03 16:44:05 +08:00
88caf1ed70 if read the log.go then calldepth add 1 2014-11-03 16:43:07 +08:00
304beaf89f update the log call deep 2014-11-03 16:40:08 +08:00
2288ac868c remove the deep Caller 2014-11-03 16:34:36 +08:00
8d797a4a5e file the static filter 2014-11-03 16:14:40 +08:00
10db97b193 add some tips for the admin server start 2014-11-03 15:08:51 +08:00
716962672f fix #751
add config ListenTCP4

when user want to listen on the TCP4, because now almost use the ipv4.
but default lister on the ipv6
2014-11-03 15:06:25 +08:00
90cff5f042 fix #824 2014-11-02 21:01:51 +08:00
da127bbc22 fix #855 #859 2014-10-31 16:31:23 +08:00
945b1da3a8 fix the gofmt 2014-10-31 15:48:57 +08:00
94c84b846f fix the init logger 2014-10-31 09:02:16 +08:00
db43892fe6 improve the Put #896 2014-10-31 00:28:51 +08:00
71149218d1 fix the log level 2014-10-30 17:43:32 +08:00
1636a7271c Revert "fix the log test"
This reverts commit ddbfc25e56.
2014-10-30 16:57:55 +08:00
68c3bdfdd4 Revert "logs:default support fileline"
This reverts commit 1f26852610.
2014-10-30 16:57:48 +08:00
ecd0a5487e fix the import cycle not allowed 2014-10-30 16:12:54 +08:00
fda841208d fix #893 2014-10-30 16:05:48 +08:00
57e62e5e57 update the file upload to io.Pipe 2014-10-30 11:16:09 +08:00
824e3f8f5b fix the only file upload param 2014-10-29 16:00:08 +08:00
1822dd95ac Merge pull request #892 from dockercn/master
Add a beego session backend using LedisDB
2014-10-29 14:18:20 +08:00
ddbfc25e56 fix the log test 2014-10-28 19:34:11 +08:00
1f26852610 logs:default support fileline 2014-10-28 19:33:14 +08:00
0188fb3711 Merge pull request #1 from chliang2030598/master
add session store in ledis
2014-10-27 06:02:19 +00:00
0bcd828d73 add session store in ledis 2014-10-26 22:56:00 -07:00
e11a27f1d1 Merge pull request #888 from astaxie/revert-887-add-column-default-attribute
Revert "Add column default attribute"
2014-10-26 10:41:34 +08:00
90caeb4cf7 Revert "Add column default attribute" 2014-10-26 10:41:22 +08:00
6c9249034d Merge pull request #887 from supar/add-column-default-attribute
Add column default attribute
2014-10-26 10:28:56 +08:00
14114018ea config ini support include 2014-10-24 19:03:27 +08:00
6f5162461e Add column DEFAULT attribute. Do not add if field is key or in
relations.
2014-10-24 14:51:35 +04:00
c34c514bba Skip add DEFAULT if the field is in relations (rel or reverse) 2014-10-24 14:37:46 +04:00
8ac2b9bf66 Merge branch 'master' into develop 2014-10-24 15:10:11 +08:00
2a85c79ce5 Merge pull request #881 from astaxie/revert-870-add-column-default-attribute
Revert "Add column default attribute"
2014-10-24 14:58:27 +08:00
767083bd56 Revert "Add column default attribute" 2014-10-24 14:58:17 +08:00
1a79513293 Merge branch 'master' into develop 2014-10-24 14:50:53 +08:00
c6cb1f92e8 Merge pull request #870 from supar/add-column-default-attribute
Add column default attribute
2014-10-24 14:49:53 +08:00
9c0aad06c5 Merge pull request #880 from chenghuama/patch-3
Update ini.go
2014-10-24 14:15:49 +08:00
180c6aafac Update ini.go
支持BOM格式的ini文件
2014-10-24 13:45:00 +08:00
710f5b6234 fix the test fun for pull request 873 2014-10-20 22:23:29 +08:00
dbf944adce Merge pull request #873 from WithGJR/develop
add new feature to 'renderform' function, user could add HTML id and class now
2014-10-20 22:03:52 +08:00
6c9ff81fc1 fix: if user didn't set id or class, then it won't be displayed in HTML code 2014-10-20 18:59:46 +08:00
ec6383c07d Merge pull request #858 from bsingr/develop
Allow to use fastcgi via standard io.
2014-10-20 18:30:10 +08:00
76db5cded4 fix the init mime 2014-10-20 18:21:17 +08:00
1b3e7de463 add new feature to 'renderform' function, user could add HTML id and class now 2014-10-20 17:49:16 +08:00
ab28edaf25 Fix comma in the switch, fix wronf function name 2014-10-17 13:02:18 +04:00
04431a7a15 Fix function name fmt.Stprintf -> fmt.Sprintf 2014-10-17 12:59:24 +04:00
b00c42b3df Fix undefind variable fieldType 2014-10-17 12:56:44 +04:00
4cae7af3f9 Add attribute DEFAULT '' to the CREAT, ALTER constructors 2014-10-17 12:53:59 +04:00
e4988b714e Add property colDefault to fieldInfo object, set its true if there is
orm configuration default `orm:"default(1)"`
2014-10-17 12:27:53 +04:00
24489df63d Merge pull request #867 from WithGJR/develop
fix router bug: when the request is PUT or DELETE, router can't find the...
2014-10-16 23:07:20 +08:00
fb8e9ae1a3 Merge pull request #868 from reterVision/patch-2
Use SETEX command to set session
2014-10-16 21:42:23 +08:00
1eb9aef687 Use SETEX command to set session
In order to be compatible with older version Redis, use `SETEX` command instead of `SET x y EX 360`.
2014-10-16 20:16:17 +08:00
efc14a1e8d fix router bug with more better way 2014-10-16 18:58:12 +08:00
fa1281002e fix router bug: when the request is PUT or DELETE, router can't find the actual route and will throw 404 page to user 2014-10-16 18:26:01 +08:00
812950b60d Allow to use fastcgi via standard io. 2014-10-13 13:47:44 +02:00
f9e991b538 Merge pull request #853 from tossp/email
支持发送邮件内嵌附件
2014-10-12 11:02:53 +08:00
fc07419938 Update mail.go 2014-10-11 00:42:01 +08:00
d69eee23f0 添加错误返回
不知道英文区的人能否看懂Cnglish。。。
2014-10-11 00:38:31 +08:00
41de7c7db6 fix
修改一个错误。
看到text/template包的写法,和你的想法是一致的。
2014-10-11 00:02:36 +08:00
6a33647f30 修改参数类型
为了保持向后兼容,
2014-10-10 23:40:02 +08:00
e5134873be 支持发送邮件内嵌附件
为*Email.AttachFile和Email.Attach增加了一个参数"id".
当id不为空时,设置头部信息Content-Disposition为inline,并添加Content-ID头的值为id
2014-10-10 14:40:07 +08:00
8d20ea04b0 Merge pull request #852 from pabdavis/statistics-json
[Proposal] Ability to get statistics data unformatted
2014-10-10 10:59:30 +08:00
5c1e8e42b9 Reworked implementation to not return encoded json 2014-10-09 17:07:28 -04:00
ca3e7568a1 Add ability to get statistics in json format 2014-10-09 16:32:56 -04:00
2823167848 Merge pull request #849 from pabdavis/runmode-env
Support run mode set by environment variable
2014-10-10 00:50:16 +08:00
a27f5c0dc0 Remove dependency of third party lib 2014-10-09 09:17:10 -04:00
8af0475251 fix #833 2014-10-09 18:47:22 +08:00
9c07332cfc Update README.md 2014-10-09 09:34:22 +08:00
a06e0f27ad Support run mode set by env var BEEGO_RUNMODE 2014-10-08 15:00:07 -04:00
a760e46f98 Merge pull request #837 from bsingr/develop
Insert pagination utilities from beego/wetalk. Refs #835.
2014-10-08 23:00:46 +08:00
262665f4e5 Remove PaginationController interface and pass context instead. Refs #835. 2014-10-08 16:01:42 +02:00
0b3763cc67 Cleanup pagination documentation. Refs #835. 2014-10-08 16:00:00 +02:00
c147f26cd1 Merge pull request #847 from pabdavis/multifilter-fix2
Changes to handle multi filters on execution pt
2014-10-08 21:24:28 +08:00
1ba7847913 Changing check from nil to len based on slice 2014-10-08 09:21:34 -04:00
52df979aca update the godocs 2014-10-08 14:02:57 +08:00
b6f789c497 Changes to handle multi filters on execution pt 2014-10-07 16:35:30 -04:00
fa6cbc08d9 Document usage of utils/pagination. Refs #835. 2014-10-07 11:23:08 +02:00
c4f8f45da4 Move pagination to utils/pagination. Refs #837, #835. 2014-10-06 11:37:08 +02:00
6fca4a8218 Insert pagination utilities from beego/wetalk. Refs #835. 2014-10-02 11:40:46 +02:00
aae89576c6 fix #814 2014-10-01 22:31:44 +08:00
a907a86476 fix #814 2014-10-01 22:28:49 +08:00
8716185de8 fix #794 2014-10-01 22:10:33 +08:00
31e6133413 beego: improve static file index.html simple code 2014-10-01 08:57:10 +08:00
3a5de83ec2 beego: support router case sensitive 2014-09-28 22:10:43 +08:00
f5f3395560 update the isdir 2014-09-24 14:48:08 +08:00
8164367762 beego: flash add success & Set 2014-09-23 23:54:38 +08:00
e1475b72b9 Merge pull request #826 from SnailKnows/patch-2
Update beego.go
2014-09-23 00:31:21 +08:00
f267ee8a12 fix the same name controller for UrlFor 2014-09-23 00:26:07 +08:00
7e060e6e5c fix the static file dir 2014-09-23 00:03:47 +08:00
727d2f9ea1 fix not found when has mulit static dir
robot &robots
2014-09-22 23:44:50 +08:00
67c0c232a1 Update beego.go 2014-09-20 18:56:46 +08:00
6c62198b59 remove the go style 2014-09-15 23:01:12 +08:00
e48e1ddaa9 Merge pull request #812 from ZhengYang/develop
More SQL keywords added and code cleanup
2014-09-11 20:48:18 +08:00
1f9281c830 minor code refactor 2014-09-11 15:17:48 +08:00
ccab9a7044 add more sql keywords 2014-09-11 13:48:39 +08:00
9013f5c6c7 Merge pull request #808 from ZhengYang/develop
more complete support for sql language
2014-09-09 14:21:49 +08:00
29b7ff84e1 more complete support for sql language 2014-09-09 14:17:12 +08:00
fb0cc55822 update the orm read me 2014-09-09 11:55:28 +08:00
0820e21738 Merge pull request #804 from ZhengYang/develop
QueryBuilder for building SQL queries quickly
2014-09-08 19:24:39 +08:00
38eb29fa7b err msg spell correction 2014-09-08 18:41:42 +08:00
cca0a3f76d name correction: QueryBuilder instead of QueryWriter 2014-09-08 18:31:32 +08:00
f9a9b5a905 new query builder based on driver 2014-09-08 17:56:55 +08:00
c667895ce5 added new querybuilder 2014-09-08 17:47:15 +08:00
b2cdabb8a0 added query builder for orm 2014-09-08 17:37:01 +08:00
647a47517d httplib: fix the header function for User-Agent 2014-09-05 23:21:41 +08:00
f7cd1479ba beego: improve the log debug for running server 2014-09-05 17:04:02 +08:00
fcc359af11 beego: fix the Upper for the _method value 2014-09-04 22:13:03 +08:00
08b3e4191e Merge branch 'master' into develop 2014-09-04 21:58:45 +08:00
4f4f7ce257 beego: fix the router for *.* with other regexp 2014-09-04 21:58:17 +08:00
d06e02474f Merge pull request #795 from mvpmvh/context_params
Context params
2014-09-04 09:11:35 +08:00
4d65330ca1 changing my package namespace to astaxie 2014-09-03 19:47:09 -05:00
2dfe1fc61c Merge pull request #792 from haowang1013/develop
fixed uninitialized return error if StartAndGC fails
2014-09-03 23:21:17 +08:00
29b60d6058 fixed uninitialized return error if StartAndGC fails 2014-09-03 22:43:06 +08:00
6eee223352 beego: fix the Upper for the _method value 2014-09-03 09:25:34 +08:00
0692f92890 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-09-02 14:42:02 +08:00
41adcf9966 change the version 1.4.0 to 1.4.1 2014-09-02 14:41:14 +08:00
e6b42a4070 Merge pull request #787 from francoishill/patch-21
Proposal for adding a ":" for stack trace printout
2014-08-31 08:52:32 +08:00
bc4780091b Proposal for adding a ":" for stack trace printout
Mainly useful for Sublime users with goto anything.
2014-08-30 21:27:21 +02:00
d8614e80e7 beego: update the Url to Path 2014-08-30 22:22:23 +08:00
c83a2a0925 modify the comments 2014-08-28 10:21:32 +08:00
50a21d60c1 apiauth add more comments & improve 2014-08-28 10:05:02 +08:00
5a087b28d2 aws api auth plugins 2014-08-28 00:25:50 +08:00
9b40271878 fix the less ` 2014-08-27 15:53:08 +08:00
770dc702f0 Merge pull request #782 from francoishill/patch-19
Extra field if username is empty
2014-08-27 15:36:07 +08:00
e146100a23 Merge pull request #783 from francoishill/patch-20
Typo in the printout of some level's prefixes
2014-08-27 15:35:31 +08:00
2d94f7797b Type in the printout of each level's prefix 2014-08-26 14:03:03 +02:00
61ce608847 Update smtp.go 2014-08-26 12:47:05 +02:00
2fe559701c fix the import 2014-08-26 15:43:18 +08:00
37fe175c26 Merge pull request #776 from devYu/master
udpate ini.go
2014-08-26 14:43:20 +08:00
fcc9d8c45f Merge pull request #778 from francoishill/patch-15
Update file.go
2014-08-26 14:42:52 +08:00
e51a9d6481 Merge pull request #779 from francoishill/patch-17
Print error if occurred in WriteMsg
2014-08-26 14:42:05 +08:00
e9a1daa3ee Merge pull request #780 from francoishill/patch-18
Allow mail with self-signed certificates
2014-08-26 14:41:36 +08:00
44ea260db1 Allow mail with self-signed certificates
For more information, refer to https://groups.google.com/forum/#!topic/golang-nuts/c9zEiH6ixyw
2014-08-26 06:52:18 +02:00
1d1ad69954 Print error if occurred in WriteMsg 2014-08-26 06:37:35 +02:00
59773dfabe Update file.go
New pull request for "Panic sometimes occurs at time 00h00 on windows, then the app crashes."
2014-08-26 06:25:59 +02:00
ccb61f0416 gofmt
Signed-off-by: devYu <devysq@gmail.com>
2014-08-25 21:31:53 +08:00
14629c214b 优化GetData
1. 去掉重复的ToLower
2. getData内部统一ToLower
3.调整getData中对空字符串判断位置
====
4. 待确定:在getData中是否有必要进行lock操作
2014-08-25 20:14:14 +08:00
a3888cef7f fix the comments for the \d 2014-08-25 19:48:02 +08:00
f6a1a6c9bf Merge pull request #773 from lei-cao/develop
Added data table admin ui
2014-08-25 07:32:07 +08:00
38ee43701d remove http: 2014-08-25 01:40:23 +08:00
421b796f1a Added data table admin ui 2014-08-25 01:37:11 +08:00
db51ddab96 GetInt(), GetInt8(), GetInt16(), GetInt32(), GetInt64() and Example tests 2014-08-23 20:24:29 -05:00
baf2c63d5c Merge branch 'astaxie-master' 2014-08-23 10:09:37 -05:00
771179a3c6 Merge pull request #769 from JessonChan/develop
improve and typo fixed
2014-08-23 09:46:43 +08:00
c07b1d881b typo fixed 2014-08-23 07:07:12 +08:00
d8f2b05e08 improve the code and delete NO NEED URL CHECK 2014-08-23 07:02:47 +08:00
7b2fe824d5 typo fixed 2014-08-23 06:48:40 +08:00
02301caac1 modify desc 2014-08-23 06:47:42 +08:00
98c2307763 Merge pull request #767 from JessonChan/develop
bug fixed
2014-08-22 22:02:40 +08:00
485c2e865c bug fixed 2014-08-22 17:43:05 +08:00
b390667374 Merge remote-tracking branch 'upstream/develop' into develop
Conflicts:
	httplib/httplib.go
2014-08-22 17:39:57 +08:00
f684de2385 fixed bug-request dump too early 2014-08-22 17:12:46 +08:00
35b4022ee0 httplib: set the default proto 2014-08-22 17:03:56 +08:00
77294a5881 utils: fix the SliceIntersect 2014-08-22 16:50:07 +08:00
7b39bd7042 refactor 2014-08-22 16:43:42 +08:00
01e4084587 toolbox: fix the go routine asleep 2014-08-22 13:56:36 +08:00
bf429a3a20 Merge pull request #762 from pdf/pointer_field_support
Add support for basic type pointer fields
2014-08-22 13:41:58 +08:00
f8ff79d77d Merge pull request #764 from smallfish/master
Update README
2014-08-22 13:41:01 +08:00
e9487d3571 Update README 2014-08-22 13:32:54 +08:00
d7c3727f96 Add support for basic type pointer fields
Allows models like:

```
type User struct {
	Id    int64
	Name  string
	Email *string `orm:"null"`
}
```

This helps a lot when doing JSON marshalling/unmarshalling.

Pointer fields should always be declared with the NULL orm tag for sanity, this
probably requires documentation.
2014-08-22 14:25:32 +10:00
03eb1fc104 toolbox: add notify when add & delete task 2014-08-21 15:56:41 +08:00
0a967875da Merge pull request #757 from smallfish/develop
Add new function SetBasicAuth, and update README
2014-08-21 09:01:45 +08:00
6ae8bc1a16 Update README 2014-08-21 00:08:08 +08:00
e70537f8b3 Update README 2014-08-21 00:03:03 +08:00
9a583323a8 Add SetBasicAuth function for HTTP Auth 2014-08-20 23:36:58 +08:00
ff9c8d94e6 Merge pull request #753 from smallfish/develop
format comments
2014-08-20 16:40:26 +08:00
7f977a0c8c beego: change the colour 2014-08-20 11:54:25 +08:00
aaabeff44f change the route info 2014-08-20 10:59:38 +08:00
f85ac088c3 format comments 2014-08-19 16:48:30 +08:00
26da23266a Merge branch 'master' into develop 2014-08-19 09:49:07 +08:00
80274684e0 context: redirect should writer to response instantly 2014-08-19 09:48:21 +08:00
be005f9774 Merge pull request #750 from smallfish/develop
Update httplib support read data from response buffer, add some testcase...
2014-08-18 21:33:02 +08:00
c16b7be9ac rollback the ToFile func implement, and add testcase 2014-08-18 21:29:45 +08:00
de87529387 Update httplib support read data from response buffer, add some testcases 2014-08-18 21:01:49 +08:00
77 changed files with 3543 additions and 1261 deletions

View File

@ -1,16 +0,0 @@
{
"file_line": 500,
"func_line": 80,
"params_num":4,
"results_num":3,
"formated": true,
"pkg_name": true,
"camel_name":true,
"ignore":[
"a/*",
"b/*/c/*.go"
],
"fatal":[
"formated"
]
}

View File

@ -1,32 +1,37 @@
## beego
## Beego
[![Build Status](https://drone.io/github.com/astaxie/beego/status.png)](https://drone.io/github.com/astaxie/beego/latest)
[![GoDoc](http://godoc.org/github.com/astaxie/beego?status.svg)](http://godoc.org/github.com/astaxie/beego)
beego is an open-source, high-performance, modularity, full-stack web framework.
beego is an open-source, high-performance, modular, full-stack web framework.
More info [beego.me](http://beego.me)
## Installation
go get github.com/astaxie/beego
## Features
* RESTful support
* MVC architecture
* modularity
* auto API documents
* annotation router
* namespace
* powerful develop tools
* full stack for web & API
* Modularity
* Auto API documents
* Annotation router
* Namespace
* Powerful development tools
* Full stack for Web & API
## Documentation
[English](http://beego.me/docs/intro/)
* [English](http://beego.me/docs/intro/)
* [中文文档](http://beego.me/docs/intro/)
[API](http://godoc.org/github.com/astaxie/beego)
[中文文档](http://beego.me/docs/intro/)
## Community
* [http://beego.me/community](http://beego.me/community)
## LICENSE
beego is licensed under the Apache Licence, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0.html).
(http://www.apache.org/licenses/LICENSE-2.0.html).

131
admin.go
View File

@ -113,8 +113,6 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
m["SessionName"] = SessionName
m["SessionGCMaxLifetime"] = SessionGCMaxLifetime
m["SessionSavePath"] = SessionSavePath
m["SessionHashFunc"] = SessionHashFunc
m["SessionHashKey"] = SessionHashKey
m["SessionCookieLifeTime"] = SessionCookieLifeTime
m["UseFcgi"] = UseFcgi
m["MaxMemory"] = MaxMemory
@ -142,121 +140,116 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
tmpl.Execute(rw, data)
case "router":
resultList := new([][]string)
content := make(map[string]interface{})
var result = []string{
fmt.Sprintf("header"),
var fields = []string{
fmt.Sprintf("Router Pattern"),
fmt.Sprintf("Methods"),
fmt.Sprintf("Controller"),
}
*resultList = append(*resultList, result)
content["Fields"] = fields
methods := []string{}
methodsData := make(map[string]interface{})
for method, t := range BeeApp.Handlers.routers {
var result = []string{
fmt.Sprintf("success"),
fmt.Sprintf("Method: %s", method),
fmt.Sprintf(""),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
resultList := new([][]string)
printTree(resultList, t)
methods = append(methods, method)
methodsData[method] = resultList
}
data["Content"] = resultList
content["Data"] = methodsData
content["Methods"] = methods
data["Content"] = content
data["Title"] = "Routers"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(routerAndFilterTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
tmpl.Execute(rw, data)
case "filter":
resultList := new([][]string)
content := make(map[string]interface{})
var result = []string{
fmt.Sprintf("header"),
var fields = []string{
fmt.Sprintf("Router Pattern"),
fmt.Sprintf("Filter Function"),
}
*resultList = append(*resultList, result)
content["Fields"] = fields
filterTypes := []string{}
filterTypeData := make(map[string]interface{})
if BeeApp.Handlers.enableFilter {
var result = []string{
fmt.Sprintf("success"),
fmt.Sprintf("Before Router"),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
var filterType string
if bf, ok := BeeApp.Handlers.filters[BeforeRouter]; ok {
filterType = "Before Router"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
result = []string{
fmt.Sprintf("success"),
fmt.Sprintf("Before Exec"),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok {
filterType = "Before Exec"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
result = []string{
fmt.Sprintf("success"),
fmt.Sprintf("AfterExec Exec"),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok {
filterType = "After Exec"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
result = []string{
fmt.Sprintf("success"),
fmt.Sprintf("Finish Router"),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok {
filterType = "Finish Router"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
}
data["Content"] = resultList
content["Data"] = filterTypeData
content["Methods"] = filterTypes
data["Content"] = content
data["Title"] = "Filters"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(routerAndFilterTpl))
@ -281,7 +274,6 @@ func printTree(resultList *[][]string, t *Tree) {
if v, ok := l.runObject.(*controllerInfo); ok {
if v.routerType == routerTypeBeego {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", v.pattern),
fmt.Sprintf("%s", v.methods),
fmt.Sprintf("%s", v.controllerType),
@ -289,7 +281,6 @@ func printTree(resultList *[][]string, t *Tree) {
*resultList = append(*resultList, result)
} else if v.routerType == routerTypeRESTFul {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", v.pattern),
fmt.Sprintf("%s", v.methods),
fmt.Sprintf(""),
@ -297,7 +288,6 @@ func printTree(resultList *[][]string, t *Tree) {
*resultList = append(*resultList, result)
} else if v.routerType == routerTypeHandler {
var result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", v.pattern),
fmt.Sprintf(""),
fmt.Sprintf(""),
@ -352,13 +342,15 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
func healthcheck(rw http.ResponseWriter, req *http.Request) {
data := make(map[interface{}]interface{})
resultList := new([][]string)
var result = []string{
fmt.Sprintf("header"),
var result = []string{}
fields := []string{
fmt.Sprintf("Name"),
fmt.Sprintf("Message"),
fmt.Sprintf("Status"),
}
*resultList = append(*resultList, result)
resultList := new([][]string)
content := make(map[string]interface{})
for name, h := range toolbox.AdminCheckList {
if err := h.Check(); err != nil {
@ -379,7 +371,9 @@ func healthcheck(rw http.ResponseWriter, req *http.Request) {
*resultList = append(*resultList, result)
}
data["Content"] = resultList
content["Fields"] = fields
content["Data"] = resultList
data["Content"] = content
data["Title"] = "Health Check"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(healthCheckTpl))
@ -403,32 +397,36 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
if err != nil {
data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
}
data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is %s", taskname, t.GetStatus())}
data["Message"] = []string{"success", 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)}
}
}
// List Tasks
content := make(map[string]interface{})
resultList := new([][]string)
var result = []string{
fmt.Sprintf("header"),
var result = []string{}
var fields = []string{
fmt.Sprintf("Task Name"),
fmt.Sprintf("Task Spec"),
fmt.Sprintf("Task Function"),
fmt.Sprintf("Task Status"),
fmt.Sprintf("Last Time"),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
for tname, tk := range toolbox.AdminTaskList {
result = []string{
fmt.Sprintf(""),
fmt.Sprintf("%s", tname),
fmt.Sprintf("%s", tk.GetSpec()),
fmt.Sprintf("%s", tk.GetStatus()),
fmt.Sprintf("%s", tk.GetPrev().String()),
}
*resultList = append(*resultList, result)
}
data["Content"] = resultList
content["Fields"] = fields
content["Data"] = resultList
data["Content"] = content
data["Title"] = "Tasks"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(tasksTpl))
@ -460,6 +458,7 @@ func (admin *adminApp) Run() {
for p, f := range admin.routers {
http.Handle(p, f)
}
BeeLogger.Info("Admin server Running on %s", addr)
err := http.ListenAndServe(addr, nil)
if err != nil {
BeeLogger.Critical("Admin ListenAndServe: ", err)

View File

@ -61,31 +61,35 @@ var gcAjaxTpl = `
{{end}}
`
var qpsTpl = `
{{define "content"}}
var qpsTpl = `{{define "content"}}
<h1>Requests statistics</h1>
<table class="table table-striped table-hover ">
{{range $i, $slice := .Content}}
<tr>
{{range $j, $elem := $slice}}
{{if eq $i 0}}
<th>
{{else}}
<td>
{{end}}
{{$elem}}
{{if eq $i 0}}
</th>
{{else}}
</td>
{{end}}
{{end}}
<thead>
<tr>
{{range .Content.Fields}}
<th>
{{.}}
</th>
{{end}}
</tr>
</thead>
<tbody>
{{range $i, $elem := .Content.Data}}
<tr>
{{range $elem}}
<td>
{{.}}
</td>
{{end}}
</tr>
{{end}}
</tbody>
</tr>
{{end}}
</table>
{{end}}
`
{{end}}`
var configTpl = `
{{define "content"}}
@ -98,49 +102,51 @@ var configTpl = `
{{end}}
`
var routerAndFilterTpl = `
{{define "content"}}
var routerAndFilterTpl = `{{define "content"}}
<h1>{{.Title}}</h1>
{{range .Content.Methods}}
<div class="panel panel-default">
<div class="panel-heading lead success"><strong>{{.}}</strong></div>
<div class="panel-body">
<table class="table table-striped table-hover ">
{{range $i, $slice := .Content}}
<tr>
<thead>
<tr>
{{range $.Content.Fields}}
<th>
{{.}}
</th>
{{end}}
</tr>
</thead>
{{ $header := index $slice 0}}
{{if eq "header" $header }}
{{range $j, $elem := $slice}}
{{if ne $j 0}}
<th>
{{$elem}}
</th>
{{end}}
{{end}}
{{else if eq "success" $header}}
{{range $j, $elem := $slice}}
{{if ne $j 0}}
<th class="success">
{{$elem}}
</th>
{{end}}
{{end}}
{{else}}
{{range $j, $elem := $slice}}
{{if ne $j 0}}
<td>
{{$elem}}
</td>
{{end}}
{{end}}
{{end}}
<tbody>
{{$slice := index $.Content.Data .}}
{{range $i, $elem := $slice}}
<tr>
{{range $elem}}
<td>
{{.}}
</td>
{{end}}
</tr>
{{end}}
</tbody>
</tr>
{{end}}
</table>
</div>
</div>
{{end}}
`
var tasksTpl = `
{{define "content"}}
{{end}}`
var tasksTpl = `{{define "content"}}
<h1>{{.Title}}</h1>
@ -161,59 +167,51 @@ bg-warning
<table class="table table-striped table-hover ">
{{range $i, $slice := .Content}}
<thead>
<tr>
{{ $header := index $slice 0}}
{{if eq "header" $header }}
{{range $j, $elem := $slice}}
{{if ne $j 0}}
<th>
{{$elem}}
</th>
{{end}}
{{end}}
<th>
Run Task
</th>
{{else}}
{{range $j, $elem := $slice}}
{{if ne $j 0}}
<td>
{{$elem}}
</td>
{{end}}
{{end}}
<td>
<a class="btn btn-primary btn-sm" href="/task?taskname={{index $slice 1}}">Run</a>
</td>
{{range .Content.Fields}}
<th>
{{.}}
</th>
{{end}}
</tr>
</thead>
<tbody>
{{range $i, $slice := .Content.Data}}
<tr>
{{range $slice}}
<td>
{{.}}
</td>
{{end}}
<td>
<a class="btn btn-primary btn-sm" href="/task?taskname={{index $slice 0}}">Run</a>
</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
`
{{end}}`
var healthCheckTpl = `
{{define "content"}}
<h1>{{.Title}}</h1>
<table class="table table-striped table-hover ">
{{range $i, $slice := .Content}}
{{ $header := index $slice 0}}
{{if eq "header" $header }}
<thead>
<tr>
{{range $j, $elem := $slice}}
{{if ne $j 0}}
{{range .Content.Fields}}
<th>
{{$elem}}
{{.}}
</th>
{{end}}
{{end}}
{{end}}
</tr>
{{else}}
</thead>
<tbody>
{{range $i, $slice := .Content.Data}}
{{ $header := index $slice 0}}
{{ if eq "success" $header}}
<tr class="success">
{{else if eq "error" $header}}
@ -228,10 +226,13 @@ var healthCheckTpl = `
</td>
{{end}}
{{end}}
<td>
{{$header}}
</td>
</tr>
{{end}}
{{end}}
</tbody>
</table>
{{end}}`
@ -251,7 +252,8 @@ Welcome to Beego Admin Dashboard
</title>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<link href="//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.css" rel="stylesheet">
<style type="text/css">
ul.nav li.dropdown:hover > ul.dropdown-menu {
@ -336,9 +338,17 @@ Healthcheck
{{template "content" .}}
</div>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src="//cdn.datatables.net/1.10.2/js/jquery.dataTables.min.js"></script>
<script src="//cdn.datatables.net/plug-ins/725b2a2115b/integration/bootstrap/3/dataTables.bootstrap.js
"></script>
<script type="text/javascript">
$(document).ready(function() {
$('.table').dataTable();
});
</script>
{{template "scripts" .}}
</body>
</html>

64
app.go
View File

@ -19,14 +19,12 @@ import (
"net"
"net/http"
"net/http/fcgi"
"os"
"time"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/utils"
)
// FilterFunc defines filter function type.
type FilterFunc func(*context.Context)
// App defines beego application with a new PatternServeMux.
type App struct {
Handlers *ControllerRegistor
@ -48,8 +46,6 @@ func (app *App) Run() {
addr = fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
}
BeeLogger.Info("Running on %s", addr)
var (
err error
l net.Listener
@ -57,15 +53,28 @@ func (app *App) Run() {
endRunning := make(chan bool, 1)
if UseFcgi {
if HttpPort == 0 {
l, err = net.Listen("unix", addr)
if UseStdIo {
err = fcgi.Serve(nil, app.Handlers) // standard I/O
if err == nil {
BeeLogger.Info("Use FCGI via standard I/O")
} else {
BeeLogger.Info("Cannot use FCGI via standard I/O", err)
}
} else {
l, err = net.Listen("tcp", addr)
if HttpPort == 0 {
// remove the Socket file before start
if utils.FileExists(addr) {
os.Remove(addr)
}
l, err = net.Listen("unix", addr)
} else {
l, err = net.Listen("tcp", addr)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
} else {
app.Server.Addr = addr
app.Server.Handler = app.Handlers
@ -78,6 +87,7 @@ func (app *App) Run() {
if HttpsPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", HttpAddr, HttpsPort)
}
BeeLogger.Info("https server Running on %s", app.Server.Addr)
err := app.Server.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
if err != nil {
BeeLogger.Critical("ListenAndServeTLS: ", err)
@ -90,11 +100,29 @@ func (app *App) Run() {
if EnableHttpListen {
go func() {
app.Server.Addr = addr
err := app.Server.ListenAndServe()
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
BeeLogger.Info("http server Running on %s", app.Server.Addr)
if ListenTCP4 && HttpAddr == "" {
ln, err := net.Listen("tcp4", app.Server.Addr)
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
err = app.Server.Serve(ln)
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
return
}
} else {
err := app.Server.ListenAndServe()
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}
}()
}

114
beego.go
View File

@ -14,13 +14,13 @@
// beego is an open-source, high-performance, modularity, full-stack web framework
//
// package main
// package main
//
// import "github.com/astaxie/beego"
// import "github.com/astaxie/beego"
//
// func main() {
// beego.Run()
// }
// func main() {
// beego.Run()
// }
//
// more infomation: http://beego.me
package beego
@ -33,83 +33,15 @@ import (
"strconv"
"strings"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/session"
)
// beego web framework version.
const VERSION = "1.4.0"
const VERSION = "1.4.3"
type hookfunc func() error //hook function to run
var hooks []hookfunc //hook function slice to store the hookfunc
type groupRouter struct {
pattern string
controller ControllerInterface
mappingMethods string
}
// RouterGroups which will store routers
type GroupRouters []groupRouter
// Get a new GroupRouters
func NewGroupRouters() GroupRouters {
return make(GroupRouters, 0)
}
// Add Router in the GroupRouters
// it is for plugin or module to register router
func (gr *GroupRouters) AddRouter(pattern string, c ControllerInterface, mappingMethod ...string) {
var newRG groupRouter
if len(mappingMethod) > 0 {
newRG = groupRouter{
pattern,
c,
mappingMethod[0],
}
} else {
newRG = groupRouter{
pattern,
c,
"",
}
}
*gr = append(*gr, newRG)
}
func (gr *GroupRouters) AddAuto(c ControllerInterface) {
newRG := groupRouter{
"",
c,
"",
}
*gr = append(*gr, newRG)
}
// AddGroupRouter with the prefix
// it will register the router in BeeApp
// the follow code is write in modules:
// GR:=NewGroupRouters()
// GR.AddRouter("/login",&UserController,"get:Login")
// GR.AddRouter("/logout",&UserController,"get:Logout")
// GR.AddRouter("/register",&UserController,"get:Reg")
// the follow code is write in app:
// import "github.com/beego/modules/auth"
// AddRouterGroup("/admin", auth.GR)
func AddGroupRouter(prefix string, groups GroupRouters) *App {
for _, v := range groups {
if v.pattern == "" {
BeeApp.Handlers.AddAutoPrefix(prefix, v.controller)
} else if v.mappingMethods != "" {
BeeApp.Handlers.Add(prefix+v.pattern, v.controller, v.mappingMethods)
} else {
BeeApp.Handlers.Add(prefix+v.pattern, v.controller)
}
}
return BeeApp
}
// Router adds a patterned controller handler to BeeApp.
// it's an alias method of App.Router.
// usage:
@ -280,15 +212,6 @@ func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
return BeeApp
}
// ErrorHandler registers http.HandlerFunc to each http err code string.
// usage:
// beego.ErrorHandler("404",NotFound)
// beego.ErrorHandler("500",InternalServerError)
func Errorhandler(err string, h http.HandlerFunc) *App {
middleware.Errorhandler(err, h)
return BeeApp
}
// SetViewsPath sets view directory path in beego application.
func SetViewsPath(path string) *App {
ViewsPath = path
@ -308,15 +231,20 @@ func SetStaticPath(url string, path string) *App {
// DelStaticPath removes the static folder setting in this url pattern in beego application.
func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
delete(StaticDir, url)
return BeeApp
}
// InsertFilter adds a FilterFunc with pattern condition and action constant.
// The pos means action constant including
// beego.BeforeRouter, beego.AfterStatic, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
func InsertFilter(pattern string, pos int, filter FilterFunc) *App {
BeeApp.Handlers.InsertFilter(pattern, pos, filter)
// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
return BeeApp
}
@ -359,6 +287,9 @@ func initBeforeHttpRun() {
}
}
//init mime
AddAPPStartHook(initMime)
// do hooks function
for _, hk := range hooks {
err := hk()
@ -373,10 +304,8 @@ func initBeforeHttpRun() {
if sessionConfig == "" {
sessionConfig = `{"cookieName":"` + SessionName + `",` +
`"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` +
`"providerConfig":"` + SessionSavePath + `",` +
`"providerConfig":"` + filepath.ToSlash(SessionSavePath) + `",` +
`"secure":` + strconv.FormatBool(EnableHttpTLS) + `,` +
`"sessionIDHashFunc":"` + SessionHashFunc + `",` +
`"sessionIDHashKey":"` + SessionHashKey + `",` +
`"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
`"domain":"` + SessionDomain + `",` +
`"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
@ -396,17 +325,12 @@ func initBeforeHttpRun() {
}
}
middleware.VERSION = VERSION
middleware.AppName = AppName
middleware.RegisterErrorHandler()
registerDefaultErrorHandler()
if EnableDocs {
Get("/docs", serverDocs)
Get("/docs/*", serverDocs)
}
//init mime
AddAPPStartHook(initMime)
}
// this function is for test package init

6
cache/cache.go vendored
View File

@ -81,13 +81,13 @@ func Register(name string, adapter Cache) {
// Create a new cache driver by adapter name and config string.
// config need to be correct JSON as string: {"interval":360}.
// it will start gc automatically.
func NewCache(adapterName, config string) (adapter Cache, e error) {
func NewCache(adapterName, config string) (adapter Cache, err error) {
adapter, ok := adapters[adapterName]
if !ok {
e = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
return
}
err := adapter.StartAndGC(config)
err = adapter.StartAndGC(config)
if err != nil {
adapter = nil
}

4
cache/cache_test.go vendored
View File

@ -15,6 +15,7 @@
package cache
import (
"os"
"testing"
"time"
)
@ -67,7 +68,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")
}
@ -112,4 +113,5 @@ func TestFileCache(t *testing.T) {
if v := bm.Get("astaxie"); v.(string) != "author" {
t.Error("get err")
}
os.RemoveAll("cache")
}

2
cache/file.go vendored
View File

@ -92,8 +92,6 @@ func (fc *FileCache) StartAndGC(config string) error {
// Init will make new dir for file cache if not exist.
func (fc *FileCache) Init() {
app := filepath.Dir(os.Args[0])
fc.CachePath = filepath.Join(app, fc.CachePath)
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
}

17
cache/redis/redis.go vendored
View File

@ -32,6 +32,7 @@ package redis
import (
"encoding/json"
"errors"
"strconv"
"time"
"github.com/garyburd/redigo/redis"
@ -48,6 +49,7 @@ var (
type RedisCache struct {
p *redis.Pool // redis connection pool
conninfo string
dbNum int
key string
}
@ -75,14 +77,13 @@ func (rc *RedisCache) Get(key string) interface{} {
// put cache to redis.
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
var err error
if _, err = rc.do("SET", key, val); err != nil {
if _, err = rc.do("SETEX", key, timeout, val); err != nil {
return err
}
if _, err = rc.do("HSET", rc.key, key, true); err != nil {
return err
}
_, err = rc.do("EXPIRE", key, timeout)
return err
}
@ -138,7 +139,7 @@ func (rc *RedisCache) ClearAll() error {
}
// start redis cache adapter.
// config is like {"key":"collection key","conn":"connection info"}
// config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
// the cache item in redis are stored forever,
// so no gc operation.
func (rc *RedisCache) StartAndGC(config string) error {
@ -152,9 +153,12 @@ func (rc *RedisCache) StartAndGC(config string) error {
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
}
if _, ok := cf["dbNum"]; !ok {
cf["dbNum"] = "0"
}
rc.key = cf["key"]
rc.conninfo = cf["conn"]
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
rc.connectInit()
c := rc.p.Get()
@ -167,6 +171,11 @@ func (rc *RedisCache) StartAndGC(config string) error {
func (rc *RedisCache) connectInit() {
dialFunc := func() (c redis.Conn, err error) {
c, err = redis.Dial("tcp", rc.conninfo)
_, selecterr := c.Do("SELECT", rc.dbNum)
if selecterr != nil {
c.Close()
return nil, selecterr
}
return
}
// initialize a new pool

534
config.go
View File

@ -15,7 +15,6 @@
package beego
import (
"errors"
"fmt"
"html/template"
"os"
@ -41,6 +40,7 @@ var (
EnableHttpListen bool
HttpAddr string
HttpPort int
ListenTCP4 bool
EnableHttpTLS bool
HttpsPort int
HttpCertFile string
@ -48,20 +48,19 @@ var (
RecoverPanic bool // flag of auto recover panic
AutoRender bool // flag of render template automatically
ViewsPath string
RunMode string // run mode, "dev" or "prod"
AppConfig config.ConfigContainer
AppConfig *beegoAppConfig
RunMode string // run mode, "dev" or "prod"
GlobalSessions *session.Manager // global session mananger
SessionOn bool // flag of starting session auto. default is false.
SessionProvider string // default session provider, memory, mysql , redis ,etc.
SessionName string // the cookie name when saving session id into cookie.
SessionGCMaxLifetime int64 // session gc time for auto cleaning expired session.
SessionSavePath string // if use mysql/redis/file provider, define save path to connection info.
SessionHashFunc string // session hash generation func.
SessionHashKey string // session hash salt string.
SessionCookieLifeTime int // the life time of session id in cookie.
SessionAutoSetCookie bool // auto setcookie
SessionDomain string // the cookie domain default is empty
UseFcgi bool
UseStdIo bool
MaxMemory int64
EnableGzip bool // flag of enable gzip
DirectoryIndex bool // flag of display directory index. default is false.
@ -81,8 +80,111 @@ var (
FlashSeperator string // used to seperate flash key:value
AppConfigProvider string // config provider
EnableDocs bool // enable generate docs & server docs API Swagger
RouterCaseSensitive bool // router case sensitive default is true
AccessLogs bool // print access logs, default is false
)
type beegoAppConfig struct {
innerConfig config.ConfigContainer
}
func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, error) {
ac, err := config.NewConfig(AppConfigProvider, AppConfigPath)
if err != nil {
return nil, err
}
rac := &beegoAppConfig{ac}
return rac, nil
}
func (b *beegoAppConfig) Set(key, val string) error {
return b.innerConfig.Set(key, val)
}
func (b *beegoAppConfig) String(key string) string {
v := b.innerConfig.String(RunMode + "::" + key)
if v == "" {
return b.innerConfig.String(key)
}
return v
}
func (b *beegoAppConfig) Strings(key string) []string {
v := b.innerConfig.Strings(RunMode + "::" + key)
if v[0] == "" {
return b.innerConfig.Strings(key)
}
return v
}
func (b *beegoAppConfig) Int(key string) (int, error) {
v, err := b.innerConfig.Int(RunMode + "::" + key)
if err != nil {
return b.innerConfig.Int(key)
}
return v, nil
}
func (b *beegoAppConfig) Int64(key string) (int64, error) {
v, err := b.innerConfig.Int64(RunMode + "::" + key)
if err != nil {
return b.innerConfig.Int64(key)
}
return v, nil
}
func (b *beegoAppConfig) Bool(key string) (bool, error) {
v, err := b.innerConfig.Bool(RunMode + "::" + key)
if err != nil {
return b.innerConfig.Bool(key)
}
return v, nil
}
func (b *beegoAppConfig) Float(key string) (float64, error) {
v, err := b.innerConfig.Float(RunMode + "::" + key)
if err != nil {
return b.innerConfig.Float(key)
}
return v, nil
}
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string {
return b.innerConfig.DefaultString(key, defaultval)
}
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string {
return b.innerConfig.DefaultStrings(key, defaultval)
}
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int {
return b.innerConfig.DefaultInt(key, defaultval)
}
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 {
return b.innerConfig.DefaultInt64(key, defaultval)
}
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool {
return b.innerConfig.DefaultBool(key, defaultval)
}
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 {
return b.innerConfig.DefaultFloat(key, defaultval)
}
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
return b.innerConfig.DIY(key)
}
func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) {
return b.innerConfig.GetSection(section)
}
func (b *beegoAppConfig) SaveConfigFile(filename string) error {
return b.innerConfig.SaveConfigFile(filename)
}
func init() {
// create beego application
BeeApp = NewApp()
@ -134,12 +236,11 @@ func init() {
SessionName = "beegosessionID"
SessionGCMaxLifetime = 3600
SessionSavePath = ""
SessionHashFunc = "sha1"
SessionHashKey = "beegoserversessionkey"
SessionCookieLifeTime = 0 //set cookie default is the brower life
SessionAutoSetCookie = true
UseFcgi = false
UseStdIo = false
MaxMemory = 1 << 26 //64MB
@ -164,6 +265,8 @@ func init() {
FlashName = "BEEGO_FLASH"
FlashSeperator = "BEEGOFLASH"
RouterCaseSensitive = true
runtime.GOMAXPROCS(runtime.NumCPU())
// init BeeLogger
@ -172,262 +275,211 @@ func init() {
if err != nil {
fmt.Println("init console log error:", err)
}
SetLogFuncCall(true)
err = ParseConfig()
if err != nil && !os.IsNotExist(err) {
if err != nil && os.IsNotExist(err) {
// for init if doesn't have app.conf will not panic
Info(err)
ac := config.NewFakeConfig()
AppConfig = &beegoAppConfig{ac}
Warning(err)
}
}
// ParseConfig parsed default config file.
// now only support ini, next will support json.
func ParseConfig() (err error) {
AppConfig, err = config.NewConfig(AppConfigProvider, AppConfigPath)
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
if err != nil {
AppConfig = config.NewFakeConfig()
return err
} else {
}
envRunMode := os.Getenv("BEEGO_RUNMODE")
// set the runmode first
if envRunMode != "" {
RunMode = envRunMode
} else if runmode := AppConfig.String("RunMode"); runmode != "" {
RunMode = runmode
}
if v, err := GetConfig("string", "HttpAddr"); err == nil {
HttpAddr = v.(string)
HttpAddr = AppConfig.String("HttpAddr")
if v, err := AppConfig.Int("HttpPort"); err == nil {
HttpPort = v
}
if v, err := AppConfig.Bool("ListenTCP4"); err == nil {
ListenTCP4 = v
}
if v, err := AppConfig.Bool("EnableHttpListen"); err == nil {
EnableHttpListen = v
}
if maxmemory, err := AppConfig.Int64("MaxMemory"); err == nil {
MaxMemory = maxmemory
}
if appname := AppConfig.String("AppName"); appname != "" {
AppName = appname
}
if autorender, err := AppConfig.Bool("AutoRender"); err == nil {
AutoRender = autorender
}
if autorecover, err := AppConfig.Bool("RecoverPanic"); err == nil {
RecoverPanic = autorecover
}
if views := AppConfig.String("ViewsPath"); views != "" {
ViewsPath = views
}
if sessionon, err := AppConfig.Bool("SessionOn"); err == nil {
SessionOn = sessionon
}
if sessProvider := AppConfig.String("SessionProvider"); sessProvider != "" {
SessionProvider = sessProvider
}
if sessName := AppConfig.String("SessionName"); sessName != "" {
SessionName = sessName
}
if sesssavepath := AppConfig.String("SessionSavePath"); sesssavepath != "" {
SessionSavePath = sesssavepath
}
if sessMaxLifeTime, err := AppConfig.Int64("SessionGCMaxLifetime"); err == nil && sessMaxLifeTime != 0 {
SessionGCMaxLifetime = sessMaxLifeTime
}
if sesscookielifetime, err := AppConfig.Int("SessionCookieLifeTime"); err == nil && sesscookielifetime != 0 {
SessionCookieLifeTime = sesscookielifetime
}
if usefcgi, err := AppConfig.Bool("UseFcgi"); err == nil {
UseFcgi = usefcgi
}
if enablegzip, err := AppConfig.Bool("EnableGzip"); err == nil {
EnableGzip = enablegzip
}
if directoryindex, err := AppConfig.Bool("DirectoryIndex"); err == nil {
DirectoryIndex = directoryindex
}
if timeout, err := AppConfig.Int64("HttpServerTimeOut"); err == nil {
HttpServerTimeOut = timeout
}
if errorsshow, err := AppConfig.Bool("ErrorsShow"); err == nil {
ErrorsShow = errorsshow
}
if copyrequestbody, err := AppConfig.Bool("CopyRequestBody"); err == nil {
CopyRequestBody = copyrequestbody
}
if xsrfkey := AppConfig.String("XSRFKEY"); xsrfkey != "" {
XSRFKEY = xsrfkey
}
if enablexsrf, err := AppConfig.Bool("EnableXSRF"); err == nil {
EnableXSRF = enablexsrf
}
if expire, err := AppConfig.Int("XSRFExpire"); err == nil {
XSRFExpire = expire
}
if tplleft := AppConfig.String("TemplateLeft"); tplleft != "" {
TemplateLeft = tplleft
}
if tplright := AppConfig.String("TemplateRight"); tplright != "" {
TemplateRight = tplright
}
if httptls, err := AppConfig.Bool("EnableHttpTLS"); err == nil {
EnableHttpTLS = httptls
}
if httpsport, err := AppConfig.Int("HttpsPort"); err == nil {
HttpsPort = httpsport
}
if certfile := AppConfig.String("HttpCertFile"); certfile != "" {
HttpCertFile = certfile
}
if keyfile := AppConfig.String("HttpKeyFile"); keyfile != "" {
HttpKeyFile = keyfile
}
if serverName := AppConfig.String("BeegoServerName"); serverName != "" {
BeegoServerName = serverName
}
if flashname := AppConfig.String("FlashName"); flashname != "" {
FlashName = flashname
}
if flashseperator := AppConfig.String("FlashSeperator"); flashseperator != "" {
FlashSeperator = flashseperator
}
if sd := AppConfig.String("StaticDir"); sd != "" {
for k := range StaticDir {
delete(StaticDir, k)
}
if v, err := GetConfig("int", "HttpPort"); err == nil {
HttpPort = v.(int)
}
if v, err := GetConfig("bool", "EnableHttpListen"); err == nil {
EnableHttpListen = v.(bool)
}
if maxmemory, err := GetConfig("int64", "MaxMemory"); err == nil {
MaxMemory = maxmemory.(int64)
}
if appname, _ := GetConfig("string", "AppName"); appname != "" {
AppName = appname.(string)
}
if runmode, _ := GetConfig("string", "RunMode"); runmode != "" {
RunMode = runmode.(string)
}
if autorender, err := GetConfig("bool", "AutoRender"); err == nil {
AutoRender = autorender.(bool)
}
if autorecover, err := GetConfig("bool", "RecoverPanic"); err == nil {
RecoverPanic = autorecover.(bool)
}
if views, _ := GetConfig("string", "ViewsPath"); views != "" {
ViewsPath = views.(string)
}
if sessionon, err := GetConfig("bool", "SessionOn"); err == nil {
SessionOn = sessionon.(bool)
}
if sessProvider, _ := GetConfig("string", "SessionProvider"); sessProvider != "" {
SessionProvider = sessProvider.(string)
}
if sessName, _ := GetConfig("string", "SessionName"); sessName != "" {
SessionName = sessName.(string)
}
if sesssavepath, _ := GetConfig("string", "SessionSavePath"); sesssavepath != "" {
SessionSavePath = sesssavepath.(string)
}
if sesshashfunc, _ := GetConfig("string", "SessionHashFunc"); sesshashfunc != "" {
SessionHashFunc = sesshashfunc.(string)
}
if sesshashkey, _ := GetConfig("string", "SessionHashKey"); sesshashkey != "" {
SessionHashKey = sesshashkey.(string)
}
if sessMaxLifeTime, err := GetConfig("int64", "SessionGCMaxLifetime"); err == nil && sessMaxLifeTime != 0 {
SessionGCMaxLifetime = sessMaxLifeTime.(int64)
}
if sesscookielifetime, err := GetConfig("int", "SessionCookieLifeTime"); err == nil && sesscookielifetime != 0 {
SessionCookieLifeTime = sesscookielifetime.(int)
}
if usefcgi, err := GetConfig("bool", "UseFcgi"); err == nil {
UseFcgi = usefcgi.(bool)
}
if enablegzip, err := GetConfig("bool", "EnableGzip"); err == nil {
EnableGzip = enablegzip.(bool)
}
if directoryindex, err := GetConfig("bool", "DirectoryIndex"); err == nil {
DirectoryIndex = directoryindex.(bool)
}
if timeout, err := GetConfig("int64", "HttpServerTimeOut"); err == nil {
HttpServerTimeOut = timeout.(int64)
}
if errorsshow, err := GetConfig("bool", "ErrorsShow"); err == nil {
ErrorsShow = errorsshow.(bool)
}
if copyrequestbody, err := GetConfig("bool", "CopyRequestBody"); err == nil {
CopyRequestBody = copyrequestbody.(bool)
}
if xsrfkey, _ := GetConfig("string", "XSRFKEY"); xsrfkey != "" {
XSRFKEY = xsrfkey.(string)
}
if enablexsrf, err := GetConfig("bool", "EnableXSRF"); err == nil {
EnableXSRF = enablexsrf.(bool)
}
if expire, err := GetConfig("int", "XSRFExpire"); err == nil {
XSRFExpire = expire.(int)
}
if tplleft, _ := GetConfig("string", "TemplateLeft"); tplleft != "" {
TemplateLeft = tplleft.(string)
}
if tplright, _ := GetConfig("string", "TemplateRight"); tplright != "" {
TemplateRight = tplright.(string)
}
if httptls, err := GetConfig("bool", "EnableHttpTLS"); err == nil {
EnableHttpTLS = httptls.(bool)
}
if httpsport, err := GetConfig("int", "HttpsPort"); err == nil {
HttpsPort = httpsport.(int)
}
if certfile, _ := GetConfig("string", "HttpCertFile"); certfile != "" {
HttpCertFile = certfile.(string)
}
if keyfile, _ := GetConfig("string", "HttpKeyFile"); keyfile != "" {
HttpKeyFile = keyfile.(string)
}
if serverName, _ := GetConfig("string", "BeegoServerName"); serverName != "" {
BeegoServerName = serverName.(string)
}
if flashname, _ := GetConfig("string", "FlashName"); flashname != "" {
FlashName = flashname.(string)
}
if flashseperator, _ := GetConfig("string", "FlashSeperator"); flashseperator != "" {
FlashSeperator = flashseperator.(string)
}
if sd, _ := GetConfig("string", "StaticDir"); sd != "" {
for k := range StaticDir {
delete(StaticDir, k)
sds := strings.Fields(sd)
for _, v := range sds {
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1]
} else {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0]
}
sds := strings.Fields(sd.(string))
for _, v := range sds {
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1]
} else {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0]
}
}
if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
extensions := strings.Split(sgz, ",")
if len(extensions) > 0 {
StaticExtensionsToGzip = []string{}
for _, ext := range extensions {
if len(ext) == 0 {
continue
}
}
}
if sgz, _ := GetConfig("string", "StaticExtensionsToGzip"); sgz != "" {
extensions := strings.Split(sgz.(string), ",")
if len(extensions) > 0 {
StaticExtensionsToGzip = []string{}
for _, ext := range extensions {
if len(ext) == 0 {
continue
}
extWithDot := ext
if extWithDot[:1] != "." {
extWithDot = "." + extWithDot
}
StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
extWithDot := ext
if extWithDot[:1] != "." {
extWithDot = "." + extWithDot
}
StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
}
}
}
if enableadmin, err := GetConfig("bool", "EnableAdmin"); err == nil {
EnableAdmin = enableadmin.(bool)
}
if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil {
EnableAdmin = enableadmin
}
if adminhttpaddr, _ := GetConfig("string", "AdminHttpAddr"); adminhttpaddr != "" {
AdminHttpAddr = adminhttpaddr.(string)
}
if adminhttpaddr := AppConfig.String("AdminHttpAddr"); adminhttpaddr != "" {
AdminHttpAddr = adminhttpaddr
}
if adminhttpport, err := GetConfig("int", "AdminHttpPort"); err == nil {
AdminHttpPort = adminhttpport.(int)
}
if adminhttpport, err := AppConfig.Int("AdminHttpPort"); err == nil {
AdminHttpPort = adminhttpport
}
if enabledocs, err := GetConfig("bool", "EnableDocs"); err == nil {
EnableDocs = enabledocs.(bool)
}
if enabledocs, err := AppConfig.Bool("EnableDocs"); err == nil {
EnableDocs = enabledocs
}
if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil {
RouterCaseSensitive = casesensitive
}
return nil
}
// Getconfig throw the Runmode
// [dev]
// name = astaixe
// IsEnable = false
// [prod]
// name = slene
// IsEnable = true
//
// usage:
// GetConfig("string", "name")
// GetConfig("bool", "IsEnable")
func GetConfig(typ, key string) (interface{}, error) {
switch typ {
case "string":
v := AppConfig.String(RunMode + "::" + key)
if v == "" {
v = AppConfig.String(key)
}
return v, nil
case "strings":
v := AppConfig.Strings(RunMode + "::" + key)
if len(v) == 0 {
v = AppConfig.Strings(key)
}
return v, nil
case "int":
v, err := AppConfig.Int(RunMode + "::" + key)
if err != nil || v == 0 {
return AppConfig.Int(key)
}
return v, nil
case "bool":
v, err := AppConfig.Bool(RunMode + "::" + key)
if err != nil {
return AppConfig.Bool(key)
}
return v, nil
case "int64":
v, err := AppConfig.Int64(RunMode + "::" + key)
if err != nil || v == 0 {
return AppConfig.Int64(key)
}
return v, nil
case "float":
v, err := AppConfig.Float(RunMode + "::" + key)
if err != nil || v == 0 {
return AppConfig.Float(key)
}
return v, nil
}
return "", errors.New("not support type")
}

View File

@ -48,6 +48,10 @@ type IniConfig struct {
// ParseFile creates a new Config and parses the file configuration from the named file.
func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
return ini.parseFile(name)
}
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
file, err := os.Open(name)
if err != nil {
return nil, err
@ -66,6 +70,13 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
var comment bytes.Buffer
buf := bufio.NewReader(file)
// check the BOM
head, err := buf.Peek(3)
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
for i := 1; i <= 3; i++ {
buf.ReadByte()
}
}
section := DEFAULT_SECTION
for {
line, _, err := buf.ReadLine()
@ -108,13 +119,48 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
cfg.data[section] = make(map[string]string)
}
keyValue := bytes.SplitN(line, bEqual, 2)
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
key = strings.ToLower(key)
// handle include "other.conf"
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
includefiles := strings.Fields(key)
if includefiles[0] == "include" && len(includefiles) == 2 {
otherfile := strings.Trim(includefiles[1], "\"")
if !path.IsAbs(otherfile) {
otherfile = path.Join(path.Dir(name), otherfile)
}
i, err := ini.parseFile(otherfile)
if err != nil {
return nil, err
}
for sec, dt := range i.data {
if _, ok := cfg.data[sec]; !ok {
cfg.data[sec] = make(map[string]string)
}
for k, v := range dt {
cfg.data[sec][k] = v
}
}
for sec, comm := range i.sectionComment {
cfg.sectionComment[sec] = comm
}
for k, comm := range i.keyComment {
cfg.keyComment[k] = comm
}
continue
}
}
if len(keyValue) != 2 {
return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
}
val := bytes.TrimSpace(keyValue[1])
if bytes.HasPrefix(val, bDQuote) {
val = bytes.Trim(val, `"`)
}
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
key = strings.ToLower(key)
cfg.data[section][key] = string(val)
if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String()
@ -147,7 +193,7 @@ type IniConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.getdata(strings.ToLower(key)))
return strconv.ParseBool(c.getdata(key))
}
// DefaultBool returns the boolean value for a given key.
@ -162,7 +208,7 @@ func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
// Int returns the integer value for a given key.
func (c *IniConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.getdata(strings.ToLower(key)))
return strconv.Atoi(c.getdata(key))
}
// DefaultInt returns the integer value for a given key.
@ -177,7 +223,7 @@ func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
// Int64 returns the int64 value for a given key.
func (c *IniConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.getdata(strings.ToLower(key)), 10, 64)
return strconv.ParseInt(c.getdata(key), 10, 64)
}
// DefaultInt64 returns the int64 value for a given key.
@ -192,7 +238,7 @@ func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
// Float returns the float value for a given key.
func (c *IniConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.getdata(strings.ToLower(key)), 64)
return strconv.ParseFloat(c.getdata(key), 64)
}
// DefaultFloat returns the float64 value for a given key.
@ -207,8 +253,7 @@ func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float6
// String returns the string value for a given key.
func (c *IniConfigContainer) String(key string) string {
key = strings.ToLower(key)
return c.getdata(strings.ToLower(key))
return c.getdata(key)
}
// DefaultString returns the string value for a given key.
@ -338,15 +383,15 @@ func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
// section.key or key
func (c *IniConfigContainer) getdata(key string) string {
c.RLock()
defer c.RUnlock()
if len(key) == 0 {
return ""
}
c.RLock()
defer c.RUnlock()
var (
section, k string
sectionKey []string = strings.Split(key, "::")
sectionKey []string = strings.Split(strings.ToLower(key), "::")
)
if len(sectionKey) >= 2 {
section = sectionKey[0]

View File

@ -17,13 +17,10 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"sync"
"time"
)
// JsonConfig is a json config parser and implements Config interface.
@ -41,13 +38,19 @@ func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) {
if err != nil {
return nil, err
}
return js.ParseData(content)
}
// ParseData returns a ConfigContainer with json string
func (js *JsonConfig) ParseData(data []byte) (ConfigContainer, error) {
x := &JsonConfigContainer{
data: make(map[string]interface{}),
}
err = json.Unmarshal(content, &x.data)
err := json.Unmarshal(data, &x.data)
if err != nil {
var wrappingArray []interface{}
err2 := json.Unmarshal(content, &wrappingArray)
err2 := json.Unmarshal(data, &wrappingArray)
if err2 != nil {
return nil, err
}
@ -56,16 +59,6 @@ func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) {
return x, nil
}
func (js *JsonConfig) ParseData(data []byte) (ConfigContainer, error) {
// Save memory data to temporary file
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond()))
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
return nil, err
}
return js.Parse(tmpName)
}
// A Config represents the json configuration.
// Only when get value, support key as section:name type.
type JsonConfigContainer struct {
@ -88,11 +81,10 @@ func (c *JsonConfigContainer) Bool(key string) (bool, error) {
// DefaultBool return the bool value if has no error
// otherwise return the defaultval
func (c *JsonConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil {
return defaultval
} else {
if v, err := c.Bool(key); err == nil {
return v
}
return defaultval
}
// Int returns the integer value for a given key.
@ -110,11 +102,10 @@ func (c *JsonConfigContainer) Int(key string) (int, error) {
// DefaultInt returns the integer value for a given key.
// if err != nil return defaltval
func (c *JsonConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil {
return defaultval
} else {
if v, err := c.Int(key); err == nil {
return v
}
return defaultval
}
// Int64 returns the int64 value for a given key.
@ -132,11 +123,10 @@ func (c *JsonConfigContainer) Int64(key string) (int64, error) {
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval
func (c *JsonConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil {
return defaultval
} else {
if v, err := c.Int64(key); err == nil {
return v
}
return defaultval
}
// Float returns the float value for a given key.
@ -154,11 +144,10 @@ func (c *JsonConfigContainer) Float(key string) (float64, error) {
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval
func (c *JsonConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil {
return defaultval
} else {
if v, err := c.Float(key); err == nil {
return v
}
return defaultval
}
// String returns the string value for a given key.
@ -175,35 +164,37 @@ func (c *JsonConfigContainer) String(key string) string {
// DefaultString returns the string value for a given key.
// if err != nil return defaltval
func (c *JsonConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" {
return defaultval
} else {
// TODO FIXME should not use "" to replace non existance
if v := c.String(key); v != "" {
return v
}
return defaultval
}
// Strings returns the []string value for a given key.
func (c *JsonConfigContainer) Strings(key string) []string {
stringVal := c.String(key)
if stringVal == "" {
return []string{}
}
return strings.Split(c.String(key), ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *JsonConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 {
return defaultval
} else {
if v := c.Strings(key); len(v) > 0 {
return v
}
return defaultval
}
// GetSection returns map for the given section
func (c *JsonConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok {
return v.(map[string]string), nil
} else {
return nil, errors.New("not exist setction")
}
return nil, errors.New("nonexist section " + section)
}
// SaveConfigFile save the config into file
@ -222,7 +213,7 @@ func (c *JsonConfigContainer) SaveConfigFile(filename string) (err error) {
return err
}
// WriteValue writes a new value for key.
// Set writes a new value for key.
func (c *JsonConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
@ -241,18 +232,20 @@ func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) {
// section.key or key
func (c *JsonConfigContainer) getData(key string) interface{} {
c.RLock()
defer c.RUnlock()
if len(key) == 0 {
return nil
}
sectionKey := strings.Split(key, "::")
if len(sectionKey) >= 2 {
curValue, ok := c.data[sectionKey[0]]
c.RLock()
defer c.RUnlock()
sectionKeys := strings.Split(key, "::")
if len(sectionKeys) >= 2 {
curValue, ok := c.data[sectionKeys[0]]
if !ok {
return nil
}
for _, key := range sectionKey[1:] {
for _, key := range sectionKeys[1:] {
if v, ok := curValue.(map[string]interface{}); ok {
if curValue, ok = v[key]; !ok {
return nil

View File

@ -21,6 +21,7 @@ import (
var jsoncontext = `{
"appname": "beeapi",
"testnames": "foo;bar",
"httpport": 8080,
"mysqlport": 3600,
"PI": 3.1415976,
@ -28,8 +29,8 @@ var jsoncontext = `{
"autorender": false,
"copyrequestbody": true,
"database": {
"host": "host",
"port": "port",
"host": "host",
"port": "port",
"database": "database",
"username": "username",
"password": "password",
@ -122,6 +123,12 @@ func TestJson(t *testing.T) {
if jsonconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v := jsonconf.Strings("unknown"); len(v) > 0 {
t.Fatal("unknown strings, the length should be 0")
}
if v := jsonconf.Strings("testnames"); len(v) != 2 {
t.Fatal("testnames length should be 2")
}
if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
@ -179,4 +186,8 @@ func TestJson(t *testing.T) {
if _, err := jsonconf.Bool("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Bool")
}
if !jsonconf.DefaultBool("unknow", true) {
t.Error("unknown keys with default value wrong")
}
}

View File

@ -31,7 +31,6 @@ import (
"strings"
"time"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/utils"
)
@ -49,26 +48,13 @@ type Context struct {
// It sends http response header directly.
func (ctx *Context) Redirect(status int, localurl string) {
ctx.Output.Header("Location", localurl)
ctx.Output.SetStatus(status)
ctx.ResponseWriter.WriteHeader(status)
}
// Abort stops this request.
// if middleware.ErrorMaps exists, panic body.
// if middleware.HTTPExceptionMaps exists, panic HTTPException struct with status and body string.
// if beego.ErrorMaps exists, panic body.
func (ctx *Context) Abort(status int, body string) {
ctx.ResponseWriter.WriteHeader(status)
// first panic from ErrorMaps, is is user defined error functions.
if _, ok := middleware.ErrorMaps[body]; ok {
panic(body)
}
// second panic from HTTPExceptionMaps, it is system defined functions.
if e, ok := middleware.HTTPExceptionMaps[status]; ok {
if len(body) >= 1 {
e.Description = body
}
panic(e)
}
// last panic user string
panic(body)
}
@ -154,8 +140,11 @@ func (ctx *Context) CheckXsrfCookie() bool {
}
if token == "" {
ctx.Abort(403, "'_xsrf' argument missing from POST")
} else if ctx._xsrf_token != token {
return false
}
if ctx._xsrf_token != token {
ctx.Abort(403, "XSRF cookie does not match POST argument")
return false
}
return true
}

View File

@ -27,7 +27,7 @@ import (
"github.com/astaxie/beego/session"
)
// BeegoInput operates the http request header ,data ,cookie and body.
// BeegoInput operates the http request header, data, cookie and body.
// it also contains router params and current session.
type BeegoInput struct {
CruSession session.SessionStore
@ -60,7 +60,7 @@ func (input *BeegoInput) Uri() string {
// Url returns request url path (without query string, fragment).
func (input *BeegoInput) Url() string {
return input.Request.URL.String()
return input.Request.URL.Path
}
// Site returns base site url as scheme://domain type.
@ -72,11 +72,11 @@ func (input *BeegoInput) Site() string {
func (input *BeegoInput) Scheme() string {
if input.Request.URL.Scheme != "" {
return input.Request.URL.Scheme
} else if input.Request.TLS == nil {
return "http"
} else {
return "https"
}
if input.Request.TLS == nil {
return "http"
}
return "https"
}
// Domain returns host name.
@ -153,12 +153,12 @@ func (input *BeegoInput) IsSecure() bool {
return input.Scheme() == "https"
}
// IsSecure returns boolean of this request is in webSocket.
// IsWebsocket returns boolean of this request is in webSocket.
func (input *BeegoInput) IsWebsocket() bool {
return input.Header("Upgrade") == "websocket"
}
// IsSecure returns boolean of whether file uploads in this request or not..
// IsUpload returns boolean of whether file uploads in this request or not..
func (input *BeegoInput) IsUpload() bool {
return strings.Contains(input.Header("Content-Type"), "multipart/form-data")
}
@ -189,16 +189,24 @@ func (input *BeegoInput) Proxy() []string {
return []string{}
}
// Referer returns http referer header.
func (input *BeegoInput) Referer() string {
return input.Header("Referer")
}
// Refer returns http referer header.
func (input *BeegoInput) Refer() string {
return input.Header("Referer")
return input.Referer()
}
// SubDomains returns sub domain string.
// if aa.bb.domain.com, returns aa.bb .
func (input *BeegoInput) SubDomains() string {
parts := strings.Split(input.Host(), ".")
return strings.Join(parts[len(parts)-2:], ".")
if len(parts) >= 3 {
return strings.Join(parts[:len(parts)-2], ".")
}
return ""
}
// Port returns request client port.
@ -237,6 +245,7 @@ func (input *BeegoInput) Query(key string) string {
}
// Header returns request header item string by a given string.
// if non-existed, return empty string.
func (input *BeegoInput) Header(key string) string {
return input.Request.Header.Get(key)
}
@ -252,11 +261,12 @@ func (input *BeegoInput) Cookie(key string) string {
}
// Session returns current session item value by a given key.
// if non-existed, return empty string.
func (input *BeegoInput) Session(key interface{}) interface{} {
return input.CruSession.Get(key)
}
// Body returns the raw request body data as bytes.
// CopyBody returns the raw request body data as bytes.
func (input *BeegoInput) CopyBody() []byte {
requestbody, _ := ioutil.ReadAll(input.Request.Body)
input.Request.Body.Close()

View File

@ -70,3 +70,45 @@ func TestParse(t *testing.T) {
}
fmt.Println(user)
}
func TestSubDomain(t *testing.T) {
r, _ := http.NewRequest("GET", "http://www.example.com/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil)
beegoInput := NewInput(r)
subdomain := beegoInput.SubDomains()
if subdomain != "www" {
t.Fatal("Subdomain parse error, got" + subdomain)
}
r, _ = http.NewRequest("GET", "http://localhost/", nil)
beegoInput.Request = r
if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, should be empty, got " + beegoInput.SubDomains())
}
r, _ = http.NewRequest("GET", "http://aa.bb.example.com/", nil)
beegoInput.Request = r
if beegoInput.SubDomains() != "aa.bb" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
}
/* TODO Fix this
r, _ = http.NewRequest("GET", "http://127.0.0.1/", nil)
beegoInput.Request = r
if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
}
*/
r, _ = http.NewRequest("GET", "http://example.com/", nil)
beegoInput.Request = r
if beegoInput.SubDomains() != "" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
}
r, _ = http.NewRequest("GET", "http://aa.bb.cc.dd.example.com/", nil)
beegoInput.Request = r
if beegoInput.SubDomains() != "aa.bb.cc.dd" {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
}
}

View File

@ -188,7 +188,7 @@ func sanitizeValue(v string) string {
// Json writes json to response body.
// if coding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) error {
output.Header("Content-Type", "application/json;charset=UTF-8")
output.Header("Content-Type", "application/json; charset=utf-8")
var content []byte
var err error
if hasIndent {
@ -209,7 +209,7 @@ func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) e
// Jsonp writes jsonp to response body.
func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/javascript;charset=UTF-8")
output.Header("Content-Type", "application/javascript; charset=utf-8")
var content []byte
var err error
if hasIndent {
@ -235,7 +235,7 @@ func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error {
// Xml writes xml string to response body.
func (output *BeegoOutput) Xml(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/xml;charset=UTF-8")
output.Header("Content-Type", "application/xml; charset=utf-8")
var content []byte
var err error
if hasIndent {

View File

@ -270,16 +270,22 @@ func (c *Controller) Redirect(url string, code int) {
// Aborts stops controller handler and show the error data if code is defined in ErrorMap or code string.
func (c *Controller) Abort(code string) {
status, err := strconv.Atoi(code)
if err == nil {
c.Ctx.Abort(status, code)
} else {
c.Ctx.Abort(200, code)
if err != nil {
status = 200
}
c.CustomAbort(status, code)
}
// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body.
func (c *Controller) CustomAbort(status int, body string) {
c.Ctx.Abort(status, body)
c.Ctx.ResponseWriter.WriteHeader(status)
// first panic from ErrorMaps, is is user defined error functions.
if _, ok := ErrorMaps[body]; ok {
panic(body)
}
// last panic user string
c.Ctx.ResponseWriter.Write([]byte(body))
panic(USERSTOPRUN)
}
// StopRun makes panic of USERSTOPRUN error and go to recover function if defined.
@ -289,7 +295,7 @@ func (c *Controller) StopRun() {
// UrlFor does another controller handler in this request function.
// it goes to this controller method if endpoint is not clear.
func (c *Controller) UrlFor(endpoint string, values ...string) string {
func (c *Controller) UrlFor(endpoint string, values ...interface{}) string {
if len(endpoint) <= 0 {
return ""
}
@ -363,38 +369,144 @@ func (c *Controller) ParseForm(obj interface{}) error {
return ParseForm(c.Input(), obj)
}
// GetString returns the input value by key string.
func (c *Controller) GetString(key string) string {
return c.Ctx.Input.Query(key)
// GetString returns the input value by key string or the default value while it's present and input is blank
func (c *Controller) GetString(key string, def ...string) string {
var defv string
if len(def) > 0 {
defv = def[0]
}
if v := c.Ctx.Input.Query(key); v != "" {
return v
} else {
return defv
}
}
// GetStrings returns the input string slice by key string.
// GetStrings returns the input string slice by key string or the default value while it's present and input is blank
// it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection.
func (c *Controller) GetStrings(key string) []string {
func (c *Controller) GetStrings(key string, def ...[]string) []string {
var defv []string
if len(def) > 0 {
defv = def[0]
}
f := c.Input()
if f == nil {
return []string{}
return defv
}
vs := f[key]
if len(vs) > 0 {
return vs
} else {
return defv
}
return []string{}
}
// GetInt returns input value as int64.
func (c *Controller) GetInt(key string) (int64, error) {
return strconv.ParseInt(c.Ctx.Input.Query(key), 10, 64)
// GetInt returns input as an int or the default value while it's present and input is blank
func (c *Controller) GetInt(key string, def ...int) (int, error) {
var defv int
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.Atoi(strv)
} else {
return defv, nil
}
}
// GetBool returns input value as bool.
func (c *Controller) GetBool(key string) (bool, error) {
return strconv.ParseBool(c.Ctx.Input.Query(key))
// GetInt8 return input as an int8 or the default value while it's present and input is blank
func (c *Controller) GetInt8(key string, def ...int8) (int8, error) {
var defv int8
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
i64, err := strconv.ParseInt(strv, 10, 8)
i8 := int8(i64)
return i8, err
} else {
return defv, nil
}
}
// GetFloat returns input value as float64.
func (c *Controller) GetFloat(key string) (float64, error) {
return strconv.ParseFloat(c.Ctx.Input.Query(key), 64)
// GetInt16 returns input as an int16 or the default value while it's present and input is blank
func (c *Controller) GetInt16(key string, def ...int16) (int16, error) {
var defv int16
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
i64, err := strconv.ParseInt(strv, 10, 16)
i16 := int16(i64)
return i16, err
} else {
return defv, nil
}
}
// GetInt32 returns input as an int32 or the default value while it's present and input is blank
func (c *Controller) GetInt32(key string, def ...int32) (int32, error) {
var defv int32
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32)
i32 := int32(i64)
return i32, err
} else {
return defv, nil
}
}
// GetInt64 returns input value as int64 or the default value while it's present and input is blank.
func (c *Controller) GetInt64(key string, def ...int64) (int64, error) {
var defv int64
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.ParseInt(strv, 10, 64)
} else {
return defv, nil
}
}
// GetBool returns input value as bool or the default value while it's present and input is blank.
func (c *Controller) GetBool(key string, def ...bool) (bool, error) {
var defv bool
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.ParseBool(strv)
} else {
return defv, nil
}
}
// GetFloat returns input value as float64 or the default value while it's present and input is blank.
func (c *Controller) GetFloat(key string, def ...float64) (float64, error) {
var defv float64
if len(def) > 0 {
defv = def[0]
}
if strv := c.Ctx.Input.Query(key); strv != "" {
return strconv.ParseFloat(c.Ctx.Input.Query(key), 64)
} else {
return defv, nil
}
}
// GetFile returns the file data in file upload field named as key.

75
controller_test.go Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"fmt"
"github.com/astaxie/beego/context"
)
func ExampleGetInt() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt("age")
fmt.Printf("%T", val)
//Output: int
}
func ExampleGetInt8() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt8("age")
fmt.Printf("%T", val)
//Output: int8
}
func ExampleGetInt16() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt16("age")
fmt.Printf("%T", val)
//Output: int16
}
func ExampleGetInt32() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt32("age")
fmt.Printf("%T", val)
//Output: int32
}
func ExampleGetInt64() {
i := &context.BeegoInput{Params: map[string]string{"age": "40"}}
ctx := &context.Context{Input: i}
ctrlr := Controller{Ctx: ctx}
val, _ := ctrlr.GetInt64("age")
fmt.Printf("%T", val)
//Output: int64
}

View File

@ -12,20 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package middleware
package beego
import (
"fmt"
"html/template"
"net/http"
"reflect"
"runtime"
"strconv"
"strings"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/utils"
)
var (
AppName string
VERSION string
const (
errorTypeHandler = iota
errorTypeController
)
var tpl = `
<!DOCTYPE html>
<html>
@ -76,18 +82,18 @@ var tpl = `
`
// render default application error page with error and stack string.
func ShowErr(err interface{}, rw http.ResponseWriter, r *http.Request, Stack string) {
func showErr(err interface{}, ctx *context.Context, Stack string) {
t, _ := template.New("beegoerrortemp").Parse(tpl)
data := make(map[string]string)
data["AppError"] = AppName + ":" + fmt.Sprint(err)
data["RequestMethod"] = r.Method
data["RequestURL"] = r.RequestURI
data["RemoteAddr"] = r.RemoteAddr
data["RequestMethod"] = ctx.Input.Method()
data["RequestURL"] = ctx.Input.Uri()
data["RemoteAddr"] = ctx.Input.IP()
data["Stack"] = Stack
data["BeegoVersion"] = VERSION
data["GoVersion"] = runtime.Version()
rw.WriteHeader(500)
t.Execute(rw, data)
ctx.Output.SetStatus(500)
t.Execute(ctx.ResponseWriter, data)
}
var errtpl = `
@ -190,15 +196,68 @@ var errtpl = `
</html>
`
type errorInfo struct {
controllerType reflect.Type
handler http.HandlerFunc
method string
errorType int
}
// map of http handlers for each error string.
var ErrorMaps map[string]http.HandlerFunc
var ErrorMaps map[string]*errorInfo
func init() {
ErrorMaps = make(map[string]http.HandlerFunc)
ErrorMaps = make(map[string]*errorInfo)
}
// show 401 unauthorized error.
func unauthorized(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Unauthorized"
data["Content"] = template.HTML("<br>The page you have requested can't be authorized." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The credentials you supplied are incorrect" +
"<br>There are errors in the website address" +
"</ul>")
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
}
// show 402 Payment Required
func paymentRequired(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Payment Required"
data["Content"] = template.HTML("<br>The page you have requested Payment Required." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The credentials you supplied are incorrect" +
"<br>There are errors in the website address" +
"</ul>")
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
}
// show 403 forbidden error.
func forbidden(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Forbidden"
data["Content"] = template.HTML("<br>The page you have requested is forbidden." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>Your address may be blocked" +
"<br>The site may be disabled" +
"<br>You need to log in" +
"</ul>")
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
}
// show 404 notfound error.
func NotFound(rw http.ResponseWriter, r *http.Request) {
func notFound(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Page Not Found"
@ -211,45 +270,66 @@ func NotFound(rw http.ResponseWriter, r *http.Request) {
"<br>You like 404 pages" +
"</ul>")
data["BeegoVersion"] = VERSION
//rw.WriteHeader(http.StatusNotFound)
t.Execute(rw, data)
}
// show 401 unauthorized error.
func Unauthorized(rw http.ResponseWriter, r *http.Request) {
// show 405 Method Not Allowed
func methodNotAllowed(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Unauthorized"
data["Content"] = template.HTML("<br>The page you have requested can't be authorized." +
data["Title"] = "Method Not Allowed"
data["Content"] = template.HTML("<br>The method you have requested Not Allowed." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The credentials you supplied are incorrect" +
"<br>There are errors in the website address" +
"<br>The method specified in the Request-Line is not allowed for the resource identified by the Request-URI" +
"<br>The response MUST include an Allow header containing a list of valid methods for the requested resource." +
"</ul>")
data["BeegoVersion"] = VERSION
//rw.WriteHeader(http.StatusUnauthorized)
t.Execute(rw, data)
}
// show 403 forbidden error.
func Forbidden(rw http.ResponseWriter, r *http.Request) {
// show 500 internal server error.
func internalServerError(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Forbidden"
data["Content"] = template.HTML("<br>The page you have requested is forbidden." +
"<br>Perhaps you are here because:" +
data["Title"] = "Internal Server Error"
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
"<br><br><ul>" +
"<br>Your address may be blocked" +
"<br>The site may be disabled" +
"<br>You need to log in" +
"</ul>")
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
}
// show 501 Not Implemented.
func notImplemented(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Not Implemented"
data["Content"] = template.HTML("<br>The page you have requested is Not Implemented." +
"<br><br><ul>" +
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
data["BeegoVersion"] = VERSION
t.Execute(rw, data)
}
// show 502 Bad Gateway.
func badGateway(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Bad Gateway"
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
"<br><br><ul>" +
"<br>The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request." +
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
data["BeegoVersion"] = VERSION
//rw.WriteHeader(http.StatusForbidden)
t.Execute(rw, data)
}
// show 503 service unavailable error.
func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
func serviceUnavailable(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Service Unavailable"
@ -260,80 +340,151 @@ func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
"<br>Please try again later." +
"</ul>")
data["BeegoVersion"] = VERSION
//rw.WriteHeader(http.StatusServiceUnavailable)
t.Execute(rw, data)
}
// show 500 internal server error.
func InternalServerError(rw http.ResponseWriter, r *http.Request) {
// show 504 Gateway Timeout.
func gatewayTimeout(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Internal Server Error"
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
data["Title"] = "Gateway Timeout"
data["Content"] = template.HTML("<br>The page you have requested is unavailable." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>Please try again later and report the error to the website administrator" +
"<br></ul>")
"<br><br>The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI." +
"<br>Please try again later." +
"</ul>")
data["BeegoVersion"] = VERSION
//rw.WriteHeader(http.StatusInternalServerError)
t.Execute(rw, data)
}
// show 500 internal error with simple text string.
func SimpleServerError(rw http.ResponseWriter, r *http.Request) {
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
// add http handler for given error string.
func Errorhandler(err string, h http.HandlerFunc) {
ErrorMaps[err] = h
}
// register default error http handlers, 404,401,403,500 and 503.
func RegisterErrorHandler() {
if _, ok := ErrorMaps["404"]; !ok {
ErrorMaps["404"] = NotFound
func registerDefaultErrorHandler() {
if _, ok := ErrorMaps["401"]; !ok {
Errorhandler("401", unauthorized)
}
if _, ok := ErrorMaps["401"]; !ok {
ErrorMaps["401"] = Unauthorized
if _, ok := ErrorMaps["402"]; !ok {
Errorhandler("402", paymentRequired)
}
if _, ok := ErrorMaps["403"]; !ok {
ErrorMaps["403"] = Forbidden
Errorhandler("403", forbidden)
}
if _, ok := ErrorMaps["503"]; !ok {
ErrorMaps["503"] = ServiceUnavailable
if _, ok := ErrorMaps["404"]; !ok {
Errorhandler("404", notFound)
}
if _, ok := ErrorMaps["405"]; !ok {
Errorhandler("405", methodNotAllowed)
}
if _, ok := ErrorMaps["500"]; !ok {
ErrorMaps["500"] = InternalServerError
Errorhandler("500", internalServerError)
}
if _, ok := ErrorMaps["501"]; !ok {
Errorhandler("501", notImplemented)
}
if _, ok := ErrorMaps["502"]; !ok {
Errorhandler("502", badGateway)
}
if _, ok := ErrorMaps["503"]; !ok {
Errorhandler("503", serviceUnavailable)
}
if _, ok := ErrorMaps["504"]; !ok {
Errorhandler("504", gatewayTimeout)
}
}
// ErrorHandler registers http.HandlerFunc to each http err code string.
// usage:
// beego.ErrorHandler("404",NotFound)
// beego.ErrorHandler("500",InternalServerError)
func Errorhandler(code string, h http.HandlerFunc) *App {
errinfo := &errorInfo{}
errinfo.errorType = errorTypeHandler
errinfo.handler = h
errinfo.method = code
ErrorMaps[code] = errinfo
return BeeApp
}
// ErrorController registers ControllerInterface to each http err code string.
// usage:
// beego.ErrorHandler(&controllers.ErrorController{})
func ErrorController(c ControllerInterface) *App {
reflectVal := reflect.ValueOf(c)
rt := reflectVal.Type()
ct := reflect.Indirect(reflectVal).Type()
for i := 0; i < rt.NumMethod(); i++ {
if !utils.InSlice(rt.Method(i).Name, exceptMethod) && strings.HasPrefix(rt.Method(i).Name, "Error") {
errinfo := &errorInfo{}
errinfo.errorType = errorTypeController
errinfo.controllerType = ct
errinfo.method = rt.Method(i).Name
errname := strings.TrimPrefix(rt.Method(i).Name, "Error")
ErrorMaps[errname] = errinfo
}
}
return BeeApp
}
// show error string as simple text message.
// if error string is empty, show 500 error as default.
func Exception(errcode string, w http.ResponseWriter, r *http.Request, msg string) {
func exception(errcode string, ctx *context.Context) {
code, err := strconv.Atoi(errcode)
if err != nil {
code = 503
}
ctx.ResponseWriter.WriteHeader(code)
if h, ok := ErrorMaps[errcode]; ok {
isint, err := strconv.Atoi(errcode)
if err != nil {
isint = 500
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(isint)
h(w, r)
executeError(h, ctx)
return
} else if h, ok := ErrorMaps["503"]; ok {
executeError(h, ctx)
return
} else {
isint, err := strconv.Atoi(errcode)
if err != nil {
isint = 500
}
if isint == 400 {
msg = "404 page not found"
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(isint)
fmt.Fprintln(w, msg)
return
ctx.WriteString(errcode)
}
}
func executeError(err *errorInfo, ctx *context.Context) {
if err.errorType == errorTypeHandler {
err.handler(ctx.ResponseWriter, ctx.Request)
return
}
if err.errorType == errorTypeController {
//Invoke the request handler
vc := reflect.New(err.controllerType)
execController, ok := vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
//call the controller init function
execController.Init(ctx, err.controllerType.Name(), err.method, vc.Interface())
//call prepare function
execController.Prepare()
execController.URLMapping()
in := make([]reflect.Value, 0)
method := vc.MethodByName(err.method)
method.Call(in)
//render template
if ctx.Output.Status == 0 {
if AutoRender {
if err := execController.Render(); err != nil {
panic(err)
}
}
}
// finish all runrouter. release resource
execController.Finish()
}
}

View File

@ -17,47 +17,47 @@ type ObjectController struct {
beego.Controller
}
func (this *ObjectController) Post() {
func (o *ObjectController) Post() {
var ob models.Object
json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
objectid := models.AddOne(ob)
this.Data["json"] = map[string]string{"ObjectId": objectid}
this.ServeJson()
o.Data["json"] = map[string]string{"ObjectId": objectid}
o.ServeJson()
}
func (this *ObjectController) Get() {
objectId := this.Ctx.Input.Params[":objectId"]
func (o *ObjectController) Get() {
objectId := o.Ctx.Input.Params[":objectId"]
if objectId != "" {
ob, err := models.GetOne(objectId)
if err != nil {
this.Data["json"] = err
o.Data["json"] = err
} else {
this.Data["json"] = ob
o.Data["json"] = ob
}
} else {
obs := models.GetAll()
this.Data["json"] = obs
o.Data["json"] = obs
}
this.ServeJson()
o.ServeJson()
}
func (this *ObjectController) Put() {
objectId := this.Ctx.Input.Params[":objectId"]
func (o *ObjectController) Put() {
objectId := o.Ctx.Input.Params[":objectId"]
var ob models.Object
json.Unmarshal(this.Ctx.Input.RequestBody, &ob)
json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
err := models.Update(objectId, ob.Score)
if err != nil {
this.Data["json"] = err
o.Data["json"] = err
} else {
this.Data["json"] = "update success!"
o.Data["json"] = "update success!"
}
this.ServeJson()
o.ServeJson()
}
func (this *ObjectController) Delete() {
objectId := this.Ctx.Input.Params[":objectId"]
func (o *ObjectController) Delete() {
objectId := o.Ctx.Input.Params[":objectId"]
models.Delete(objectId)
this.Data["json"] = "delete success!"
this.ServeJson()
o.Data["json"] = "delete success!"
o.ServeJson()
}

View File

@ -14,7 +14,7 @@ type MainController struct {
beego.Controller
}
func (this *MainController) Get() {
this.Data["host"] = this.Ctx.Request.Host
this.TplNames = "index.tpl"
func (m *MainController) Get() {
m.Data["host"] = m.Ctx.Request.Host
m.TplNames = "index.tpl"
}

View File

@ -150,14 +150,14 @@ type WSController struct {
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func (this *WSController) Get() {
ws, err := upgrader.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request,nil)
func (w *WSController) Get() {
ws, err := upgrader.Upgrade(w.Ctx.ResponseWriter, w.Ctx.Request, nil)
if _, ok := err.(websocket.HandshakeError); ok {
http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400)
http.Error(w.Ctx.ResponseWriter, "Not a websocket handshake", 400)
return
} else if err != nil {
return

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Chat Example</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
<script type="text/javascript">
$(function() {

View File

@ -14,12 +14,18 @@
package beego
import "github.com/astaxie/beego/context"
// FilterFunc defines filter function type.
type FilterFunc func(*context.Context)
// FilterRouter defines filter operation before controller handler execution.
// it can match patterned url and do filter function when action arrives.
type FilterRouter struct {
filterFunc FilterFunc
tree *Tree
pattern string
filterFunc FilterFunc
tree *Tree
pattern string
returnOnOutput bool
}
// ValidRouter check current request is valid for this filter.

View File

@ -32,6 +32,24 @@ func NewFlash() *FlashData {
}
}
// Set message to flash
func (fd *FlashData) Set(key string, msg string, args ...interface{}) {
if len(args) == 0 {
fd.Data[key] = msg
} else {
fd.Data[key] = fmt.Sprintf(msg, args...)
}
}
// Success writes success message to flash.
func (fd *FlashData) Success(msg string, args ...interface{}) {
if len(args) == 0 {
fd.Data["success"] = msg
} else {
fd.Data["success"] = fmt.Sprintf(msg, args...)
}
}
// Notice writes notice message to flash.
func (fd *FlashData) Notice(msg string, args ...interface{}) {
if len(args) == 0 {

View File

@ -25,12 +25,12 @@ type TestFlashController struct {
Controller
}
func (this *TestFlashController) TestWriteFlash() {
func (t *TestFlashController) TestWriteFlash() {
flash := NewFlash()
flash.Notice("TestFlashString")
flash.Store(&this.Controller)
flash.Store(&t.Controller)
// we choose to serve json because we don't want to load a template html file
this.ServeJson(true)
t.ServeJson(true)
}
func TestFlashHeader(t *testing.T) {

View File

@ -6,53 +6,71 @@ httplib is an libs help you to curl remote url.
## GET
you can use Get to crawl data.
import "httplib"
import "github.com/astaxie/beego/httplib"
str, err := httplib.Get("http://beego.me/").String()
if err != nil {
t.Fatal(err)
// error
}
fmt.Println(str)
## POST
POST data to remote url
b:=httplib.Post("http://beego.me/")
b.Param("username","astaxie")
b.Param("password","123456")
str, err := b.String()
req := httplib.Post("http://beego.me/")
req.Param("username","astaxie")
req.Param("password","123456")
str, err := req.String()
if err != nil {
t.Fatal(err)
// error
}
fmt.Println(str)
## set timeout
you can set timeout in request.default is 60 seconds.
## Set timeout
set Get timeout:
The default timeout is `60` seconds, function prototype:
SetTimeout(connectTimeout, readWriteTimeout time.Duration)
Exmaple:
// GET
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
set post timeout:
// POST
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
- first param is connectTimeout.
- second param is readWriteTimeout
## debug
if you want to debug the request info, set the debug on
## Debug
If you want to debug the request info, set the debug on
httplib.Get("http://beego.me/").Debug(true)
## support HTTPS client
if request url is https. You can set the client support TSL:
## Set HTTP Basic Auth
str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String()
if err != nil {
// error
}
fmt.Println(str)
## Set HTTPS
If request url is https, You can set the client support TSL:
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
more info about the tls.Config please visit http://golang.org/pkg/crypto/tls/#Config
## set cookie
More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config
## Set HTTP Version
some servers need to specify the protocol version of HTTP
httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1")
## Set Cookie
some http request need setcookie. So set it like this:
cookie := &http.Cookie{}
@ -60,21 +78,20 @@ some http request need setcookie. So set it like this:
cookie.Value = "astaxie"
httplib.Get("http://beego.me/").SetCookie(cookie)
## upload file
httplib support mutil file upload, use `b.PostFile()`
## Upload file
b:=httplib.Post("http://beego.me/")
b.Param("username","astaxie")
b.Param("password","123456")
b.PostFile("uploadfile1", "httplib.pdf")
b.PostFile("uploadfile2", "httplib.txt")
str, err := b.String()
httplib support mutil file upload, use `req.PostFile()`
req := httplib.Post("http://beego.me/")
req.Param("username","astaxie")
req.PostFile("uploadfile1", "httplib.pdf")
str, err := req.String()
if err != nil {
t.Fatal(err)
// error
}
fmt.Println(str)
## set HTTP version
some servers need to specify the protocol version of HTTP
httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1")
See godoc for further documentation and examples.
* [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib)

View File

@ -14,9 +14,9 @@
// Usage:
//
// import "github.com/astaxie/beego/context"
// import "github.com/astaxie/beego/httplib"
//
// b:=httplib.Post("http://beego.me/")
// b := httplib.Post("http://beego.me/")
// b.Param("username","astaxie")
// b.Param("password","123456")
// b.PostFile("uploadfile1", "httplib.pdf")
@ -37,6 +37,7 @@ import (
"encoding/xml"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net"
"net/http"
@ -53,7 +54,7 @@ var defaultSetting = BeegoHttpSettings{false, "beegoServer", 60 * time.Second, 6
var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex
// createDefaultCookieJar creates a global cookiejar to store cookies.
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie() {
settingMutex.Lock()
defer settingMutex.Unlock()
@ -73,44 +74,42 @@ func SetDefaultSetting(setting BeegoHttpSettings) {
}
}
// return *BeegoHttpRequest with specific method
func newBeegoRequest(url, method string) *BeegoHttpRequest {
var resp http.Response
req := http.Request{
Method: method,
Header: make(http.Header),
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
}
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting, &resp, nil}
}
// Get returns *BeegoHttpRequest with GET method.
func Get(url string) *BeegoHttpRequest {
var req http.Request
req.Method = "GET"
req.Header = http.Header{}
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting}
return newBeegoRequest(url, "GET")
}
// Post returns *BeegoHttpRequest with POST method.
func Post(url string) *BeegoHttpRequest {
var req http.Request
req.Method = "POST"
req.Header = http.Header{}
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting}
return newBeegoRequest(url, "POST")
}
// Put returns *BeegoHttpRequest with PUT method.
func Put(url string) *BeegoHttpRequest {
var req http.Request
req.Method = "PUT"
req.Header = http.Header{}
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting}
return newBeegoRequest(url, "PUT")
}
// Delete returns *BeegoHttpRequest DELETE GET method.
// Delete returns *BeegoHttpRequest DELETE method.
func Delete(url string) *BeegoHttpRequest {
var req http.Request
req.Method = "DELETE"
req.Header = http.Header{}
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting}
return newBeegoRequest(url, "DELETE")
}
// Head returns *BeegoHttpRequest with HEAD method.
func Head(url string) *BeegoHttpRequest {
var req http.Request
req.Method = "HEAD"
req.Header = http.Header{}
return &BeegoHttpRequest{url, &req, map[string]string{}, map[string]string{}, defaultSetting}
return newBeegoRequest(url, "HEAD")
}
// BeegoHttpSettings
@ -132,6 +131,8 @@ type BeegoHttpRequest struct {
params map[string]string
files map[string]string
setting BeegoHttpSettings
resp *http.Response
body []byte
}
// Change request settings
@ -140,6 +141,12 @@ func (b *BeegoHttpRequest) Setting(setting BeegoHttpSettings) *BeegoHttpRequest
return b
}
// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password.
func (b *BeegoHttpRequest) SetBasicAuth(username, password string) *BeegoHttpRequest {
b.req.SetBasicAuth(username, password)
return b
}
// SetEnableCookie sets enable/disable cookiejar
func (b *BeegoHttpRequest) SetEnableCookie(enable bool) *BeegoHttpRequest {
b.setting.EnableCookie = enable
@ -246,7 +253,63 @@ func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
return b
}
func (b *BeegoHttpRequest) buildUrl(paramBody string) {
// build GET url with query string
if b.req.Method == "GET" && len(paramBody) > 0 {
if strings.Index(b.url, "?") != -1 {
b.url += "&" + paramBody
} else {
b.url = b.url + "?" + paramBody
}
return
}
// build POST url and body
if b.req.Method == "POST" && b.req.Body == nil {
// with files
if len(b.files) > 0 {
pr, pw := io.Pipe()
bodyWriter := multipart.NewWriter(pw)
go func() {
for formname, filename := range b.files {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
if err != nil {
log.Fatal(err)
}
fh, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
//iocopy
_, err = io.Copy(fileWriter, fh)
fh.Close()
if err != nil {
log.Fatal(err)
}
}
for k, v := range b.params {
bodyWriter.WriteField(k, v)
}
bodyWriter.Close()
pw.Close()
}()
b.Header("Content-Type", bodyWriter.FormDataContentType())
b.req.Body = ioutil.NopCloser(pr)
return
}
// with params
if len(paramBody) > 0 {
b.Header("Content-Type", "application/x-www-form-urlencoded")
b.Body(paramBody)
}
}
}
func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
if b.resp.StatusCode != 0 {
return b.resp, nil
}
var paramBody string
if len(b.params) > 0 {
var buf bytes.Buffer
@ -260,63 +323,13 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
paramBody = paramBody[0 : len(paramBody)-1]
}
if b.req.Method == "GET" && len(paramBody) > 0 {
if strings.Index(b.url, "?") != -1 {
b.url += "&" + paramBody
} else {
b.url = b.url + "?" + paramBody
}
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 {
if len(b.files) > 0 {
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
for formname, filename := range b.files {
fileWriter, err := bodyWriter.CreateFormFile(formname, filename)
if err != nil {
return nil, err
}
fh, err := os.Open(filename)
if err != nil {
return nil, err
}
//iocopy
_, err = io.Copy(fileWriter, fh)
fh.Close()
if err != nil {
return nil, err
}
}
for k, v := range b.params {
bodyWriter.WriteField(k, v)
}
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
b.Header("Content-Type", contentType)
b.req.Body = ioutil.NopCloser(bodyBuf)
b.req.ContentLength = int64(bodyBuf.Len())
} else {
b.Header("Content-Type", "application/x-www-form-urlencoded")
b.Body(paramBody)
}
}
b.buildUrl(paramBody)
url, err := url.Parse(b.url)
if url.Scheme == "" {
b.url = "http://" + b.url
url, err = url.Parse(b.url)
}
if err != nil {
return nil, err
}
b.req.URL = url
if b.setting.ShowDebug {
dump, err := httputil.DumpRequest(b.req, true)
if err != nil {
println(err.Error())
}
println(string(dump))
}
trans := b.setting.Transport
@ -342,14 +355,12 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
}
}
var jar http.CookieJar
var jar http.CookieJar = nil
if b.setting.EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie()
}
jar = defaultCookieJar
} else {
jar = nil
}
client := &http.Client{
@ -357,14 +368,23 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
Jar: jar,
}
if b.setting.UserAgent != "" {
if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" {
b.req.Header.Set("User-Agent", b.setting.UserAgent)
}
if b.setting.ShowDebug {
dump, err := httputil.DumpRequest(b.req, true)
if err != nil {
println(err.Error())
}
println(string(dump))
}
resp, err := client.Do(b.req)
if err != nil {
return nil, err
}
b.resp = resp
return resp, nil
}
@ -382,6 +402,9 @@ func (b *BeegoHttpRequest) String() (string, error) {
// Bytes returns the body []byte in response.
// it calls Response inner.
func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
if b.body != nil {
return b.body, nil
}
resp, err := b.getResponse()
if err != nil {
return nil, err
@ -390,11 +413,11 @@ func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
return nil, nil
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
b.body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return data, nil
return b.body, nil
}
// ToFile saves the body data in response to one file.
@ -425,8 +448,7 @@ func (b *BeegoHttpRequest) ToJson(v interface{}) error {
if err != nil {
return err
}
err = json.Unmarshal(data, v)
return err
return json.Unmarshal(data, v)
}
// ToXml returns the map that marshals from the body bytes as xml in response .
@ -436,8 +458,7 @@ func (b *BeegoHttpRequest) ToXml(v interface{}) error {
if err != nil {
return err
}
err = xml.Unmarshal(data, v)
return err
return xml.Unmarshal(data, v)
}
// Response executes request client gets response mannually.

View File

@ -15,47 +15,90 @@
package httplib
import (
"io/ioutil"
"os"
"strings"
"testing"
)
func TestSimpleGet(t *testing.T) {
str, err := Get("http://httpbin.org/get").String()
func TestResponse(t *testing.T) {
req := Get("http://httpbin.org/get")
resp, err := req.Response()
if err != nil {
t.Fatal(err)
}
t.Log(str)
t.Log(resp)
}
func TestGet(t *testing.T) {
req := Get("http://httpbin.org/get")
b, err := req.Bytes()
if err != nil {
t.Fatal(err)
}
t.Log(b)
s, err := req.String()
if err != nil {
t.Fatal(err)
}
t.Log(s)
if string(b) != s {
t.Fatal("request data not match")
}
}
func TestSimplePost(t *testing.T) {
v := "smallfish"
req := Post("http://httpbin.org/post")
req.Param("username", v)
str, err := req.String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, v)
if n == -1 {
t.Fatal(v + " not found in post")
}
}
func TestPostFile(t *testing.T) {
v := "smallfish"
req := Post("http://httpbin.org/post")
req.Param("username", v)
req.PostFile("uploadfile", "httplib_test.go")
str, err := req.String()
//func TestPostFile(t *testing.T) {
// v := "smallfish"
// req := Post("http://httpbin.org/post")
// req.Debug(true)
// req.Param("username", v)
// req.PostFile("uploadfile", "httplib_test.go")
// str, err := req.String()
// if err != nil {
// t.Fatal(err)
// }
// t.Log(str)
// n := strings.Index(str, v)
// if n == -1 {
// t.Fatal(v + " not found in post")
// }
//}
func TestSimplePut(t *testing.T) {
str, err := Put("http://httpbin.org/put").String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, v)
if n == -1 {
t.Fatal(v + " not found in post")
}
func TestSimpleDelete(t *testing.T) {
str, err := Delete("http://httpbin.org/delete").String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
}
func TestWithCookie(t *testing.T) {
@ -65,17 +108,31 @@ func TestWithCookie(t *testing.T) {
t.Fatal(err)
}
t.Log(str)
str, err = Get("http://httpbin.org/cookies").SetEnableCookie(true).String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, v)
if n == -1 {
t.Fatal(v + " not found in cookie")
}
}
func TestWithBasicAuth(t *testing.T) {
str, err := Get("http://httpbin.org/basic-auth/user/passwd").SetBasicAuth("user", "passwd").String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, "authenticated")
if n == -1 {
t.Fatal("authenticated not found in response")
}
}
func TestWithUserAgent(t *testing.T) {
v := "beego"
str, err := Get("http://httpbin.org/headers").SetUserAgent(v).String()
@ -83,6 +140,7 @@ func TestWithUserAgent(t *testing.T) {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, v)
if n == -1 {
t.Fatal(v + " not found in user-agent")
@ -102,8 +160,57 @@ func TestWithSetting(t *testing.T) {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, v)
if n == -1 {
t.Fatal(v + " not found in user-agent")
}
}
func TestToJson(t *testing.T) {
req := Get("http://httpbin.org/ip")
resp, err := req.Response()
if err != nil {
t.Fatal(err)
}
t.Log(resp)
// httpbin will return http remote addr
type Ip struct {
Origin string `json:"origin"`
}
var ip Ip
err = req.ToJson(&ip)
if err != nil {
t.Fatal(err)
}
t.Log(ip.Origin)
if n := strings.Count(ip.Origin, "."); n != 3 {
t.Fatal("response is not valid ip")
}
}
func TestToFile(t *testing.T) {
f := "beego_testfile"
req := Get("http://httpbin.org/ip")
err := req.ToFile(f)
if err != nil {
t.Fatal(err)
}
defer os.Remove(f)
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")
str, err := req.String()
if err != nil {
t.Fatal(err)
}
t.Log(str)
}

View File

@ -43,11 +43,7 @@ func NewConn() LoggerInterface {
// init connection writer with json config.
// json config only need key "level".
func (c *ConnWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), c)
if err != nil {
return err
}
return nil
return json.Unmarshal([]byte(jsonconfig), c)
}
// write message in connection.
@ -77,10 +73,9 @@ func (c *ConnWriter) Flush() {
// destroy connection writer and close tcp listener.
func (c *ConnWriter) Destroy() {
if c.innerWriter == nil {
return
if c.innerWriter != nil {
c.innerWriter.Close()
}
c.innerWriter.Close()
}
func (c *ConnWriter) connect() error {

View File

@ -38,8 +38,8 @@ var colors = []Brush{
NewBrush("1;31"), // Error red
NewBrush("1;33"), // Warning yellow
NewBrush("1;32"), // Notice green
NewBrush("1;34"), // Informational green
NewBrush("1;30"), // Debug black
NewBrush("1;34"), // Informational blue
NewBrush("1;34"), // Debug blue
}
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
@ -50,9 +50,10 @@ type ConsoleWriter struct {
// create ConsoleWriter returning as LoggerInterface.
func NewConsole() LoggerInterface {
cw := new(ConsoleWriter)
cw.lg = log.New(os.Stdout, "", log.Ldate|log.Ltime)
cw.Level = LevelDebug
cw := &ConsoleWriter{
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
Level: LevelDebug,
}
return cw
}
@ -62,11 +63,7 @@ func (c *ConsoleWriter) Init(jsonconfig string) error {
if len(jsonconfig) == 0 {
return nil
}
err := json.Unmarshal([]byte(jsonconfig), c)
if err != nil {
return err
}
return nil
return json.Unmarshal([]byte(jsonconfig), c)
}
// write message in console.
@ -76,9 +73,10 @@ func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
}
if goos := runtime.GOOS; goos == "windows" {
c.lg.Println(msg)
} else {
c.lg.Println(colors[level](msg))
return nil
}
c.lg.Println(colors[level](msg))
return nil
}

View File

@ -15,10 +15,11 @@
package logs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"log"
"os"
"path/filepath"
@ -122,11 +123,7 @@ func (w *FileLogWriter) startLogger() error {
return err
}
w.mw.SetFd(fd)
err = w.initFd()
if err != nil {
return err
}
return nil
return w.initFd()
}
func (w *FileLogWriter) docheck(size int) {
@ -169,18 +166,44 @@ func (w *FileLogWriter) initFd() error {
}
w.maxsize_cursize = int(finfo.Size())
w.daily_opendate = time.Now().Day()
w.maxlines_curlines = 0
if finfo.Size() > 0 {
content, err := ioutil.ReadFile(w.Filename)
count, err := w.lines()
if err != nil {
return err
}
w.maxlines_curlines = len(strings.Split(string(content), "\n"))
} else {
w.maxlines_curlines = 0
w.maxlines_curlines = count
}
return nil
}
func (w *FileLogWriter) lines() (int, error) {
fd, err := os.Open(w.Filename)
if err != nil {
return 0, err
}
defer fd.Close()
buf := make([]byte, 32768) // 32k
count := 0
lineSep := []byte{'\n'}
for {
c, err := fd.Read(buf)
if err != nil && err != io.EOF {
return count, err
}
count += bytes.Count(buf[:c], lineSep)
if err == io.EOF {
break
}
}
return count, nil
}
// DoRotate means it need to write file in new file.
// new file name like xx.log.2013-01-01.2
func (w *FileLogWriter) DoRotate() error {
@ -226,13 +249,20 @@ func (w *FileLogWriter) DoRotate() error {
func (w *FileLogWriter) deleteOldLog() {
dir := filepath.Dir(w.Filename)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) {
defer func() {
if r := recover(); r != nil {
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r)
fmt.Println(returnErr)
}
}()
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) {
os.Remove(path)
}
}
return nil
return
})
}

View File

@ -155,6 +155,9 @@ func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
lm.level = loglevel
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if _, filename := path.Split(file); filename == "log.go" && (line == 97 || line == 83) {
_, file, line, ok = runtime.Caller(bl.loggerFuncCallDepth + 1)
}
if ok {
_, filename := path.Split(file)
lm.msg = fmt.Sprintf("[%s:%d] %s", filename, line, msg)
@ -193,7 +196,10 @@ func (bl *BeeLogger) startLogger() {
select {
case bm := <-bl.msg:
for _, l := range bl.outputs {
l.WriteMsg(bm.msg, bm.level)
err := l.WriteMsg(bm.msg, bm.level)
if err != nil {
fmt.Println("ERROR, unable to WriteMsg:", err)
}
}
}
}
@ -201,13 +207,13 @@ func (bl *BeeLogger) startLogger() {
// Log EMERGENCY level message.
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
msg := fmt.Sprintf("[D] "+format, v...)
msg := fmt.Sprintf("[M] "+format, v...)
bl.writerMsg(LevelEmergency, msg)
}
// Log ALERT level message.
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
msg := fmt.Sprintf("[D] "+format, v...)
msg := fmt.Sprintf("[A] "+format, v...)
bl.writerMsg(LevelAlert, msg)
}
@ -231,7 +237,7 @@ func (bl *BeeLogger) Warning(format string, v ...interface{}) {
// Log NOTICE level message.
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
msg := fmt.Sprintf("[W] "+format, v...)
msg := fmt.Sprintf("[N] "+format, v...)
bl.writerMsg(LevelNotice, msg)
}
@ -281,11 +287,14 @@ func (bl *BeeLogger) Close() {
if len(bl.msg) > 0 {
bm := <-bl.msg
for _, l := range bl.outputs {
l.WriteMsg(bm.msg, bm.level)
err := l.WriteMsg(bm.msg, bm.level)
if err != nil {
fmt.Println("ERROR, unable to WriteMsg (while closing logger):", err)
}
}
} else {
break
continue
}
break
}
for _, l := range bl.outputs {
l.Flush()

View File

@ -15,15 +15,18 @@
package logs
import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/smtp"
"strings"
"time"
)
const (
subjectPhrase = "Diagnostic message from server"
// no usage
// subjectPhrase = "Diagnostic message from server"
)
// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server.
@ -32,6 +35,7 @@ type SmtpWriter struct {
Password string `json:"password"`
Host string `json:"Host"`
Subject string `json:"subject"`
FromAddress string `json:"fromAddress"`
RecipientAddresses []string `json:"sendTos"`
Level int `json:"level"`
}
@ -48,6 +52,7 @@ func NewSmtpWriter() LoggerInterface {
// "password:"password",
// "host":"smtp.gmail.com:465",
// "subject":"email title",
// "fromAddress":"from@example.com",
// "sendTos":["email1","email2"],
// "level":LevelError
// }
@ -71,6 +76,59 @@ func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth {
)
}
func (s *SmtpWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAddress string, recipients []string, msgContent []byte) error {
client, err := smtp.Dial(hostAddressWithPort)
if err != nil {
return err
}
host, _, _ := net.SplitHostPort(hostAddressWithPort)
tlsConn := &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}
if err = client.StartTLS(tlsConn); err != nil {
return err
}
if auth != nil {
if err = client.Auth(auth); err != nil {
return err
}
}
if err = client.Mail(fromAddress); err != nil {
return err
}
for _, rec := range recipients {
if err = client.Rcpt(rec); err != nil {
return err
}
}
w, err := client.Data()
if err != nil {
return err
}
_, err = w.Write([]byte(msgContent))
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
err = client.Quit()
if err != nil {
return err
}
return nil
}
// write message in smtp writer.
// it will send an email with subject and only this message.
func (s *SmtpWriter) WriteMsg(msg string, level int) error {
@ -86,18 +144,10 @@ func (s *SmtpWriter) WriteMsg(msg string, level int) error {
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
content_type := "Content-Type: text/plain" + "; charset=UTF-8"
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.Username + "<" + s.Username +
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
">\r\nSubject: " + s.Subject + "\r\n" + content_type + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
err := smtp.SendMail(
s.Host,
auth,
s.Username,
s.RecipientAddresses,
mailmsg,
)
return err
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
}
// implementing method. empty.

View File

@ -1,49 +0,0 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package middleware
import "fmt"
// http exceptions
type HTTPException struct {
StatusCode int // http status code 4xx, 5xx
Description string
}
// return http exception error string, e.g. "400 Bad Request".
func (e *HTTPException) Error() string {
return fmt.Sprintf("%d %s", e.StatusCode, e.Description)
}
// map of http exceptions for each http status code int.
// defined 400,401,403,404,405,500,502,503 and 504 default.
var HTTPExceptionMaps map[int]HTTPException
func init() {
HTTPExceptionMaps = make(map[int]HTTPException)
// Normal 4XX HTTP Status
HTTPExceptionMaps[400] = HTTPException{400, "Bad Request"}
HTTPExceptionMaps[401] = HTTPException{401, "Unauthorized"}
HTTPExceptionMaps[403] = HTTPException{403, "Forbidden"}
HTTPExceptionMaps[404] = HTTPException{404, "Not Found"}
HTTPExceptionMaps[405] = HTTPException{405, "Method Not Allowed"}
// Normal 5XX HTTP Status
HTTPExceptionMaps[500] = HTTPException{500, "Internal Server Error"}
HTTPExceptionMaps[502] = HTTPException{502, "Bad Gateway"}
HTTPExceptionMaps[503] = HTTPException{503, "Service Unavailable"}
HTTPExceptionMaps[504] = HTTPException{504, "Gateway Timeout"}
}

View File

@ -34,7 +34,6 @@ type Translation struct {
}
func NewLocale(filepath string, defaultlocal string) *Translation {
i18n := make(map[string]map[string]string)
file, err := os.Open(filepath)
if err != nil {
panic("open " + filepath + " err :" + err.Error())
@ -43,8 +42,9 @@ func NewLocale(filepath string, defaultlocal string) *Translation {
if err != nil {
panic("read " + filepath + " err :" + err.Error())
}
err = json.Unmarshal(data, &i18n)
if err != nil {
i18n := make(map[string]map[string]string)
if err = json.Unmarshal(data, &i18n); err != nil {
panic("json.Unmarshal " + filepath + " err :" + err.Error())
}
return &Translation{

46
migration/ddl.go Normal file
View File

@ -0,0 +1,46 @@
// 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 migration
type Table struct {
TableName string
Columns []*Column
}
func (t *Table) Create() string {
return ""
}
func (t *Table) Drop() string {
return ""
}
type Column struct {
Name string
Type string
Default interface{}
}
func Create(tbname string, columns ...Column) string {
return ""
}
func Drop(tbname string, columns ...Column) string {
return ""
}
func TableDDL(tbname string, columns ...Column) string {
return ""
}

View File

@ -19,7 +19,6 @@ import (
"strings"
beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
)
type namespaceCond func(*beecontext.Context) bool
@ -57,7 +56,7 @@ func NewNamespace(prefix string, params ...innnerNamespace) *Namespace {
func (n *Namespace) Cond(cond namespaceCond) *Namespace {
fn := func(ctx *beecontext.Context) {
if !cond(ctx) {
middleware.Exception("405", ctx.ResponseWriter, ctx.Request, "Method not allowed")
exception("405", ctx)
}
}
if v, ok := n.handlers.filters[BeforeRouter]; ok {
@ -217,7 +216,7 @@ func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
n.handlers.routers[k] = t
}
}
if n.handlers.enableFilter {
if ni.handlers.enableFilter {
for pos, filterList := range ni.handlers.filters {
for _, mr := range filterList {
t := NewTree()

View File

@ -6,8 +6,6 @@ A powerful orm framework for go.
It is heavily influenced by Django ORM, SQLAlchemy.
now, beta, unstable, may be changing some api make your app build failed.
**Support Database:**
* MySQL: [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
@ -119,7 +117,7 @@ o.Begin()
...
user := User{Name: "slene"}
id, err := o.Insert(&user)
if err != nil {
if err == nil {
o.Commit()
} else {
o.Rollback()

View File

@ -104,7 +104,11 @@ func getColumnAddQuery(al *alias, fi *fieldInfo) string {
typ += " " + "NOT NULL"
}
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s", Q, fi.mi.table, Q, Q, fi.column, Q, typ)
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s %s",
Q, fi.mi.table, Q,
Q, fi.column, Q,
typ, getColumnDefault(fi),
)
}
// create database creation string.
@ -155,6 +159,9 @@ func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
//if fi.initial.String() != "" {
// column += " DEFAULT " + fi.initial.String()
//}
// Append attribute DEFAULT
column += getColumnDefault(fi)
if fi.unique {
column += " " + "UNIQUE"
@ -239,3 +246,44 @@ func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
return
}
// Get string value for the attribute "DEFAULT" for the CREATE, ALTER commands
func getColumnDefault(fi *fieldInfo) string {
var (
v, t, d string
)
// Skip default attribute if field is in relations
if fi.rel || fi.reverse {
return v
}
t = " DEFAULT '%s' "
// These defaults will be useful if there no config value orm:"default" and NOT NULL is on
switch fi.fieldType {
case TypeDateField, TypeDateTimeField:
return v;
case TypeBooleanField, TypeBitField, TypeSmallIntegerField, TypeIntegerField,
TypeBigIntegerField, TypePositiveBitField, TypePositiveSmallIntegerField,
TypePositiveIntegerField, TypePositiveBigIntegerField, TypeFloatField,
TypeDecimalField:
d = "0"
}
if fi.colDefault {
if !fi.initial.Exist() {
v = fmt.Sprintf(t, "")
} else {
v = fmt.Sprintf(t, fi.initial.String())
}
} else {
if !fi.null {
v = fmt.Sprintf(t, d)
}
}
return v
}

104
orm/db.go
View File

@ -122,6 +122,12 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
if nb.Valid {
value = nb.Bool
}
} else if field.Kind() == reflect.Ptr {
if field.IsNil() {
value = nil
} else {
value = field.Elem().Bool()
}
} else {
value = field.Bool()
}
@ -131,6 +137,12 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
if ns.Valid {
value = ns.String
}
} else if field.Kind() == reflect.Ptr {
if field.IsNil() {
value = nil
} else {
value = field.Elem().String()
}
} else {
value = field.String()
}
@ -140,6 +152,12 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
if nf.Valid {
value = nf.Float64
}
} else if field.Kind() == reflect.Ptr {
if field.IsNil() {
value = nil
} else {
value = field.Elem().Float()
}
} else {
vu := field.Interface()
if _, ok := vu.(float32); ok {
@ -161,13 +179,27 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
default:
switch {
case fi.fieldType&IsPostiveIntegerField > 0:
value = field.Uint()
if field.Kind() == reflect.Ptr {
if field.IsNil() {
value = nil
} else {
value = field.Elem().Uint()
}
} else {
value = field.Uint()
}
case fi.fieldType&IsIntegerField > 0:
if ni, ok := field.Interface().(sql.NullInt64); ok {
value = nil
if ni.Valid {
value = ni.Int64
}
} else if field.Kind() == reflect.Ptr {
if field.IsNil() {
value = nil
} else {
value = field.Elem().Int()
}
} else {
value = field.Int()
}
@ -1177,6 +1209,11 @@ setValue:
nb.Valid = true
}
field.Set(reflect.ValueOf(nb))
} else if field.Kind() == reflect.Ptr {
if value != nil {
v := value.(bool)
field.Set(reflect.ValueOf(&v))
}
} else {
if value == nil {
value = false
@ -1194,6 +1231,11 @@ setValue:
ns.Valid = true
}
field.Set(reflect.ValueOf(ns))
} else if field.Kind() == reflect.Ptr {
if value != nil {
v := value.(string)
field.Set(reflect.ValueOf(&v))
}
} else {
if value == nil {
value = ""
@ -1208,6 +1250,56 @@ setValue:
}
field.Set(reflect.ValueOf(value))
}
case fieldType == TypePositiveBitField && field.Kind() == reflect.Ptr:
if value != nil {
v := uint8(value.(uint64))
field.Set(reflect.ValueOf(&v))
}
case fieldType == TypePositiveSmallIntegerField && field.Kind() == reflect.Ptr:
if value != nil {
v := uint16(value.(uint64))
field.Set(reflect.ValueOf(&v))
}
case fieldType == TypePositiveIntegerField && field.Kind() == reflect.Ptr:
if value != nil {
if field.Type() == reflect.TypeOf(new(uint)) {
v := uint(value.(uint64))
field.Set(reflect.ValueOf(&v))
} else {
v := uint32(value.(uint64))
field.Set(reflect.ValueOf(&v))
}
}
case fieldType == TypePositiveBigIntegerField && field.Kind() == reflect.Ptr:
if value != nil {
v := value.(uint64)
field.Set(reflect.ValueOf(&v))
}
case fieldType == TypeBitField && field.Kind() == reflect.Ptr:
if value != nil {
v := int8(value.(int64))
field.Set(reflect.ValueOf(&v))
}
case fieldType == TypeSmallIntegerField && field.Kind() == reflect.Ptr:
if value != nil {
v := int16(value.(int64))
field.Set(reflect.ValueOf(&v))
}
case fieldType == TypeIntegerField && field.Kind() == reflect.Ptr:
if value != nil {
if field.Type() == reflect.TypeOf(new(int)) {
v := int(value.(int64))
field.Set(reflect.ValueOf(&v))
} else {
v := int32(value.(int64))
field.Set(reflect.ValueOf(&v))
}
}
case fieldType == TypeBigIntegerField && field.Kind() == reflect.Ptr:
if value != nil {
v := value.(int64)
field.Set(reflect.ValueOf(&v))
}
case fieldType&IsIntegerField > 0:
if fieldType&IsPostiveIntegerField > 0 {
if isNative {
@ -1244,6 +1336,16 @@ setValue:
nf.Valid = true
}
field.Set(reflect.ValueOf(nf))
} else if field.Kind() == reflect.Ptr {
if value != nil {
if field.Type() == reflect.TypeOf(new(float32)) {
v := float32(value.(float64))
field.Set(reflect.ValueOf(&v))
} else {
v := value.(float64)
field.Set(reflect.ValueOf(&v))
}
}
} else {
if value == nil {

View File

@ -116,6 +116,7 @@ type fieldInfo struct {
null bool
index bool
unique bool
colDefault bool
initial StrTo
size int
auto_now bool
@ -280,6 +281,11 @@ checkType:
fi.pk = attrs["pk"]
fi.unique = attrs["unique"]
// Mark object property if there is attribute "default" in the orm configuration
if _, ok := tags["default"]; ok {
fi.colDefault = true
}
switch fieldType {
case RelManyToMany, RelReverseMany, RelReverseOne:
fi.null = false

View File

@ -155,6 +155,24 @@ type DataNull struct {
NullBool sql.NullBool `orm:"null"`
NullFloat64 sql.NullFloat64 `orm:"null"`
NullInt64 sql.NullInt64 `orm:"null"`
BooleanPtr *bool `orm:"null"`
CharPtr *string `orm:"null;size(50)"`
TextPtr *string `orm:"null;type(text)"`
BytePtr *byte `orm:"null"`
RunePtr *rune `orm:"null"`
IntPtr *int `orm:"null"`
Int8Ptr *int8 `orm:"null"`
Int16Ptr *int16 `orm:"null"`
Int32Ptr *int32 `orm:"null"`
Int64Ptr *int64 `orm:"null"`
UintPtr *uint `orm:"null"`
Uint8Ptr *uint8 `orm:"null"`
Uint16Ptr *uint16 `orm:"null"`
Uint32Ptr *uint32 `orm:"null"`
Uint64Ptr *uint64 `orm:"null"`
Float32Ptr *float32 `orm:"null"`
Float64Ptr *float64 `orm:"null"`
DecimalPtr *float64 `orm:"digits(8);decimals(4);null"`
}
type String string

View File

@ -111,45 +111,73 @@ func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col
// return field type as type constant from reflect.Value
func getFieldType(val reflect.Value) (ft int, err error) {
elm := reflect.Indirect(val)
switch elm.Kind() {
case reflect.Int8:
switch val.Type() {
case reflect.TypeOf(new(int8)):
ft = TypeBitField
case reflect.Int16:
case reflect.TypeOf(new(int16)):
ft = TypeSmallIntegerField
case reflect.Int32, reflect.Int:
case reflect.TypeOf(new(int32)),
reflect.TypeOf(new(int)):
ft = TypeIntegerField
case reflect.Int64:
case reflect.TypeOf(new(int64)):
ft = TypeBigIntegerField
case reflect.Uint8:
case reflect.TypeOf(new(uint8)):
ft = TypePositiveBitField
case reflect.Uint16:
case reflect.TypeOf(new(uint16)):
ft = TypePositiveSmallIntegerField
case reflect.Uint32, reflect.Uint:
case reflect.TypeOf(new(uint32)),
reflect.TypeOf(new(uint)):
ft = TypePositiveIntegerField
case reflect.Uint64:
case reflect.TypeOf(new(uint64)):
ft = TypePositiveBigIntegerField
case reflect.Float32, reflect.Float64:
case reflect.TypeOf(new(float32)),
reflect.TypeOf(new(float64)):
ft = TypeFloatField
case reflect.Bool:
case reflect.TypeOf(new(bool)):
ft = TypeBooleanField
case reflect.String:
case reflect.TypeOf(new(string)):
ft = TypeCharField
default:
if elm.Interface() == nil {
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
}
switch elm.Interface().(type) {
case sql.NullInt64:
elm := reflect.Indirect(val)
switch elm.Kind() {
case reflect.Int8:
ft = TypeBitField
case reflect.Int16:
ft = TypeSmallIntegerField
case reflect.Int32, reflect.Int:
ft = TypeIntegerField
case reflect.Int64:
ft = TypeBigIntegerField
case sql.NullFloat64:
case reflect.Uint8:
ft = TypePositiveBitField
case reflect.Uint16:
ft = TypePositiveSmallIntegerField
case reflect.Uint32, reflect.Uint:
ft = TypePositiveIntegerField
case reflect.Uint64:
ft = TypePositiveBigIntegerField
case reflect.Float32, reflect.Float64:
ft = TypeFloatField
case sql.NullBool:
case reflect.Bool:
ft = TypeBooleanField
case sql.NullString:
case reflect.String:
ft = TypeCharField
case time.Time:
ft = TypeDateTimeField
default:
if elm.Interface() == nil {
panic(fmt.Errorf("%s is nil pointer, may be miss setting tag", val))
}
switch elm.Interface().(type) {
case sql.NullInt64:
ft = TypeBigIntegerField
case sql.NullFloat64:
ft = TypeFloatField
case sql.NullBool:
ft = TypeBooleanField
case sql.NullString:
ft = TypeCharField
case time.Time:
ft = TypeDateTimeField
}
}
}
if ft&IsFieldType == 0 {

View File

@ -489,10 +489,6 @@ func (o *orm) Driver() Driver {
return driver(o.alias.Name)
}
func (o *orm) GetDB() dbQuerier {
panic(ErrNotImplement)
}
// create new orm
func NewOrm() Ormer {
BootStrap() // execute only once

View File

@ -287,6 +287,25 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, AssertIs(d.NullInt64.Valid, false))
throwFail(t, AssertIs(d.NullFloat64.Valid, false))
throwFail(t, AssertIs(d.BooleanPtr, nil))
throwFail(t, AssertIs(d.CharPtr, nil))
throwFail(t, AssertIs(d.TextPtr, nil))
throwFail(t, AssertIs(d.BytePtr, nil))
throwFail(t, AssertIs(d.RunePtr, nil))
throwFail(t, AssertIs(d.IntPtr, nil))
throwFail(t, AssertIs(d.Int8Ptr, nil))
throwFail(t, AssertIs(d.Int16Ptr, nil))
throwFail(t, AssertIs(d.Int32Ptr, nil))
throwFail(t, AssertIs(d.Int64Ptr, nil))
throwFail(t, AssertIs(d.UintPtr, nil))
throwFail(t, AssertIs(d.Uint8Ptr, nil))
throwFail(t, AssertIs(d.Uint16Ptr, nil))
throwFail(t, AssertIs(d.Uint32Ptr, nil))
throwFail(t, AssertIs(d.Uint64Ptr, nil))
throwFail(t, AssertIs(d.Float32Ptr, nil))
throwFail(t, AssertIs(d.Float64Ptr, nil))
throwFail(t, AssertIs(d.DecimalPtr, nil))
_, err = dORM.Raw(`INSERT INTO data_null (boolean) VALUES (?)`, nil).Exec()
throwFail(t, err)
@ -294,12 +313,49 @@ func TestNullDataTypes(t *testing.T) {
err = dORM.Read(&d)
throwFail(t, err)
booleanPtr := true
charPtr := string("test")
textPtr := string("test")
bytePtr := byte('t')
runePtr := rune('t')
intPtr := int(42)
int8Ptr := int8(42)
int16Ptr := int16(42)
int32Ptr := int32(42)
int64Ptr := int64(42)
uintPtr := uint(42)
uint8Ptr := uint8(42)
uint16Ptr := uint16(42)
uint32Ptr := uint32(42)
uint64Ptr := uint64(42)
float32Ptr := float32(42.0)
float64Ptr := float64(42.0)
decimalPtr := float64(42.0)
d = DataNull{
DateTime: time.Now(),
NullString: sql.NullString{String: "test", Valid: true},
NullBool: sql.NullBool{Bool: true, Valid: true},
NullInt64: sql.NullInt64{Int64: 42, Valid: true},
NullFloat64: sql.NullFloat64{Float64: 42.42, Valid: true},
BooleanPtr: &booleanPtr,
CharPtr: &charPtr,
TextPtr: &textPtr,
BytePtr: &bytePtr,
RunePtr: &runePtr,
IntPtr: &intPtr,
Int8Ptr: &int8Ptr,
Int16Ptr: &int16Ptr,
Int32Ptr: &int32Ptr,
Int64Ptr: &int64Ptr,
UintPtr: &uintPtr,
Uint8Ptr: &uint8Ptr,
Uint16Ptr: &uint16Ptr,
Uint32Ptr: &uint32Ptr,
Uint64Ptr: &uint64Ptr,
Float32Ptr: &float32Ptr,
Float64Ptr: &float64Ptr,
DecimalPtr: &decimalPtr,
}
id, err = dORM.Insert(&d)
@ -321,6 +377,25 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, AssertIs(d.NullFloat64.Valid, true))
throwFail(t, AssertIs(d.NullFloat64.Float64, 42.42))
throwFail(t, AssertIs(*d.BooleanPtr, booleanPtr))
throwFail(t, AssertIs(*d.CharPtr, charPtr))
throwFail(t, AssertIs(*d.TextPtr, textPtr))
throwFail(t, AssertIs(*d.BytePtr, bytePtr))
throwFail(t, AssertIs(*d.RunePtr, runePtr))
throwFail(t, AssertIs(*d.IntPtr, intPtr))
throwFail(t, AssertIs(*d.Int8Ptr, int8Ptr))
throwFail(t, AssertIs(*d.Int16Ptr, int16Ptr))
throwFail(t, AssertIs(*d.Int32Ptr, int32Ptr))
throwFail(t, AssertIs(*d.Int64Ptr, int64Ptr))
throwFail(t, AssertIs(*d.UintPtr, uintPtr))
throwFail(t, AssertIs(*d.Uint8Ptr, uint8Ptr))
throwFail(t, AssertIs(*d.Uint16Ptr, uint16Ptr))
throwFail(t, AssertIs(*d.Uint32Ptr, uint32Ptr))
throwFail(t, AssertIs(*d.Uint64Ptr, uint64Ptr))
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
}
func TestDataCustomTypes(t *testing.T) {

57
orm/qb.go Normal file
View File

@ -0,0 +1,57 @@
// 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 orm
import "errors"
type QueryBuilder interface {
Select(fields ...string) QueryBuilder
From(tables ...string) QueryBuilder
InnerJoin(table string) QueryBuilder
LeftJoin(table string) QueryBuilder
RightJoin(table string) QueryBuilder
On(cond string) QueryBuilder
Where(cond string) QueryBuilder
And(cond string) QueryBuilder
Or(cond string) QueryBuilder
In(vals ...string) QueryBuilder
OrderBy(fields ...string) QueryBuilder
Asc() QueryBuilder
Desc() QueryBuilder
Limit(limit int) QueryBuilder
Offset(offset int) QueryBuilder
GroupBy(fields ...string) QueryBuilder
Having(cond string) QueryBuilder
Update(tables ...string) QueryBuilder
Set(kv ...string) QueryBuilder
Delete(tables ...string) QueryBuilder
InsertInto(table string, fields ...string) QueryBuilder
Values(vals ...string) QueryBuilder
Subquery(sub string, alias string) string
String() string
}
func NewQueryBuilder(driver string) (qb QueryBuilder, err error) {
if driver == "mysql" {
qb = new(MySQLQueryBuilder)
} else if driver == "postgres" {
err = errors.New("postgres query builder is not supported yet!")
} else if driver == "sqlite" {
err = errors.New("sqlite query builder is not supported yet!")
} else {
err = errors.New("unknown driver for query builder!")
}
return
}

153
orm/qb_mysql.go Normal file
View File

@ -0,0 +1,153 @@
// 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 orm
import (
"fmt"
"strconv"
"strings"
)
const COMMA_SPACE = ", "
type MySQLQueryBuilder struct {
Tokens []string
}
func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "SELECT", strings.Join(fields, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) InnerJoin(table string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "INNER JOIN", table)
return qb
}
func (qb *MySQLQueryBuilder) LeftJoin(table string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "LEFT JOIN", table)
return qb
}
func (qb *MySQLQueryBuilder) RightJoin(table string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "RIGHT JOIN", table)
return qb
}
func (qb *MySQLQueryBuilder) On(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "ON", cond)
return qb
}
func (qb *MySQLQueryBuilder) Where(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "WHERE", cond)
return qb
}
func (qb *MySQLQueryBuilder) And(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "AND", cond)
return qb
}
func (qb *MySQLQueryBuilder) Or(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "OR", cond)
return qb
}
func (qb *MySQLQueryBuilder) In(vals ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "IN", "(", strings.Join(vals, COMMA_SPACE), ")")
return qb
}
func (qb *MySQLQueryBuilder) OrderBy(fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "ORDER BY", strings.Join(fields, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Asc() QueryBuilder {
qb.Tokens = append(qb.Tokens, "ASC")
return qb
}
func (qb *MySQLQueryBuilder) Desc() QueryBuilder {
qb.Tokens = append(qb.Tokens, "DESC")
return qb
}
func (qb *MySQLQueryBuilder) Limit(limit int) QueryBuilder {
qb.Tokens = append(qb.Tokens, "LIMIT", strconv.Itoa(limit))
return qb
}
func (qb *MySQLQueryBuilder) Offset(offset int) QueryBuilder {
qb.Tokens = append(qb.Tokens, "OFFSET", strconv.Itoa(offset))
return qb
}
func (qb *MySQLQueryBuilder) GroupBy(fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "GROUP BY", strings.Join(fields, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Having(cond string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "HAVING", cond)
return qb
}
func (qb *MySQLQueryBuilder) Update(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "UPDATE", strings.Join(tables, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Set(kv ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "SET", strings.Join(kv, COMMA_SPACE))
return qb
}
func (qb *MySQLQueryBuilder) Delete(tables ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "DELETE")
if len(tables) != 0 {
qb.Tokens = append(qb.Tokens, strings.Join(tables, COMMA_SPACE))
}
return qb
}
func (qb *MySQLQueryBuilder) InsertInto(table string, fields ...string) QueryBuilder {
qb.Tokens = append(qb.Tokens, "INSERT INTO", table)
if len(fields) != 0 {
fieldsStr := strings.Join(fields, COMMA_SPACE)
qb.Tokens = append(qb.Tokens, "(", fieldsStr, ")")
}
return qb
}
func (qb *MySQLQueryBuilder) Values(vals ...string) QueryBuilder {
valsStr := strings.Join(vals, COMMA_SPACE)
qb.Tokens = append(qb.Tokens, "VALUES", "(", valsStr, ")")
return qb
}
func (qb *MySQLQueryBuilder) Subquery(sub string, alias string) string {
return fmt.Sprintf("(%s) AS %s", sub, alias)
}
func (qb *MySQLQueryBuilder) String() string {
return strings.Join(qb.Tokens, " ")
}

View File

@ -51,7 +51,6 @@ type Ormer interface {
Rollback() error
Raw(string, ...interface{}) RawSeter
Driver() Driver
GetDB() dbQuerier
}
// insert prepared statement

View File

@ -42,20 +42,25 @@ func init() {
var (
lastupdateFilename string = "lastupdate.tmp"
commentFilename string
pkgLastupdate map[string]int64
genInfoList map[string][]ControllerComments
)
const COMMENTFL = "commentsRouter_"
func init() {
pkgLastupdate = make(map[string]int64)
genInfoList = make(map[string][]ControllerComments)
}
func parserPkg(pkgRealpath, pkgpath string) error {
rep := strings.NewReplacer("/", "_", ".", "_")
commentFilename = COMMENTFL + rep.Replace(pkgpath) + ".go"
if !compareFile(pkgRealpath) {
Info(pkgRealpath + " don't has updated")
Info(pkgRealpath + " has not changed, not reloading")
return nil
}
genInfoList = make(map[string][]ControllerComments)
fileSet := token.NewFileSet()
astPkgs, err := parser.ParseDir(fileSet, pkgRealpath, func(info os.FileInfo) bool {
name := info.Name()
@ -148,14 +153,14 @@ func genRouterCode() {
beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"],
beego.ControllerComments{
"` + strings.TrimSpace(c.Method) + `",
"` + c.Router + `",
` + "`" + c.Router + "`" + `,
` + allmethod + `,
` + params + `})
`
}
}
if globalinfo != "" {
f, err := os.Create(path.Join(workPath, "routers", "commentsRouter.go"))
f, err := os.Create(path.Join(workPath, "routers", commentFilename))
if err != nil {
panic(err)
}
@ -165,7 +170,7 @@ func genRouterCode() {
}
func compareFile(pkgRealpath string) bool {
if !utils.FileExists(path.Join(workPath, "routers", "commentsRouter.go")) {
if !utils.FileExists(path.Join(workPath, "routers", commentFilename)) {
return true
}
if utils.FileExists(path.Join(workPath, lastupdateFilename)) {

176
plugins/apiauth/apiauth.go Normal file
View File

@ -0,0 +1,176 @@
// 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 apiauth provides handlers to enable apiauth support.
//
// Simple Usage:
// import(
// "github.com/astaxie/beego"
// "github.com/astaxie/beego/plugins/apiauth"
// )
//
// func main(){
// // apiauth every request
// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APIBaiscAuth("appid","appkey"))
// beego.Run()
// }
//
// Advanced Usage:
//
// func getAppSecret(appid string) string {
// // get appsecret by appid
// // maybe store in configure, maybe in database
// }
//
// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APIAuthWithFunc(getAppSecret, 360))
//
// Infomation:
//
// In the request user should include these params in the query
//
// 1. appid
//
// appid is asigned to the application
//
// 2. signature
//
// get the signature use apiauth.Signature()
//
// when you send to server remember use url.QueryEscape()
//
// 3. timestamp:
//
// send the request time, the format is yyyy-mm-dd HH:ii:ss
//
package apiauth
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/url"
"sort"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
)
type AppIdToAppSecret func(string) string
func APIBaiscAuth(appid, appkey string) beego.FilterFunc {
ft := func(aid string) string {
if aid == appid {
return appkey
}
return ""
}
return APIAuthWithFunc(ft, 300)
}
func APIAuthWithFunc(f AppIdToAppSecret, timeout int) beego.FilterFunc {
return func(ctx *context.Context) {
if ctx.Input.Query("appid") == "" {
ctx.Output.SetStatus(403)
ctx.WriteString("miss query param: appid")
return
}
appsecret := f(ctx.Input.Query("appid"))
if appsecret == "" {
ctx.Output.SetStatus(403)
ctx.WriteString("not exist this appid")
return
}
if ctx.Input.Query("signature") == "" {
ctx.Output.SetStatus(403)
ctx.WriteString("miss query param: signature")
return
}
if ctx.Input.Query("timestamp") == "" {
ctx.Output.SetStatus(403)
ctx.WriteString("miss query param: timestamp")
return
}
u, err := time.Parse("2006-01-02 15:04:05", ctx.Input.Query("timestamp"))
if err != nil {
ctx.Output.SetStatus(403)
ctx.WriteString("timestamp format is error, should 2006-01-02 15:04:05")
return
}
t := time.Now()
if t.Sub(u).Seconds() > float64(timeout) {
ctx.Output.SetStatus(403)
ctx.WriteString("timeout! the request time is long ago, please try again")
return
}
if ctx.Input.Query("signature") !=
Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.Uri()) {
ctx.Output.SetStatus(403)
ctx.WriteString("auth failed")
}
}
}
func Signature(appsecret, method string, params url.Values, RequestURI string) (result string) {
var query string
pa := make(map[string]string)
for k, v := range params {
pa[k] = v[0]
}
vs := mapSorter(pa)
vs.Sort()
for i := 0; i < vs.Len(); i++ {
if vs.Keys[i] == "signature" {
continue
}
if vs.Keys[i] != "" && vs.Vals[i] != "" {
query = fmt.Sprintf("%v%v%v", query, vs.Keys[i], vs.Vals[i])
}
}
string_to_sign := fmt.Sprintf("%v\n%v\n%v\n", method, query, RequestURI)
sha256 := sha256.New
hash := hmac.New(sha256, []byte(appsecret))
hash.Write([]byte(string_to_sign))
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
}
type valSorter struct {
Keys []string
Vals []string
}
func mapSorter(m map[string]string) *valSorter {
vs := &valSorter{
Keys: make([]string, 0, len(m)),
Vals: make([]string, 0, len(m)),
}
for k, v := range m {
vs.Keys = append(vs.Keys, k)
vs.Vals = append(vs.Vals, v)
}
return vs
}
func (vs *valSorter) Sort() {
sort.Sort(vs)
}
func (vs *valSorter) Len() int { return len(vs.Keys) }
func (vs *valSorter) Less(i, j int) bool { return vs.Keys[i] < vs.Keys[j] }
func (vs *valSorter) Swap(i, j int) {
vs.Vals[i], vs.Vals[j] = vs.Vals[j], vs.Vals[i]
vs.Keys[i], vs.Keys[j] = vs.Keys[j], vs.Keys[i]
}

View File

@ -27,6 +27,7 @@
//
//
// Advanced Usage:
//
// func SecretAuth(username, password string) bool {
// return username == "astaxie" && password == "helloBeego"
// }

View File

@ -217,6 +217,7 @@ func Allow(opts *Options) beego.FilterFunc {
ctx.Output.Header(key, value)
}
ctx.Output.SetStatus(http.StatusOK)
ctx.WriteString("")
return
}
headers = opts.Header(origin)

215
router.go
View File

@ -30,7 +30,6 @@ import (
"time"
beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/toolbox"
"github.com/astaxie/beego/utils"
)
@ -72,9 +71,31 @@ var (
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
"GetControllerAndAction"}
url_placeholder = "{{placeholder}}"
url_placeholder = "{{placeholder}}"
DefaultLogFilter FilterHandler = &logFilter{}
)
type FilterHandler interface {
Filter(*beecontext.Context) bool
}
// default log filter static file will not show
type logFilter struct {
}
func (l *logFilter) Filter(ctx *beecontext.Context) bool {
requestPath := path.Clean(ctx.Input.Request.URL.Path)
if requestPath == "/favicon.ico" || requestPath == "/robots.txt" {
return true
}
for prefix, _ := range StaticDir {
if strings.HasPrefix(requestPath, prefix) {
return true
}
}
return false
}
// To append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter
func ExceptMethodAppend(action string) {
exceptMethod = append(exceptMethod, action)
@ -131,7 +152,7 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM
if val := reflectVal.MethodByName(colon[1]); val.IsValid() {
methods[strings.ToUpper(m)] = colon[1]
} else {
panic(colon[1] + " method doesn't exist in the controller " + t.Name())
panic("'" + colon[1] + "' method doesn't exist in the controller " + t.Name())
}
} else {
panic(v + " is an invalid method mapping. Method doesn't exist " + m)
@ -163,6 +184,9 @@ func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingM
}
func (p *ControllerRegistor) addToRouter(method, pattern string, r *controllerInfo) {
if !RouterCaseSensitive {
pattern = strings.ToLower(pattern)
}
if t, ok := p.routers[method]; ok {
t.AddRouter(pattern, r)
} else {
@ -376,11 +400,21 @@ func (p *ControllerRegistor) AddAutoPrefix(prefix string, c ControllerInterface)
}
// Add a FilterFunc with pattern rule and action constant.
func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc) error {
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
func (p *ControllerRegistor) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error {
mr := new(FilterRouter)
mr.tree = NewTree()
mr.pattern = pattern
mr.filterFunc = filter
if !RouterCaseSensitive {
pattern = strings.ToLower(pattern)
}
if len(params) == 0 {
mr.returnOnOutput = true
} else {
mr.returnOnOutput = params[0]
}
mr.tree.AddRouter(pattern, true)
return p.insertFilterRouter(pos, mr)
}
@ -394,7 +428,7 @@ func (p *ControllerRegistor) insertFilterRouter(pos int, mr *FilterRouter) error
// UrlFor does another controller handler in this request function.
// it can access any controller method.
func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
func (p *ControllerRegistor) UrlFor(endpoint string, values ...interface{}) string {
paths := strings.Split(endpoint, ".")
if len(paths) <= 1 {
Warn("urlfor endpoint must like path.controller.method")
@ -409,16 +443,16 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
key := ""
for k, v := range values {
if k%2 == 0 {
key = v
key = fmt.Sprint(v)
} else {
params[key] = v
params[key] = fmt.Sprint(v)
}
}
}
controllName := strings.Join(paths[:len(paths)-1], ".")
controllName := strings.Join(paths[:len(paths)-1], "/")
methodName := paths[len(paths)-1]
for _, t := range p.routers {
ok, url := p.geturl(t, "/", controllName, methodName, params)
for m, t := range p.routers {
ok, url := p.geturl(t, "/", controllName, methodName, params, m)
if ok {
return url
}
@ -426,24 +460,25 @@ func (p *ControllerRegistor) UrlFor(endpoint string, values ...string) string {
return ""
}
func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName string, params map[string]string) (bool, string) {
func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName string, params map[string]string, httpMethod string) (bool, string) {
for k, subtree := range t.fixrouters {
u := path.Join(url, k)
ok, u := p.geturl(subtree, u, controllName, methodName, params)
ok, u := p.geturl(subtree, u, controllName, methodName, params, httpMethod)
if ok {
return ok, u
}
}
if t.wildcard != nil {
url = path.Join(url, url_placeholder)
ok, u := p.geturl(t.wildcard, url, controllName, methodName, params)
u := path.Join(url, url_placeholder)
ok, u := p.geturl(t.wildcard, u, controllName, methodName, params, httpMethod)
if ok {
return ok, u
}
}
for _, l := range t.leaves {
if c, ok := l.runObject.(*controllerInfo); ok {
if c.routerType == routerTypeBeego && c.controllerType.Name() == controllName {
if c.routerType == routerTypeBeego &&
strings.HasSuffix(path.Join(c.controllerType.PkgPath(), c.controllerType.Name()), controllName) {
find := false
if _, ok := HTTPMETHOD[strings.ToUpper(methodName)]; ok {
if len(c.methods) == 0 {
@ -455,8 +490,8 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin
}
}
if !find {
for _, md := range c.methods {
if md == methodName {
for m, md := range c.methods {
if (m == "*" || m == httpMethod) && md == methodName {
find = true
}
}
@ -541,7 +576,6 @@ func (p *ControllerRegistor) geturl(t *Tree, url, controllName, methodName strin
// Implement http.Handler interface.
func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
defer p.recoverPanic(rw, r)
starttime := time.Now()
var runrouter reflect.Type
var findrouter bool
@ -564,15 +598,23 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
context.Output.Context = context
context.Output.EnableGzip = EnableGzip
defer p.recoverPanic(context)
var urlPath string
if !RouterCaseSensitive {
urlPath = strings.ToLower(r.URL.Path)
} else {
urlPath = r.URL.Path
}
// defined filter function
do_filter := func(pos int) (started bool) {
if p.enableFilter {
if l, ok := p.filters[pos]; ok {
for _, filterR := range l {
if ok, p := filterR.ValidRouter(r.URL.Path); ok {
if ok, p := filterR.ValidRouter(urlPath); ok {
context.Input.Params = p
filterR.filterFunc(context)
if w.started {
if filterR.returnOnOutput && w.started {
return true
}
}
@ -602,7 +644,13 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
// session init
if SessionOn {
context.Input.CruSession = GlobalSessions.SessionStart(w, r)
var err error
context.Input.CruSession, err = GlobalSessions.SessionStart(w, r)
if err != nil {
Error(err)
exception("503", context)
return
}
defer func() {
context.Input.CruSession.SessionRelease(w)
}()
@ -626,8 +674,18 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
}
if !findrouter {
if t, ok := p.routers[r.Method]; ok {
runObject, p := t.Match(r.URL.Path)
http_method := r.Method
if http_method == "POST" && context.Input.Query("_method") == "PUT" {
http_method = "PUT"
}
if http_method == "POST" && context.Input.Query("_method") == "DELETE" {
http_method = "DELETE"
}
if t, ok := p.routers[http_method]; ok {
runObject, p := t.Match(urlPath)
if r, ok := runObject.(*controllerInfo); ok {
routerInfo = r
findrouter = true
@ -645,7 +703,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
//if no matches to url, throw a not found exception
if !findrouter {
middleware.Exception("404", rw, r, "")
exception("404", context)
goto Admin
}
@ -661,7 +719,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
isRunable = true
routerInfo.runfunction(context)
} else {
middleware.Exception("405", rw, r, "Method Not Allowed")
exception("405", context)
goto Admin
}
} else if routerInfo.routerType == routerTypeHandler {
@ -705,7 +763,7 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request)
if EnableXSRF {
execController.XsrfToken()
if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
(r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) {
(r.Method == "POST" && (context.Input.Query("_method") == "DELETE" || context.Input.Query("_method") == "PUT")) {
execController.CheckXsrfCookie()
}
}
@ -772,7 +830,7 @@ Admin:
}
}
if RunMode == "dev" {
if RunMode == "dev" || AccessLogs {
var devinfo string
if findrouter {
if routerInfo != nil {
@ -783,7 +841,9 @@ Admin:
} else {
devinfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeend.String(), "notmatch")
}
Debug(devinfo)
if DefaultLogFilter == nil || !DefaultLogFilter.Filter(context) {
Debug(devinfo)
}
}
// Call WriteHeader if status code has been set changed
@ -792,82 +852,65 @@ Admin:
}
}
func (p *ControllerRegistor) recoverPanic(rw http.ResponseWriter, r *http.Request) {
func (p *ControllerRegistor) recoverPanic(context *beecontext.Context) {
if err := recover(); err != nil {
if err == USERSTOPRUN {
return
}
if _, ok := err.(middleware.HTTPException); ok {
// catch intented errors, only for HTTP 4XX and 5XX
} else {
if RunMode == "dev" {
if !RecoverPanic {
panic(err)
} else {
if ErrorsShow {
if handler, ok := middleware.ErrorMaps[fmt.Sprint(err)]; ok {
handler(rw, r)
return
}
if RunMode == "dev" {
if !RecoverPanic {
panic(err)
} else {
if ErrorsShow {
if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok {
executeError(handler, context)
return
}
var stack string
Critical("the request url is ", r.URL.Path)
}
var stack string
Critical("the request url is ", context.Input.Url())
Critical("Handler crashed with error", err)
for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
Critical(fmt.Sprintf("%s:%d", file, line))
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
}
showErr(err, context, stack)
}
} else {
if !RecoverPanic {
panic(err)
} else {
// in production model show all infomation
if ErrorsShow {
if handler, ok := ErrorMaps[fmt.Sprint(err)]; ok {
executeError(handler, context)
return
} else if handler, ok := ErrorMaps["503"]; ok {
executeError(handler, context)
return
} else {
context.WriteString(fmt.Sprint(err))
}
} else {
Critical("the request url is ", context.Input.Url())
Critical("Handler crashed with error", err)
for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
Critical(file, line)
stack = stack + fmt.Sprintln(file, line)
}
middleware.ShowErr(err, rw, r, stack)
}
} else {
if !RecoverPanic {
panic(err)
} else {
// in production model show all infomation
if ErrorsShow {
handler := p.getErrorHandler(fmt.Sprint(err))
handler(rw, r)
return
} else {
Critical("the request url is ", r.URL.Path)
Critical("Handler crashed with error", err)
for i := 1; ; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
Critical(file, line)
}
Critical(fmt.Sprintf("%s:%d", file, line))
}
}
}
}
}
}
// there always should be error handler that sets error code accordingly for all unhandled errors.
// in order to have custom UI for error page it's necessary to override "500" error.
func (p *ControllerRegistor) getErrorHandler(errorCode string) func(rw http.ResponseWriter, r *http.Request) {
handler := middleware.SimpleServerError
ok := true
if errorCode != "" {
handler, ok = middleware.ErrorMaps[errorCode]
if !ok {
handler, ok = middleware.ErrorMaps["500"]
}
if !ok || handler == nil {
handler = middleware.SimpleServerError
}
}
return handler
}
//responseWriter is a wrapper for the http.ResponseWriter
//started set to true if response was written to then don't execute other handler
type responseWriter struct {

View File

@ -17,6 +17,7 @@ package beego
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/astaxie/beego/context"
@ -26,33 +27,33 @@ type TestController struct {
Controller
}
func (this *TestController) Get() {
this.Data["Username"] = "astaxie"
this.Ctx.Output.Body([]byte("ok"))
func (tc *TestController) Get() {
tc.Data["Username"] = "astaxie"
tc.Ctx.Output.Body([]byte("ok"))
}
func (this *TestController) Post() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Query(":name")))
func (tc *TestController) Post() {
tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Query(":name")))
}
func (this *TestController) Param() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Query(":name")))
func (tc *TestController) Param() {
tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Query(":name")))
}
func (this *TestController) List() {
this.Ctx.Output.Body([]byte("i am list"))
func (tc *TestController) List() {
tc.Ctx.Output.Body([]byte("i am list"))
}
func (this *TestController) Params() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Params["0"] + this.Ctx.Input.Params["1"] + this.Ctx.Input.Params["2"]))
func (tc *TestController) Params() {
tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Params["0"] + tc.Ctx.Input.Params["1"] + tc.Ctx.Input.Params["2"]))
}
func (this *TestController) Myext() {
this.Ctx.Output.Body([]byte(this.Ctx.Input.Param(":ext")))
func (tc *TestController) Myext() {
tc.Ctx.Output.Body([]byte(tc.Ctx.Input.Param(":ext")))
}
func (this *TestController) GetUrl() {
this.Ctx.Output.Body([]byte(this.UrlFor(".Myext")))
func (tc *TestController) GetUrl() {
tc.Ctx.Output.Body([]byte(tc.UrlFor(".Myext")))
}
func (t *TestController) GetParams() {
@ -385,3 +386,196 @@ func testRequest(method, path string) (*httptest.ResponseRecorder, *http.Request
return recorder, request
}
// Execution point: BeforeRouter
// expectation: only BeforeRouter function is executed, notmatch output as router doesn't handle
func TestFilterBeforeRouter(t *testing.T) {
testName := "TestFilterBeforeRouter"
url := "/beforeRouter"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoBeforeRouter1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "BeforeRouter1") == false {
t.Errorf(testName + " BeforeRouter did not run")
}
if strings.Contains(rw.Body.String(), "hello") == true {
t.Errorf(testName + " BeforeRouter did not return properly")
}
}
// Execution point: BeforeExec
// expectation: only BeforeExec function is executed, match as router determines route only
func TestFilterBeforeExec(t *testing.T) {
testName := "TestFilterBeforeExec"
url := "/beforeExec"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
mux.InsertFilter(url, BeforeExec, beegoBeforeExec1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "BeforeExec1") == false {
t.Errorf(testName + " BeforeExec did not run")
}
if strings.Contains(rw.Body.String(), "hello") == true {
t.Errorf(testName + " BeforeExec did not return properly")
}
if strings.Contains(rw.Body.String(), "BeforeRouter") == true {
t.Errorf(testName + " BeforeRouter ran in error")
}
}
// Execution point: AfterExec
// expectation: only AfterExec function is executed, match as router handles
func TestFilterAfterExec(t *testing.T) {
testName := "TestFilterAfterExec"
url := "/afterExec"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput)
mux.InsertFilter(url, AfterExec, beegoAfterExec1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "AfterExec1") == false {
t.Errorf(testName + " AfterExec did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
if strings.Contains(rw.Body.String(), "BeforeRouter") == true {
t.Errorf(testName + " BeforeRouter ran in error")
}
if strings.Contains(rw.Body.String(), "BeforeExec") == true {
t.Errorf(testName + " BeforeExec ran in error")
}
}
// Execution point: FinishRouter
// expectation: only FinishRouter function is executed, match as router handles
func TestFilterFinishRouter(t *testing.T) {
testName := "TestFilterFinishRouter"
url := "/finishRouter"
mux := NewControllerRegister()
mux.InsertFilter(url, BeforeRouter, beegoFilterNoOutput)
mux.InsertFilter(url, BeforeExec, beegoFilterNoOutput)
mux.InsertFilter(url, AfterExec, beegoFilterNoOutput)
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "FinishRouter1") == true {
t.Errorf(testName + " FinishRouter did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
if strings.Contains(rw.Body.String(), "AfterExec1") == true {
t.Errorf(testName + " AfterExec ran in error")
}
if strings.Contains(rw.Body.String(), "BeforeRouter") == true {
t.Errorf(testName + " BeforeRouter ran in error")
}
if strings.Contains(rw.Body.String(), "BeforeExec") == true {
t.Errorf(testName + " BeforeExec ran in error")
}
}
// Execution point: FinishRouter
// expectation: only first FinishRouter function is executed, match as router handles
func TestFilterFinishRouterMultiFirstOnly(t *testing.T) {
testName := "TestFilterFinishRouterMultiFirstOnly"
url := "/finishRouterMultiFirstOnly"
mux := NewControllerRegister()
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1)
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "FinishRouter1") == false {
t.Errorf(testName + " FinishRouter1 did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
// not expected in body
if strings.Contains(rw.Body.String(), "FinishRouter2") == true {
t.Errorf(testName + " FinishRouter2 did run")
}
}
// Execution point: FinishRouter
// expectation: both FinishRouter functions execute, match as router handles
func TestFilterFinishRouterMulti(t *testing.T) {
testName := "TestFilterFinishRouterMulti"
url := "/finishRouterMulti"
mux := NewControllerRegister()
mux.InsertFilter(url, FinishRouter, beegoFinishRouter1, false)
mux.InsertFilter(url, FinishRouter, beegoFinishRouter2)
mux.Get(url, beegoFilterFunc)
rw, r := testRequest("GET", url)
mux.ServeHTTP(rw, r)
if strings.Contains(rw.Body.String(), "FinishRouter1") == false {
t.Errorf(testName + " FinishRouter1 did not run")
}
if strings.Contains(rw.Body.String(), "hello") == false {
t.Errorf(testName + " handler did not run properly")
}
if strings.Contains(rw.Body.String(), "FinishRouter2") == false {
t.Errorf(testName + " FinishRouter2 did not run properly")
}
}
func beegoFilterNoOutput(ctx *context.Context) {
return
}
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")
}
func beegoFinishRouter2(ctx *context.Context) {
ctx.WriteString("|FinishRouter2")
}

View File

@ -0,0 +1,180 @@
package session
import (
"net/http"
"strconv"
"strings"
"sync"
"github.com/astaxie/beego/session"
"github.com/siddontang/ledisdb/config"
"github.com/siddontang/ledisdb/ledis"
)
var ledispder = &LedisProvider{}
var c *ledis.DB
// ledis session store
type LedisSessionStore struct {
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}
// set value in ledis session
func (ls *LedisSessionStore) Set(key, value interface{}) error {
ls.lock.Lock()
defer ls.lock.Unlock()
ls.values[key] = value
return nil
}
// get value in ledis session
func (ls *LedisSessionStore) Get(key interface{}) interface{} {
ls.lock.RLock()
defer ls.lock.RUnlock()
if v, ok := ls.values[key]; ok {
return v
} else {
return nil
}
}
// delete value in ledis session
func (ls *LedisSessionStore) Delete(key interface{}) error {
ls.lock.Lock()
defer ls.lock.Unlock()
delete(ls.values, key)
return nil
}
// clear all values in ledis session
func (ls *LedisSessionStore) Flush() error {
ls.lock.Lock()
defer ls.lock.Unlock()
ls.values = make(map[interface{}]interface{})
return nil
}
// get ledis session id
func (ls *LedisSessionStore) SessionID() string {
return ls.sid
}
// save session values to ledis
func (ls *LedisSessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(ls.values)
if err != nil {
return
}
c.Set([]byte(ls.sid), b)
c.Expire([]byte(ls.sid), ls.maxlifetime)
}
// ledis session provider
type LedisProvider struct {
maxlifetime int64
savePath string
db int
}
// init ledis session
// savepath like ledis server saveDataPath,pool size
// e.g. 127.0.0.1:6379,100,astaxie
func (lp *LedisProvider) SessionInit(maxlifetime int64, savePath string) error {
var err error
lp.maxlifetime = maxlifetime
configs := strings.Split(savepath, ",")
if len(configs) == 1 {
lp.savePath = configs[0]
} else if len(configs) == 2 {
lp.savePath = configs[0]
lp.db, err = strconv.Atoi(configs[1])
if err != nil {
return err
}
}
cfg := new(config.Config)
cfg.DataDir = lp.savePath
nowLedis, err := ledis.Open(cfg)
c, err = nowLedis.Select(lp.db)
if err != nil {
println(err)
return nil
}
return nil
}
// read ledis session by sid
func (lp *LedisProvider) SessionRead(sid string) (session.SessionStore, error) {
kvs, err := c.Get([]byte(sid))
var kv map[interface{}]interface{}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(kvs)
if err != nil {
return nil, err
}
}
ls := &LedisSessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime}
return ls, nil
}
// check ledis session exist by sid
func (lp *LedisProvider) SessionExist(sid string) bool {
count, _ := c.Exists([]byte(sid))
if count == 0 {
return false
} else {
return true
}
}
// generate new sid for ledis session
func (lp *LedisProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) {
count, _ := c.Exists([]byte(sid))
if count == 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([]byte(sid), []byte(""))
c.Expire([]byte(sid), lp.maxlifetime)
} else {
data, _ := c.Get([]byte(oldsid))
c.Set([]byte(sid), data)
c.Expire([]byte(sid), lp.maxlifetime)
}
kvs, err := c.Get([]byte(sid))
var kv map[interface{}]interface{}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob([]byte(kvs))
if err != nil {
return nil, err
}
}
ls := &LedisSessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime}
return ls, nil
}
// delete ledis session by id
func (lp *LedisProvider) SessionDestroy(sid string) error {
c.Del([]byte(sid))
return nil
}
// Impelment method, no used.
func (lp *LedisProvider) SessionGC() {
return
}
// @todo
func (lp *LedisProvider) SessionAll() int {
return 0
}
func init() {
session.Register("ledis", ledispder)
}

View File

@ -109,7 +109,7 @@ func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) {
return
}
c.Do("SET", rs.sid, string(b), "EX", rs.maxlifetime)
c.Do("SETEX", rs.sid, rs.maxlifetime, string(b))
}
// redis session provider
@ -118,12 +118,13 @@ type RedisProvider struct {
savePath string
poolsize int
password string
dbNum int
poollist *redis.Pool
}
// init redis session
// savepath like redis server addr,pool size,password
// e.g. 127.0.0.1:6379,100,astaxie
// savepath like redis server addr,pool size,password,dbnum
// e.g. 127.0.0.1:6379,100,astaxie,0
func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
@ -143,6 +144,16 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
if len(configs) > 2 {
rp.password = configs[2]
}
if len(configs) > 3 {
dbnum, err := strconv.Atoi(configs[1])
if err != nil || dbnum < 0 {
rp.dbNum = 0
} else {
rp.dbNum = dbnum
}
} else {
rp.dbNum = 0
}
rp.poollist = redis.NewPool(func() (redis.Conn, error) {
c, err := redis.Dial("tcp", rp.savePath)
if err != nil {
@ -154,6 +165,11 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error {
return nil, err
}
}
_, err = c.Do("SELECT", rp.dbNum)
if err != nil {
c.Close()
return nil, err
}
return c, err
}, rp.poolsize)

View File

@ -29,7 +29,10 @@ func TestCookie(t *testing.T) {
}
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
sess := globalSessions.SessionStart(w, r)
sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("set error,", err)
}
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set error,", err)

View File

@ -26,9 +26,12 @@ func TestMem(t *testing.T) {
go globalSessions.GC()
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
sess := globalSessions.SessionStart(w, r)
sess, err := globalSessions.SessionStart(w, r)
if err != nil {
t.Fatal("set error,", err)
}
defer sess.SessionRelease(w)
err := sess.Set("username", "astaxie")
err = sess.Set("username", "astaxie")
if err != nil {
t.Fatal("set error,", err)
}

View File

@ -28,19 +28,13 @@
package session
import (
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/astaxie/beego/utils"
)
// SessionStore contains all data for one session process with specific id.
@ -81,16 +75,15 @@ func Register(name string, provide Provider) {
}
type managerConfig struct {
CookieName string `json:"cookieName"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"`
Secure bool `json:"secure"`
SessionIDHashFunc string `json:"sessionIDHashFunc"`
SessionIDHashKey string `json:"sessionIDHashKey"`
CookieLifeTime int `json:"cookieLifeTime"`
ProviderConfig string `json:"providerConfig"`
Domain string `json:"domain"`
CookieName string `json:"cookieName"`
EnableSetCookie bool `json:"enableSetCookie,omitempty"`
Gclifetime int64 `json:"gclifetime"`
Maxlifetime int64 `json:"maxLifetime"`
Secure bool `json:"secure"`
CookieLifeTime int `json:"cookieLifeTime"`
ProviderConfig string `json:"providerConfig"`
Domain string `json:"domain"`
SessionIdLength int64 `json:"sessionIdLength"`
}
// Manager contains Provider and its configuration.
@ -129,11 +122,9 @@ func NewManager(provideName, config string) (*Manager, error) {
if err != nil {
return nil, err
}
if cf.SessionIDHashFunc == "" {
cf.SessionIDHashFunc = "sha1"
}
if cf.SessionIDHashKey == "" {
cf.SessionIDHashKey = string(generateRandomKey(16))
if cf.SessionIdLength == 0 {
cf.SessionIdLength = 16
}
return &Manager{
@ -144,11 +135,14 @@ func NewManager(provideName, config string) (*Manager, error) {
// Start session. generate or read the session id from http request.
// if session id exists, return SessionStore with this id.
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore) {
cookie, err := r.Cookie(manager.config.CookieName)
if err != nil || cookie.Value == "" {
sid := manager.sessionId(r)
session, _ = manager.provider.SessionRead(sid)
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore, err error) {
cookie, errs := r.Cookie(manager.config.CookieName)
if errs != nil || cookie.Value == "" {
sid, errs := manager.sessionId(r)
if errs != nil {
return nil, errs
}
session, err = manager.provider.SessionRead(sid)
cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: "/",
@ -163,12 +157,18 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se
}
r.AddCookie(cookie)
} else {
sid, _ := url.QueryUnescape(cookie.Value)
sid, errs := url.QueryUnescape(cookie.Value)
if errs != nil {
return nil, errs
}
if manager.provider.SessionExist(sid) {
session, _ = manager.provider.SessionRead(sid)
session, err = manager.provider.SessionRead(sid)
} else {
sid = manager.sessionId(r)
session, _ = manager.provider.SessionRead(sid)
sid, err = manager.sessionId(r)
if err != nil {
return nil, err
}
session, err = manager.provider.SessionRead(sid)
cookie = &http.Cookie{Name: manager.config.CookieName,
Value: url.QueryEscape(sid),
Path: "/",
@ -219,7 +219,10 @@ func (manager *Manager) GC() {
// 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 SessionStore) {
sid := manager.sessionId(r)
sid, err := manager.sessionId(r)
if err != nil {
return
}
cookie, err := r.Cookie(manager.config.CookieName)
if err != nil && cookie.Value == "" {
//delete old cookie
@ -251,36 +254,16 @@ func (manager *Manager) GetActiveSession() int {
return manager.provider.SessionAll()
}
// Set hash function for generating session id.
func (manager *Manager) SetHashFunc(hasfunc, hashkey string) {
manager.config.SessionIDHashFunc = hasfunc
manager.config.SessionIDHashKey = hashkey
}
// Set cookie with https.
func (manager *Manager) SetSecure(secure bool) {
manager.config.Secure = secure
}
// generate session id with rand string, unix nano time, remote addr by hash function.
func (manager *Manager) sessionId(r *http.Request) (sid string) {
bs := make([]byte, 32)
if n, err := io.ReadFull(rand.Reader, bs); n != 32 || err != nil {
bs = utils.RandomCreateBytes(32)
func (manager *Manager) sessionId(r *http.Request) (string, error) {
b := make([]byte, manager.config.SessionIdLength)
n, err := rand.Read(b)
if n != len(b) || err != nil {
return "", fmt.Errorf("Could not successfully read from the system CSPRNG.")
}
sig := fmt.Sprintf("%s%d%s", r.RemoteAddr, time.Now().UnixNano(), bs)
if manager.config.SessionIDHashFunc == "md5" {
h := md5.New()
h.Write([]byte(sig))
sid = hex.EncodeToString(h.Sum(nil))
} else if manager.config.SessionIDHashFunc == "sha1" {
h := hmac.New(sha1.New, []byte(manager.config.SessionIDHashKey))
fmt.Fprintf(h, "%s", sig)
sid = hex.EncodeToString(h.Sum(nil))
} else {
h := hmac.New(sha1.New, []byte(manager.config.SessionIDHashKey))
fmt.Fprintf(h, "%s", sig)
sid = hex.EncodeToString(h.Sum(nil))
}
return
return hex.EncodeToString(b), nil
}

View File

@ -22,7 +22,6 @@ import (
"strings"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/utils"
)
@ -31,6 +30,7 @@ func serverStaticRouter(ctx *context.Context) {
return
}
requestPath := path.Clean(ctx.Input.Request.URL.Path)
i := 0
for prefix, staticDir := range StaticDir {
if len(prefix) == 0 {
continue
@ -41,8 +41,13 @@ func serverStaticRouter(ctx *context.Context) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, file)
return
} else {
http.NotFound(ctx.ResponseWriter, ctx.Request)
return
i++
if i == len(StaticDir) {
http.NotFound(ctx.ResponseWriter, ctx.Request)
return
} else {
continue
}
}
}
if strings.HasPrefix(requestPath, prefix) {
@ -59,9 +64,20 @@ func serverStaticRouter(ctx *context.Context) {
return
}
//if the request is dir and DirectoryIndex is false then
if finfo.IsDir() && !DirectoryIndex {
middleware.Exception("403", ctx.ResponseWriter, ctx.Request, "403 Forbidden")
return
if finfo.IsDir() {
if !DirectoryIndex {
exception("403", ctx)
return
} else if ctx.Input.Request.URL.Path[len(ctx.Input.Request.URL.Path)-1] != '/' {
http.Redirect(ctx.ResponseWriter, ctx.Request, ctx.Input.Request.URL.Path+"/", 302)
return
}
} else if strings.HasSuffix(requestPath, "/index.html") {
file := path.Join(staticDir, requestPath)
if utils.FileExists(file) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, file)
return
}
}
//This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request

View File

@ -42,6 +42,9 @@ func init() {
beegoTplFuncMap["dateformat"] = DateFormat
beegoTplFuncMap["date"] = Date
beegoTplFuncMap["compare"] = Compare
beegoTplFuncMap["compare_not"] = CompareNot
beegoTplFuncMap["not_nil"] = NotNil
beegoTplFuncMap["not_null"] = NotNil
beegoTplFuncMap["substr"] = Substr
beegoTplFuncMap["html2str"] = Html2str
beegoTplFuncMap["str2html"] = Str2html

View File

@ -139,6 +139,14 @@ func Compare(a, b interface{}) (equal bool) {
return
}
func CompareNot(a, b interface{}) (equal bool) {
return ! Compare(a, b)
}
func NotNil(a interface{}) (is_nil bool) {
return CompareNot(a, nil)
}
func Config(returnType, key string, defaultVal interface{}) (value interface{}, err error) {
switch returnType {
case "String":
@ -246,7 +254,7 @@ func Htmlunquote(src string) string {
// /user/John%20Doe
//
// more detail http://beego.me/docs/mvc/controller/urlbuilding.md
func UrlFor(endpoint string, values ...string) string {
func UrlFor(endpoint string, values ...interface{}) string {
return BeeApp.Handlers.UrlFor(endpoint, values...)
}
@ -302,6 +310,14 @@ func ParseForm(form url.Values, obj interface{}) error {
switch fieldT.Type.Kind() {
case reflect.Bool:
if strings.ToLower(value) == "on" || strings.ToLower(value) == "1" || strings.ToLower(value) == "yes" {
fieldV.SetBool(true)
continue
}
if strings.ToLower(value) == "off" || strings.ToLower(value) == "0" || strings.ToLower(value) == "no" {
fieldV.SetBool(false)
continue
}
b, err := strconv.ParseBool(value)
if err != nil {
return err
@ -329,11 +345,45 @@ func ParseForm(form url.Values, obj interface{}) error {
fieldV.Set(reflect.ValueOf(value))
case reflect.String:
fieldV.SetString(value)
case reflect.Struct:
switch fieldT.Type.String() {
case "time.Time":
format := time.RFC3339
if len(tags) > 1 {
format = tags[1]
}
t, err := time.Parse(format, value)
if err != nil {
return err
}
fieldV.Set(reflect.ValueOf(t))
}
case reflect.Slice:
if fieldT.Type == sliceOfInts {
formVals := form[tag]
fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(int(1))), len(formVals), len(formVals)))
for i := 0; i < len(formVals); i++ {
val, err := strconv.Atoi(formVals[i])
if err != nil {
return err
}
fieldV.Index(i).SetInt(int64(val))
}
} else if fieldT.Type == sliceOfStrings {
formVals := form[tag]
fieldV.Set(reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf("")), len(formVals), len(formVals)))
for i := 0; i < len(formVals); i++ {
fieldV.Index(i).SetString(formVals[i])
}
}
}
}
return nil
}
var sliceOfInts = reflect.TypeOf([]int(nil))
var sliceOfStrings = reflect.TypeOf([]string(nil))
var unKind = map[reflect.Kind]bool{
reflect.Uintptr: true,
reflect.Complex64: true,
@ -368,23 +418,31 @@ func RenderForm(obj interface{}) template.HTML {
fieldT := objT.Field(i)
label, name, fType, ignored := parseFormTag(fieldT)
label, name, fType, id, class, ignored := parseFormTag(fieldT)
if ignored {
continue
}
raw = append(raw, renderFormField(label, name, fType, fieldV.Interface()))
raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class))
}
return template.HTML(strings.Join(raw, "</br>"))
}
// renderFormField returns a string containing HTML of a single form field.
func renderFormField(label, name, fType string, value interface{}) string {
if isValidForInput(fType) {
return fmt.Sprintf(`%v<input name="%v" type="%v" value="%v">`, label, name, fType, value)
func renderFormField(label, name, fType string, value interface{}, id string, class string) string {
if id != "" {
id = " id=\"" + id + "\""
}
return fmt.Sprintf(`%v<%v name="%v">%v</%v>`, label, fType, name, value, fType)
if class != "" {
class = " class=\"" + class + "\""
}
if isValidForInput(fType) {
return fmt.Sprintf(`%v<input%v%v name="%v" type="%v" value="%v">`, label, id, class, name, fType, value)
}
return fmt.Sprintf(`%v<%v%v%v name="%v">%v</%v>`, label, fType, id, class, name, value, fType)
}
// isValidForInput checks if fType is a valid value for the `type` property of an HTML input element.
@ -400,12 +458,14 @@ func isValidForInput(fType string) bool {
// parseFormTag takes the stuct-tag of a StructField and parses the `form` value.
// returned are the form label, name-property, type and wether the field should be ignored.
func parseFormTag(fieldT reflect.StructField) (label, name, fType string, ignored bool) {
func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool) {
tags := strings.Split(fieldT.Tag.Get("form"), ",")
label = fieldT.Name + ": "
name = fieldT.Name
fType = "text"
ignored = false
id = fieldT.Tag.Get("id")
class = fieldT.Tag.Get("class")
switch len(tags) {
case 1:

View File

@ -72,7 +72,7 @@ func TestDate(t *testing.T) {
}
}
func TestCompare(t *testing.T) {
func TestCompareRelated(t *testing.T) {
if !Compare("abc", "abc") {
t.Error("should be equal")
}
@ -82,6 +82,15 @@ func TestCompare(t *testing.T) {
if !Compare("1", 1) {
t.Error("should be equal")
}
if CompareNot("abc", "abc") {
t.Error("should be equal")
}
if !CompareNot("abc", "aBc") {
t.Error("should be not equal")
}
if !NotNil("a string") {
t.Error("should not be nil")
}
}
func TestHtmlquote(t *testing.T) {
@ -102,12 +111,14 @@ func TestHtmlunquote(t *testing.T) {
func TestParseForm(t *testing.T) {
type user struct {
Id int `form:"-"`
tag string `form:"tag"`
Name interface{} `form:"username"`
Age int `form:"age,text"`
Email string
Intro string `form:",textarea"`
Id int `form:"-"`
tag string `form:"tag"`
Name interface{} `form:"username"`
Age int `form:"age,text"`
Email string
Intro string `form:",textarea"`
StrBool bool `form:"strbool"`
Date time.Time `form:"date,2006-01-02"`
}
u := user{}
@ -119,6 +130,8 @@ func TestParseForm(t *testing.T) {
"age": []string{"40"},
"Email": []string{"test@gmail.com"},
"Intro": []string{"I am an engineer!"},
"strbool": []string{"yes"},
"date": []string{"2014-11-12"},
}
if err := ParseForm(form, u); err == nil {
t.Fatal("nothing will be changed")
@ -144,6 +157,13 @@ func TestParseForm(t *testing.T) {
if u.Intro != "I am an engineer!" {
t.Errorf("Intro should equal `I am an engineer!` but got `%v`", u.Intro)
}
if u.StrBool != true {
t.Errorf("strboll should equal `true`, but got `%v`", u.StrBool)
}
y, m, d := u.Date.Date()
if y != 2014 || m.String() != "November" || d != 12 {
t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String())
}
}
func TestRenderForm(t *testing.T) {
@ -175,12 +195,12 @@ func TestRenderForm(t *testing.T) {
}
func TestRenderFormField(t *testing.T) {
html := renderFormField("Label: ", "Name", "text", "Value")
html := renderFormField("Label: ", "Name", "text", "Value", "", "")
if html != `Label: <input name="Name" type="text" value="Value">` {
t.Errorf("Wrong html output for input[type=text]: %v ", html)
}
html = renderFormField("Label: ", "Name", "textarea", "Value")
html = renderFormField("Label: ", "Name", "textarea", "Value", "", "")
if html != `Label: <textarea name="Name">Value</textarea>` {
t.Errorf("Wrong html output for textarea: %v ", html)
}
@ -192,33 +212,34 @@ func TestParseFormTag(t *testing.T) {
All int `form:"name,text,年龄:"`
NoName int `form:",hidden,年龄:"`
OnlyLabel int `form:",,年龄:"`
OnlyName int `form:"name"`
OnlyName int `form:"name" id:"name" class:"form-name"`
Ignored int `form:"-"`
}
objT := reflect.TypeOf(&user{}).Elem()
label, name, fType, ignored := parseFormTag(objT.Field(0))
label, name, fType, id, class, ignored := parseFormTag(objT.Field(0))
if !(name == "name" && label == "年龄:" && fType == "text" && ignored == false) {
t.Errorf("Form Tag with name, label and type was not correctly parsed.")
}
label, name, fType, ignored = parseFormTag(objT.Field(1))
label, name, fType, id, class, ignored = parseFormTag(objT.Field(1))
if !(name == "NoName" && label == "年龄:" && fType == "hidden" && ignored == false) {
t.Errorf("Form Tag with label and type but without name was not correctly parsed.")
}
label, name, fType, ignored = parseFormTag(objT.Field(2))
label, name, fType, id, class, ignored = parseFormTag(objT.Field(2))
if !(name == "OnlyLabel" && label == "年龄:" && fType == "text" && ignored == false) {
t.Errorf("Form Tag containing only label was not correctly parsed.")
}
label, name, fType, ignored = parseFormTag(objT.Field(3))
if !(name == "name" && label == "OnlyName: " && fType == "text" && ignored == false) {
label, name, fType, id, class, ignored = parseFormTag(objT.Field(3))
if !(name == "name" && label == "OnlyName: " && fType == "text" && ignored == false &&
id == "name" && class == "form-name") {
t.Errorf("Form Tag containing only name was not correctly parsed.")
}
label, name, fType, ignored = parseFormTag(objT.Field(4))
label, name, fType, id, class, ignored = parseFormTag(objT.Field(4))
if ignored == false {
t.Errorf("Form Tag that should be ignored was not correctly parsed.")
}

View File

@ -83,13 +83,16 @@ func (m *UrlMap) AddStatistics(requestMethod, requestUrl, requestController stri
}
// put url statistics result in io.Writer
func (m *UrlMap) GetMap() [][]string {
func (m *UrlMap) GetMap() map[string]interface{} {
m.lock.RLock()
defer m.lock.RUnlock()
resultLists := make([][]string, 0)
var result = []string{"requestUrl", "method", "times", "used", "max used", "min used", "avg used"}
resultLists = append(resultLists, result)
var fields = []string{"requestUrl", "method", "times", "used", "max used", "min used", "avg used"}
resultLists := make([][]string, 0)
content := make(map[string]interface{})
content["Fields"] = fields
for k, v := range m.urlmap {
for kk, vv := range v {
result := []string{
@ -104,6 +107,28 @@ func (m *UrlMap) GetMap() [][]string {
resultLists = append(resultLists, result)
}
}
content["Data"] = resultLists
return content
}
func (m *UrlMap) GetMapData() []map[string]interface{} {
resultLists := make([]map[string]interface{}, 0)
for k, v := range m.urlmap {
for kk, vv := range v {
result := map[string]interface{}{
"request_url": k,
"method": kk,
"times": vv.RequestNum,
"total_time": toS(vv.TotalTime),
"max_time": toS(vv.MaxTime),
"min_time": toS(vv.MinTime),
"avg_time": toS(time.Duration(int64(vv.TotalTime) / vv.RequestNum)),
}
resultLists = append(resultLists, result)
}
}
return resultLists
}

View File

@ -15,6 +15,7 @@
package toolbox
import (
"encoding/json"
"testing"
"time"
)
@ -28,4 +29,12 @@ func TestStatics(t *testing.T) {
StatisticsMap.AddStatistics("POST", "/api/user/xiemengjun", "&admin.user", time.Duration(13000))
StatisticsMap.AddStatistics("DELETE", "/api/user", "&admin.user", time.Duration(1400))
t.Log(StatisticsMap.GetMap())
data := StatisticsMap.GetMapData()
b, err := json.Marshal(data)
if err != nil {
t.Errorf(err.Error())
}
t.Log(string(b))
}

View File

@ -33,6 +33,8 @@ type bounds struct {
var (
AdminTaskList map[string]Tasker
stop chan bool
changed chan bool
isstart bool
seconds = bounds{0, 59, nil}
minutes = bounds{0, 59, nil}
hours = bounds{0, 23, nil}
@ -82,6 +84,7 @@ type TaskFunc func() error
// task interface
type Tasker interface {
GetSpec() string
GetStatus() string
Run() error
SetNext(time.Time)
@ -100,6 +103,7 @@ type taskerr struct {
type Task struct {
Taskname string
Spec *Schedule
SpecStr string
DoFunc TaskFunc
Prev time.Time
Next time.Time
@ -114,16 +118,22 @@ func NewTask(tname string, spec string, f TaskFunc) *Task {
Taskname: tname,
DoFunc: f,
ErrLimit: 100,
SpecStr: spec,
}
task.SetCron(spec)
return task
}
//get spec string
func (s *Task) GetSpec() string {
return s.SpecStr
}
// get current task status
func (tk *Task) GetStatus() string {
var str string
for _, v := range tk.Errlist {
str += v.t.String() + ":" + v.errinfo + "\n"
str += v.t.String() + ":" + v.errinfo + "<br>"
}
return str
}
@ -379,6 +389,7 @@ func dayMatches(s *Schedule, t time.Time) bool {
// start all tasks
func StartTask() {
isstart = true
go run()
}
@ -411,6 +422,8 @@ func run() {
e.SetNext(effective)
}
continue
case <-changed:
continue
case <-stop:
return
}
@ -419,12 +432,24 @@ func run() {
// start all tasks
func StopTask() {
isstart = false
stop <- true
}
// add task with name
func AddTask(taskname string, t Tasker) {
AdminTaskList[taskname] = t
if isstart {
changed <- true
}
}
// add task with name
func DeleteTask(taskname string) {
delete(AdminTaskList, taskname)
if isstart {
changed <- true
}
}
// sort map for tasker
@ -578,4 +603,5 @@ func all(r bounds) uint64 {
func init() {
AdminTaskList = make(map[string]Tasker)
stop = make(chan bool)
changed = make(chan bool)
}

21
tree.go
View File

@ -237,12 +237,18 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
regexpStr = "/" + regexpStr
}
} else if reg != "" {
for _, w := range params {
if w == "." || w == ":" {
continue
if seg == "*.*" {
regexpStr = "/([^.]+).(.+)"
} else {
for _, w := range params {
if w == "." || w == ":" {
continue
}
regexpStr = "/([^/]+)" + regexpStr
}
regexpStr = "/([^/]+)" + regexpStr
}
}
t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
} else {
@ -388,6 +394,9 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string
}
return true, params
}
if len(wildcardValues) <= j {
return false, nil
}
params[v] = wildcardValues[j]
j += 1
}
@ -396,6 +405,7 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string
}
return true, params
}
if !leaf.regexps.MatchString(path.Join(wildcardValues...)) {
return false, nil
}
@ -412,6 +422,9 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string
// "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"]
func splitPath(key string) []string {
if key == "" {
return []string{}
}
elements := strings.Split(key, "/")
if elements[0] == "" {
elements = elements[1:]

View File

@ -42,6 +42,9 @@ func init() {
routers = append(routers, testinfo{"/*.*", "/nice/api.json", map[string]string{":path": "nice/api", ":ext": "json"}})
routers = append(routers, testinfo{"/:name/*.*", "/nice/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}})
routers = append(routers, testinfo{"/:name/test/*.*", "/nice/test/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}})
routers = append(routers, testinfo{"/dl/:width:int/:height:int/*.*",
"/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg",
map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}})
routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}})
routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}})
@ -146,7 +149,11 @@ func TestAddTree2(t *testing.T) {
}
func TestSplitPath(t *testing.T) {
a := splitPath("/")
a := splitPath("")
if len(a) != 0 {
t.Fatal("/ should retrun []")
}
a = splitPath("/")
if len(a) != 0 {
t.Fatal("/ should retrun []")
}

View File

@ -157,19 +157,37 @@ func (e *Email) Bytes() ([]byte, error) {
}
// Add attach file to the send mail
func (e *Email) AttachFile(filename string) (a *Attachment, err error) {
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
if len(args) < 1 && len(args) > 2 {
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
return
}
filename := args[0]
id := ""
if len(args) > 1 {
id = args[1]
}
f, err := os.Open(filename)
if err != nil {
return
}
ct := mime.TypeByExtension(filepath.Ext(filename))
basename := path.Base(filename)
return e.Attach(f, basename, ct)
return e.Attach(f, basename, ct, id)
}
// Attach is used to attach content from an io.Reader to the email.
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) {
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
if len(args) < 1 && len(args) > 2 {
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
return
}
c := args[0] //Content-Type
id := ""
if len(args) > 1 {
id = args[1] //Content-ID
}
var buffer bytes.Buffer
if _, err = io.Copy(&buffer, r); err != nil {
return
@ -186,7 +204,12 @@ func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, e
// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
at.Header.Set("Content-Type", "application/octet-stream")
}
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
if id != "" {
at.Header.Set("Content-Disposition", fmt.Sprintf("inline;\r\n filename=\"%s\"", filename))
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", id))
} else {
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
}
at.Header.Set("Content-Transfer-Encoding", "base64")
e.Attachments = append(e.Attachments, at)
return at, nil
@ -269,7 +292,7 @@ func qpEscape(dest []byte, c byte) {
const nums = "0123456789ABCDEF"
dest[0] = '='
dest[1] = nums[(c&0xf0)>>4]
dest[2] = nums[(c & 0xf)]
dest[2] = nums[(c&0xf)]
}
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer

View File

@ -0,0 +1,26 @@
// 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 pagination
import (
"github.com/astaxie/beego/context"
)
// Instantiates a Paginator and assigns it to context.Input.Data["paginator"].
func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) {
paginator = NewPaginator(context.Request, per, nums)
context.Input.Data["paginator"] = &paginator
return
}

59
utils/pagination/doc.go Normal file
View File

@ -0,0 +1,59 @@
/*
The pagination package provides utilities to setup a paginator within the
context of a http request.
Usage
In your beego.Controller:
package controllers
import "github.com/astaxie/beego/utils/pagination"
type PostsController struct {
beego.Controller
}
func (this *PostsController) ListAllPosts() {
// sets this.Data["paginator"] with the current offset (from the url query param)
postsPerPage := 20
paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts())
// fetch the next 20 posts
this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage)
}
In your view templates:
{{if .paginator.HasPages}}
<ul class="pagination pagination">
{{if .paginator.HasPrev}}
<li><a href="{{.paginator.PageLinkFirst}}">{{ i18n .Lang "paginator.first_page"}}</a></li>
<li><a href="{{.paginator.PageLinkPrev}}">&laquo;</a></li>
{{else}}
<li class="disabled"><a>{{ i18n .Lang "paginator.first_page"}}</a></li>
<li class="disabled"><a>&laquo;</a></li>
{{end}}
{{range $index, $page := .paginator.Pages}}
<li{{if $.paginator.IsActive .}} class="active"{{end}}>
<a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
</li>
{{end}}
{{if .paginator.HasNext}}
<li><a href="{{.paginator.PageLinkNext}}">&raquo;</a></li>
<li><a href="{{.paginator.PageLinkLast}}">{{ i18n .Lang "paginator.last_page"}}</a></li>
{{else}}
<li class="disabled"><a>&raquo;</a></li>
<li class="disabled"><a>{{ i18n .Lang "paginator.last_page"}}</a></li>
{{end}}
</ul>
{{end}}
See also
http://beego.me/docs/mvc/view/page.md
*/
package pagination

View File

@ -0,0 +1,189 @@
// 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 pagination
import (
"math"
"net/http"
"net/url"
"strconv"
)
// Paginator within the state of a http request.
type Paginator struct {
Request *http.Request
PerPageNums int
MaxPages int
nums int64
pageRange []int
pageNums int
page int
}
// Returns the total number of pages.
func (p *Paginator) PageNums() int {
if p.pageNums != 0 {
return p.pageNums
}
pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums))
if p.MaxPages > 0 {
pageNums = math.Min(pageNums, float64(p.MaxPages))
}
p.pageNums = int(pageNums)
return p.pageNums
}
// Returns the total number of items (e.g. from doing SQL count).
func (p *Paginator) Nums() int64 {
return p.nums
}
// Sets the total number of items.
func (p *Paginator) SetNums(nums interface{}) {
p.nums, _ = ToInt64(nums)
}
// Returns the current page.
func (p *Paginator) Page() int {
if p.page != 0 {
return p.page
}
if p.Request.Form == nil {
p.Request.ParseForm()
}
p.page, _ = strconv.Atoi(p.Request.Form.Get("p"))
if p.page > p.PageNums() {
p.page = p.PageNums()
}
if p.page <= 0 {
p.page = 1
}
return p.page
}
// Returns a list of all pages.
//
// Usage (in a view template):
//
// {{range $index, $page := .paginator.Pages}}
// <li{{if $.paginator.IsActive .}} class="active"{{end}}>
// <a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
// </li>
// {{end}}
func (p *Paginator) Pages() []int {
if p.pageRange == nil && p.nums > 0 {
var pages []int
pageNums := p.PageNums()
page := p.Page()
switch {
case page >= pageNums-4 && pageNums > 9:
start := pageNums - 9 + 1
pages = make([]int, 9)
for i, _ := range pages {
pages[i] = start + i
}
case page >= 5 && pageNums > 9:
start := page - 5 + 1
pages = make([]int, int(math.Min(9, float64(page+4+1))))
for i, _ := range pages {
pages[i] = start + i
}
default:
pages = make([]int, int(math.Min(9, float64(pageNums))))
for i, _ := range pages {
pages[i] = i + 1
}
}
p.pageRange = pages
}
return p.pageRange
}
// Returns URL for a given page index.
func (p *Paginator) PageLink(page int) string {
link, _ := url.ParseRequestURI(p.Request.URL.String())
values := link.Query()
if page == 1 {
values.Del("p")
} else {
values.Set("p", strconv.Itoa(page))
}
link.RawQuery = values.Encode()
return link.String()
}
// Returns URL to the previous page.
func (p *Paginator) PageLinkPrev() (link string) {
if p.HasPrev() {
link = p.PageLink(p.Page() - 1)
}
return
}
// Returns URL to the next page.
func (p *Paginator) PageLinkNext() (link string) {
if p.HasNext() {
link = p.PageLink(p.Page() + 1)
}
return
}
// Returns URL to the first page.
func (p *Paginator) PageLinkFirst() (link string) {
return p.PageLink(1)
}
// Returns URL to the last page.
func (p *Paginator) PageLinkLast() (link string) {
return p.PageLink(p.PageNums())
}
// Returns true if the current page has a predecessor.
func (p *Paginator) HasPrev() bool {
return p.Page() > 1
}
// Returns true if the current page has a successor.
func (p *Paginator) HasNext() bool {
return p.Page() < p.PageNums()
}
// Returns true if the given page index points to the current page.
func (p *Paginator) IsActive(page int) bool {
return p.Page() == page
}
// Returns the current offset.
func (p *Paginator) Offset() int {
return (p.Page() - 1) * p.PerPageNums
}
// Returns true if there is more than one page.
func (p *Paginator) HasPages() bool {
return p.PageNums() > 1
}
// Instantiates a paginator struct for the current http request.
func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator {
p := Paginator{}
p.Request = req
if per <= 0 {
per = 10
}
p.PerPageNums = per
p.SetNums(nums)
return &p
}

34
utils/pagination/utils.go Normal file
View File

@ -0,0 +1,34 @@
// 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 pagination
import (
"fmt"
"reflect"
)
// convert any numeric value to int64
func ToInt64(value interface{}) (d int64, err error) {
val := reflect.ValueOf(value)
switch value.(type) {
case int, int8, int16, int32, int64:
d = val.Int()
case uint, uint8, uint16, uint32, uint64:
d = int64(val.Uint())
default:
err = fmt.Errorf("ToInt64 need numeric not `%T`", value)
}
return
}

View File

@ -106,10 +106,10 @@ func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) {
return
}
// SliceIntersect returns diff slice of slice1 - slice2.
// SliceIntersect returns slice that are present in all the slice1 and slice2.
func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) {
for _, v := range slice1 {
if !InSliceIface(v, slice2) {
if InSliceIface(v, slice2) {
diffslice = append(diffslice, v)
}
}