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

708 Commits

Author SHA1 Message Date
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
c4fa17921e comments for godocs 2014-08-18 16:44:40 +08:00
724137e605 Merge branch 'master' into develop 2014-08-18 16:42:16 +08:00
a144769515 update the documents & comments 2014-08-18 16:41:43 +08:00
05089be427 Merge pull request #749 from smallfish/master
rename SetAgent and ToXML, and update some testcase
2014-08-18 15:07:11 +08:00
7668c54d05 update testcase for httplib 2014-08-18 15:03:34 +08:00
86752a55b6 rename SetAgent and ToXML 2014-08-18 15:03:10 +08:00
e07d780dcf update the router info 2014-08-18 15:00:27 +08:00
e566322643 beego: change the version from 1.4.0 to 1.3.2 2014-08-18 14:46:43 +08:00
51ee1e77c2 beego: close the file when finish init 2014-08-18 14:42:17 +08:00
986e91b7d6 beego: update the debug info rules 2014-08-18 14:35:43 +08:00
8b8638c507 Merge pull request #748 from smallfish/master
Remove some unnecessary code
2014-08-17 21:36:10 +08:00
d27c5c8daf Remove some unnecessary code 2014-08-17 21:13:29 +08:00
86121deac4 Merge pull request #747 from francoishill/patch-12
Another change to match with logs/log.go (and RFC 5424 specs)
2014-08-16 10:10:16 +08:00
6b02e1e9d4 Merge pull request #746 from francoishill/patch-11
To match with logs/log.go (and RFC 5424 specs)
2014-08-16 10:09:32 +08:00
83696d95c3 Merge pull request #745 from francoishill/patch-10
Makes more sense to Use Debug instead of Info?
2014-08-16 09:40:01 +08:00
7be8114616 To match with logs/log.go (and RFC 5424 specs) 2014-08-15 21:30:59 +02:00
1dbbb89ad1 To match with logs/log.go (and RFC 5424 specs) 2014-08-15 21:12:57 +02:00
75904effd9 Makes more sense to Use Debug instead of Info? 2014-08-15 21:11:16 +02:00
cd9e614a71 plugins: basic auth & cors 2014-08-15 17:15:20 +08:00
c1234e7c6d fix the responseWriter 2014-08-15 15:24:46 +08:00
b611b9dab6 change the adminui link 2014-08-15 15:19:35 +08:00
cad3da337a toolbox: fix the program name 2014-08-15 15:17:11 +08:00
3969cd3b40 toolbox: improve the profile 2014-08-15 15:09:59 +08:00
f4867aad5a Merge pull request #744 from lei-cao/develop
ajax refresh gc message
2014-08-15 14:46:37 +08:00
17006cfb26 ajax refresh gc message 2014-08-15 14:24:55 +08:00
7d1b03ee5d toolbox: fix the test case 2014-08-15 00:14:30 +08:00
84a4379f0d Merge pull request #739 from lei-cao/develop
Added the UI for Admin monitor page
2014-08-14 23:57:09 +08:00
ab71201c96 Merge pull request #741 from smallfish/master
Update ctx.WriteString multiple buf output
2014-08-14 23:56:20 +08:00
c347dd9e7b Fix the comments and deleted the println 2014-08-14 23:50:15 +08:00
75e2611cc4 Update ctx.WriteString multiple buf output 2014-08-14 21:37:52 +08:00
d314d12c77 Added the UI for Admin monitor page 2014-08-14 17:35:23 +08:00
564c3bbeb5 migration: update the debug error 2014-08-14 13:44:05 +08:00
31f0ac4ce3 migration: update the params orders 2014-08-14 13:41:54 +08:00
57a9670b0a migration: reset the up state sql 2014-08-14 13:37:48 +08:00
14cd9e51ac revert the sort map for reset 2014-08-14 11:54:15 +08:00
886bb782a5 sort the reset 2014-08-14 11:44:10 +08:00
d2119f715c update the migrations 2014-08-14 11:39:59 +08:00
f98b1810ab update the reset 2014-08-14 10:56:49 +08:00
55a7711017 migration: skip reset 2014-08-14 10:19:55 +08:00
436edda926 Merge branch 'master' into develop 2014-08-13 20:39:14 +08:00
1f1190fbc3 Merge pull request #738 from smallfish/master
Add CustomAbort() for Controller, support status code and body
2014-08-13 20:16:36 +08:00
20463fa725 Add CustomAbort() for Controller, support status code and body 2014-08-13 17:26:22 +08:00
ae37f95239 migration: delete the unique key for name 2014-08-13 16:42:16 +08:00
da6726f3da Merge pull request #737 from ZhengYang/develop
change time format for migration module
2014-08-13 16:37:13 +08:00
32469cd69d change time format 2014-08-13 16:09:13 +08:00
e572f45296 update the error output 2014-08-13 14:50:32 +08:00
efcaa3d934 update the migration database time format 2014-08-13 11:16:19 +08:00
5ecfe0c335 beego hook change the path & fix the migration bug 2014-08-13 10:43:05 +08:00
d325a66fee update the error info 2014-08-12 16:35:59 +08:00
fe9017c819 update the description 2014-08-12 15:50:28 +08:00
b97279a74f update the migration 2014-08-12 15:49:30 +08:00
6a78898bb1 beego: fix the tree for addtree & add testcase 2014-08-12 15:09:12 +08:00
f201859fa7 beego: fix the addTree 2014-08-12 00:15:39 +08:00
52fdfc5665 beego: fix the tree addTree for regexp 2014-08-12 00:02:27 +08:00
8ed6d06572 fix the regexps bugs 2014-08-11 22:40:55 +08:00
b5a2347e1d Merge pull request #733 from liulixiang1988/develop
improve the 'geturl'
2014-08-11 22:25:53 +08:00
118e07158e improve the 'geturl'
If we have a url mapping like this:
`beego.Router(“/test”, &controllers.WeightController{},
"get:GetDetails”)`
when u use `UrlFor(“WeightController.GetDetails”, “foo”, 1, “bar”, 2 `,
it should return `/test?foo=1&bar=2` rather than `/test`.
2014-08-11 22:19:59 +08:00
7a376c32be delete forms 2014-08-11 22:13:57 +08:00
3d74a1a436 make the getconfig public
// Getconfig throw the Runmode
// [dev]
// name = astaixe
// IsEnable = false
// [prod]
// name = slene
// IsEnable = true
//
// usage:
// GetConfig("string", "name")
// GetConfig("bool", "IsEnable")
2014-08-11 22:13:18 +08:00
00eac0e4cb Merge pull request #729 from francoishill/patch-9
Update captcha.go
2014-08-11 21:41:22 +08:00
58ac0d5ea4 Update captcha.go
Captcha must be deleted if the user entered a "challenge" with a different length than the captcha.
2014-08-09 15:35:29 +02:00
2773fda883 session:change the driver from beego to bradfitz 2014-08-08 16:43:39 +08:00
99c03a2b9c fix the nil judge 2014-08-08 13:16:51 +08:00
b1b4dbb0e4 fix the nil judge 2014-08-08 12:02:44 +08:00
77e1f26dd4 Merge pull request #728 from nizsheanez/null_pointer_panic_improve
[orm] improve null pointer panic message
2014-08-08 09:01:51 +08:00
2820f630c8 config: add more method
DefaultString(key string, defaultval string) string      // support
section::key type in key string when using ini and json type;
Int,Int64,Bool,Float,DIY are same.
	DefaultStrings(key string, defaultval []string) []string //get string
slice
	DefaultInt(key string, defaultval int) int
	DefaultInt64(key string, defaultval int64) int64
	DefaultBool(key string, defaultval bool) bool
	DefaultFloat(key string, defaultval float64) float64
	DIY(key string) (interface{}, error)
	GetSection(section string) (map[string]string, error)
	SaveConfigFile(filename string) error
2014-08-07 17:24:21 +08:00
1f6c5599aa migration: version 1 2014-08-07 16:30:28 +08:00
df8c73b23a improve null pointer panic message 2014-08-07 12:14:10 +07:00
ea6982fcea beego: template fund when the start> len(bt) 2014-08-05 23:52:06 +08:00
a3f40234ca Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-08-05 08:56:34 +08:00
2c420573d4 fix #703 2014-08-05 08:56:04 +08:00
c0d3cc6fc5 beego: fix the domain string 2014-08-05 08:55:46 +08:00
3f7ecea089 beego: Display function move from toolbox to utils 2014-08-04 20:52:18 +08:00
f4147058fc fix when delete the commentsRouter.go 2014-08-04 17:39:14 +08:00
1fb24aca34 beego: commentsrouter use the workPath fix #708 2014-08-04 17:34:52 +08:00
885c0678ff move filter wrong http method 2014-08-04 16:21:34 +08:00
d7a5281bda session: support cookie domain 2014-08-04 16:21:06 +08:00
474a16a7a0 beego: improve the static file server 2014-08-04 15:31:27 +08:00
1d36b19cab Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-08-04 15:00:42 +08:00
deed251794 Merge pull request #720 from MicroMoon/patch-1
revise the comment of SliceIntersect
2014-08-02 23:28:08 +08:00
22ba252c8f fix the comment of SliceIntersect
The implementations of SliceDiff and SliceIntersect are the same.
However, the comments of them are opposite.
The former is (slice1 - slice2) while the latter is (slice2- slice1)
2014-08-02 17:54:57 +08:00
289f050c04 cache: fix comments the return err 2014-08-02 10:12:03 +08:00
bbd31131a4 beego:parse judge weather the commentsRouter exist 2014-08-02 10:11:45 +08:00
008ae39ff6 Merge branch 'master' into develop 2014-08-01 17:04:22 +08:00
0836b9e13f fix #718 2014-08-01 17:03:28 +08:00
7a39a3c52f middleware: error page set the content-type 2014-08-01 16:46:51 +08:00
509af636b1 Merge pull request #716 from 4eek/docfix-validations-HasErrors
Fixed docs: HasErrors is a method not a variable
2014-07-29 22:04:26 +08:00
daf8b1101f Fixed docs: HasErrors is a method not a variable 2014-07-29 15:05:04 +02:00
428aec1c24 Merge pull request #715 from MicroMoon/develop
revise a comment
2014-07-28 09:52:26 +08:00
a804d242d9 revise a comment
If it gets a msg from the buffer chan, it will write the message to
outputs.
2014-07-27 07:13:17 -04:00
7ddd20340b orm: delete the old docs 2014-07-26 23:25:59 +08:00
e7fcb824c1 utils: fix the safemap Items 2014-07-26 23:25:51 +08:00
ee9749d640 beego:fix #685
move XsrfToken& CheckXsrfCookie to context
2014-07-24 23:12:21 +08:00
d7090689e8 cache: change the memcache &redis driver
change the memcache to the newest
2014-07-24 22:54:56 +08:00
52d153da87 Merge pull request #710 from francoishill/patch-8
Update smtp.go (develop branch)
2014-07-22 13:45:24 +08:00
6ce55e8884 Update smtp.go (develop branch)
For mail servers that do not require Authentication we must pass NIL for the SendMail parameter 2 (the auth parameter). Otherwise it fails to send the mail.
2014-07-22 07:21:47 +02:00
91ee42ceeb beego:update the Abort Status 2014-07-18 15:38:29 +08:00
d17f107fc4 beego: fix #702 auto render 2014-07-18 13:29:54 +08:00
ae8bb8ce82 Merge pull request #701 from fuxiaohei/develop
code style simplify
2014-07-17 16:51:01 +08:00
38188098c5 fix testing fail 2014-07-17 16:48:10 +08:00
c372328f88 code style simplify 2014-07-17 16:22:52 +08:00
a6ced64441 code style simplify 2014-07-17 16:22:41 +08:00
69094b2786 remove koding link 2014-07-17 16:05:17 +08:00
08397fef45 Merge pull request #700 from fuxiaohei/develop
code style simplify
2014-07-17 15:58:37 +08:00
84da1c924a code style simplify 2014-07-17 15:56:06 +08:00
f733b5707a code style simplify 2014-07-17 15:49:40 +08:00
9d0798edc6 Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-07-17 15:46:28 +08:00
1858f3073b session: fix #688 2014-07-16 23:05:38 +08:00
e6d6419a65 beego: static file support robots.txt 2014-07-16 22:27:53 +08:00
e52386b52d code style simplify 2014-07-15 10:01:26 +08:00
024817aacb Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-07-15 09:47:49 +08:00
9d0ad3f974 code style simplify 2014-07-13 18:11:13 +08:00
6357c88d97 Merge pull request #694 from fuxiaohei/develop
code style simplify
2014-07-13 18:10:51 +08:00
9457e61a0c code style simplify 2014-07-12 22:12:06 +08:00
f657509a42 Merge pull request #693 from fuxiaohei/develop
code style simplify
2014-07-12 19:33:32 +08:00
20e05a3908 code style simplify 2014-07-12 16:03:14 +08:00
77c40e6f7b code style simplify 2014-07-12 15:51:47 +08:00
ea2ed90ab0 Merge remote-tracking branch 'remotes/astaxie/develop' into develop 2014-07-12 15:32:37 +08:00
15759f60ed Merge pull request #692 from fuxiaohei/develop
code style simplify
2014-07-12 14:18:30 +08:00
0e3e22efbe merge update 2014-07-12 09:57:43 +08:00
a906bf1174 code style simplify 2014-07-12 09:52:15 +08:00
a6379481cf Merge pull request #691 from FGM/logger-rfc5424
Issue #682: convert logs package to RFC5424 logging levels.
2014-07-11 21:35:56 +08:00
7d09ac252a Issue #682: convert logs package to RFC5424 logging levels. 2014-07-11 11:15:34 +02:00
0ff058bd25 code style simplify 2014-07-11 10:01:49 +08:00
dbe75f90d5 Merge pull request #689 from fuxiaohei/develop
code style simplify
2014-07-11 09:55:56 +08:00
ca1fd96ea5 Merge pull request #687 from fuxiaohei/develop
code style simplify
2014-07-10 16:48:04 +08:00
0b1b121db8 code style simplify 2014-07-10 13:38:05 +08:00
8683ee7c67 code style simplify 2014-07-10 13:28:54 +08:00
007db805be code style simplify 2014-07-10 13:04:18 +08:00
19c3a5b41c beego: improve the router debug infomation 2014-07-09 09:38:36 +08:00
e635e274d4 Merge branch 'master' into develop 2014-07-09 08:43:37 +08:00
46cde6e579 Merge pull request #683 from chrisport/develop
beego/context: Fix ignored Header in case SetStatus has been called before
2014-07-09 08:39:54 +08:00
3bb4d6f013 beego/context: Fix ignored Header in case SetStatus has been called before 2014-07-08 23:24:47 +03:00
cec151fda7 Merge pull request #674 from reterVision/master
Make redis cache timeout not trivial.
2014-07-07 09:49:54 +08:00
b6c4e27a75 Adjust the action order in Put function. 2014-07-05 22:30:51 +08:00
2933d1fedd Make redis cache timeout not trivial.
- Instead of using HASH for all the caches, use HASH + normal KEYs.
- HASH being used as a collection of all KEYs, this is useful when
  application wants to clear all keys.
2014-07-05 22:27:31 +08:00
cbffcaa7a8 Merge pull request #673 from sandysong/master
修正Detect Engine错误
2014-07-04 17:58:26 +08:00
707c951302 修正Detect Engine错误 2014-07-04 16:57:37 +08:00
fefd8ddb5b beego: update licence& fix #669 2014-07-03 23:40:21 +08:00
14dee37a21 beego: autorouter params 2014-07-01 16:55:23 +08:00
17a9c3c3a9 ORM:revert default value 2014-07-01 09:30:08 +08:00
8946f816f9 orm:change the models_test default value 2014-07-01 00:30:05 +08:00
36ba1e49b1 beego:change to 1.3.2 2014-07-01 00:06:35 +08:00
34936dde35 Merge branch 'master' into develop 2014-06-30 23:50:45 +08:00
aa004ed973 beego:form render textarea 2014-06-30 23:49:55 +08:00
53353fce56 beego:fix the :id & * mixed router 2014-06-30 23:49:11 +08:00
f92ce9af96 Merge pull request #654 from wb14123/cmd_default
Generate default value while run ORM cmd tool
2014-06-30 21:36:10 +08:00
8b021c8ea1 Merge pull request #664 from kioopi/renderform-textarea
Makes RenderForm use textarea-element when form type is `textarea`
2014-06-30 21:14:27 +08:00
34572193c6 Adds html5 input types to valid intput types of RenderForm. 2014-06-30 10:38:32 +02:00
4dde2c59ff fix the parser.go lastupdate 2014-06-30 15:57:36 +08:00
c83c17d917 Merge pull request #660 from ljyf5593/patch-1
beego: fix #657
2014-06-30 12:06:24 +08:00
4994d36b66 templateform.RenderForm now renders textarea
When RenderForm encounters a field with the structTag `form` value
type `textarea` it renders an actual <textarea> html tag.
2014-06-29 20:49:56 +02:00
a991b9dcde Removes unused FormType map from templatefunc 2014-06-29 20:49:35 +02:00
3fe9e6a28a extract func parseFormTag from templatefunc.RenderForm
Extracted a func `parseFormTag` that takes a reflect.StructField
and returns the different positional parts of the `form` structTag
with default values as documented in
http://beego.me/docs/mvc/view/view.md#renderform

This makes RenderForm shorter and makes it possible to test the
parsing separately.
2014-06-29 20:49:08 +02:00
62e9c89010 middleware: support i18n 2014-06-27 17:53:53 +08:00
ac96c2b15e beego: fix #657
路由地址不区分大小写问题
2014-06-27 11:06:29 +08:00
0f170a80da update the comments fix #658 2014-06-25 10:39:37 +08:00
9c5348f690 beego: autoroute 2014-06-23 15:52:30 +08:00
90f91b10c5 beego: change to beego 1.3.1 2014-06-23 15:31:51 +08:00
a5a6a30744 beego: fix the router rule for * 2014-06-23 15:28:53 +08:00
1f6e689e5d beego: fix #652 2014-06-23 15:28:29 +08:00
7b7a95677a Generate default value while run ORM cmd tool 2014-06-22 10:52:14 +08:00
af4f153830 beego: update the router rule for *
* not match the empty route
2014-06-21 11:44:24 +08:00
469f283b68 beego:fix captcha filter router 2014-06-19 20:29:36 +08:00
085c362ffb beego:fix router expge 2014-06-18 23:32:47 +08:00
c3a07555c4 beego:change version to 1.3.0 2014-06-18 15:15:45 +08:00
2b8e411174 merger master httplib 2014-06-18 15:04:08 +08:00
720a77c1f9 beego:rever docs 2014-06-18 11:15:43 +08:00
b943b74fc5 beego:init GlobalDocApi 2014-06-18 11:09:47 +08:00
67be7b532d beego:doc move to swagger 2014-06-18 10:36:20 +08:00
8be83fe488 Merge pull request #642 from JessonChan/develop
ignore nil time
2014-06-17 15:25:03 +08:00
cff632f553 beego: swagger EnableDocs 2014-06-16 16:05:19 +08:00
4990d88861 Merge pull request #648 from redaready/develop
update chat example
2014-06-14 12:13:50 +08:00
7075ad8a28 update chat example 2014-06-14 00:21:26 +02:00
0e278ae358 beego:format the admin print route 2014-06-13 00:14:30 +08:00
e38a23b30e beego:admin add print method 2014-06-13 00:08:43 +08:00
117904be73 beego:fix the some regexp routes to different func 2014-06-12 23:08:05 +08:00
3b807845f2 beego:addtree support regexp 2014-06-12 20:50:29 +08:00
00b710e168 beego:namespace sub router add url to pattern 2014-06-11 23:51:19 +08:00
e25fcffbc0 config:json add support array 2014-06-11 22:47:11 +08:00
c13141b8bf beego:fix when user defined function equal to HTTP 2014-06-11 22:45:54 +08:00
7a7ff735e3 Merge pull request #644 from chrisport/develop
config: fix error when json config starts with an array
2014-06-11 22:02:28 +08:00
3b934bb910 config: fix error when json config starts with an array 2014-06-11 11:33:32 +03:00
aa275fb5ce beego:fix #639 2014-06-11 13:26:45 +08:00
deb553be7f beego:confgi support difference run mode section
runmode = dev
appname = doraemon
[dev]
httpport = 8880
sessionon = true

[prod]
httpport = 8888
sessionon = true

[test]
httpport = 8080
sessionon = false
2014-06-11 12:00:50 +08:00
3db9633ebd remove websocket logic because not support handler 2014-06-11 11:12:17 +08:00
2f8a70d548 beego: router support param has _ 2014-06-11 09:33:35 +08:00
7c0d0900ac beego:fix static file router 2014-06-11 01:19:39 +08:00
6809c97611 beego: improve performance 2014-06-11 01:11:32 +08:00
675643c68d beego: run mode support test 2014-06-10 22:47:48 +08:00
06f4bf493d ignore nil time 2014-06-10 22:10:58 +08:00
4786fb0948 beego:fix typo NewControllerRegister 2014-06-10 20:12:57 +08:00
fdb5672b7a beego:delete debug information 2014-06-10 18:10:32 +08:00
107a7a21c0 beego: dev mode print request router & pattern 2014-06-10 18:09:07 +08:00
dbebf8df4b beego:namespace support nest
ns := NewNamespace("/v3",
		NSAutoRouter(&TestController{}),
		NSNamespace("/shop",
			NSGet("/order/:id", func(ctx *context.Context) {
				ctx.Output.Body([]byte(ctx.Input.Param(":id")))
			}),
		),
	)
2014-06-10 17:11:02 +08:00
f7b01aab13 beego: modify the filter sequence 2014-06-10 11:02:41 +08:00
2570f075d9 beego:change ControllerComments exported 2014-06-09 17:46:13 +08:00
21cb8ea4a3 beego:AST code 2014-06-09 17:33:04 +08:00
6c8a7f1382 beego: router change to method Tree 2014-06-09 10:11:37 +08:00
e00eab7f49 beego: change to tree 2014-06-08 20:24:07 +08:00
bfabcfcb6b beego:router tree 2014-06-08 20:24:07 +08:00
f06ba52ede Merge pull request #633 from dlt/develop
fixed typo on constant applicationXml
2014-06-07 01:10:42 +08:00
fcae000a79 fixed typo on constant applicationXml 2014-06-06 13:56:34 -03:00
3e4c015982 Merge pull request #631 from curvesoft/master
cookiejar support
2014-06-04 23:02:52 +08:00
d689be30e8 remove httplib_test.php 2014-06-04 22:12:37 +08:00
7b110a0b73 remove httplib_test.php 2014-06-04 22:09:43 +08:00
e3033b57a6 1.gofmt httplib.go httplib_test.go
2.replace test url to http://httpbin.org functions
2014-06-04 21:15:24 +08:00
bd537554ea 1.gofmt httplib.go httplib_test.go
2.replace test url to http://httpbin.org functions
2014-06-04 21:04:50 +08:00
ebb3b91df9 1、增加cookiejar支持
2、增加Setting结构,便于统一设置请求参数
3、增加服务端测试php脚本
2014-06-03 21:20:10 +08:00
a65ad1a4bc fix the time test case 2014-05-31 14:28:44 +08:00
bdc01f52a0 Merge pull request #626 from mvpmvh/michael
Michael
2014-05-31 14:25:40 +08:00
a673a85d4a added tests config/json_test that test missing key usecases. created a template function to fetch AppConfig values 2014-05-30 23:48:23 -05:00
61008fe75c udpated timezone in templatefunc_test. changed error message to be more descriptive when tests fail 2014-05-30 14:12:21 -05:00
5dee6b7d19 beego: fix the namespace cond 2014-05-28 10:23:31 +08:00
f6c7a6bd32 beego: improve the admin router print 2014-05-27 17:27:22 +08:00
d2eece9a39 session: #620 make the session never read empty 2014-05-27 15:45:35 +08:00
c3a23b28ee beego: improve the RandomCreateBytes #620
when rand.Read is failed. will use the math/rand to generate the rand
bytes
2014-05-27 15:29:43 +08:00
9083927c6a beego: enhance the XSRFKEY from 15 to 32 #620 2014-05-27 15:00:10 +08:00
3f7e91e6a4 beego:fix *.* router bug 2014-05-26 10:15:56 +08:00
a2a6f47afa beego: support other config provider 2014-05-25 22:37:38 +08:00
23229ef9ef beego: BeegoServerName & beego.Run
BeegoServerName change to beegoServer+Version
beego.Run(“:8089”)
2014-05-25 22:35:20 +08:00
0d17d974cd beego: update namespace 2014-05-23 15:56:25 +08:00
17104c25a2 beego: Refactoring Filter & add comments 2014-05-20 18:47:41 +08:00
8b374d7f90 beego: add benchmark 2014-05-20 18:20:44 +08:00
fa3234147a httplib:drone can't upload file 2014-05-20 17:34:52 +08:00
33ad6c1370 beego: remove app funciont & fix #590
config := tls.Config{
    ClientAuth: tls.RequireAndVerifyClientCert,
    Certificates: []tls.Certificate{cert},
    ClientCAs: pool,
}
config.Rand = rand.Reader

beego.BeeApp.Server. TLSConfig = &config
2014-05-20 17:28:06 +08:00
04290dfc68 beego: delete hotupdate 2014-05-20 16:41:39 +08:00
03080b3ef2 beego:1.2.0 2014-05-20 15:53:41 +08:00
3f2a712ba8 beego:change default port 2014-05-20 15:40:05 +08:00
f215aa4810 beego: change the error tips 2014-05-20 15:34:27 +08:00
18a02d7d60 beego:support https & http listen 2014-05-20 15:30:17 +08:00
3f4d750dc4 utils: improve the file grep 2014-05-20 14:32:06 +08:00
9f01aeed31 beego:remove unused code 2014-05-19 18:52:48 +08:00
b45f0b9bf6 beego: fix #478 2014-05-17 02:56:50 +08:00
cf04ade603 merger master 2014-05-17 02:29:41 +08:00
92f6181616 beego: change the version to 1.2.0 2014-05-17 02:26:52 +08:00
9270a0504a beego: admin support link 2014-05-17 02:26:52 +08:00
1da37f6ce1 beego: controller add ServeFormatted
ServeFormatted serve Xml OR Json, depending on the value of the Accept
header
2014-05-17 02:26:52 +08:00
ef6d9b9a94 session: support memcache interface 2014-05-17 02:26:52 +08:00
c265786251 session:support struct.
gob.Register(v)
2014-05-17 02:26:51 +08:00
c5c806b58e beego: XSRF support Controller level fix #610
default value is true when you Enable Global XSRF, also can control in
the prepare function to change the value.
2014-05-17 02:26:51 +08:00
e657dcfd5f beego: support namespace
ns := beego.NewNamespace("/v1/api/")
ns.Cond(func(ctx *context.Context)bool{
	    if ctx.Input.Domain() == "www.beego.me" {
	    	return true
	    }
	    return false
	})
.Filter("before", Authenticate)
.Router("/order",	&admin.OrderController{})
.Get("/version",func (ctx *context.Context) {
	ctx.Output.Body([]byte("1.0.0"))
})
.Post("/login",func (ctx *context.Context) {
	if ctx.Query("username") == "admin" && ctx.Query("username") ==
"password" {

	}
})
.Namespace(
	NewNamespace("/shop").
		Get("/order/:id", func(ctx *context.Context) {
		ctx.Output.Body([]byte(ctx.Input.Param(":id")))
	}),
)
2014-05-17 02:26:51 +08:00
2ed9b2bffd orm: add test for unexported struct field 2014-05-17 02:26:51 +08:00
55ad951bce beego: support more router
//design model
	beego.Get(router, beego.FilterFunc)
	beego.Post(router, beego.FilterFunc)
	beego.Put(router, beego.FilterFunc)
	beego.Head(router, beego.FilterFunc)
	beego.Options(router, beego.FilterFunc)
	beego.Delete(router, beego.FilterFunc)
	beego.Handler(router, http.Handler)

//example

beego.Get("/user", func(ctx *context.Context) {
	ctx.Output.Body([]byte("Get userlist"))
})

beego.Post("/user", func(ctx *context.Context) {
	ctx.Output.Body([]byte("add userlist"))
})

beego.Delete("/user/:id", func(ctx *context.Context) {
	ctx.Output.Body([]byte([]byte(ctx.Input.Param(":id")))
})

import (
    "http"
    "github.com/gorilla/rpc"
    "github.com/gorilla/rpc/json"
)

func init() {
    s := rpc.NewServer()
    s.RegisterCodec(json.NewCodec(), "application/json")
    s.RegisterService(new(HelloService), "")
    beego.Handler("/rpc", s)
}
2014-05-17 02:26:51 +08:00
ef815bf5fc config: fix the import issue 2014-05-17 02:26:51 +08:00
6082a0af3e bug fixed 2014-05-17 02:26:51 +08:00
be30fb7937 refator func 2014-05-17 02:26:51 +08:00
f4e7d63e65 httplib support to set the protocol version for incoming requests 2014-05-17 02:26:51 +08:00
14688f240f httplib:support file upload 2014-05-17 02:26:51 +08:00
dce09837b9 fix the typo 2014-05-17 02:26:50 +08:00
3b9a404138 beego: support other analisys & fix typo 2014-05-17 02:26:50 +08:00
a6f55b59cf beego: add link in the admin console 2014-05-17 02:26:50 +08:00
c188cbbcb4 update all files License 2014-05-17 02:26:50 +08:00
4245521660 fix #576 2014-05-17 02:26:50 +08:00
05e5baaa9f beego:add post test case 2014-05-17 02:26:50 +08:00
54b92e9599 context:add Bind function
// Bind data from request.Form[key] to dest
// like
/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=
astaxie
// var id int  beegoInput.Bind(&id, "id")  id ==123
// var isok bool  beegoInput.Bind(&isok, "isok")  id ==true
// var ft float64  beegoInput.Bind(&ft, "ft")  ft ==1.2
// ol := make([]int, 0, 2)  beegoInput.Bind(&ol, "ol")  ol ==[1 2]
// ul := make([]string, 0, 2)  beegoInput.Bind(&ul, "ul")  ul ==[str
array]
// user struct{Name}  beegoInput.Bind(&user, "user")  user ==
{Name:"astaxie"}
2014-05-17 02:26:50 +08:00
aa68ffecec beego: support not-empty value in router fix #555 2014-05-17 02:26:50 +08:00
78991c81ab make Maxage work 2014-05-17 02:26:49 +08:00
348ff13857 update the error message 2014-05-17 02:26:49 +08:00
52817fb668 allow unexported fields on model structs 2014-05-17 02:26:49 +08:00
2c59ff1cc6 beego: admin support link 2014-05-17 02:20:48 +08:00
6bdf0838ce beego: controller add ServeFormatted
ServeFormatted serve Xml OR Json, depending on the value of the Accept
header
2014-05-17 01:26:59 +08:00
31a63c5d50 session: support memcache interface 2014-05-17 01:19:47 +08:00
237aaadd65 session:support struct.
gob.Register(v)
2014-05-17 00:43:51 +08:00
34ddcef1dc beego: XSRF support Controller level fix #610
default value is true when you Enable Global XSRF, also can control in
the prepare function to change the value.
2014-05-17 00:12:25 +08:00
f6ce2656db beego: support namespace
ns := beego.NewNamespace("/v1/api/")
ns.Cond(func(ctx *context.Context)bool{
	    if ctx.Input.Domain() == "www.beego.me" {
	    	return true
	    }
	    return false
	})
.Filter("before", Authenticate)
.Router("/order",	&admin.OrderController{})
.Get("/version",func (ctx *context.Context) {
	ctx.Output.Body([]byte("1.0.0"))
})
.Post("/login",func (ctx *context.Context) {
	if ctx.Query("username") == "admin" && ctx.Query("username") ==
"password" {

	}
})
.Namespace(
	NewNamespace("/shop").
		Get("/order/:id", func(ctx *context.Context) {
		ctx.Output.Body([]byte(ctx.Input.Param(":id")))
	}),
)
2014-05-16 23:47:29 +08:00
b647026dff orm: add test for unexported struct field 2014-05-16 13:14:15 +08:00
568c0c47f0 Merge pull request #542 from kylemcc/develop
orm: allow unexported fields on model structs
2014-05-16 13:11:55 +08:00
2629de28f2 beego: support more router
//design model
	beego.Get(router, beego.FilterFunc)
	beego.Post(router, beego.FilterFunc)
	beego.Put(router, beego.FilterFunc)
	beego.Head(router, beego.FilterFunc)
	beego.Options(router, beego.FilterFunc)
	beego.Delete(router, beego.FilterFunc)
	beego.Handler(router, http.Handler)

//example

beego.Get("/user", func(ctx *context.Context) {
	ctx.Output.Body([]byte("Get userlist"))
})

beego.Post("/user", func(ctx *context.Context) {
	ctx.Output.Body([]byte("add userlist"))
})

beego.Delete("/user/:id", func(ctx *context.Context) {
	ctx.Output.Body([]byte([]byte(ctx.Input.Param(":id")))
})

import (
    "http"
    "github.com/gorilla/rpc"
    "github.com/gorilla/rpc/json"
)

func init() {
    s := rpc.NewServer()
    s.RegisterCodec(json.NewCodec(), "application/json")
    s.RegisterService(new(HelloService), "")
    beego.Handler("/rpc", s)
}
2014-05-16 10:18:19 +08:00
10d2c7c328 config: fix the import issue 2014-05-16 10:18:19 +08:00
af7ac98bd6 Merge pull request #609 from JessonChan/develop
[important] bug fixed
2014-05-15 11:47:07 +08:00
6f78f1d4b2 bug fixed 2014-05-15 11:34:44 +08:00
9f95fd3309 Merge pull request #608 from JessonChan/develop
refactor func
2014-05-14 20:47:01 +08:00
74c309cefd refator func 2014-05-14 20:08:51 +08:00
b6d63c84ae Merge pull request #605 from francoishill/patch-6
Update app.go
2014-05-14 13:12:15 +08:00
bc2f1fb79d Update app.go 2014-05-13 17:19:50 +02:00
29e113a48a Merge pull request #597 from tobyzxj/develop
httplib support to set the protocol version for incoming requests
2014-05-09 16:25:33 +08:00
3caf1896d6 httplib support to set the protocol version for incoming requests 2014-05-09 15:48:50 +08:00
d5d5f23756 httplib:support file upload 2014-05-08 16:58:08 +08:00
46641ef3b6 fix the typo 2014-05-08 10:51:29 +08:00
8ed459512f Merge pull request #591 from luosangnanka/master
Update Session package README.md
2014-05-05 16:43:53 +08:00
25768f0109 Update README.md
Update the json format of session for file, redis, mysql, cookie, there are errors in these json string, such as after the param `ProviderConfig` and there is a lost of `"` in the line 61 of the `gclifetime`.
2014-05-05 16:21:50 +08:00
a56f67e073 Update README.md
Update the json format of session for file, redis, mysql, there are errors in these json string, after `ProviderConfig` params.
2014-05-05 16:15:49 +08:00
8164f9821d Update README.md
Update the json format of session for redis, there is an error in that json string
2014-05-05 16:13:03 +08:00
b2a69f505c beego: support other analisys & fix typo 2014-04-28 18:07:30 +08:00
e307bd7ba9 beego:hotfix for multipart/form-data 2014-04-15 05:05:53 +08:00
b2bd829d39 beego: add link in the admin console 2014-04-15 05:03:20 +08:00
f9b8617fa3 context: fix multipart/form-data 2014-04-15 05:02:50 +08:00
6c6e4ecfbc update all files License 2014-04-12 13:18:18 +08:00
8bcf03c652 fix #576 2014-04-11 16:08:43 +08:00
1ea449aa3a beego:add post test case 2014-04-10 22:33:32 +08:00
a99802b7d1 beego:query data from Form & params 2014-04-10 22:21:08 +08:00
b212ec8dab beego:query data from Form & params 2014-04-10 22:20:46 +08:00
3e16feb1e2 beego: fix flash errors 2014-04-10 18:16:08 +08:00
e50cbecf80 beego: fix flash errors 2014-04-10 18:14:18 +08:00
127b85bcaa context:add Bind function
// Bind data from request.Form[key] to dest
// like
/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=
astaxie
// var id int  beegoInput.Bind(&id, "id")  id ==123
// var isok bool  beegoInput.Bind(&isok, "isok")  id ==true
// var ft float64  beegoInput.Bind(&ft, "ft")  ft ==1.2
// ol := make([]int, 0, 2)  beegoInput.Bind(&ol, "ol")  ol ==[1 2]
// ul := make([]string, 0, 2)  beegoInput.Bind(&ul, "ul")  ul ==[str
array]
// user struct{Name}  beegoInput.Bind(&user, "user")  user ==
{Name:"astaxie"}
2014-04-10 00:31:16 +08:00
89dde6cd9d Merge branch 'develop' of https://github.com/astaxie/beego into develop 2014-04-09 21:43:32 +08:00
5a52949761 beego: support not-empty value in router fix #555 2014-04-09 21:42:57 +08:00
a78162e9e4 Merge pull request #573 from linluxiang/master
Make the Maxage Config of cookie session work
2014-04-09 14:42:29 +08:00
931e6162ac Merge pull request #574 from dz1984/fix/cache_test
Fix/cache test
2014-04-09 14:42:01 +08:00
1bb876f2df make Maxage work 2014-04-09 13:18:44 +08:00
18f70e6ee4 update the error message 2014-04-09 12:39:12 +08:00
82ca85dc65 release new version 1.1.4 2014-04-08 17:49:12 +08:00
f9cc9e9eb3 beego: release new version 1.1.4 2014-04-08 17:45:57 +08:00
18fee2ad9a beego: fixed serious Directory Traversal 2014-04-08 17:43:25 +08:00
4124760706 beego: filter the static file's url 2014-04-07 14:20:30 +08:00
3fe4f8c362 toolbox: modify the godocs 2014-04-06 01:05:20 +08:00
5a863b45f4 beego: BeeAdminApp private 2014-04-06 01:02:10 +08:00
3ad30d48b5 beego: fix the godoc 2014-04-06 00:53:18 +08:00
3255a43568 beego: move staticServer to New file 2014-04-06 00:18:21 +08:00
73d757e3f4 context: improve the formParse 2014-04-06 00:08:03 +08:00
deb28dd873 fix session test case 2014-04-04 10:16:34 +08:00
7f394feab5 beego: hot fix for TestBeegoInit can't parsefile 2014-04-04 10:04:36 +08:00
8cbea70e07 beego: hot fix for TestBeegoInit can't parsefile 2014-04-04 10:04:22 +08:00
f222f5b238 beego: hot fix for console logs & go run can't find file. 2014-04-04 09:57:57 +08:00
3f1de576e4 fix go run hello.go & console log 2014-04-04 09:57:51 +08:00
f48ca96a7e beego: fix log output when SetLogger has error 2014-04-04 09:57:45 +08:00
9421a21037 beego: fix log output when SetLogger has error 2014-04-04 09:57:37 +08:00
5c06cd090c beego: hot fix for console logs & go run can't find file. 2014-04-04 09:56:25 +08:00
fc982feeb9 fix go run hello.go & console log 2014-04-04 09:49:57 +08:00
31de651053 beego: fix log output when SetLogger has error 2014-04-04 09:49:57 +08:00
f4d62d3193 beego: fix dependency of cache / session sub package 2014-04-04 08:31:22 +08:00
acbdeb62e8 beego: fix log output when SetLogger has error 2014-04-04 08:22:26 +08:00
d58e9e6e12 beego: move dependency module to sub package 2014-04-03 23:41:48 +08:00
6497f29ed7 version 1.1.2 release 2014-04-03 15:56:31 +08:00
1705b42546 beego: change version from 1.1.1 to 1.1.2 2014-04-03 15:54:37 +08:00
5505cc09ed beego: move init to a fund & add a new fund TestBeegoInit
support Test with everything init
2014-04-03 15:07:20 +08:00
12e1ab0f80 beego: setLogger return error 2014-04-02 23:45:44 +08:00
9c5ceb70cc Logs: modify StartLogger to private 2014-04-02 23:43:37 +08:00
bf0b1af64f add workPath don't chdir when go run or go test 2014-04-01 18:08:00 +08:00
9c959fba4d fix string 2014-03-29 14:59:55 +08:00
5588bfc35e support filter to get router. get runController & runMethod 2014-03-29 14:55:34 +08:00
2f4acf46c6 modify the template file 2014-03-27 08:49:57 +08:00
c7437d7590 fix Cookie for session 2014-03-26 13:51:35 +08:00
4f819dbd9a Add a function SetLogFuncCall to enable 2014-03-26 00:06:25 +08:00
3f5fee2dc6 Logs support file & filenum 2014-03-25 23:48:58 +08:00
c7f16b5d5a Merge pull request #551 from steamonimo/develop
session provider for postgresql
2014-03-25 21:28:42 +08:00
8d1268c0a9 session provider for postgresql
This provider is based on the mysql provider: sess_mysql.go
2014-03-25 12:45:23 +01:00
c921b0aa5d fix #533 change the function name 2014-03-21 14:33:11 +08:00
589f97130c add w.Rotate 2014-03-21 14:33:11 +08:00
443aaadcce fix #533 change the function name 2014-03-21 14:24:00 +08:00
ff1938054a add w.Rotate 2014-03-21 14:07:03 +08:00
d79c297880 rollback: set httponly default is false. 2014-03-20 09:51:25 +08:00
65631e0522 fix orm test 2014-03-19 10:00:26 +08:00
a879e412a1 #514 2014-03-19 09:46:09 +08:00
4785ac14d7 allow unexported fields on model structs 2014-03-18 18:00:07 -05:00
50bc1ef757 rollback: set httponly default is false. 2014-03-17 12:27:04 +08:00
7bacb25725 Merge pull request #538 from admpub/patch-2
validators bug fixed
2014-03-14 15:44:05 +08:00
ad8418720f bug fixed 2014-03-14 14:47:52 +08:00
b59dae6fb8 Merge pull request #537 from jfolkins/develop
(REF#519) Enhancement: Allow developer to set FlashName and FlashSeperator values
2014-03-14 14:22:56 +08:00
4951314837 added FlashName,FlashSeperator, & Tests 2014-03-13 22:34:22 -07:00
8188873216 omit the data init 2014-03-14 12:00:53 +08:00
5d392b76c7 Merge pull request #531 from unphp/develop
Update router.go
2014-03-14 10:08:47 +08:00
c6a34b8efd Merge branch 'develop' of github.com:astaxie/beego into develop 2014-03-13 23:32:03 +08:00
95e67ba2c2 orm now support custom builtin types as model struct field or query args fix #489 2014-03-13 23:31:47 +08:00
439b1afb85 Merge branch 'release/1.1.1' 2014-03-12 21:25:41 +08:00
745e9fb0fb change version to 1.1.1 2014-03-12 21:24:23 +08:00
769f7c751b fix static file route 2014-03-12 21:06:20 +08:00
a8c2deb014 Merge pull request #530 from cnphpbb/develop
Update beego.go - Slice types exist trap
2014-03-12 18:56:12 +08:00
624f6258ee fix read / 2014-03-12 18:29:45 +08:00
43c977ab62 Update router.go
To append a slice's value into "exceptMethod", for controller's methods shouldn't reflect to AutoRouter
2014-03-12 17:20:53 +08:00
6c92ca2a16 fix bug for static file like /static /static_js /static_css 2014-03-12 17:03:34 +08:00
0f015d75d2 Update beego.go - Modify GroupRouters all function
Modify the file beego.go. 
Type GroupRouters all function 
Slice types exist trap bug.
2014-03-12 16:50:13 +08:00
217c3a2e87 Merge pull request #508 from voidd/develop
Couchbase session provider
2014-03-12 16:23:41 +08:00
ff6120cb93 Merge branch 'develop' of https://github.com/astaxie/beego into develop
Conflicts:
	session/sess_file.go
2014-03-12 15:57:57 +08:00
53aaf3b4a9 orm add ResetModelCache api for test case 2014-03-12 15:56:05 +08:00
d5b5c18cf9 orm add GetDB api #433 2014-03-12 15:56:05 +08:00
cacdb3228d orm add operator between #518 2014-03-12 15:56:05 +08:00
d0949b64c6 fix issue#521, return error when init redis session 2014-03-12 15:56:05 +08:00
d49984d47d Fix basic auth plugin example.
NewBasicAuthenticator requires passing a second argument for Realm.
2014-03-12 15:56:05 +08:00
9f3af59250 add support for sql.Null* types
Change instructions for sqlite3 tests to use in memory db for much faster
2014-03-12 15:56:05 +08:00
57afd3d979 GetStrings action as GetString 2014-03-12 15:56:05 +08:00
f7430a2ce1 enhance the static file path. If user foget / path.Join will auto fix it. 2014-03-12 15:56:05 +08:00
7389f0507e fix #505 2014-03-12 15:56:04 +08:00
ee889e9975 skip cookie args when value is nil 2014-03-12 15:56:04 +08:00
d8b9db8d3e move SetSecureCookie / GetSecureCookie to *context.Context and alias in Controller 2014-03-12 15:56:04 +08:00
9b498feac7 update output.Cookie 2014-03-12 15:56:04 +08:00
69982c62c8 path default is /
httponly default true
seuce default not set
2014-03-12 15:56:04 +08:00
b405e19f56 delete MaxAge cookielifeTime replace 2014-03-12 15:56:04 +08:00
828235b4c1 httplib support set transport and proxy 2014-03-12 15:56:04 +08:00
430a0a971f orm insert skip auto_now_add when user custom a value 2014-03-12 15:56:04 +08:00
5be22a99a8 fix captcha urlPrefix 2014-03-12 15:56:04 +08:00
5d02b18db4 register interface to gob automatically 2014-03-12 15:56:04 +08:00
97b68bdd66 fix bug, can not remove session file 2014-03-12 15:56:04 +08:00
5583fa2054 orm add ResetModelCache api for test case 2014-03-10 20:52:04 +08:00
00a410ad1a orm add GetDB api #433 2014-03-10 20:50:54 +08:00
6ca30386b8 orm add operator between #518 2014-03-10 20:19:29 +08:00
03b17a2ca9 Merge pull request #522 from pengfei-xue/develop
fix issue#521, return error when init redis session
2014-03-07 16:45:34 +08:00
9957a867cd fix issue#521, return error when init redis session 2014-03-07 16:35:34 +08:00
f3ba41a991 Merge pull request #517 from zkirill/develop
Fix basic auth plugin example.
2014-03-03 09:58:40 +08:00
4befa1bc1b Fix basic auth plugin example.
NewBasicAuthenticator requires passing a second argument for Realm.
2014-03-02 17:44:23 -08:00
9e3ebc88c4 Merge pull request #513 from hobeone/develop
add support for sql.Null* types, thx hobeone
2014-02-28 12:17:58 +08:00
6e00cfb464 add support for sql.Null* types
Change instructions for sqlite3 tests to use in memory db for much faster
2014-02-27 19:53:35 -08:00
c358c18018 Merge pull request #511 from francoishill/patch-2
Update sess_file.go
2014-02-28 09:40:28 +08:00
adf2a590fc Update sess_file.go
Lock required to ensure the File sessions work correct.
2014-02-27 15:34:38 +02:00
edb8bac5bc Merge pull request #510 from jfolkins/fix_sessregenid
fix: added nil check on c.CruSession to prevent crash
2014-02-27 10:34:39 +08:00
47d7ac06b7 fix: added nil check on c.CruSession to prevent crash 2014-02-26 16:44:31 -08:00
d05270d2ec GetStrings action as GetString 2014-02-26 15:02:58 +08:00
04a19685ed enhance the static file path. If user foget / path.Join will auto fix it. 2014-02-26 14:44:41 +08:00
62555771d0 fix #505 2014-02-24 15:55:38 +08:00
9dc93cbab0 skip cookie args when value is nil 2014-02-22 14:40:18 +08:00
7f5fb871de move SetSecureCookie / GetSecureCookie to *context.Context and alias in Controller 2014-02-22 11:58:53 +08:00
03037170e1 update output.Cookie 2014-02-22 11:12:57 +08:00
002e0854ab path default is /
httponly default true
seuce default not set
2014-02-22 10:37:14 +08:00
2bc70f62ce delete MaxAge cookielifeTime replace 2014-02-22 01:04:47 +08:00
8bf0e67b79 httplib support set transport and proxy 2014-02-20 13:53:13 +08:00
b310be1fcf orm insert skip auto_now_add when user custom a value 2014-02-20 13:45:31 +08:00
a54353b51c fix captcha urlPrefix 2014-02-20 13:44:34 +08:00
04c2ba01bc Merge branch 'develop' of https://github.com/voidd/beego 2014-02-19 22:44:06 +04:00
296bcab425 couchbase session provider 2014-02-19 15:54:16 +04:00
060b321182 Merge pull request #497 from pengfei-xue/develop
register interface to gob automatically
2014-02-18 16:47:59 +08:00
05a0a4b046 register interface to gob automatically 2014-02-14 17:52:57 +08:00
8906d3e77c Merge pull request #494 from pengfei-xue/develop
fix bug, can not remove session file
2014-02-13 20:19:42 +08:00
e822642cb0 fix bug, can not remove session file 2014-02-13 18:24:05 +08:00
a38a4f0343 Merge pull request #492 from TimothyYe/master
Fix spelling mistake
2014-02-10 14:51:26 +08:00
e8a22660e4 Update error.go
Fix the spelling mistake of error page.
2014-02-10 12:55:53 +08:00
92196c602b Merge branch 'release/release1.1.0' 2014-02-10 11:39:03 +08:00
76222ac8d0 change 1.0.1 to 1.1.0 2014-02-10 11:33:53 +08:00
a184c23603 basic auth for plugin 2014-02-10 11:31:54 +08:00
1b778509c9 should copy the data direct. don't need range 2014-02-08 10:42:34 +08:00
c4250872ca controller data inherit the context's data 2014-02-07 17:25:56 +08:00
17dd72241b Merge pull request #491 from fuxiaohei/develop
add comments for testing, utils and validation packages
2014-02-07 16:25:05 +08:00
ce2984f09a add comments for testing, utils and validation packages 2014-02-07 16:07:31 +08:00
846d766499 Merge branch 'develop' of git://github.com/astaxie/beego 2014-02-07 15:34:01 +08:00
bbc71142d7 controller can controller whether render the template.
EnableReander default is true.
2014-02-07 00:38:58 +08:00
74804bc586 Merge pull request #490 from fuxiaohei/develop
add comments for session and toolbox package
2014-02-04 06:25:18 -08:00
1d08a54f44 add comments for toolbox packages 2014-01-29 19:12:00 +08:00
682544165f add comments for session packages, part 2 2014-01-29 18:15:09 +08:00
3f0ec5c0ca Merge branch 'develop' of git://github.com/astaxie/beego into develop 2014-01-29 01:06:49 +08:00
0e2872324f add comments for session packages, part 1 2014-01-29 01:05:56 +08:00
2fb575838d Merge pull request #474 from pengfei-xue/develop
fix bug, redis session doesnt work
2014-01-28 01:50:20 -08:00
ab8f8d532a Merge pull request #487 from cloudaice/log-feature
fixed bug: in logs package check if platform is windows
2014-01-27 20:50:25 -08:00
d93f112083 fixed bug: in logs package check if platform is windows 2014-01-28 11:26:43 +08:00
9384e87083 orm 1. add api: NewOrmWithDB, AddAliasWthDB; 2. RawSeter -> add api: RowsToMap, RowsToStruct; 3. RawSeter -> change api: Values, ValuesList, ValuesFlat add optional params comumns. 2014-01-27 01:48:00 +08:00
34eff4cc1f bugfix, delete the sid if it's values is empty
* regenerate sid, if the old key doesn't exists, set the new one directly
2014-01-25 10:55:49 +08:00
8296713ba4 Merge pull request #477 from kylemcc/read_or_create
Add a ReadOrCreate method:
2014-01-24 18:01:24 -08:00
d014ccfb8e bug fix, session stored in redis cannot be deleted 2014-01-23 19:28:58 +08:00
190039b6f8 Add a ReadOrCreate method:
m := &User{Name: "Kyle"}
// Returns a boolean indicating whether the object was created,
// the primary key of the object, or an error.
created, id, err := orm.ReadOrCreate(m, "Name")
2014-01-22 09:15:21 -06:00
edf7982567 Merge pull request #473 from cloudaice/logs-feature
diffrent level logs display diffrent color
2014-01-21 19:03:32 -08:00
1509a6b681 fix bug, redis session doesnt work 2014-01-21 18:48:16 +08:00
11e6c2829b diffrent level logs display diffrent color 2014-01-21 18:00:17 +08:00
38f93a7ba9 Merge pull request #470 from fuxiaohei/develop
add comments for orm and middleware packages.
2014-01-17 17:44:52 -08:00
6b5108ef92 Merge pull request #1 from fuxiaohei/develop
merge develop
2014-01-17 07:48:39 -08:00
828a306069 add comments for orm package, done 2014-01-17 23:28:54 +08:00
4c527dde65 add comments for orm packages, part 2 2014-01-17 17:25:17 +08:00
f5a5ebe16b add comments for orm packages, part 1 2014-01-17 17:04:15 +08:00
32799bc259 add comments for middleware packages, fix typo error 2014-01-17 16:03:01 +08:00
91d75e8925 add readme for captcha, and enhanced performance 2014-01-17 12:07:30 +08:00
3e40041219 Merge pull request #468 from cloudaice/patch-1
Update README.md
2014-01-16 17:28:13 -08:00
7d5ee0d692 Update README.md 2014-01-17 00:17:43 +08:00
91cbe1f29b add some comments for captcha 2014-01-16 21:34:59 +08:00
f419c12427 add captcha util 2014-01-16 20:53:35 +08:00
fee3c2b8f9 add Strings interface can return []string sep by ;
Example:
peers = one;Two;Three
2014-01-15 17:19:03 +08:00
b016102d34 add coding 2014-01-15 09:40:33 +08:00
c20e1ab1e2 Merge pull request #463 from NormanZhang/develop
Update SessionExist to close the db connection
2014-01-14 06:13:34 -08:00
dc767b65df Update SessionExist to close the db connection
close the mysql connection
2014-01-14 19:54:32 +08:00
63f19974cd Merge pull request #460 from pengfei-xue/develop
use connection pool for redis, support auto connection
2014-01-11 06:39:20 -08:00
6e9ba0ea7f fix SessionRegenerateID should release old SessionStore and release new SessionStore in router.go 2014-01-11 17:01:33 +08:00
3b99f37aa1 add a empty fake config Initialize AppConfig to avoid nil pointer runtime error. 2014-01-11 14:28:11 +08:00
e8f5c10488 Merge pull request #457 from luxuchu/patch-1
fix #453
2014-01-10 22:21:27 -08:00
cb55009c8b remove mutex 2014-01-10 20:31:43 +08:00
b64e70e7df use connection pool for redis cache 2014-01-10 18:31:15 +08:00
8d79f8387b #441 fix detect timezone in mysql 2014-01-10 16:50:03 +08:00
afadb3f6df Update beego.go 2014-01-10 13:31:08 +08:00
844412c302 fix #453 2014-01-09 21:37:50 +08:00
299cb9130b Merge pull request #454 from pengfei-xue/develop
support redis cache auto connection
2014-01-09 04:49:31 -08:00
0b42e5573b align memcache operations with redis 2014-01-09 18:50:30 +08:00
a369b15ef2 reset cache connection to nil, if err isio.EOF
* this will support auto-connection
2014-01-09 18:49:18 +08:00
e34f8c4634 add cookie test 2014-01-08 23:24:31 +08:00
d7f2c738c8 add attach file 2014-01-08 22:35:42 +08:00
d06c04277f support send mail 2014-01-08 22:31:26 +08:00
aa2fef0d36 update sessionRelease
1. mysql fix last access time not update
2. mysql & redid Release when data is empty
3. add maxlifetime distinct Gclifetime
2014-01-08 20:54:20 +08:00
b766f65c26 #436 support insert multi 2014-01-06 11:31:35 +08:00
6f3a759ba5 gmfim add lock. fix #445 2014-01-05 23:16:47 +08:00
338124e3fb fix #443 2014-01-05 15:43:48 +08:00
31bdb793cf make fix 2014-01-05 15:21:50 +08:00
9cbd475701 beego support new version session 2014-01-05 14:59:39 +08:00
481448fa90 modify session module
change a log
2014-01-05 14:48:36 +08:00
95c65de97c fix #440 2014-01-04 22:30:17 +08:00
ef79a2b484 fix #440 2014-01-04 00:04:15 +08:00
20cfece1ab Merge pull request #438 from Codonaut/error_page_improvements
Error page improvements
2014-01-02 07:17:49 -08:00
c433b7029f added back a <br> 2014-01-02 09:54:15 -05:00
f5cf2876dd Improved the language on the error pages 2014-01-02 09:53:09 -05:00
480aa521e5 fix #430 2014-01-01 20:50:06 +08:00
d57557dc55 add AutoRouterWithPrefix 2014-01-01 17:57:57 +08:00
803d91c077 support modules design!
// 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)
2013-12-31 23:43:15 +08:00
62ee48dcbf Merge branch 'develop' of https://github.com/astaxie/beego into develop 2013-12-31 20:48:46 +08:00
1e57587fe9 support Hijacker #428 2013-12-31 20:47:48 +08:00
61c0b3e286 fix db locked 2013-12-31 09:55:29 +08:00
383a04f4c2 move initmime from beego.Run to hookfunc 2013-12-31 00:34:47 +08:00
eea272482b Merge pull request #425 from fuxiaohei/master
add comments in logs package.
2013-12-30 07:38:51 -08:00
94ad13c846 add comments in logs package 2013-12-30 23:32:57 +08:00
412a4a04de #384 2013-12-30 23:04:13 +08:00
e0e8fa6e2a fix #413 2013-12-30 22:51:54 +08:00
a1e29b0b75 Merge pull request #422 from pengfei-xue/devel
simplify condition test for trailing /
2013-12-30 04:58:50 -08:00
984b0cbf31 1. :all param default expr change from (.+) to (.*)
2. add hookfunc to support appstart hook
2013-12-30 15:06:51 +08:00
3118c6c23f Merge commit '7a3d05ebf3fd36ea7e534de64ad38c23367ac97f' 2013-12-30 11:37:20 +08:00
3a08eec1f9 simplify condition test for trailing / 2013-12-30 11:29:35 +08:00
ecfd11adb4 fix typo healthcheck url 2013-12-29 11:01:19 +08:00
95dc670eb4 fix #416 2013-12-28 23:06:20 +08:00
7a3d05ebf3 when pattern is /admin while the url is /admin/ should return 200. fix #416 2013-12-28 23:04:45 +08:00
62f54cbbee fix typo error 2013-12-28 20:14:36 +08:00
4d7f7ffa37 Merge pull request #418 from fuxiaohei/master
add comments for httplib package.
2013-12-27 16:52:29 -08:00
cb876268b5 add comments for httplib package. 2013-12-27 17:11:39 +08:00
094f2fbab8 Merge pull request #415 from fuxiaohei/master
add comments for context package.
2013-12-26 07:40:19 -08:00
2d77c4dc49 fix code with no need line 2013-12-26 00:44:49 +08:00
f535916fae add comments for context package. 2013-12-25 20:13:38 +08:00
673993fa2b Merge pull request #412 from fuxiaohei/master
add comment in config package.
2013-12-24 07:06:02 -08:00
6f3803ce8c Merge remote-tracking branch 'astaxie/master' 2013-12-24 21:59:37 +08:00
a1f6039d82 gofmt code 2013-12-24 21:59:00 +08:00
0183608a59 add comments for config package. 2013-12-24 21:57:33 +08:00
5b1afcdb5a add timeout description for file and memory cache. 2013-12-24 21:56:48 +08:00
053e7a6aa6 Merge remote-tracking branch 'astaxie/master' 2013-12-24 21:09:17 +08:00
ba94479efd Merge remote-tracking branch 'astaxie/master' 2013-12-24 13:05:09 +08:00
ba3a9bee4c Merge remote-tracking branch 'astaxie/master' 2013-12-22 16:25:08 +08:00
f96eec6dea fix a code broken when documenting 2013-12-22 15:31:49 +08:00
185 changed files with 17256 additions and 5241 deletions

3
.gitignore vendored
View File

@ -1 +1,4 @@
.idea
.DS_Store
*.swp
*.swo

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2014 astaxie
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,43 +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 a Go Framework inspired by tornado and sinatra.
It is a simple & powerful web framework.
beego is an open-source, high-performance, modularity, full-stack web framework.
More info [beego.me](http://beego.me)
## Installation
go get github.com/astaxie/beego
## Features
* RESTful support
* MVC architecture
* Session support (store in memory, file, Redis or MySQL)
* Cache support (store in memory, Redis or Memcache)
* Global Config
* Intelligent routing
* Thread-safe map
* Friendly displaying of errors
* Useful template functions
* Modularity
* Auto API documents
* Annotation router
* Namespace
* Powerful develop 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://gowalker.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).
## Use case
- Displaying API documentation: [gowalker](https://github.com/Unknwon/gowalker)
- seocms: [seocms](https://github.com/chinakr/seocms)
- CMS: [toropress](https://github.com/insionng/toropress)

473
admin.go
View File

@ -1,16 +1,33 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"text/template"
"time"
"github.com/astaxie/beego/toolbox"
"github.com/astaxie/beego/utils"
)
// BeeAdminApp is the default AdminApp used by admin module.
var BeeAdminApp *AdminApp
// BeeAdminApp is the default adminApp used by admin module.
var beeAdminApp *adminApp
// FilterMonitorFunc is default monitor filter when admin module is enable.
// if this func returns, admin module records qbs for this request by condition of this function logic.
@ -31,226 +48,403 @@ var BeeAdminApp *AdminApp
var FilterMonitorFunc func(string, string, time.Duration) bool
func init() {
BeeAdminApp = &AdminApp{
beeAdminApp = &adminApp{
routers: make(map[string]http.HandlerFunc),
}
BeeAdminApp.Route("/", AdminIndex)
BeeAdminApp.Route("/qps", QpsIndex)
BeeAdminApp.Route("/prof", ProfIndex)
BeeAdminApp.Route("/healthcheck", Healthcheck)
BeeAdminApp.Route("/task", TaskStatus)
BeeAdminApp.Route("/runtask", RunTask)
BeeAdminApp.Route("/listconf", ListConf)
beeAdminApp.Route("/", adminIndex)
beeAdminApp.Route("/qps", qpsIndex)
beeAdminApp.Route("/prof", profIndex)
beeAdminApp.Route("/healthcheck", healthcheck)
beeAdminApp.Route("/task", taskStatus)
beeAdminApp.Route("/listconf", listConf)
FilterMonitorFunc = func(string, string, time.Duration) bool { return true }
}
// AdminIndex is the default http.Handler for admin module.
// it matches url pattern "/".
func AdminIndex(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte("Welcome to Admin Dashboard\n"))
rw.Write([]byte("There are servral functions:\n"))
rw.Write([]byte("1. Record all request and request time, http://localhost:8088/qps\n"))
rw.Write([]byte("2. Get runtime profiling data by the pprof, http://localhost:8088/prof\n"))
rw.Write([]byte("3. Get healthcheck result from http://localhost:8088/prof\n"))
rw.Write([]byte("4. Get current task infomation from taskhttp://localhost:8088/task \n"))
rw.Write([]byte("5. To run a task passed a param http://localhost:8088/runtask\n"))
rw.Write([]byte("6. Get all confige & router infomation http://localhost:8088/listconf\n"))
func adminIndex(rw http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(indexTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
data := make(map[interface{}]interface{})
tmpl.Execute(rw, data)
}
// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
// it's registered with url pattern "/qbs" in admin module.
func QpsIndex(rw http.ResponseWriter, r *http.Request) {
toolbox.StatisticsMap.GetMap(rw)
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(qpsTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
data := make(map[interface{}]interface{})
data["Content"] = toolbox.StatisticsMap.GetMap()
tmpl.Execute(rw, data)
}
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
// it's registered with url pattern "/listconf" in admin module.
func ListConf(rw http.ResponseWriter, r *http.Request) {
func listConf(rw http.ResponseWriter, r *http.Request) {
r.ParseForm()
command := r.Form.Get("command")
if command != "" {
data := make(map[interface{}]interface{})
switch command {
case "conf":
fmt.Fprintln(rw, "list all beego's conf:")
fmt.Fprintln(rw, "AppName:", AppName)
fmt.Fprintln(rw, "AppPath:", AppPath)
fmt.Fprintln(rw, "AppConfigPath:", AppConfigPath)
fmt.Fprintln(rw, "StaticDir:", StaticDir)
fmt.Fprintln(rw, "StaticExtensionsToGzip:", StaticExtensionsToGzip)
fmt.Fprintln(rw, "HttpAddr:", HttpAddr)
fmt.Fprintln(rw, "HttpPort:", HttpPort)
fmt.Fprintln(rw, "HttpTLS:", HttpTLS)
fmt.Fprintln(rw, "HttpCertFile:", HttpCertFile)
fmt.Fprintln(rw, "HttpKeyFile:", HttpKeyFile)
fmt.Fprintln(rw, "RecoverPanic:", RecoverPanic)
fmt.Fprintln(rw, "AutoRender:", AutoRender)
fmt.Fprintln(rw, "ViewsPath:", ViewsPath)
fmt.Fprintln(rw, "RunMode:", RunMode)
fmt.Fprintln(rw, "SessionOn:", SessionOn)
fmt.Fprintln(rw, "SessionProvider:", SessionProvider)
fmt.Fprintln(rw, "SessionName:", SessionName)
fmt.Fprintln(rw, "SessionGCMaxLifetime:", SessionGCMaxLifetime)
fmt.Fprintln(rw, "SessionSavePath:", SessionSavePath)
fmt.Fprintln(rw, "SessionHashFunc:", SessionHashFunc)
fmt.Fprintln(rw, "SessionHashKey:", SessionHashKey)
fmt.Fprintln(rw, "SessionCookieLifeTime:", SessionCookieLifeTime)
fmt.Fprintln(rw, "UseFcgi:", UseFcgi)
fmt.Fprintln(rw, "MaxMemory:", MaxMemory)
fmt.Fprintln(rw, "EnableGzip:", EnableGzip)
fmt.Fprintln(rw, "DirectoryIndex:", DirectoryIndex)
fmt.Fprintln(rw, "EnableHotUpdate:", EnableHotUpdate)
fmt.Fprintln(rw, "HttpServerTimeOut:", HttpServerTimeOut)
fmt.Fprintln(rw, "ErrorsShow:", ErrorsShow)
fmt.Fprintln(rw, "XSRFKEY:", XSRFKEY)
fmt.Fprintln(rw, "EnableXSRF:", EnableXSRF)
fmt.Fprintln(rw, "XSRFExpire:", XSRFExpire)
fmt.Fprintln(rw, "CopyRequestBody:", CopyRequestBody)
fmt.Fprintln(rw, "TemplateLeft:", TemplateLeft)
fmt.Fprintln(rw, "TemplateRight:", TemplateRight)
fmt.Fprintln(rw, "BeegoServerName:", BeegoServerName)
fmt.Fprintln(rw, "EnableAdmin:", EnableAdmin)
fmt.Fprintln(rw, "AdminHttpAddr:", AdminHttpAddr)
fmt.Fprintln(rw, "AdminHttpPort:", AdminHttpPort)
m := make(map[string]interface{})
m["AppName"] = AppName
m["AppPath"] = AppPath
m["AppConfigPath"] = AppConfigPath
m["StaticDir"] = StaticDir
m["StaticExtensionsToGzip"] = StaticExtensionsToGzip
m["HttpAddr"] = HttpAddr
m["HttpPort"] = HttpPort
m["HttpTLS"] = EnableHttpTLS
m["HttpCertFile"] = HttpCertFile
m["HttpKeyFile"] = HttpKeyFile
m["RecoverPanic"] = RecoverPanic
m["AutoRender"] = AutoRender
m["ViewsPath"] = ViewsPath
m["RunMode"] = RunMode
m["SessionOn"] = SessionOn
m["SessionProvider"] = SessionProvider
m["SessionName"] = SessionName
m["SessionGCMaxLifetime"] = SessionGCMaxLifetime
m["SessionSavePath"] = SessionSavePath
m["SessionCookieLifeTime"] = SessionCookieLifeTime
m["UseFcgi"] = UseFcgi
m["MaxMemory"] = MaxMemory
m["EnableGzip"] = EnableGzip
m["DirectoryIndex"] = DirectoryIndex
m["HttpServerTimeOut"] = HttpServerTimeOut
m["ErrorsShow"] = ErrorsShow
m["XSRFKEY"] = XSRFKEY
m["EnableXSRF"] = EnableXSRF
m["XSRFExpire"] = XSRFExpire
m["CopyRequestBody"] = CopyRequestBody
m["TemplateLeft"] = TemplateLeft
m["TemplateRight"] = TemplateRight
m["BeegoServerName"] = BeegoServerName
m["EnableAdmin"] = EnableAdmin
m["AdminHttpAddr"] = AdminHttpAddr
m["AdminHttpPort"] = AdminHttpPort
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(configTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
data["Content"] = m
tmpl.Execute(rw, data)
case "router":
fmt.Fprintln(rw, "Print all router infomation:")
for _, router := range BeeApp.Handlers.fixrouters {
if router.hasMethod {
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name())
} else {
fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name())
}
content := make(map[string]interface{})
var fields = []string{
fmt.Sprintf("Router Pattern"),
fmt.Sprintf("Methods"),
fmt.Sprintf("Controller"),
}
for _, router := range BeeApp.Handlers.routers {
if router.hasMethod {
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name())
} else {
fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name())
}
}
if BeeApp.Handlers.enableAuto {
for controllerName, methodObj := range BeeApp.Handlers.autoRouter {
fmt.Fprintln(rw, controllerName, "----")
for methodName, obj := range methodObj {
fmt.Fprintln(rw, " ", methodName, "-----", obj.Name())
}
}
content["Fields"] = fields
methods := []string{}
methodsData := make(map[string]interface{})
for method, t := range BeeApp.Handlers.routers {
resultList := new([][]string)
printTree(resultList, t)
methods = append(methods, method)
methodsData[method] = 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":
fmt.Fprintln(rw, "Print all filter infomation:")
content := make(map[string]interface{})
var fields = []string{
fmt.Sprintf("Router Pattern"),
fmt.Sprintf("Filter Function"),
}
content["Fields"] = fields
filterTypes := []string{}
filterTypeData := make(map[string]interface{})
if BeeApp.Handlers.enableFilter {
fmt.Fprintln(rw, "BeforeRouter:")
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 {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
var result = []string{
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
fmt.Fprintln(rw, "AfterStatic:")
if bf, ok := BeeApp.Handlers.filters[AfterStatic]; ok {
for _, f := range bf {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
}
}
fmt.Fprintln(rw, "BeforeExec:")
if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok {
filterType = "Before Exec"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
var result = []string{
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
fmt.Fprintln(rw, "AfterExec:")
if bf, ok := BeeApp.Handlers.filters[AfterExec]; ok {
filterType = "After Exec"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
var result = []string{
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
fmt.Fprintln(rw, "FinishRouter:")
if bf, ok := BeeApp.Handlers.filters[FinishRouter]; ok {
filterType = "Finish Router"
filterTypes = append(filterTypes, filterType)
resultList := new([][]string)
for _, f := range bf {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
var result = []string{
fmt.Sprintf("%s", f.pattern),
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
}
*resultList = append(*resultList, result)
}
filterTypeData[filterType] = resultList
}
}
content["Data"] = filterTypeData
content["Methods"] = filterTypes
data["Content"] = content
data["Title"] = "Filters"
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(routerAndFilterTpl))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
tmpl.Execute(rw, data)
default:
rw.Write([]byte("command not support"))
}
} else {
rw.Write([]byte("ListConf support this command:\n"))
rw.Write([]byte("1. command=conf\n"))
rw.Write([]byte("2. command=router\n"))
rw.Write([]byte("3. command=filter\n"))
}
}
func printTree(resultList *[][]string, t *Tree) {
for _, tr := range t.fixrouters {
printTree(resultList, tr)
}
if t.wildcard != nil {
printTree(resultList, t.wildcard)
}
for _, l := range t.leaves {
if v, ok := l.runObject.(*controllerInfo); ok {
if v.routerType == routerTypeBeego {
var result = []string{
fmt.Sprintf("%s", v.pattern),
fmt.Sprintf("%s", v.methods),
fmt.Sprintf("%s", v.controllerType),
}
*resultList = append(*resultList, result)
} else if v.routerType == routerTypeRESTFul {
var result = []string{
fmt.Sprintf("%s", v.pattern),
fmt.Sprintf("%s", v.methods),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
} else if v.routerType == routerTypeHandler {
var result = []string{
fmt.Sprintf("%s", v.pattern),
fmt.Sprintf(""),
fmt.Sprintf(""),
}
*resultList = append(*resultList, result)
}
}
}
}
// ProfIndex is a http.Handler for showing profile command.
// it's in url pattern "/prof" in admin module.
func ProfIndex(rw http.ResponseWriter, r *http.Request) {
func profIndex(rw http.ResponseWriter, r *http.Request) {
r.ParseForm()
command := r.Form.Get("command")
format := r.Form.Get("format")
data := make(map[string]interface{})
var result bytes.Buffer
if command != "" {
toolbox.ProcessInput(command, rw)
toolbox.ProcessInput(command, &result)
data["Content"] = result.String()
if format == "json" && command == "gc summary" {
dataJson, err := json.Marshal(data)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
rw.Write(dataJson)
return
}
data["Title"] = command
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
tmpl = template.Must(tmpl.Parse(profillingTpl))
if command == "gc summary" {
tmpl = template.Must(tmpl.Parse(gcAjaxTpl))
} else {
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
}
tmpl.Execute(rw, data)
} else {
rw.Write([]byte("request url like '/prof?command=lookup goroutine'\n"))
rw.Write([]byte("the command have below types:\n"))
rw.Write([]byte("1. lookup goroutine\n"))
rw.Write([]byte("2. lookup heap\n"))
rw.Write([]byte("3. lookup threadcreate\n"))
rw.Write([]byte("4. lookup block\n"))
rw.Write([]byte("5. start cpuprof\n"))
rw.Write([]byte("6. stop cpuprof\n"))
rw.Write([]byte("7. get memprof\n"))
rw.Write([]byte("8. gc summary\n"))
}
}
// Healthcheck is a http.Handler calling health checking and showing the result.
// it's in "/healthcheck" pattern in admin module.
func Healthcheck(rw http.ResponseWriter, req *http.Request) {
func healthcheck(rw http.ResponseWriter, req *http.Request) {
data := make(map[interface{}]interface{})
var result = []string{}
fields := []string{
fmt.Sprintf("Name"),
fmt.Sprintf("Message"),
fmt.Sprintf("Status"),
}
resultList := new([][]string)
content := make(map[string]interface{})
for name, h := range toolbox.AdminCheckList {
if err := h.Check(); err != nil {
fmt.Fprintf(rw, "%s : ok\n", name)
result = []string{
fmt.Sprintf("error"),
fmt.Sprintf("%s", name),
fmt.Sprintf("%s", err.Error()),
}
} else {
fmt.Fprintf(rw, "%s : %s\n", name, err.Error())
result = []string{
fmt.Sprintf("success"),
fmt.Sprintf("%s", name),
fmt.Sprintf("OK"),
}
}
*resultList = append(*resultList, result)
}
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))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
tmpl.Execute(rw, data)
}
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
// it's in "/task" pattern in admin module.
func TaskStatus(rw http.ResponseWriter, req *http.Request) {
for tname, tk := range toolbox.AdminTaskList {
fmt.Fprintf(rw, "%s:%s:%s", tname, tk.GetStatus(), tk.GetPrev().String())
}
}
func taskStatus(rw http.ResponseWriter, req *http.Request) {
data := make(map[interface{}]interface{})
// RunTask is a http.Handler to run a Task from the "query string.
// the request url likes /runtask?taskname=sendmail.
func RunTask(rw http.ResponseWriter, req *http.Request) {
// Run Task
req.ParseForm()
taskname := req.Form.Get("taskname")
if t, ok := toolbox.AdminTaskList[taskname]; ok {
err := t.Run()
if err != nil {
fmt.Fprintf(rw, "%v", err)
if taskname != "" {
if t, ok := toolbox.AdminTaskList[taskname]; ok {
err := t.Run()
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())}
} else {
data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)}
}
fmt.Fprintf(rw, "%s run success,Now the Status is %s", t.GetStatus())
} else {
fmt.Fprintf(rw, "there's no task which named:%s", taskname)
}
// List Tasks
content := make(map[string]interface{})
resultList := new([][]string)
var result = []string{}
var fields = []string{
fmt.Sprintf("Task Name"),
fmt.Sprintf("Task Spec"),
fmt.Sprintf("Task Function"),
fmt.Sprintf(""),
}
for tname, tk := range toolbox.AdminTaskList {
result = []string{
fmt.Sprintf("%s", tname),
fmt.Sprintf("%s", tk.GetStatus()),
fmt.Sprintf("%s", tk.GetPrev().String()),
}
*resultList = append(*resultList, result)
}
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))
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
tmpl.Execute(rw, data)
}
// AdminApp is an http.HandlerFunc map used as BeeAdminApp.
type AdminApp struct {
// adminApp is an http.HandlerFunc map used as beeAdminApp.
type adminApp struct {
routers map[string]http.HandlerFunc
}
// Route adds http.HandlerFunc to AdminApp with url pattern.
func (admin *AdminApp) Route(pattern string, f http.HandlerFunc) {
// Route adds http.HandlerFunc to adminApp with url pattern.
func (admin *adminApp) Route(pattern string, f http.HandlerFunc) {
admin.routers[pattern] = f
}
// Run AdminApp http server.
// Run adminApp http server.
// Its addr is defined in configuration file as adminhttpaddr and adminhttpport.
func (admin *AdminApp) Run() {
func (admin *adminApp) Run() {
if len(toolbox.AdminTaskList) > 0 {
toolbox.StartTask()
}
@ -262,6 +456,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)

355
adminui.go Normal file

File diff suppressed because one or more lines are too long

209
app.go
View File

@ -1,3 +1,17 @@
// 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 (
@ -6,22 +20,18 @@ import (
"net/http"
"net/http/fcgi"
"time"
"github.com/astaxie/beego/context"
)
// FilterFunc defines filter function type.
type FilterFunc func(*context.Context)
// App defines beego application with a new PatternServeMux.
type App struct {
Handlers *ControllerRegistor
Server *http.Server
}
// NewApp returns a new beego application.
func NewApp() *App {
cr := NewControllerRegistor()
app := &App{Handlers: cr}
cr := NewControllerRegister()
app := &App{Handlers: cr, Server: &http.Server{}}
return app
}
@ -33,132 +43,83 @@ func (app *App) Run() {
addr = fmt.Sprintf("%s:%d", HttpAddr, HttpPort)
}
BeeLogger.Info("Running on %s", addr)
var (
err error
l net.Listener
)
endRunning := make(chan bool, 1)
if UseFcgi {
if HttpPort == 0 {
l, err = net.Listen("unix", addr)
} else {
l, err = net.Listen("tcp", addr)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
} else {
if EnableHotUpdate {
server := &http.Server{
Handler: app.Handlers,
ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,
WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
}
laddr, err := net.ResolveTCPAddr("tcp", addr)
if nil != err {
BeeLogger.Critical("ResolveTCPAddr:", err)
}
l, err = GetInitListener(laddr)
theStoppable = newStoppable(l)
err = server.Serve(theStoppable)
theStoppable.wg.Wait()
CloseSelf()
} else {
s := &http.Server{
Addr: addr,
Handler: app.Handlers,
ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,
WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
}
if HttpTLS {
err = s.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
if UseStdIo {
err = fcgi.Serve(nil, app.Handlers) // standard I/O
if err == nil {
BeeLogger.Info("Use FCGI via standard I/O")
} else {
err = s.ListenAndServe()
BeeLogger.Info("Cannot use FCGI via standard I/O", err)
}
} else {
if HttpPort == 0 {
l, err = net.Listen("unix", addr)
} else {
l, err = net.Listen("tcp", addr)
}
if err != nil {
BeeLogger.Critical("Listen: ", err)
}
err = fcgi.Serve(l, app.Handlers)
}
} else {
app.Server.Addr = addr
app.Server.Handler = app.Handlers
app.Server.ReadTimeout = time.Duration(HttpServerTimeOut) * time.Second
app.Server.WriteTimeout = time.Duration(HttpServerTimeOut) * time.Second
if EnableHttpTLS {
go func() {
time.Sleep(20 * time.Microsecond)
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)
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
}
if EnableHttpListen {
go func() {
app.Server.Addr = addr
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
}
}
}()
}
}
if err != nil {
BeeLogger.Critical("ListenAndServe: ", err)
time.Sleep(100 * time.Microsecond)
}
}
// Router adds a url-patterned controller handler.
// The path argument supports regex rules and specific placeholders.
// The c argument needs a controller handler implemented beego.ControllerInterface.
// The mapping methods argument only need one string to define custom router rules.
// usage:
// simple router
// beego.Router("/admin", &admin.UserController{})
// beego.Router("/admin/index", &admin.ArticleController{})
//
// regex router
//
// beego.Router(“/api/:id([0-9]+)“, &controllers.RController{})
//
// custom rules
// beego.Router("/api/list",&RestController{},"*:ListFood")
// beego.Router("/api/create",&RestController{},"post:CreateFood")
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
func (app *App) Router(path string, c ControllerInterface, mappingMethods ...string) *App {
app.Handlers.Add(path, c, mappingMethods...)
return app
}
// AutoRouter adds beego-defined controller handler.
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
// visit the url /main/list to exec List function or /main/page to exec Page function.
func (app *App) AutoRouter(c ControllerInterface) *App {
app.Handlers.AddAuto(c)
return app
}
// UrlFor creates a url with another registered controller handler with params.
// The endpoint is formed as path.controller.name to defined the controller method which will run.
// The values need key-pair data to assign into controller method.
func (app *App) UrlFor(endpoint string, values ...string) string {
return app.Handlers.UrlFor(endpoint, values...)
}
// [Deprecated] use InsertFilter.
// Filter adds a FilterFunc under pattern condition and named action.
// The actions contains BeforeRouter,AfterStatic,BeforeExec,AfterExec and FinishRouter.
func (app *App) Filter(pattern, action string, filter FilterFunc) *App {
app.Handlers.AddFilter(pattern, action, filter)
return app
}
// 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 (app *App) InsertFilter(pattern string, pos int, filter FilterFunc) *App {
app.Handlers.InsertFilter(pattern, pos, filter)
return app
}
// SetViewsPath sets view directory path in beego application.
// it returns beego application self.
func (app *App) SetViewsPath(path string) *App {
ViewsPath = path
return app
}
// SetStaticPath sets static directory path and proper url pattern in beego application.
// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public".
// it returns beego application self.
func (app *App) SetStaticPath(url string, path string) *App {
StaticDir[url] = path
return app
}
// DelStaticPath removes the static folder setting in this url pattern in beego application.
// it returns beego application self.
func (app *App) DelStaticPath(url string) *App {
delete(StaticDir, url)
return app
<-endRunning
}

376
beego.go
View File

@ -1,9 +1,36 @@
// 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.
// beego is an open-source, high-performance, modularity, full-stack web framework
//
// package main
//
// import "github.com/astaxie/beego"
//
// func main() {
// beego.Run()
// }
//
// more infomation: http://beego.me
package beego
import (
"net/http"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/astaxie/beego/middleware"
@ -11,12 +38,128 @@ import (
)
// beego web framework version.
const VERSION = "1.0.1"
const VERSION = "1.4.2"
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:
// simple router
// beego.Router("/admin", &admin.UserController{})
// beego.Router("/admin/index", &admin.ArticleController{})
//
// regex router
//
// beego.Router("/api/:id([0-9]+)", &controllers.RController{})
//
// custom rules
// beego.Router("/api/list",&RestController{},"*:ListFood")
// beego.Router("/api/create",&RestController{},"post:CreateFood")
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
BeeApp.Router(rootpath, c, mappingMethods...)
BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
return BeeApp
}
// Router add list from
// usage:
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
// type BankAccount struct{
// beego.Controller
// }
//
// register the function
// func (b *BankAccount)Mapping(){
// b.Mapping("ShowAccount" , b.ShowAccount)
// b.Mapping("ModifyAccount", b.ModifyAccount)
//}
//
// //@router /account/:id [get]
// func (b *BankAccount) ShowAccount(){
// //logic
// }
//
//
// //@router /account/:id [post]
// func (b *BankAccount) ModifyAccount(){
// //logic
// }
//
// the comments @router url methodlist
// url support all the function Router's pattern
// methodlist [get post head put delete options *]
func Include(cList ...ControllerInterface) *App {
BeeApp.Handlers.Include(cList...)
return BeeApp
}
@ -31,8 +174,109 @@ func RESTRouter(rootpath string, c ControllerInterface) *App {
// AutoRouter adds defined controller handler to BeeApp.
// it's same to App.AutoRouter.
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
// visit the url /main/list to exec List function or /main/page to exec Page function.
func AutoRouter(c ControllerInterface) *App {
BeeApp.AutoRouter(c)
BeeApp.Handlers.AddAuto(c)
return BeeApp
}
// AutoPrefix adds controller handler to BeeApp with prefix.
// it's same to App.AutoRouterWithPrefix.
// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page,
// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function.
func AutoPrefix(prefix string, c ControllerInterface) *App {
BeeApp.Handlers.AddAutoPrefix(prefix, c)
return BeeApp
}
// register router for Get method
// usage:
// beego.Get("/", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Get(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Get(rootpath, f)
return BeeApp
}
// register router for Post method
// usage:
// beego.Post("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Post(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Post(rootpath, f)
return BeeApp
}
// register router for Delete method
// usage:
// beego.Delete("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Delete(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Delete(rootpath, f)
return BeeApp
}
// register router for Put method
// usage:
// beego.Put("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Put(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Put(rootpath, f)
return BeeApp
}
// register router for Head method
// usage:
// beego.Head("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Head(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Head(rootpath, f)
return BeeApp
}
// register router for Options method
// usage:
// beego.Options("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Options(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Options(rootpath, f)
return BeeApp
}
// register router for Patch method
// usage:
// beego.Patch("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Patch(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Patch(rootpath, f)
return BeeApp
}
// register router for all method
// usage:
// beego.Any("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Any(rootpath string, f FilterFunc) *App {
BeeApp.Handlers.Any(rootpath, f)
return BeeApp
}
// register router for own Handler
// usage:
// beego.Handler("/api", func(ctx *context.Context){
// ctx.Output.Body("hello world")
// })
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
BeeApp.Handlers.Handler(rootpath, h, options...)
return BeeApp
}
@ -45,72 +289,109 @@ func Errorhandler(err string, h http.HandlerFunc) *App {
return BeeApp
}
// SetViewsPath sets view directory to BeeApp.
// it's alias of App.SetViewsPath.
// SetViewsPath sets view directory path in beego application.
func SetViewsPath(path string) *App {
BeeApp.SetViewsPath(path)
ViewsPath = path
return BeeApp
}
// SetStaticPath sets static directory and url prefix to BeeApp.
// it's alias of App.SetStaticPath.
// SetStaticPath sets static directory path and proper url pattern in beego application.
// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public".
func SetStaticPath(url string, path string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
StaticDir[url] = path
return BeeApp
}
// DelStaticPath removes the static folder setting in this url pattern in beego application.
// it's alias of App.DelStaticPath.
func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
delete(StaticDir, url)
return BeeApp
}
// [Deprecated] use InsertFilter.
// Filter adds a FilterFunc under pattern condition and named action.
// The actions contains BeforeRouter,AfterStatic,BeforeExec,AfterExec and FinishRouter.
// it's alias of App.Filter.
func AddFilter(pattern, action string, filter FilterFunc) *App {
BeeApp.Filter(pattern, action, filter)
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.
// it's alias of App.InsertFilter.
func InsertFilter(pattern string, pos int, filter FilterFunc) *App {
BeeApp.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
}
// The hookfunc will run in beego.Run()
// such as sessionInit, middlerware start, buildtemplate, admin start
func AddAPPStartHook(hf hookfunc) {
hooks = append(hooks, hf)
}
// Run beego application.
// it's alias of App.Run.
func Run() {
// beego.Run() default run on HttpPort
// beego.Run(":8089")
// beego.Run("127.0.0.1:8089")
func Run(params ...string) {
if len(params) > 0 && params[0] != "" {
strs := strings.Split(params[0], ":")
if len(strs) > 0 && strs[0] != "" {
HttpAddr = strs[0]
}
if len(strs) > 1 && strs[1] != "" {
HttpPort, _ = strconv.Atoi(strs[1])
}
}
initBeforeHttpRun()
if EnableAdmin {
go beeAdminApp.Run()
}
BeeApp.Run()
}
func initBeforeHttpRun() {
// if AppConfigPath not In the conf/app.conf reParse config
if AppConfigPath != filepath.Join(AppPath, "conf", "app.conf") {
err := ParseConfig()
if err != nil {
if err != nil && AppConfigPath != filepath.Join(workPath, "conf", "app.conf") {
// configuration is critical to app, panic here if parse failed
panic(err)
}
}
//init mime
initMime()
AddAPPStartHook(initMime)
// do hooks function
for _, hk := range hooks {
err := hk()
if err != nil {
panic(err)
}
}
if SessionOn {
GlobalSessions, _ = session.NewManager(SessionProvider,
SessionName,
SessionGCMaxLifetime,
SessionSavePath,
HttpTLS,
SessionHashFunc,
SessionHashKey,
SessionCookieLifeTime)
var err error
sessionConfig := AppConfig.String("sessionConfig")
if sessionConfig == "" {
sessionConfig = `{"cookieName":"` + SessionName + `",` +
`"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` +
`"providerConfig":"` + filepath.ToSlash(SessionSavePath) + `",` +
`"secure":` + strconv.FormatBool(EnableHttpTLS) + `,` +
`"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
`"domain":"` + SessionDomain + `",` +
`"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
}
GlobalSessions, err = session.NewManager(SessionProvider,
sessionConfig)
if err != nil {
panic(err)
}
go GlobalSessions.GC()
}
@ -123,11 +404,28 @@ func Run() {
middleware.VERSION = VERSION
middleware.AppName = AppName
middleware.RegisterErrorHander()
middleware.RegisterErrorHandler()
if EnableAdmin {
go BeeAdminApp.Run()
if EnableDocs {
Get("/docs", serverDocs)
Get("/docs/*", serverDocs)
}
BeeApp.Run()
}
// this function is for test package init
func TestBeegoInit(apppath string) {
AppPath = apppath
RunMode = "test"
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
err := ParseConfig()
if err != nil && !os.IsNotExist(err) {
// for init if doesn't have app.conf will not panic
Info(err)
}
os.Chdir(AppPath)
initBeforeHttpRun()
}
func init() {
hooks = make([]hookfunc, 0)
}

4
cache/README.md vendored
View File

@ -22,7 +22,7 @@ First you must import it
Then init a Cache (example with memory adapter)
bm, err := NewCache("memory", `{"interval":60}`)
bm, err := cache.NewCache("memory", `{"interval":60}`)
Use it like this:
@ -43,7 +43,7 @@ interval means the gc time. The cache will check at each time interval, whether
## Memcache adapter
memory adapter use the vitess's [Memcache](http://code.google.com/p/vitess/go/memcache) client.
Memcache adapter use the vitess's [Memcache](http://code.google.com/p/vitess/go/memcache) client.
Configure like this:

51
cache/cache.go vendored
View File

@ -1,3 +1,33 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
//
// import(
// "github.com/astaxie/beego/cache"
// )
//
// bm, err := cache.NewCache("memory", `{"interval":60}`)
//
// Use it like this:
//
// bm.Put("astaxie", 1, 10)
// bm.Get("astaxie")
// bm.IsExist("astaxie")
// bm.Delete("astaxie")
//
// more docs http://beego.me/docs/module/cache.md
package cache
import (
@ -7,7 +37,7 @@ import (
// Cache interface contains all behaviors for cache adapter.
// usage:
// cache.Register("file",cache.NewFileCache()) // this operation is run in init method of file.go.
// c := cache.NewCache("file","{....}")
// c,err := cache.NewCache("file","{....}")
// c.Put("key",value,3600)
// v := c.Get("key")
//
@ -25,11 +55,11 @@ type Cache interface {
Incr(key string) error
// decrease cached int value by key, as a counter.
Decr(key string) error
// check cached value is existed or not.
// check if cached value exists or not.
IsExist(key string) bool
// clear all cache.
ClearAll() error
// start gc routine via config string setting.
// start gc routine based on config string settings.
StartAndGC(config string) error
}
@ -42,23 +72,24 @@ func Register(name string, adapter Cache) {
if adapter == nil {
panic("cache: Register adapter is nil")
}
if _, dup := adapters[name]; dup {
if _, ok := adapters[name]; ok {
panic("cache: Register called twice for adapter " + name)
}
adapters[name] = adapter
}
// Create a new cache driver by adapter and config string.
// 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) (Cache, error) {
func NewCache(adapterName, config string) (adapter Cache, err error) {
adapter, ok := adapters[adapterName]
if !ok {
return nil, fmt.Errorf("cache: unknown adaptername %q (forgotten 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 {
return nil, err
adapter = nil
}
return adapter, nil
return
}

66
cache/cache_test.go vendored
View File

@ -1,3 +1,17 @@
// 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 cache
import (
@ -5,7 +19,7 @@ import (
"time"
)
func Test_cache(t *testing.T) {
func TestCache(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
if err != nil {
t.Error("init err")
@ -40,7 +54,7 @@ func Test_cache(t *testing.T) {
}
if err = bm.Decr("astaxie"); err != nil {
t.Error("Incr Error", err)
t.Error("Decr Error", err)
}
if v := bm.Get("astaxie"); v.(int) != 1 {
@ -51,3 +65,51 @@ func Test_cache(t *testing.T) {
t.Error("delete err")
}
}
func TestFileCache(t *testing.T) {
bm, err := NewCache("file", `{"CachePath":"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
if err != nil {
t.Error("init err")
}
if err = bm.Put("astaxie", 1, 10); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
if v := bm.Get("astaxie"); v.(int) != 2 {
t.Error("get err")
}
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
if v := bm.Get("astaxie"); v.(int) != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
//test string
if err = bm.Put("astaxie", "author", 10); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v := bm.Get("astaxie"); v.(string) != "author" {
t.Error("get err")
}
}

56
cache/conv.go vendored
View File

@ -1,3 +1,17 @@
// 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 cache
import (
@ -13,12 +27,11 @@ func GetString(v interface{}) string {
case []byte:
return string(result)
default:
if v == nil {
return ""
} else {
if v != nil {
return fmt.Sprintf("%v", result)
}
}
return ""
}
// convert interface to int.
@ -31,12 +44,9 @@ func GetInt(v interface{}) int {
case int64:
return int(result)
default:
d := GetString(v)
if d != "" {
value, err := strconv.Atoi(d)
if err == nil {
return value
}
if d := GetString(v); d != "" {
value, _ := strconv.Atoi(d)
return value
}
}
return 0
@ -52,12 +62,10 @@ func GetInt64(v interface{}) int64 {
case int64:
return result
default:
d := GetString(v)
if d != "" {
result, err := strconv.ParseInt(d, 10, 64)
if err == nil {
return result
}
if d := GetString(v); d != "" {
value, _ := strconv.ParseInt(d, 10, 64)
return value
}
}
return 0
@ -69,12 +77,9 @@ func GetFloat64(v interface{}) float64 {
case float64:
return result
default:
d := GetString(v)
if d != "" {
value, err := strconv.ParseFloat(d, 64)
if err == nil {
return value
}
if d := GetString(v); d != "" {
value, _ := strconv.ParseFloat(d, 64)
return value
}
}
return 0
@ -86,12 +91,9 @@ func GetBool(v interface{}) bool {
case bool:
return result
default:
d := GetString(v)
if d != "" {
result, err := strconv.ParseBool(d)
if err == nil {
return result
}
if d := GetString(v); d != "" {
value, _ := strconv.ParseBool(d)
return value
}
}
return false

14
cache/conv_test.go vendored
View File

@ -1,3 +1,17 @@
// 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 cache
import (

151
cache/file.go vendored
View File

@ -1,8 +1,17 @@
/**
* package: file
* User: gouki
* Date: 2013-10-22 - 14:22
*/
// 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 cache
import (
@ -47,19 +56,19 @@ type FileCache struct {
EmbedExpiry int
}
// Create new file cache with default directory and suffix.
// Create new file cache with no config.
// the level and expiry need set in method StartAndGC as config string.
func NewFileCache() *FileCache {
return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
// return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
return &FileCache{}
}
// Start and begin gc for file cache.
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
func (this *FileCache) StartAndGC(config string) error {
func (fc *FileCache) StartAndGC(config string) error {
var cfg map[string]string
json.Unmarshal([]byte(config), &cfg)
//fmt.Println(cfg)
if _, ok := cfg["CachePath"]; !ok {
cfg["CachePath"] = FileCachePath
}
@ -72,69 +81,53 @@ func (this *FileCache) StartAndGC(config string) error {
if _, ok := cfg["EmbedExpiry"]; !ok {
cfg["EmbedExpiry"] = strconv.FormatInt(FileCacheEmbedExpiry, 10)
}
this.CachePath = cfg["CachePath"]
this.FileSuffix = cfg["FileSuffix"]
this.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
this.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
fc.CachePath = cfg["CachePath"]
fc.FileSuffix = cfg["FileSuffix"]
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
this.Init()
fc.Init()
return nil
}
// Init will make new dir for file cache if not exist.
func (this *FileCache) Init() {
func (fc *FileCache) Init() {
app := filepath.Dir(os.Args[0])
this.CachePath = filepath.Join(app, this.CachePath)
ok, err := exists(this.CachePath)
if err != nil { // print error
//fmt.Println(err)
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
}
if !ok {
if err = os.Mkdir(this.CachePath, os.ModePerm); err != nil {
//fmt.Println(err);
}
}
//fmt.Println(this.getCacheFileName("123456"));
}
// get cached file name. it's md5 encoded.
func (this *FileCache) getCacheFileName(key string) string {
func (fc *FileCache) getCacheFileName(key string) string {
m := md5.New()
io.WriteString(m, key)
keyMd5 := hex.EncodeToString(m.Sum(nil))
cachePath := this.CachePath
//fmt.Println("cachepath : " , cachePath)
//fmt.Println("md5" , keyMd5);
switch this.DirectoryLevel {
cachePath := fc.CachePath
switch fc.DirectoryLevel {
case 2:
cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
case 1:
cachePath = filepath.Join(cachePath, keyMd5[0:2])
}
ok, err := exists(cachePath)
if err != nil {
//fmt.Println(err)
if ok, _ := exists(cachePath); !ok { // todo : error handle
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
}
if !ok {
if err = os.MkdirAll(cachePath, os.ModePerm); err != nil {
//fmt.Println(err);
}
}
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, this.FileSuffix))
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
}
// Get value from file cache.
// if non-exist or expired, return empty string.
func (this *FileCache) Get(key string) interface{} {
filename := this.getCacheFileName(key)
filedata, err := File_get_contents(filename)
//fmt.Println("get length:" , len(filedata));
func (fc *FileCache) Get(key string) interface{} {
fileData, err := File_get_contents(fc.getCacheFileName(key))
if err != nil {
return ""
}
var to FileCacheItem
Gob_decode([]byte(filedata), &to)
Gob_decode(fileData, &to)
if to.Expired < time.Now().Unix() {
return ""
}
@ -142,13 +135,14 @@ func (this *FileCache) Get(key string) interface{} {
}
// Put value into file cache.
// timeout means how long to keep this file, unit of second.
func (this *FileCache) Put(key string, val interface{}, timeout int64) error {
filename := this.getCacheFileName(key)
var item FileCacheItem
item.Data = val
// timeout means how long to keep this file, unit of ms.
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
func (fc *FileCache) Put(key string, val interface{}, timeout int64) error {
gob.Register(val)
item := FileCacheItem{Data: val}
if timeout == FileCacheEmbedExpiry {
item.Expired = time.Now().Unix() + (86400 * 365 * 10) //10年
item.Expired = time.Now().Unix() + (86400 * 365 * 10) // ten years
} else {
item.Expired = time.Now().Unix() + timeout
}
@ -157,13 +151,12 @@ func (this *FileCache) Put(key string, val interface{}, timeout int64) error {
if err != nil {
return err
}
err = File_put_contents(filename, data)
return err
return File_put_contents(fc.getCacheFileName(key), data)
}
// Delete file cache value.
func (this *FileCache) Delete(key string) error {
filename := this.getCacheFileName(key)
func (fc *FileCache) Delete(key string) error {
filename := fc.getCacheFileName(key)
if ok, _ := exists(filename); ok {
return os.Remove(filename)
}
@ -171,45 +164,41 @@ func (this *FileCache) Delete(key string) error {
}
// Increase cached int value.
// this value is saving forever unless Delete.
func (this *FileCache) Incr(key string) error {
data := this.Get(key)
// fc value is saving forever unless Delete.
func (fc *FileCache) Incr(key string) error {
data := fc.Get(key)
var incr int
fmt.Println(reflect.TypeOf(data).Name())
if reflect.TypeOf(data).Name() != "int" {
incr = 0
} else {
incr = data.(int) + 1
}
this.Put(key, incr, FileCacheEmbedExpiry)
fc.Put(key, incr, FileCacheEmbedExpiry)
return nil
}
// Decrease cached int value.
func (this *FileCache) Decr(key string) error {
data := this.Get(key)
func (fc *FileCache) Decr(key string) error {
data := fc.Get(key)
var decr int
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
decr = 0
} else {
decr = data.(int) - 1
}
this.Put(key, decr, FileCacheEmbedExpiry)
fc.Put(key, decr, FileCacheEmbedExpiry)
return nil
}
// Check value is exist.
func (this *FileCache) IsExist(key string) bool {
filename := this.getCacheFileName(key)
ret, _ := exists(filename)
func (fc *FileCache) IsExist(key string) bool {
ret, _ := exists(fc.getCacheFileName(key))
return ret
}
// Clean cached files.
// not implemented.
func (this *FileCache) ClearAll() error {
//this.CachePath .递归删除
func (fc *FileCache) ClearAll() error {
return nil
}
@ -227,22 +216,22 @@ func exists(path string) (bool, error) {
// Get bytes to file.
// if non-exist, create this file.
func File_get_contents(filename string) ([]byte, error) {
f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
if err != nil {
return []byte(""), err
func File_get_contents(filename string) (data []byte, e error) {
f, e := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm)
if e != nil {
return
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return []byte(""), err
stat, e := f.Stat()
if e != nil {
return
}
data := make([]byte, stat.Size())
result, err := f.Read(data)
if int64(result) == stat.Size() {
return data, err
data = make([]byte, stat.Size())
result, e := f.Read(data)
if e != nil || int64(result) != stat.Size() {
return nil, e
}
return []byte(""), err
return
}
// Put bytes to file.
@ -269,7 +258,7 @@ func Gob_encode(data interface{}) ([]byte, error) {
}
// Gob decodes file cache item.
func Gob_decode(data []byte, to interface{}) error {
func Gob_decode(data []byte, to *FileCacheItem) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
return dec.Decode(&to)

130
cache/memcache.go vendored
View File

@ -1,130 +0,0 @@
package cache
import (
"encoding/json"
"errors"
"github.com/beego/memcache"
)
// Memcache adapter.
type MemcacheCache struct {
c *memcache.Connection
conninfo string
}
// create new memcache adapter.
func NewMemCache() *MemcacheCache {
return &MemcacheCache{}
}
// get value from memcache.
func (rc *MemcacheCache) Get(key string) interface{} {
if rc.c == nil {
rc.c = rc.connectInit()
}
v, err := rc.c.Get(key)
if err != nil {
return nil
}
var contain interface{}
if len(v) > 0 {
contain = string(v[0].Value)
} else {
contain = nil
}
return contain
}
// put value to memcache. only support string.
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error {
if rc.c == nil {
rc.c = rc.connectInit()
}
v, ok := val.(string)
if !ok {
return errors.New("val must string")
}
stored, err := rc.c.Set(key, 0, uint64(timeout), []byte(v))
if err == nil && stored == false {
return errors.New("stored fail")
}
return err
}
// delete value in memcache.
func (rc *MemcacheCache) Delete(key string) error {
if rc.c == nil {
rc.c = rc.connectInit()
}
_, err := rc.c.Delete(key)
return err
}
// [Not Support]
// increase counter.
func (rc *MemcacheCache) Incr(key string) error {
return errors.New("not support in memcache")
}
// [Not Support]
// decrease counter.
func (rc *MemcacheCache) Decr(key string) error {
return errors.New("not support in memcache")
}
// check value exists in memcache.
func (rc *MemcacheCache) IsExist(key string) bool {
if rc.c == nil {
rc.c = rc.connectInit()
}
v, err := rc.c.Get(key)
if err != nil {
return false
}
if len(v) == 0 {
return false
} else {
return true
}
return true
}
// clear all cached in memcache.
func (rc *MemcacheCache) ClearAll() error {
if rc.c == nil {
rc.c = rc.connectInit()
}
err := rc.c.FlushAll()
return err
}
// start memcache adapter.
// config string is like {"conn":"connection info"}.
// if connecting error, return.
func (rc *MemcacheCache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
}
rc.conninfo = cf["conn"]
rc.c = rc.connectInit()
if rc.c == nil {
return errors.New("dial tcp conn error")
}
return nil
}
// connect to memcache and keep the connection.
func (rc *MemcacheCache) connectInit() *memcache.Connection {
c, err := memcache.Connect(rc.conninfo)
if err != nil {
return nil
}
return c
}
func init() {
Register("memcache", NewMemCache())
}

163
cache/memcache/memcache.go vendored Normal file
View File

@ -0,0 +1,163 @@
// 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 memcahe for cache provider
//
// depend on github.com/bradfitz/gomemcache/memcache
//
// go install github.com/bradfitz/gomemcache/memcache
//
// Usage:
// import(
// _ "github.com/astaxie/beego/cache/memcache"
// "github.com/astaxie/beego/cache"
// )
//
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
//
// more docs http://beego.me/docs/module/cache.md
package memcache
import (
"encoding/json"
"errors"
"strings"
"github.com/bradfitz/gomemcache/memcache"
"github.com/astaxie/beego/cache"
)
// Memcache adapter.
type MemcacheCache struct {
conn *memcache.Client
conninfo []string
}
// create new memcache adapter.
func NewMemCache() *MemcacheCache {
return &MemcacheCache{}
}
// get value from memcache.
func (rc *MemcacheCache) Get(key string) interface{} {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
if item, err := rc.conn.Get(key); err == nil {
return string(item.Value)
}
return nil
}
// put value to memcache. only support string.
func (rc *MemcacheCache) Put(key string, val interface{}, timeout int64) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
v, ok := val.(string)
if !ok {
return errors.New("val must string")
}
item := memcache.Item{Key: key, Value: []byte(v), Expiration: int32(timeout)}
return rc.conn.Set(&item)
}
// delete value in memcache.
func (rc *MemcacheCache) Delete(key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return rc.conn.Delete(key)
}
// increase counter.
func (rc *MemcacheCache) Incr(key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Increment(key, 1)
return err
}
// decrease counter.
func (rc *MemcacheCache) Decr(key string) error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
_, err := rc.conn.Decrement(key, 1)
return err
}
// check value exists in memcache.
func (rc *MemcacheCache) IsExist(key string) bool {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return false
}
}
_, err := rc.conn.Get(key)
if err != nil {
return false
}
return true
}
// clear all cached in memcache.
func (rc *MemcacheCache) ClearAll() error {
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return rc.conn.FlushAll()
}
// start memcache adapter.
// config string is like {"conn":"connection info"}.
// if connecting error, return.
func (rc *MemcacheCache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
}
rc.conninfo = strings.Split(cf["conn"], ";")
if rc.conn == nil {
if err := rc.connectInit(); err != nil {
return err
}
}
return nil
}
// connect to memcache and keep the connection.
func (rc *MemcacheCache) connectInit() error {
rc.conn = memcache.New(rc.conninfo...)
return nil
}
func init() {
cache.Register("memcache", NewMemCache())
}

41
cache/memory.go vendored
View File

@ -1,3 +1,17 @@
// 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 cache
import (
@ -26,7 +40,7 @@ type MemoryCache struct {
lock sync.RWMutex
dur time.Duration
items map[string]*MemoryItem
Every int // run an expiration check Every cloc; time
Every int // run an expiration check Every clock time
}
// NewMemoryCache returns a new MemoryCache.
@ -40,27 +54,26 @@ func NewMemoryCache() *MemoryCache {
func (bc *MemoryCache) Get(name string) interface{} {
bc.lock.RLock()
defer bc.lock.RUnlock()
itm, ok := bc.items[name]
if !ok {
return nil
if itm, ok := bc.items[name]; ok {
if (time.Now().Unix() - itm.Lastaccess.Unix()) > itm.expired {
go bc.Delete(name)
return nil
}
return itm.val
}
if (time.Now().Unix() - itm.Lastaccess.Unix()) > itm.expired {
go bc.Delete(name)
return nil
}
return itm.val
return nil
}
// Put cache to memory.
// if expired is 0, it will be cleaned by next gc operation ( default gc clock is 1 minute).
func (bc *MemoryCache) Put(name string, value interface{}, expired int64) error {
bc.lock.Lock()
defer bc.lock.Unlock()
t := MemoryItem{
bc.items[name] = &MemoryItem{
val: value,
Lastaccess: time.Now(),
expired: expired,
}
bc.items[name] = &t
return nil
}
@ -72,8 +85,7 @@ func (bc *MemoryCache) Delete(name string) error {
return errors.New("key not exist")
}
delete(bc.items, name)
_, valid := bc.items[name]
if valid {
if _, ok := bc.items[name]; ok {
return errors.New("delete key error")
}
return nil
@ -204,8 +216,7 @@ func (bc *MemoryCache) item_expired(name string) bool {
if !ok {
return true
}
sec := time.Now().Unix() - itm.Lastaccess.Unix()
if sec >= itm.expired {
if time.Now().Unix()-itm.Lastaccess.Unix() >= itm.expired {
delete(bc.items, name)
return true
}

168
cache/redis.go vendored
View File

@ -1,168 +0,0 @@
package cache
import (
"encoding/json"
"errors"
"github.com/beego/redigo/redis"
)
var (
// the collection name of redis for cache adapter.
DefaultKey string = "beecacheRedis"
)
// Redis cache adapter.
type RedisCache struct {
c redis.Conn
conninfo string
key string
}
// create new redis cache with default collection name.
func NewRedisCache() *RedisCache {
return &RedisCache{key: DefaultKey}
}
// Get cache from redis.
func (rc *RedisCache) Get(key string) interface{} {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return nil
}
}
v, err := rc.c.Do("HGET", rc.key, key)
if err != nil {
return nil
}
return v
}
// put cache to redis.
// timeout is ignored.
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return err
}
}
_, err := rc.c.Do("HSET", rc.key, key, val)
return err
}
// delete cache in redis.
func (rc *RedisCache) Delete(key string) error {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return err
}
}
_, err := rc.c.Do("HDEL", rc.key, key)
return err
}
// check cache exist in redis.
func (rc *RedisCache) IsExist(key string) bool {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return false
}
}
v, err := redis.Bool(rc.c.Do("HEXISTS", rc.key, key))
if err != nil {
return false
}
return v
}
// increase counter in redis.
func (rc *RedisCache) Incr(key string) error {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return err
}
}
_, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, 1))
if err != nil {
return err
}
return nil
}
// decrease counter in redis.
func (rc *RedisCache) Decr(key string) error {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return err
}
}
_, err := redis.Bool(rc.c.Do("HINCRBY", rc.key, key, -1))
if err != nil {
return err
}
return nil
}
// clean all cache in redis. delete this redis collection.
func (rc *RedisCache) ClearAll() error {
if rc.c == nil {
var err error
rc.c, err = rc.connectInit()
if err != nil {
return err
}
}
_, err := rc.c.Do("DEL", rc.key)
return err
}
// start redis cache adapter.
// config is like {"key":"collection key","conn":"connection info"}
// the cache item in redis are stored forever,
// so no gc operation.
func (rc *RedisCache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["key"]; !ok {
cf["key"] = DefaultKey
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
}
rc.key = cf["key"]
rc.conninfo = cf["conn"]
var err error
rc.c, err = rc.connectInit()
if err != nil {
return err
}
if rc.c == nil {
return errors.New("dial tcp conn error")
}
return nil
}
// connect to redis.
func (rc *RedisCache) connectInit() (redis.Conn, error) {
c, err := redis.Dial("tcp", rc.conninfo)
if err != nil {
return nil, err
}
return c, nil
}
func init() {
Register("redis", NewRedisCache())
}

181
cache/redis/redis.go vendored Normal file
View File

@ -0,0 +1,181 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// package redis for cache provider
//
// depend on github.com/garyburd/redigo/redis
//
// go install github.com/garyburd/redigo/redis
//
// Usage:
// import(
// _ "github.com/astaxie/beego/cache/redis"
// "github.com/astaxie/beego/cache"
// )
//
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
//
// more docs http://beego.me/docs/module/cache.md
package redis
import (
"encoding/json"
"errors"
"time"
"github.com/garyburd/redigo/redis"
"github.com/astaxie/beego/cache"
)
var (
// the collection name of redis for cache adapter.
DefaultKey string = "beecacheRedis"
)
// Redis cache adapter.
type RedisCache struct {
p *redis.Pool // redis connection pool
conninfo string
key string
}
// create new redis cache with default collection name.
func NewRedisCache() *RedisCache {
return &RedisCache{key: DefaultKey}
}
// actually do the redis cmds
func (rc *RedisCache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
c := rc.p.Get()
defer c.Close()
return c.Do(commandName, args...)
}
// Get cache from redis.
func (rc *RedisCache) Get(key string) interface{} {
if v, err := rc.do("GET", key); err == nil {
return v
}
return nil
}
// put cache to redis.
func (rc *RedisCache) Put(key string, val interface{}, timeout int64) error {
var err error
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
}
return err
}
// delete cache in redis.
func (rc *RedisCache) Delete(key string) error {
var err error
if _, err = rc.do("DEL", key); err != nil {
return err
}
_, err = rc.do("HDEL", rc.key, key)
return err
}
// check cache's existence in redis.
func (rc *RedisCache) IsExist(key string) bool {
v, err := redis.Bool(rc.do("EXISTS", key))
if err != nil {
return false
}
if v == false {
if _, err = rc.do("HDEL", rc.key, key); err != nil {
return false
}
}
return v
}
// increase counter in redis.
func (rc *RedisCache) Incr(key string) error {
_, err := redis.Bool(rc.do("INCRBY", key, 1))
return err
}
// decrease counter in redis.
func (rc *RedisCache) Decr(key string) error {
_, err := redis.Bool(rc.do("INCRBY", key, -1))
return err
}
// clean all cache in redis. delete this redis collection.
func (rc *RedisCache) ClearAll() error {
cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key))
if err != nil {
return err
}
for _, str := range cachedKeys {
if _, err = rc.do("DEL", str); err != nil {
return err
}
}
_, err = rc.do("DEL", rc.key)
return err
}
// start redis cache adapter.
// config is like {"key":"collection key","conn":"connection info"}
// the cache item in redis are stored forever,
// so no gc operation.
func (rc *RedisCache) StartAndGC(config string) error {
var cf map[string]string
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["key"]; !ok {
cf["key"] = DefaultKey
}
if _, ok := cf["conn"]; !ok {
return errors.New("config has no conn key")
}
rc.key = cf["key"]
rc.conninfo = cf["conn"]
rc.connectInit()
c := rc.p.Get()
defer c.Close()
return c.Err()
}
// connect to redis.
func (rc *RedisCache) connectInit() {
dialFunc := func() (c redis.Conn, err error) {
c, err = redis.Dial("tcp", rc.conninfo)
return
}
// initialize a new pool
rc.p = &redis.Pool{
MaxIdle: 3,
IdleTimeout: 180 * time.Second,
Dial: dialFunc,
}
}
func init() {
cache.Register("redis", NewRedisCache())
}

85
cache/redis/redis_test.go vendored Normal file
View File

@ -0,0 +1,85 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package redis
import (
"testing"
"time"
"github.com/garyburd/redigo/redis"
"github.com/astaxie/beego/cache"
)
func TestRedisCache(t *testing.T) {
bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
if err != nil {
t.Error("init err")
}
if err = bm.Put("astaxie", 1, 10); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
time.Sleep(10 * time.Second)
if bm.IsExist("astaxie") {
t.Error("check err")
}
if err = bm.Put("astaxie", 1, 10); err != nil {
t.Error("set Error", err)
}
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
t.Error("get err")
}
if err = bm.Incr("astaxie"); err != nil {
t.Error("Incr Error", err)
}
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 2 {
t.Error("get err")
}
if err = bm.Decr("astaxie"); err != nil {
t.Error("Decr Error", err)
}
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
t.Error("get err")
}
bm.Delete("astaxie")
if bm.IsExist("astaxie") {
t.Error("delete err")
}
//test string
if err = bm.Put("astaxie", "author", 10); err != nil {
t.Error("set Error", err)
}
if !bm.IsExist("astaxie") {
t.Error("check err")
}
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
t.Error("get err")
}
// test clear all
if err = bm.ClearAll(); err != nil {
t.Error("clear all err")
}
}

525
config.go
View File

@ -1,50 +1,69 @@
// 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"
"html/template"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/astaxie/beego/config"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/session"
"github.com/astaxie/beego/utils"
)
var (
BeeApp *App // beego application
AppName string
AppPath string
workPath string
AppConfigPath string
StaticDir map[string]string
TemplateCache map[string]*template.Template // template caching map
StaticExtensionsToGzip []string // files with should be compressed with gzip (.js,.css,etc)
EnableHttpListen bool
HttpAddr string
HttpPort int
HttpTLS bool
ListenTCP4 bool
EnableHttpTLS bool
HttpsPort int
HttpCertFile string
HttpKeyFile string
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.
EnableHotUpdate bool // flag of hot update checking by app self. default is false.
HttpServerTimeOut int64
ErrorsShow bool // flag of show errors in page. if true, show error and trace info in page rendered with error template.
XSRFKEY string // xsrf hash salt string.
@ -57,15 +76,134 @@ var (
EnableAdmin bool // flag of enable admin module to log every request info.
AdminHttpAddr string // http server configurations for admin module.
AdminHttpPort int
FlashName string // name of the flash variable found in response header and cookie
FlashSeperator string // used to seperate flash key:value
AppConfigProvider string // config provider
EnableDocs bool // enable generate docs & server docs API Swagger
RouterCaseSensitive bool // router case sensitive default is true
)
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 len(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()
workPath, _ = os.Getwd()
workPath, _ = filepath.Abs(workPath)
// initialize default configurations
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
os.Chdir(AppPath)
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if workPath != AppPath {
if utils.FileExists(AppConfigPath) {
os.Chdir(AppPath)
} else {
AppConfigPath = filepath.Join(workPath, "conf", "app.conf")
}
}
AppConfigProvider = "ini"
StaticDir = make(map[string]string)
StaticDir["/static"] = "static"
@ -75,9 +213,13 @@ func init() {
TemplateCache = make(map[string]*template.Template)
// set this to 0.0.0.0 to make this app available to externally
EnableHttpListen = true //default enable http Listen
HttpAddr = ""
HttpPort = 8080
HttpsPort = 10443
AppName = "beego"
RunMode = "dev" //default runmod
@ -93,18 +235,16 @@ 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
EnableGzip = false
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
HttpServerTimeOut = 0
ErrorsShow = true
@ -115,201 +255,230 @@ func init() {
TemplateLeft = "{{"
TemplateRight = "}}"
BeegoServerName = "beegoServer"
BeegoServerName = "beegoServer:" + VERSION
EnableAdmin = false
AdminHttpAddr = "127.0.0.1"
AdminHttpPort = 8088
FlashName = "BEEGO_FLASH"
FlashSeperator = "BEEGOFLASH"
RouterCaseSensitive = true
runtime.GOMAXPROCS(runtime.NumCPU())
// init BeeLogger
BeeLogger = logs.NewLogger(10000)
BeeLogger.SetLogger("console", "")
err := BeeLogger.SetLogger("console", "")
if err != nil {
fmt.Println("init console log error:", err)
}
SetLogFuncCall(true)
err := ParseConfig()
if err != nil && !os.IsNotExist(err) {
err = ParseConfig()
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("ini", AppConfigPath)
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
if err != nil {
return err
} else {
HttpAddr = AppConfig.String("HttpAddr")
}
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 := AppConfig.Int("HttpPort"); err == nil {
HttpPort = v
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 maxmemory, err := AppConfig.Int64("MaxMemory"); err == nil {
MaxMemory = maxmemory
}
if appname := AppConfig.String("AppName"); appname != "" {
AppName = appname
}
if runmode := AppConfig.String("RunMode"); runmode != "" {
RunMode = runmode
}
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 sesshashfunc := AppConfig.String("SessionHashFunc"); sesshashfunc != "" {
SessionHashFunc = sesshashfunc
}
if sesshashkey := AppConfig.String("SessionHashKey"); sesshashkey != "" {
SessionHashKey = sesshashkey
}
if sessMaxLifeTime, err := AppConfig.Int("SessionGCMaxLifetime"); err == nil && sessMaxLifeTime != 0 {
int64val, _ := strconv.ParseInt(strconv.Itoa(sessMaxLifeTime), 10, 64)
SessionGCMaxLifetime = int64val
}
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 hotupdate, err := AppConfig.Bool("HotUpdate"); err == nil {
EnableHotUpdate = hotupdate
}
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("HttpTLS"); err == nil {
HttpTLS = httptls
}
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 sd := AppConfig.String("StaticDir"); sd != "" {
for k := range StaticDir {
delete(StaticDir, k)
sds := strings.Fields(sd)
for _, v := range sds {
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1]
} else {
StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0]
}
sds := strings.Fields(sd)
for _, v := range sds {
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
StaticDir["/"+url2fsmap[0]] = url2fsmap[1]
} else {
StaticDir["/"+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 := AppConfig.String("StaticExtensionsToGzip"); sgz != "" {
extensions := strings.Split(sgz, ",")
if len(extensions) > 0 {
StaticExtensionsToGzip = []string{}
for _, ext := range extensions {
if len(ext) == 0 {
continue
}
extWithDot := ext
if extWithDot[:1] != "." {
extWithDot = "." + extWithDot
}
StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
extWithDot := ext
if extWithDot[:1] != "." {
extWithDot = "." + extWithDot
}
StaticExtensionsToGzip = append(StaticExtensionsToGzip, extWithDot)
}
}
}
if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil {
EnableAdmin = enableadmin
}
if enableadmin, err := AppConfig.Bool("EnableAdmin"); err == nil {
EnableAdmin = enableadmin
}
if adminhttpaddr := AppConfig.String("AdminHttpAddr"); adminhttpaddr != "" {
AdminHttpAddr = adminhttpaddr
}
if adminhttpaddr := AppConfig.String("AdminHttpAddr"); adminhttpaddr != "" {
AdminHttpAddr = adminhttpaddr
}
if adminhttpport, err := AppConfig.Int("AdminHttpPort"); err == nil {
AdminHttpPort = adminhttpport
}
if adminhttpport, err := AppConfig.Int("AdminHttpPort"); err == nil {
AdminHttpPort = adminhttpport
}
if enabledocs, err := AppConfig.Bool("EnableDocs"); err == nil {
EnableDocs = enabledocs
}
if casesensitive, err := AppConfig.Bool("RouterCaseSensitive"); err == nil {
RouterCaseSensitive = casesensitive
}
return nil
}

View File

@ -1,21 +1,74 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
// import(
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("ini", "config.conf")
//
// cnf APIS:
//
// cnf.Set(key, val string) error
// cnf.String(key string) string
// cnf.Strings(key string) []string
// cnf.Int(key string) (int, error)
// cnf.Int64(key string) (int64, error)
// cnf.Bool(key string) (bool, error)
// cnf.Float(key string) (float64, error)
// cnf.DefaultString(key string, defaultval string) string
// cnf.DefaultStrings(key string, defaultval []string) []string
// cnf.DefaultInt(key string, defaultval int) int
// cnf.DefaultInt64(key string, defaultval int64) int64
// cnf.DefaultBool(key string, defaultval bool) bool
// cnf.DefaultFloat(key string, defaultval float64) float64
// cnf.DIY(key string) (interface{}, error)
// cnf.GetSection(section string) (map[string]string, error)
// cnf.SaveConfigFile(filename string) error
//
// more docs http://beego.me/docs/module/config.md
package config
import (
"fmt"
)
// ConfigContainer defines how to get and set value from configuration raw data.
type ConfigContainer interface {
Set(key, val string) error
String(key string) string
Set(key, val string) error // support section::key type in given key when using ini type.
String(key string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
Strings(key string) []string //get string slice
Int(key string) (int, error)
Int64(key string) (int64, error)
Bool(key string) (bool, error)
Float(key string) (float64, error)
DefaultString(key string, defaultval string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
DefaultStrings(key string, defaultval []string) []string //get string slice
DefaultInt(key string, defaultval int) int
DefaultInt64(key string, defaultval int64) int64
DefaultBool(key string, defaultval bool) bool
DefaultFloat(key string, defaultval float64) float64
DIY(key string) (interface{}, error)
GetSection(section string) (map[string]string, error)
SaveConfigFile(filename string) error
}
// Config is the adapter interface for parsing config file to get raw data to ConfigContainer.
type Config interface {
Parse(key string) (ConfigContainer, error)
ParseData(data []byte) (ConfigContainer, error)
}
var adapters = make(map[string]Config)
@ -27,14 +80,14 @@ func Register(name string, adapter Config) {
if adapter == nil {
panic("config: Register adapter is nil")
}
if _, dup := adapters[name]; dup {
if _, ok := adapters[name]; ok {
panic("config: Register called twice for adapter " + name)
}
adapters[name] = adapter
}
// adapterNamer is ini/json/xml/yaml
// filename is the config file path
// adapterName is ini/json/xml/yaml.
// filename is the config file path.
func NewConfig(adapterName, fileaname string) (ConfigContainer, error) {
adapter, ok := adapters[adapterName]
if !ok {
@ -42,3 +95,13 @@ func NewConfig(adapterName, fileaname string) (ConfigContainer, error) {
}
return adapter.Parse(fileaname)
}
// adapterName is ini/json/xml/yaml.
// data is the config data.
func NewConfigData(adapterName string, data []byte) (ConfigContainer, error) {
adapter, ok := adapters[adapterName]
if !ok {
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
}
return adapter.ParseData(data)
}

129
config/fake.go Normal file
View File

@ -0,0 +1,129 @@
// 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 config
import (
"errors"
"strconv"
"strings"
)
type fakeConfigContainer struct {
data map[string]string
}
func (c *fakeConfigContainer) getData(key string) string {
return c.data[strings.ToLower(key)]
}
func (c *fakeConfigContainer) Set(key, val string) error {
c.data[strings.ToLower(key)] = val
return nil
}
func (c *fakeConfigContainer) String(key string) string {
return c.getData(key)
}
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.getData(key); v == "" {
return defaultval
} else {
return v
}
}
func (c *fakeConfigContainer) Strings(key string) []string {
return strings.Split(c.getData(key), ";")
}
func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 {
return defaultval
} else {
return v
}
}
func (c *fakeConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.getData(key))
}
func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil {
return defaultval
} else {
return v
}
}
func (c *fakeConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.getData(key), 10, 64)
}
func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil {
return defaultval
} else {
return v
}
}
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.getData(key))
}
func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil {
return defaultval
} else {
return v
}
}
func (c *fakeConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.getData(key), 64)
}
func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil {
return defaultval
} else {
return v
}
}
func (c *fakeConfigContainer) DIY(key string) (interface{}, error) {
if v, ok := c.data[strings.ToLower(key)]; ok {
return v, nil
}
return nil, errors.New("key not find")
}
func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) {
return nil, errors.New("not implement in the fakeConfigContainer")
}
func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
return errors.New("not implement in the fakeConfigContainer")
}
var _ ConfigContainer = new(fakeConfigContainer)
func NewFakeConfig() ConfigContainer {
return &fakeConfigContainer{
data: make(map[string]string),
}
}

View File

@ -1,34 +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 config
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"unicode"
)
var (
DEFAULT_SECTION = "default"
bNumComment = []byte{'#'} // number sign
bSemComment = []byte{';'} // semicolon
DEFAULT_SECTION = "default" // default section means if some ini items not in a section, make them in default section,
bNumComment = []byte{'#'} // number signal
bSemComment = []byte{';'} // semicolon signal
bEmpty = []byte{}
bEqual = []byte{'='}
bDQuote = []byte{'"'}
sectionStart = []byte{'['}
sectionEnd = []byte{']'}
bEqual = []byte{'='} // equal signal
bDQuote = []byte{'"'} // quote signal
sectionStart = []byte{'['} // section start signal
sectionEnd = []byte{']'} // section end signal
lineBreak = "\n"
)
// IniConfig implements Config to parse ini file.
type IniConfig struct {
}
// ParseFile creates a new Config and parses the file configuration from the
// named file.
// 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
@ -47,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()
@ -74,8 +104,7 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
}
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
section = string(line[1 : len(line)-1])
section = strings.ToLower(section) // section name case insensitive
section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
if comment.Len() > 0 {
cfg.sectionComment[section] = comment.String()
comment.Reset()
@ -83,68 +112,240 @@ func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
if _, ok := cfg.data[section]; !ok {
cfg.data[section] = make(map[string]string)
}
} else {
if _, ok := cfg.data[section]; !ok {
cfg.data[section] = make(map[string]string)
}
keyval := bytes.SplitN(line, bEqual, 2)
val := bytes.TrimSpace(keyval[1])
if bytes.HasPrefix(val, bDQuote) {
val = bytes.Trim(val, `"`)
}
continue
}
key := string(bytes.TrimSpace(keyval[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()
comment.Reset()
if _, ok := cfg.data[section]; !ok {
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, `"`)
}
cfg.data[section][key] = string(val)
if comment.Len() > 0 {
cfg.keyComment[section+"."+key] = comment.String()
comment.Reset()
}
}
return cfg, nil
}
// A Config represents the configuration.
func (ini *IniConfig) 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 ini.Parse(tmpName)
}
// A Config represents the ini configuration.
// When set and get value, support key as section:name type.
type IniConfigContainer struct {
filename string
data map[string]map[string]string //section=> key:val
sectionComment map[string]string //sction : comment
keycomment map[string]string // id: []{comment, key...}; id 1 is for main comment.
data map[string]map[string]string // section=> key:val
sectionComment map[string]string // section : comment
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
sync.RWMutex
}
// Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) {
key = strings.ToLower(key)
return strconv.ParseBool(c.getdata(key))
}
// DefaultBool returns the boolean value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil {
return defaultval
} else {
return v
}
}
// Int returns the integer value for a given key.
func (c *IniConfigContainer) Int(key string) (int, error) {
key = strings.ToLower(key)
return strconv.Atoi(c.getdata(key))
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil {
return defaultval
} else {
return v
}
}
// Int64 returns the int64 value for a given key.
func (c *IniConfigContainer) Int64(key string) (int64, error) {
key = strings.ToLower(key)
return strconv.ParseInt(c.getdata(key), 10, 64)
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil {
return defaultval
} else {
return v
}
}
// Float returns the float value for a given key.
func (c *IniConfigContainer) Float(key string) (float64, error) {
key = strings.ToLower(key)
return strconv.ParseFloat(c.getdata(key), 64)
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil {
return defaultval
} else {
return v
}
}
// String returns the string value for a given key.
func (c *IniConfigContainer) String(key string) string {
key = strings.ToLower(key)
return c.getdata(key)
}
// DefaultString returns the string value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" {
return defaultval
} else {
return v
}
}
// Strings returns the []string value for a given key.
func (c *IniConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 {
return defaultval
} else {
return v
}
}
// GetSection returns map for the given section
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
if v, ok := c.data[section]; ok {
return v, nil
} else {
return nil, errors.New("not exist setction")
}
}
// SaveConfigFile save the config into file
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
buf := bytes.NewBuffer(nil)
for section, dt := range c.data {
// Write section comments.
if v, ok := c.sectionComment[section]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
return err
}
}
if section != DEFAULT_SECTION {
// Write section name.
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
return err
}
}
for key, val := range dt {
if key != " " {
// Write key comments.
if v, ok := c.keyComment[key]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
return err
}
}
// Write key and value.
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
return err
}
}
}
// Put a line between sections.
if _, err = buf.WriteString(lineBreak); err != nil {
return err
}
}
if _, err = buf.WriteTo(f); err != nil {
return err
}
return nil
}
// WriteValue writes a new value for key.
// if write to one section, the key need be "section::key".
// if the section is not existed, it panics.
func (c *IniConfigContainer) Set(key, value string) error {
c.Lock()
defer c.Unlock()
@ -152,16 +353,19 @@ func (c *IniConfigContainer) Set(key, value string) error {
return errors.New("key is empty")
}
var section, k string
key = strings.ToLower(key)
sectionkey := strings.Split(key, "::")
if len(sectionkey) >= 2 {
section = sectionkey[0]
k = sectionkey[1]
var (
section, k string
sectionKey []string = strings.Split(key, "::")
)
if len(sectionKey) >= 2 {
section = sectionKey[0]
k = sectionKey[1]
} else {
section = DEFAULT_SECTION
k = sectionkey[0]
k = sectionKey[0]
}
if _, ok := c.data[section]; !ok {
c.data[section] = make(map[string]string)
}
@ -169,34 +373,35 @@ func (c *IniConfigContainer) Set(key, value string) error {
return nil
}
// DIY returns the raw value by a given key.
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
key = strings.ToLower(key)
if v, ok := c.data[key]; ok {
if v, ok := c.data[strings.ToLower(key)]; ok {
return v, nil
}
return v, errors.New("key not find")
}
//section.key or key
// 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
key = strings.ToLower(key)
sectionkey := strings.Split(key, "::")
if len(sectionkey) >= 2 {
section = sectionkey[0]
k = sectionkey[1]
var (
section, k string
sectionKey []string = strings.Split(strings.ToLower(key), "::")
)
if len(sectionKey) >= 2 {
section = sectionKey[0]
k = sectionKey[1]
} else {
section = DEFAULT_SECTION
k = sectionkey[0]
k = sectionKey[0]
}
if v, ok := c.data[section]; ok {
if vv, o := v[k]; o {
if vv, ok := v[k]; ok {
return vv
}
}

View File

@ -1,3 +1,17 @@
// 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 config
import (
@ -19,6 +33,7 @@ copyrequestbody = true
key1="asta"
key2 = "xie"
CaseInsensitive = true
peers = one;two;three
`
func TestIni(t *testing.T) {
@ -78,4 +93,11 @@ func TestIni(t *testing.T) {
if v, err := iniconf.Bool("demo::caseinsensitive"); err != nil || v != true {
t.Fatal("get demo.caseinsensitive error")
}
if data := iniconf.Strings("demo::peers"); len(data) != 3 {
t.Fatal("get strings error", data)
} else if data[0] != "one" {
t.Fatal("get first params error not equat to one")
}
}

View File

@ -1,108 +1,228 @@
// 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 config
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"sync"
"time"
)
// JsonConfig is a json config parser and implements Config interface.
type JsonConfig struct {
}
// Parse returns a ConfigContainer with parsed json config map.
func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
x := &JsonConfigContainer{
data: make(map[string]interface{}),
}
content, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
x := &JsonConfigContainer{
data: make(map[string]interface{}),
}
err = json.Unmarshal(content, &x.data)
if err != nil {
return nil, err
var wrappingArray []interface{}
err2 := json.Unmarshal(content, &wrappingArray)
if err2 != nil {
return nil, err
}
x.data["rootArray"] = wrappingArray
}
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 {
data map[string]interface{}
sync.RWMutex
}
// Bool returns the boolean value for a given key.
func (c *JsonConfigContainer) Bool(key string) (bool, error) {
val := c.getdata(key)
val := c.getData(key)
if val != nil {
if v, ok := val.(bool); ok {
return v, nil
} else {
return false, errors.New("not bool value")
}
} else {
return false, errors.New("not exist key:" + key)
return false, errors.New("not bool value")
}
return false, errors.New("not exist key:" + key)
}
// 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 {
return v
}
}
// Int returns the integer value for a given key.
func (c *JsonConfigContainer) Int(key string) (int, error) {
val := c.getdata(key)
val := c.getData(key)
if val != nil {
if v, ok := val.(float64); ok {
return int(v), nil
} else {
return 0, errors.New("not int value")
}
return 0, errors.New("not int value")
}
return 0, errors.New("not exist key:" + key)
}
// 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 {
return 0, errors.New("not exist key:" + key)
return v
}
}
// Int64 returns the int64 value for a given key.
func (c *JsonConfigContainer) Int64(key string) (int64, error) {
val := c.getdata(key)
val := c.getData(key)
if val != nil {
if v, ok := val.(float64); ok {
return int64(v), nil
} else {
return 0, errors.New("not int64 value")
}
return 0, errors.New("not int64 value")
}
return 0, errors.New("not exist key:" + key)
}
// 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 {
return 0, errors.New("not exist key:" + key)
return v
}
}
// Float returns the float value for a given key.
func (c *JsonConfigContainer) Float(key string) (float64, error) {
val := c.getdata(key)
val := c.getData(key)
if val != nil {
if v, ok := val.(float64); ok {
return v, nil
} else {
return 0.0, errors.New("not float64 value")
}
return 0.0, errors.New("not float64 value")
}
return 0.0, errors.New("not exist key:" + key)
}
// 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 {
return 0.0, errors.New("not exist key:" + key)
return v
}
}
// String returns the string value for a given key.
func (c *JsonConfigContainer) String(key string) string {
val := c.getdata(key)
val := c.getData(key)
if val != nil {
if v, ok := val.(string); ok {
return v
} else {
return ""
}
}
return ""
}
// 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 {
return ""
return v
}
}
// Strings returns the []string value for a given key.
func (c *JsonConfigContainer) Strings(key string) []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 {
return v
}
}
// 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")
}
}
// SaveConfigFile save the config into file
func (c *JsonConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
b, err := json.MarshalIndent(c.data, "", " ")
if err != nil {
return err
}
_, err = f.Write(b)
return err
}
// WriteValue writes a new value for key.
func (c *JsonConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
@ -110,40 +230,39 @@ func (c *JsonConfigContainer) Set(key, val string) error {
return nil
}
// DIY returns the raw value by a given key.
func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) {
val := c.getdata(key)
val := c.getData(key)
if val != nil {
return val, nil
} else {
return nil, errors.New("not exist key")
}
return nil, errors.New("not exist key")
}
//section.key or key
func (c *JsonConfigContainer) getdata(key string) interface{} {
// 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 {
cruval, ok := c.data[sectionkey[0]]
sectionKey := strings.Split(key, "::")
if len(sectionKey) >= 2 {
curValue, ok := c.data[sectionKey[0]]
if !ok {
return nil
}
for _, key := range sectionkey[1:] {
if v, ok := cruval.(map[string]interface{}); !ok {
return nil
} else if cruval, ok = v[key]; !ok {
return nil
for _, key := range sectionKey[1:] {
if v, ok := curValue.(map[string]interface{}); ok {
if curValue, ok = v[key]; !ok {
return nil
}
}
}
return cruval
} else {
if v, ok := c.data[key]; ok {
return v
}
return curValue
}
if v, ok := c.data[key]; ok {
return v
}
return nil
}

View File

@ -1,3 +1,17 @@
// 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 config
import (
@ -27,6 +41,53 @@ var jsoncontext = `{
}
}`
var jsoncontextwitharray = `[
{
"url": "user",
"serviceAPI": "http://www.test.com/user"
},
{
"url": "employee",
"serviceAPI": "http://www.test.com/employee"
}
]`
func TestJsonStartsWithArray(t *testing.T) {
f, err := os.Create("testjsonWithArray.conf")
if err != nil {
t.Fatal(err)
}
_, err = f.WriteString(jsoncontextwitharray)
if err != nil {
f.Close()
t.Fatal(err)
}
f.Close()
defer os.Remove("testjsonWithArray.conf")
jsonconf, err := NewConfig("json", "testjsonWithArray.conf")
if err != nil {
t.Fatal(err)
}
rootArray, err := jsonconf.DIY("rootArray")
if err != nil {
t.Error("array does not exist as element")
}
rootArrayCasted := rootArray.([]interface{})
if rootArrayCasted == nil {
t.Error("array from root is nil")
} else {
elem := rootArrayCasted[0].(map[string]interface{})
if elem["url"] != "user" || elem["serviceAPI"] != "http://www.test.com/user" {
t.Error("array[0] values are not valid")
}
elem2 := rootArrayCasted[1].(map[string]interface{})
if elem2["url"] != "employee" || elem2["serviceAPI"] != "http://www.test.com/employee" {
t.Error("array[1] values are not valid")
}
}
}
func TestJson(t *testing.T) {
f, err := os.Create("testjson.conf")
if err != nil {
@ -94,4 +155,28 @@ func TestJson(t *testing.T) {
t.Fatal("get host err")
}
}
if _, err := jsonconf.Int("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int")
}
if _, err := jsonconf.Int64("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an Int64")
}
if _, err := jsonconf.Float("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Float")
}
if _, err := jsonconf.DIY("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting an interface{}")
}
if val := jsonconf.String("unknown"); val != "" {
t.Error("unknown keys should return an empty string when expecting a String")
}
if _, err := jsonconf.Bool("unknown"); err == nil {
t.Error("unknown keys should return an error when expecting a Bool")
}
}

View File

@ -1,83 +0,0 @@
//xml parse should incluce in <config></config> tags
package config
import (
"errors"
"io/ioutil"
"os"
"strconv"
"sync"
"github.com/beego/x2j"
)
type XMLConfig struct {
}
func (xmls *XMLConfig) Parse(filename string) (ConfigContainer, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
x := &XMLConfigContainer{
data: make(map[string]interface{}),
}
content, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
d, err := x2j.DocToMap(string(content))
if err != nil {
return nil, err
}
x.data = d["config"].(map[string]interface{})
return x, nil
}
type XMLConfigContainer struct {
data map[string]interface{}
sync.Mutex
}
func (c *XMLConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.data[key].(string))
}
func (c *XMLConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.data[key].(string))
}
func (c *XMLConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.data[key].(string), 10, 64)
}
func (c *XMLConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.data[key].(string), 64)
}
func (c *XMLConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok {
return v
}
return ""
}
func (c *XMLConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[key]; ok {
return v, nil
}
return nil, errors.New("not exist key")
}
func init() {
Register("xml", &XMLConfig{})
}

228
config/xml/xml.go Normal file
View File

@ -0,0 +1,228 @@
// 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 xml for config provider
//
// depend on github.com/beego/x2j
//
// go install github.com/beego/x2j
//
// Usage:
// import(
// _ "github.com/astaxie/beego/config/xml"
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("xml", "config.xml")
//
// more docs http://beego.me/docs/module/config.md
package xml
import (
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/astaxie/beego/config"
"github.com/beego/x2j"
)
// XmlConfig is a xml config parser and implements Config interface.
// xml configurations should be included in <config></config> tag.
// only support key/value pair as <key>value</key> as each item.
type XMLConfig struct{}
// Parse returns a ConfigContainer with parsed xml config map.
func (xc *XMLConfig) Parse(filename string) (config.ConfigContainer, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
x := &XMLConfigContainer{data: make(map[string]interface{})}
content, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
d, err := x2j.DocToMap(string(content))
if err != nil {
return nil, err
}
x.data = d["config"].(map[string]interface{})
return x, nil
}
func (x *XMLConfig) ParseData(data []byte) (config.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 x.Parse(tmpName)
}
// A Config represents the xml configuration.
type XMLConfigContainer struct {
data map[string]interface{}
sync.Mutex
}
// Bool returns the boolean value for a given key.
func (c *XMLConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.data[key].(string))
}
// DefaultBool return the bool value if has no error
// otherwise return the defaultval
func (c *XMLConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil {
return defaultval
} else {
return v
}
}
// Int returns the integer value for a given key.
func (c *XMLConfigContainer) Int(key string) (int, error) {
return strconv.Atoi(c.data[key].(string))
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaltval
func (c *XMLConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil {
return defaultval
} else {
return v
}
}
// Int64 returns the int64 value for a given key.
func (c *XMLConfigContainer) Int64(key string) (int64, error) {
return strconv.ParseInt(c.data[key].(string), 10, 64)
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval
func (c *XMLConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil {
return defaultval
} else {
return v
}
}
// Float returns the float value for a given key.
func (c *XMLConfigContainer) Float(key string) (float64, error) {
return strconv.ParseFloat(c.data[key].(string), 64)
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval
func (c *XMLConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil {
return defaultval
} else {
return v
}
}
// String returns the string value for a given key.
func (c *XMLConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok {
return v
}
return ""
}
// DefaultString returns the string value for a given key.
// if err != nil return defaltval
func (c *XMLConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" {
return defaultval
} else {
return v
}
}
// Strings returns the []string value for a given key.
func (c *XMLConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *XMLConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 {
return defaultval
} else {
return v
}
}
// GetSection returns map for the given section
func (c *XMLConfigContainer) 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")
}
}
// SaveConfigFile save the config into file
func (c *XMLConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
b, err := xml.MarshalIndent(c.data, " ", " ")
if err != nil {
return err
}
_, err = f.Write(b)
return err
}
// WriteValue writes a new value for key.
func (c *XMLConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
// DIY returns the raw value by a given key.
func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[key]; ok {
return v, nil
}
return nil, errors.New("not exist key")
}
func init() {
config.Register("xml", &XMLConfig{})
}

View File

@ -1,8 +1,24 @@
package config
// 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 xml
import (
"os"
"testing"
"github.com/astaxie/beego/config"
)
//xml parse should incluce in <config></config> tags
@ -30,7 +46,7 @@ func TestXML(t *testing.T) {
}
f.Close()
defer os.Remove("testxml.conf")
xmlconf, err := NewConfig("xml", "testxml.conf")
xmlconf, err := config.NewConfig("xml", "testxml.conf")
if err != nil {
t.Fatal(err)
}

View File

@ -1,127 +0,0 @@
package config
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"log"
"os"
"sync"
"github.com/beego/goyaml2"
)
type YAMLConfig struct {
}
func (yaml *YAMLConfig) Parse(filename string) (ConfigContainer, error) {
y := &YAMLConfigContainer{
data: make(map[string]interface{}),
}
cnf, err := ReadYmlReader(filename)
if err != nil {
return nil, err
}
y.data = cnf
return y, nil
}
// 从Reader读取YAML
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
err = nil
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
err = nil
buf, err := ioutil.ReadAll(f)
if err != nil || len(buf) < 3 {
return
}
if string(buf[0:1]) == "{" {
log.Println("Look lile a Json, try it")
err = json.Unmarshal(buf, &cnf)
if err == nil {
log.Println("It is Json Map")
return
}
}
_map, _err := goyaml2.Read(bytes.NewBuffer(buf))
if _err != nil {
log.Println("Goyaml2 ERR>", string(buf), _err)
//err = goyaml.Unmarshal(buf, &cnf)
err = _err
return
}
if _map == nil {
log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf))
}
cnf, ok := _map.(map[string]interface{})
if !ok {
log.Println("Not a Map? >> ", string(buf), _map)
cnf = nil
}
return
}
type YAMLConfigContainer struct {
data map[string]interface{}
sync.Mutex
}
func (c *YAMLConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key].(bool); ok {
return v, nil
}
return false, errors.New("not bool value")
}
func (c *YAMLConfigContainer) Int(key string) (int, error) {
if v, ok := c.data[key].(int64); ok {
return int(v), nil
}
return 0, errors.New("not int value")
}
func (c *YAMLConfigContainer) Int64(key string) (int64, error) {
if v, ok := c.data[key].(int64); ok {
return v, nil
}
return 0, errors.New("not bool value")
}
func (c *YAMLConfigContainer) Float(key string) (float64, error) {
if v, ok := c.data[key].(float64); ok {
return v, nil
}
return 0.0, errors.New("not float64 value")
}
func (c *YAMLConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok {
return v
}
return ""
}
func (c *YAMLConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
func (c *YAMLConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[key]; ok {
return v, nil
}
return nil, errors.New("not exist key")
}
func init() {
Register("yaml", &YAMLConfig{})
}

265
config/yaml/yaml.go Normal file
View File

@ -0,0 +1,265 @@
// 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 yaml for config provider
//
// depend on github.com/beego/goyaml2
//
// go install github.com/beego/goyaml2
//
// Usage:
// import(
// _ "github.com/astaxie/beego/config/yaml"
// "github.com/astaxie/beego/config"
// )
//
// cnf, err := config.NewConfig("yaml", "config.yaml")
//
// more docs http://beego.me/docs/module/config.md
package yaml
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path"
"strings"
"sync"
"time"
"github.com/astaxie/beego/config"
"github.com/beego/goyaml2"
)
// YAMLConfig is a yaml config parser and implements Config interface.
type YAMLConfig struct{}
// Parse returns a ConfigContainer with parsed yaml config map.
func (yaml *YAMLConfig) Parse(filename string) (y config.ConfigContainer, err error) {
cnf, err := ReadYmlReader(filename)
if err != nil {
return
}
y = &YAMLConfigContainer{
data: cnf,
}
return
}
func (yaml *YAMLConfig) ParseData(data []byte) (config.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 yaml.Parse(tmpName)
}
// Read yaml file to map.
// if json like, use json package, unless goyaml2 package.
func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
buf, err := ioutil.ReadAll(f)
if err != nil || len(buf) < 3 {
return
}
if string(buf[0:1]) == "{" {
log.Println("Look like a Json, try json umarshal")
err = json.Unmarshal(buf, &cnf)
if err == nil {
log.Println("It is Json Map")
return
}
}
data, err := goyaml2.Read(bytes.NewBuffer(buf))
if err != nil {
log.Println("Goyaml2 ERR>", string(buf), err)
return
}
if data == nil {
log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf))
return
}
cnf, ok := data.(map[string]interface{})
if !ok {
log.Println("Not a Map? >> ", string(buf), data)
cnf = nil
}
return
}
// A Config represents the yaml configuration.
type YAMLConfigContainer struct {
data map[string]interface{}
sync.Mutex
}
// Bool returns the boolean value for a given key.
func (c *YAMLConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key].(bool); ok {
return v, nil
}
return false, errors.New("not bool value")
}
// DefaultBool return the bool value if has no error
// otherwise return the defaultval
func (c *YAMLConfigContainer) DefaultBool(key string, defaultval bool) bool {
if v, err := c.Bool(key); err != nil {
return defaultval
} else {
return v
}
}
// Int returns the integer value for a given key.
func (c *YAMLConfigContainer) Int(key string) (int, error) {
if v, ok := c.data[key].(int64); ok {
return int(v), nil
}
return 0, errors.New("not int value")
}
// DefaultInt returns the integer value for a given key.
// if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err != nil {
return defaultval
} else {
return v
}
}
// Int64 returns the int64 value for a given key.
func (c *YAMLConfigContainer) Int64(key string) (int64, error) {
if v, ok := c.data[key].(int64); ok {
return v, nil
}
return 0, errors.New("not bool value")
}
// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err != nil {
return defaultval
} else {
return v
}
}
// Float returns the float value for a given key.
func (c *YAMLConfigContainer) Float(key string) (float64, error) {
if v, ok := c.data[key].(float64); ok {
return v, nil
}
return 0.0, errors.New("not float64 value")
}
// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err != nil {
return defaultval
} else {
return v
}
}
// String returns the string value for a given key.
func (c *YAMLConfigContainer) String(key string) string {
if v, ok := c.data[key].(string); ok {
return v
}
return ""
}
// DefaultString returns the string value for a given key.
// if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultString(key string, defaultval string) string {
if v := c.String(key); v == "" {
return defaultval
} else {
return v
}
}
// Strings returns the []string value for a given key.
func (c *YAMLConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
}
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *YAMLConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) == 0 {
return defaultval
} else {
return v
}
}
// GetSection returns map for the given section
func (c *YAMLConfigContainer) 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")
}
}
// SaveConfigFile save the config into file
func (c *YAMLConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename.
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
err = goyaml2.Write(f, c.data)
return err
}
// WriteValue writes a new value for key.
func (c *YAMLConfigContainer) Set(key, val string) error {
c.Lock()
defer c.Unlock()
c.data[key] = val
return nil
}
// DIY returns the raw value by a given key.
func (c *YAMLConfigContainer) DIY(key string) (v interface{}, err error) {
if v, ok := c.data[key]; ok {
return v, nil
}
return nil, errors.New("not exist key")
}
func init() {
config.Register("yaml", &YAMLConfig{})
}

View File

@ -1,8 +1,24 @@
package config
// 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 yaml
import (
"os"
"testing"
"github.com/astaxie/beego/config"
)
var yamlcontext = `
@ -27,7 +43,7 @@ func TestYaml(t *testing.T) {
}
f.Close()
defer os.Remove("testyaml.conf")
yamlconf, err := NewConfig("yaml", "testyaml.conf")
yamlconf, err := config.NewConfig("yaml", "testyaml.conf")
if err != nil {
t.Fatal(err)
}

29
config_test.go Normal file
View File

@ -0,0 +1,29 @@
// 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 (
"testing"
)
func TestDefaults(t *testing.T) {
if FlashName != "BEEGO_FLASH" {
t.Errorf("FlashName was not set to default.")
}
if FlashSeperator != "BEEGOFLASH" {
t.Errorf("FlashName was not set to default.")
}
}

View File

@ -1,25 +1,62 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
//
// import "github.com/astaxie/beego/context"
//
// ctx := context.Context{Request:req,ResponseWriter:rw}
//
// more docs http://beego.me/docs/module/context.md
package context
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/astaxie/beego/middleware"
"github.com/astaxie/beego/utils"
)
// Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter.
// BeegoInput and BeegoOutput provides some api to operate request and response more easily.
type Context struct {
Input *BeegoInput
Output *BeegoOutput
Request *http.Request
ResponseWriter http.ResponseWriter
_xsrf_token string
}
// Redirect does redirection to localurl with http header status code.
// It sends http response header directly.
func (ctx *Context) Redirect(status int, localurl string) {
ctx.Output.Header("Location", localurl)
ctx.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.
func (ctx *Context) Abort(status int, body string) {
ctx.Output.SetStatus(status)
ctx.ResponseWriter.WriteHeader(status)
// first panic from ErrorMaps, is is user defined error functions.
if _, ok := middleware.ErrorMaps[body]; ok {
panic(body)
@ -32,17 +69,94 @@ func (ctx *Context) Abort(status int, body string) {
panic(e)
}
// last panic user string
panic(body)
ctx.ResponseWriter.Write([]byte(body))
panic("User stop run")
}
// Write string to response body.
// it sends response body.
func (ctx *Context) WriteString(content string) {
ctx.Output.Body([]byte(content))
ctx.ResponseWriter.Write([]byte(content))
}
// Get cookie from request by a given key.
// It's alias of BeegoInput.Cookie.
func (ctx *Context) GetCookie(key string) string {
return ctx.Input.Cookie(key)
}
// Set cookie for response.
// It's alias of BeegoOutput.Cookie.
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
ctx.Output.Cookie(name, value, others...)
}
// Get secure cookie from request by a given key.
func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
val := ctx.Input.Cookie(key)
if val == "" {
return "", false
}
parts := strings.SplitN(val, "|", 3)
if len(parts) != 3 {
return "", false
}
vs := parts[0]
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
return "", false
}
res, _ := base64.URLEncoding.DecodeString(vs)
return string(res), true
}
// Set Secure cookie for response.
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
ctx.Output.Cookie(name, cookie, others...)
}
// XsrfToken creates a xsrf token string and returns.
func (ctx *Context) XsrfToken(key string, expire int64) string {
if ctx._xsrf_token == "" {
token, ok := ctx.GetSecureCookie(key, "_xsrf")
if !ok {
token = string(utils.RandomCreateBytes(32))
ctx.SetSecureCookie(key, "_xsrf", token, expire)
}
ctx._xsrf_token = token
}
return ctx._xsrf_token
}
// CheckXsrfCookie checks xsrf token in this request is valid or not.
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
// or in form field value named as "_xsrf".
func (ctx *Context) CheckXsrfCookie() bool {
token := ctx.Input.Query("_xsrf")
if token == "" {
token = ctx.Request.Header.Get("X-Xsrftoken")
}
if token == "" {
token = ctx.Request.Header.Get("X-Csrftoken")
}
if token == "" {
ctx.Abort(403, "'_xsrf' argument missing from POST")
} else if ctx._xsrf_token != token {
ctx.Abort(403, "XSRF cookie does not match POST argument")
}
return true
}

View File

@ -1,23 +1,45 @@
// 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 context
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"github.com/astaxie/beego/session"
)
// BeegoInput operates the http request header ,data ,cookie and body.
// it also contains router params and current session.
type BeegoInput struct {
CruSession session.SessionStore
Params map[string]string
Data map[interface{}]interface{}
Request *http.Request
RequestBody []byte
CruSession session.SessionStore
Params map[string]string
Data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
Request *http.Request
RequestBody []byte
RunController reflect.Type
RunMethod string
}
// NewInput return BeegoInput generated by http.Request.
func NewInput(req *http.Request) *BeegoInput {
return &BeegoInput{
Params: make(map[string]string),
@ -26,22 +48,27 @@ func NewInput(req *http.Request) *BeegoInput {
}
}
// Protocol returns request protocol name, such as HTTP/1.1 .
func (input *BeegoInput) Protocol() string {
return input.Request.Proto
}
// Uri returns full request url with query string, fragment.
func (input *BeegoInput) Uri() string {
return input.Request.RequestURI
}
// 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.
func (input *BeegoInput) Site() string {
return input.Scheme() + "://" + input.Domain()
}
// Scheme returns request scheme as "http" or "https".
func (input *BeegoInput) Scheme() string {
if input.Request.URL.Scheme != "" {
return input.Request.URL.Scheme
@ -52,10 +79,14 @@ func (input *BeegoInput) Scheme() string {
}
}
// Domain returns host name.
// Alias of Host method.
func (input *BeegoInput) Domain() string {
return input.Host()
}
// Host returns host name.
// if no host info in request, return localhost.
func (input *BeegoInput) Host() string {
if input.Request.Host != "" {
hostParts := strings.Split(input.Request.Host, ":")
@ -67,44 +98,90 @@ func (input *BeegoInput) Host() string {
return "localhost"
}
// Method returns http request method.
func (input *BeegoInput) Method() string {
return input.Request.Method
}
// Is returns boolean of this request is on given method, such as Is("POST").
func (input *BeegoInput) Is(method string) bool {
return input.Method() == method
}
// Is this a GET method request?
func (input *BeegoInput) IsGet() bool {
return input.Is("GET")
}
// Is this a POST method request?
func (input *BeegoInput) IsPost() bool {
return input.Is("POST")
}
// Is this a Head method request?
func (input *BeegoInput) IsHead() bool {
return input.Is("HEAD")
}
// Is this a OPTIONS method request?
func (input *BeegoInput) IsOptions() bool {
return input.Is("OPTIONS")
}
// Is this a PUT method request?
func (input *BeegoInput) IsPut() bool {
return input.Is("PUT")
}
// Is this a DELETE method request?
func (input *BeegoInput) IsDelete() bool {
return input.Is("DELETE")
}
// Is this a PATCH method request?
func (input *BeegoInput) IsPatch() bool {
return input.Is("PATCH")
}
// IsAjax returns boolean of this request is generated by ajax.
func (input *BeegoInput) IsAjax() bool {
return input.Header("X-Requested-With") == "XMLHttpRequest"
}
// IsSecure returns boolean of this request is in https.
func (input *BeegoInput) IsSecure() bool {
return input.Scheme() == "https"
}
// IsSecure 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..
func (input *BeegoInput) IsUpload() bool {
return input.Request.MultipartForm != nil
return strings.Contains(input.Header("Content-Type"), "multipart/form-data")
}
// IP returns request client ip.
// if in proxy, return first proxy id.
// if error, return 127.0.0.1.
func (input *BeegoInput) IP() string {
ips := input.Proxy()
if len(ips) > 0 && ips[0] != "" {
return ips[0]
rip := strings.Split(ips[0], ":")
return rip[0]
}
ip := strings.Split(input.Request.RemoteAddr, ":")
if len(ip) > 0 {
if ip[0] != "["{
if ip[0] != "[" {
return ip[0]
}
}
return "127.0.0.1"
}
// Proxy returns proxy client ips slice.
func (input *BeegoInput) Proxy() []string {
if ips := input.Header("X-Forwarded-For"); ips != "" {
return strings.Split(ips, ",")
@ -112,15 +189,20 @@ func (input *BeegoInput) Proxy() []string {
return []string{}
}
// Refer returns http referer header.
func (input *BeegoInput) Refer() string {
return input.Header("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:], ".")
}
// Port returns request client port.
// when error or empty, return 80.
func (input *BeegoInput) Port() int {
parts := strings.Split(input.Request.Host, ":")
if len(parts) == 2 {
@ -130,10 +212,12 @@ func (input *BeegoInput) Port() int {
return 80
}
// UserAgent returns request client user agent string.
func (input *BeegoInput) UserAgent() string {
return input.Header("User-Agent")
}
// Param returns router param by a given key.
func (input *BeegoInput) Param(key string) string {
if v, ok := input.Params[key]; ok {
return v
@ -141,15 +225,24 @@ func (input *BeegoInput) Param(key string) string {
return ""
}
// Query returns input data item string by a given string.
func (input *BeegoInput) Query(key string) string {
input.Request.ParseForm()
if val := input.Param(key); val != "" {
return val
}
if input.Request.Form == nil {
input.Request.ParseForm()
}
return input.Request.Form.Get(key)
}
// Header returns request header item string by a given string.
func (input *BeegoInput) Header(key string) string {
return input.Request.Header.Get(key)
}
// Cookie returns request cookie item string by a given key.
// if non-existed, return empty string.
func (input *BeegoInput) Cookie(key string) string {
ck, err := input.Request.Cookie(key)
if err != nil {
@ -158,11 +251,13 @@ func (input *BeegoInput) Cookie(key string) string {
return ck.Value
}
// Session returns current session item value by a given key.
func (input *BeegoInput) Session(key interface{}) interface{} {
return input.CruSession.Get(key)
}
func (input *BeegoInput) Body() []byte {
// Body returns the raw request body data as bytes.
func (input *BeegoInput) CopyBody() []byte {
requestbody, _ := ioutil.ReadAll(input.Request.Body)
input.Request.Body.Close()
bf := bytes.NewBuffer(requestbody)
@ -171,6 +266,7 @@ func (input *BeegoInput) Body() []byte {
return requestbody
}
// GetData returns the stored data in this context.
func (input *BeegoInput) GetData(key interface{}) interface{} {
if v, ok := input.Data[key]; ok {
return v
@ -178,6 +274,262 @@ func (input *BeegoInput) GetData(key interface{}) interface{} {
return nil
}
// SetData stores data with given key in this context.
// This data are only available in this context.
func (input *BeegoInput) SetData(key, val interface{}) {
input.Data[key] = val
}
// parseForm or parseMultiForm based on Content-type
func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error {
// Parse the body depending on the content type.
if strings.Contains(input.Header("Content-Type"), "multipart/form-data") {
if err := input.Request.ParseMultipartForm(maxMemory); err != nil {
return errors.New("Error parsing request body:" + err.Error())
}
} else if err := input.Request.ParseForm(); err != nil {
return errors.New("Error parsing request body:" + err.Error())
}
return nil
}
// Bind data from request.Form[key] to dest
// like /?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie
// var id int beegoInput.Bind(&id, "id") id ==123
// var isok bool beegoInput.Bind(&isok, "isok") id ==true
// var ft float64 beegoInput.Bind(&ft, "ft") ft ==1.2
// ol := make([]int, 0, 2) beegoInput.Bind(&ol, "ol") ol ==[1 2]
// ul := make([]string, 0, 2) beegoInput.Bind(&ul, "ul") ul ==[str array]
// user struct{Name} beegoInput.Bind(&user, "user") user == {Name:"astaxie"}
func (input *BeegoInput) Bind(dest interface{}, key string) error {
value := reflect.ValueOf(dest)
if value.Kind() != reflect.Ptr {
return errors.New("beego: non-pointer passed to Bind: " + key)
}
value = value.Elem()
if !value.CanSet() {
return errors.New("beego: non-settable variable passed to Bind: " + key)
}
rv := input.bind(key, value.Type())
if !rv.IsValid() {
return errors.New("beego: reflect value is empty")
}
value.Set(rv)
return nil
}
func (input *BeegoInput) bind(key string, typ reflect.Type) reflect.Value {
rv := reflect.Zero(reflect.TypeOf(0))
switch typ.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val := input.Query(key)
if len(val) == 0 {
return rv
}
rv = input.bindInt(val, typ)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val := input.Query(key)
if len(val) == 0 {
return rv
}
rv = input.bindUint(val, typ)
case reflect.Float32, reflect.Float64:
val := input.Query(key)
if len(val) == 0 {
return rv
}
rv = input.bindFloat(val, typ)
case reflect.String:
val := input.Query(key)
if len(val) == 0 {
return rv
}
rv = input.bindString(val, typ)
case reflect.Bool:
val := input.Query(key)
if len(val) == 0 {
return rv
}
rv = input.bindBool(val, typ)
case reflect.Slice:
rv = input.bindSlice(&input.Request.Form, key, typ)
case reflect.Struct:
rv = input.bindStruct(&input.Request.Form, key, typ)
case reflect.Ptr:
rv = input.bindPoint(key, typ)
case reflect.Map:
rv = input.bindMap(&input.Request.Form, key, typ)
}
return rv
}
func (input *BeegoInput) bindValue(val string, typ reflect.Type) reflect.Value {
rv := reflect.Zero(reflect.TypeOf(0))
switch typ.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
rv = input.bindInt(val, typ)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
rv = input.bindUint(val, typ)
case reflect.Float32, reflect.Float64:
rv = input.bindFloat(val, typ)
case reflect.String:
rv = input.bindString(val, typ)
case reflect.Bool:
rv = input.bindBool(val, typ)
case reflect.Slice:
rv = input.bindSlice(&url.Values{"": {val}}, "", typ)
case reflect.Struct:
rv = input.bindStruct(&url.Values{"": {val}}, "", typ)
case reflect.Ptr:
rv = input.bindPoint(val, typ)
case reflect.Map:
rv = input.bindMap(&url.Values{"": {val}}, "", typ)
}
return rv
}
func (input *BeegoInput) bindInt(val string, typ reflect.Type) reflect.Value {
intValue, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return reflect.Zero(typ)
}
pValue := reflect.New(typ)
pValue.Elem().SetInt(intValue)
return pValue.Elem()
}
func (input *BeegoInput) bindUint(val string, typ reflect.Type) reflect.Value {
uintValue, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return reflect.Zero(typ)
}
pValue := reflect.New(typ)
pValue.Elem().SetUint(uintValue)
return pValue.Elem()
}
func (input *BeegoInput) bindFloat(val string, typ reflect.Type) reflect.Value {
floatValue, err := strconv.ParseFloat(val, 64)
if err != nil {
return reflect.Zero(typ)
}
pValue := reflect.New(typ)
pValue.Elem().SetFloat(floatValue)
return pValue.Elem()
}
func (input *BeegoInput) bindString(val string, typ reflect.Type) reflect.Value {
return reflect.ValueOf(val)
}
func (input *BeegoInput) bindBool(val string, typ reflect.Type) reflect.Value {
val = strings.TrimSpace(strings.ToLower(val))
switch val {
case "true", "on", "1":
return reflect.ValueOf(true)
}
return reflect.ValueOf(false)
}
type sliceValue struct {
index int // Index extracted from brackets. If -1, no index was provided.
value reflect.Value // the bound value for this slice element.
}
func (input *BeegoInput) bindSlice(params *url.Values, key string, typ reflect.Type) reflect.Value {
maxIndex := -1
numNoIndex := 0
sliceValues := []sliceValue{}
for reqKey, vals := range *params {
if !strings.HasPrefix(reqKey, key+"[") {
continue
}
// Extract the index, and the index where a sub-key starts. (e.g. field[0].subkey)
index := -1
leftBracket, rightBracket := len(key), strings.Index(reqKey[len(key):], "]")+len(key)
if rightBracket > leftBracket+1 {
index, _ = strconv.Atoi(reqKey[leftBracket+1 : rightBracket])
}
subKeyIndex := rightBracket + 1
// Handle the indexed case.
if index > -1 {
if index > maxIndex {
maxIndex = index
}
sliceValues = append(sliceValues, sliceValue{
index: index,
value: input.bind(reqKey[:subKeyIndex], typ.Elem()),
})
continue
}
// It's an un-indexed element. (e.g. element[])
numNoIndex += len(vals)
for _, val := range vals {
// Unindexed values can only be direct-bound.
sliceValues = append(sliceValues, sliceValue{
index: -1,
value: input.bindValue(val, typ.Elem()),
})
}
}
resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex)
for _, sv := range sliceValues {
if sv.index != -1 {
resultArray.Index(sv.index).Set(sv.value)
} else {
resultArray = reflect.Append(resultArray, sv.value)
}
}
return resultArray
}
func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect.Type) reflect.Value {
result := reflect.New(typ).Elem()
fieldValues := make(map[string]reflect.Value)
for reqKey, val := range *params {
if !strings.HasPrefix(reqKey, key+".") {
continue
}
fieldName := reqKey[len(key)+1:]
if _, ok := fieldValues[fieldName]; !ok {
// Time to bind this field. Get it and make sure we can set it.
fieldValue := result.FieldByName(fieldName)
if !fieldValue.IsValid() {
continue
}
if !fieldValue.CanSet() {
continue
}
boundVal := input.bindValue(val[0], fieldValue.Type())
fieldValue.Set(boundVal)
fieldValues[fieldName] = boundVal
}
}
return result
}
func (input *BeegoInput) bindPoint(key string, typ reflect.Type) reflect.Value {
return input.bind(key, typ.Elem()).Addr()
}
func (input *BeegoInput) bindMap(params *url.Values, key string, typ reflect.Type) reflect.Value {
var (
result = reflect.MakeMap(typ)
keyType = typ.Key()
valueType = typ.Elem()
)
for paramName, values := range *params {
if !strings.HasPrefix(paramName, key+"[") || paramName[len(paramName)-1] != ']' {
continue
}
key := paramName[len(key)+1 : len(paramName)-1]
result.SetMapIndex(input.bindValue(key, keyType), input.bindValue(values[0], valueType))
}
return result
}

72
context/input_test.go Normal file
View File

@ -0,0 +1,72 @@
// 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 context
import (
"fmt"
"net/http"
"testing"
)
func TestParse(t *testing.T) {
r, _ := http.NewRequest("GET", "/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil)
beegoInput := NewInput(r)
beegoInput.ParseFormOrMulitForm(1 << 20)
var id int
err := beegoInput.Bind(&id, "id")
if id != 123 || err != nil {
t.Fatal("id should has int value")
}
fmt.Println(id)
var isok bool
err = beegoInput.Bind(&isok, "isok")
if !isok || err != nil {
t.Fatal("isok should be true")
}
fmt.Println(isok)
var float float64
err = beegoInput.Bind(&float, "ft")
if float != 1.2 || err != nil {
t.Fatal("float should be equal to 1.2")
}
fmt.Println(float)
ol := make([]int, 0, 2)
err = beegoInput.Bind(&ol, "ol")
if len(ol) != 2 || err != nil || ol[0] != 1 || ol[1] != 2 {
t.Fatal("ol should has two elements")
}
fmt.Println(ol)
ul := make([]string, 0, 2)
err = beegoInput.Bind(&ul, "ul")
if len(ul) != 2 || err != nil || ul[0] != "str" || ul[1] != "array" {
t.Fatal("ul should has two elements")
}
fmt.Println(ul)
type User struct {
Name string
}
user := User{}
err = beegoInput.Bind(&user, "user")
if err != nil || user.Name != "astaxie" {
t.Fatal("user should has name")
}
fmt.Println(user)
}

View File

@ -1,3 +1,17 @@
// 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 context
import (
@ -17,20 +31,27 @@ import (
"strings"
)
// BeegoOutput does work for sending response header.
type BeegoOutput struct {
Context *Context
Status int
EnableGzip bool
}
// NewOutput returns new BeegoOutput.
// it contains nothing now.
func NewOutput() *BeegoOutput {
return &BeegoOutput{}
}
// Header sets response header item string via given key.
func (output *BeegoOutput) Header(key, val string) {
output.Context.ResponseWriter.Header().Set(key, val)
}
// Body sets response body content.
// if EnableGzip, compress content string.
// it sends out response body directly.
func (output *BeegoOutput) Body(content []byte) {
output_writer := output.Context.ResponseWriter.(io.Writer)
if output.EnableGzip == true && output.Context.Input.Header("Accept-Encoding") != "" {
@ -55,6 +76,14 @@ func (output *BeegoOutput) Body(content []byte) {
} else {
output.Header("Content-Length", strconv.Itoa(len(content)))
}
// Write status code if it has been set manually
// Set it to 0 afterwards to prevent "multiple response.WriteHeader calls"
if output.Status != 0 {
output.Context.ResponseWriter.WriteHeader(output.Status)
output.Status = 0
}
output_writer.Write(content)
switch output_writer.(type) {
case *gzip.Writer:
@ -64,43 +93,83 @@ func (output *BeegoOutput) Body(content []byte) {
}
}
// Cookie sets cookie value via given key.
// others are ordered as cookie's max age time, path,domain, secure and httponly.
func (output *BeegoOutput) Cookie(name string, value string, others ...interface{}) {
var b bytes.Buffer
fmt.Fprintf(&b, "%s=%s", sanitizeName(name), sanitizeValue(value))
if len(others) > 0 {
switch others[0].(type) {
switch v := others[0].(type) {
case int:
if others[0].(int) > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int))
} else if others[0].(int) < 0 {
if v > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", v)
} else if v < 0 {
fmt.Fprintf(&b, "; Max-Age=0")
}
case int64:
if others[0].(int64) > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int64))
} else if others[0].(int64) < 0 {
if v > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", v)
} else if v < 0 {
fmt.Fprintf(&b, "; Max-Age=0")
}
case int32:
if others[0].(int32) > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", others[0].(int32))
} else if others[0].(int32) < 0 {
if v > 0 {
fmt.Fprintf(&b, "; Max-Age=%d", v)
} else if v < 0 {
fmt.Fprintf(&b, "; Max-Age=0")
}
}
}
// the settings below
// Path, Domain, Secure, HttpOnly
// can use nil skip set
// default "/"
if len(others) > 1 {
fmt.Fprintf(&b, "; Path=%s", sanitizeValue(others[1].(string)))
if v, ok := others[1].(string); ok && len(v) > 0 {
fmt.Fprintf(&b, "; Path=%s", sanitizeValue(v))
}
} else {
fmt.Fprintf(&b, "; Path=%s", "/")
}
// default empty
if len(others) > 2 {
fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(others[2].(string)))
if v, ok := others[2].(string); ok && len(v) > 0 {
fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(v))
}
}
// default empty
if len(others) > 3 {
fmt.Fprintf(&b, "; Secure")
var secure bool
switch v := others[3].(type) {
case bool:
secure = v
default:
if others[3] != nil {
secure = true
}
}
if secure {
fmt.Fprintf(&b, "; Secure")
}
}
// default false. for session cookie default true
httponly := false
if len(others) > 4 {
if v, ok := others[4].(bool); ok && v {
// HttpOnly = true
httponly = true
}
}
if httponly {
fmt.Fprintf(&b, "; HttpOnly")
}
output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String())
}
@ -116,6 +185,8 @@ func sanitizeValue(v string) string {
return cookieValueSanitizer.Replace(v)
}
// 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")
var content []byte
@ -136,6 +207,7 @@ func (output *BeegoOutput) Json(data interface{}, hasIndent bool, coding bool) e
return nil
}
// Jsonp writes jsonp to response body.
func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error {
output.Header("Content-Type", "application/javascript;charset=UTF-8")
var content []byte
@ -161,6 +233,7 @@ func (output *BeegoOutput) Jsonp(data interface{}, hasIndent bool) error {
return nil
}
// 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")
var content []byte
@ -178,10 +251,16 @@ func (output *BeegoOutput) Xml(data interface{}, hasIndent bool) error {
return nil
}
func (output *BeegoOutput) Download(file string) {
// Download forces response for download file.
// it prepares the download response header automatically.
func (output *BeegoOutput) Download(file string, filename ...string) {
output.Header("Content-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream")
output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file))
if len(filename) > 0 && filename[0] != "" {
output.Header("Content-Disposition", "attachment; filename="+filename[0])
} else {
output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file))
}
output.Header("Content-Transfer-Encoding", "binary")
output.Header("Expires", "0")
output.Header("Cache-Control", "must-revalidate")
@ -189,6 +268,8 @@ func (output *BeegoOutput) Download(file string) {
http.ServeFile(output.Context.ResponseWriter, output.Context.Request, file)
}
// ContentType sets the content type from ext string.
// MIME type is given in mime package.
func (output *BeegoOutput) ContentType(ext string) {
if !strings.HasPrefix(ext, ".") {
ext = "." + ext
@ -199,43 +280,62 @@ func (output *BeegoOutput) ContentType(ext string) {
}
}
// SetStatus sets response status code.
// It writes response header directly.
func (output *BeegoOutput) SetStatus(status int) {
output.Context.ResponseWriter.WriteHeader(status)
output.Status = status
}
// IsCachable returns boolean of this request is cached.
// HTTP 304 means cached.
func (output *BeegoOutput) IsCachable(status int) bool {
return output.Status >= 200 && output.Status < 300 || output.Status == 304
}
// IsEmpty returns boolean of this request is empty.
// HTTP 201204 and 304 means empty.
func (output *BeegoOutput) IsEmpty(status int) bool {
return output.Status == 201 || output.Status == 204 || output.Status == 304
}
// IsOk returns boolean of this request runs well.
// HTTP 200 means ok.
func (output *BeegoOutput) IsOk(status int) bool {
return output.Status == 200
}
// IsSuccessful returns boolean of this request runs successfully.
// HTTP 2xx means ok.
func (output *BeegoOutput) IsSuccessful(status int) bool {
return output.Status >= 200 && output.Status < 300
}
// IsRedirect returns boolean of this request is redirection header.
// HTTP 301,302,307 means redirection.
func (output *BeegoOutput) IsRedirect(status int) bool {
return output.Status == 301 || output.Status == 302 || output.Status == 303 || output.Status == 307
}
// IsForbidden returns boolean of this request is forbidden.
// HTTP 403 means forbidden.
func (output *BeegoOutput) IsForbidden(status int) bool {
return output.Status == 403
}
// IsNotFound returns boolean of this request is not found.
// HTTP 404 means forbidden.
func (output *BeegoOutput) IsNotFound(status int) bool {
return output.Status == 404
}
// IsClient returns boolean of this request client sends error data.
// HTTP 4xx means forbidden.
func (output *BeegoOutput) IsClientError(status int) bool {
return output.Status >= 400 && output.Status < 500
}
// IsServerError returns boolean of this server handler errors.
// HTTP 5xx means server internal error.
func (output *BeegoOutput) IsServerError(status int) bool {
return output.Status >= 500 && output.Status < 600
}
@ -254,6 +354,7 @@ func stringsToJson(str string) string {
return jsons
}
// Sessions sets session item value with given key.
func (output *BeegoOutput) Session(name interface{}, value interface{}) {
output.Context.Input.CruSession.Set(name, value)
}

View File

@ -1,13 +1,22 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
@ -18,17 +27,32 @@ import (
"reflect"
"strconv"
"strings"
"time"
"github.com/astaxie/beego/context"
"github.com/astaxie/beego/session"
)
//commonly used mime-types
const (
applicationJson = "application/json"
applicationXml = "application/xml"
textXml = "text/xml"
)
var (
// custom error when user stop request handler manually.
USERSTOPRUN = errors.New("User stop run")
USERSTOPRUN = errors.New("User stop run")
GlobalControllerRouter map[string][]ControllerComments = make(map[string][]ControllerComments) //pkgpath+controller:comments
)
// store the comment for the controller method
type ControllerComments struct {
Method string
Router string
AllowHTTPMethods []string
Params []map[string]string
}
// Controller defines some basic http request handler operations, such as
// http context, template and view, session and xsrf.
type Controller struct {
@ -45,6 +69,9 @@ type Controller struct {
CruSession session.SessionStore
XSRFExpire int
AppController interface{}
EnableRender bool
EnableXSRF bool
methodMapping map[string]func() //method:routertree
}
// ControllerInterface is an interface to uniform all controller handler.
@ -62,11 +89,12 @@ type ControllerInterface interface {
Render() error
XsrfToken() string
CheckXsrfCookie() bool
HandlerFunc(fn string) bool
URLMapping()
}
// Init generates default values of controller operations.
func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) {
c.Data = make(map[interface{}]interface{})
c.Layout = ""
c.TplNames = ""
c.controllerName = controllerName
@ -74,6 +102,10 @@ func (c *Controller) Init(ctx *context.Context, controllerName, actionName strin
c.Ctx = ctx
c.TplExt = "tpl"
c.AppController = app
c.EnableRender = true
c.EnableXSRF = true
c.Data = ctx.Input.Data
c.methodMapping = make(map[string]func())
}
// Prepare runs after Init before request function execution.
@ -121,8 +153,29 @@ func (c *Controller) Options() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
}
// call function fn
func (c *Controller) HandlerFunc(fnname string) bool {
if v, ok := c.methodMapping[fnname]; ok {
v()
return true
} else {
return false
}
}
// URLMapping register the internal Controller router.
func (c *Controller) URLMapping() {
}
func (c *Controller) Mapping(method string, fn func()) {
c.methodMapping[method] = fn
}
// Render sends the response with rendered template bytes as text/html type.
func (c *Controller) Render() error {
if !c.EnableRender {
return nil
}
rb, err := c.RenderBytes()
if err != nil {
@ -140,7 +193,7 @@ func (c *Controller) RenderString() (string, error) {
return string(b), e
}
// RenderBytes returns the bytes of renderd tempate string. Do not send out response.
// RenderBytes returns the bytes of rendered template string. Do not send out response.
func (c *Controller) RenderBytes() ([]byte, error) {
//if the controller has set layout, then first get the tplname's content set the content to the layout
if c.Layout != "" {
@ -153,7 +206,6 @@ func (c *Controller) RenderBytes() ([]byte, error) {
newbytes := bytes.NewBufferString("")
if _, ok := BeeTemplates[c.TplNames]; !ok {
panic("can't find templatefile in the path:" + c.TplNames)
return []byte{}, errors.New("can't find templatefile in the path:" + c.TplNames)
}
err := BeeTemplates[c.TplNames].ExecuteTemplate(newbytes, c.TplNames, c.Data)
if err != nil {
@ -165,7 +217,7 @@ func (c *Controller) RenderBytes() ([]byte, error) {
if c.LayoutSections != nil {
for sectionName, sectionTpl := range c.LayoutSections {
if (sectionTpl == "") {
if sectionTpl == "" {
c.Data[sectionName] = ""
continue
}
@ -199,7 +251,6 @@ func (c *Controller) RenderBytes() ([]byte, error) {
ibytes := bytes.NewBufferString("")
if _, ok := BeeTemplates[c.TplNames]; !ok {
panic("can't find templatefile in the path:" + c.TplNames)
return []byte{}, errors.New("can't find templatefile in the path:" + c.TplNames)
}
err := BeeTemplates[c.TplNames].ExecuteTemplate(ibytes, c.TplNames, c.Data)
if err != nil {
@ -209,7 +260,6 @@ func (c *Controller) RenderBytes() ([]byte, error) {
icontent, _ := ioutil.ReadAll(ibytes)
return icontent, nil
}
return []byte{}, nil
}
// Redirect sends the redirection response to url with status code.
@ -227,6 +277,11 @@ func (c *Controller) Abort(code string) {
}
}
// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body.
func (c *Controller) CustomAbort(status int, body string) {
c.Ctx.Abort(status, body)
}
// StopRun makes panic of USERSTOPRUN error and go to recover function if defined.
func (c *Controller) StopRun() {
panic(USERSTOPRUN)
@ -243,7 +298,6 @@ func (c *Controller) UrlFor(endpoint string, values ...string) string {
} else {
return UrlFor(endpoint, values...)
}
return ""
}
// ServeJson sends a json response with encoding charset.
@ -283,12 +337,22 @@ func (c *Controller) ServeXml() {
c.Ctx.Output.Xml(c.Data["xml"], hasIndent)
}
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
func (c *Controller) ServeFormatted() {
accept := c.Ctx.Input.Header("Accept")
switch accept {
case applicationJson:
c.ServeJson()
case applicationXml, textXml:
c.ServeXml()
default:
c.ServeJson()
}
}
// Input returns the input data map from POST or PUT request body and query string.
func (c *Controller) Input() url.Values {
ct := c.Ctx.Request.Header.Get("Content-Type")
if strings.Contains(ct, "multipart/form-data") {
c.Ctx.Request.ParseMultipartForm(MaxMemory) //64MB
} else {
if c.Ctx.Request.Form == nil {
c.Ctx.Request.ParseForm()
}
return c.Ctx.Request.Form
@ -301,36 +365,65 @@ func (c *Controller) ParseForm(obj interface{}) error {
// GetString returns the input value by key string.
func (c *Controller) GetString(key string) string {
return c.Input().Get(key)
return c.Ctx.Input.Query(key)
}
// GetStrings returns the input string slice by key string.
// it's designed for multi-value input field such as checkbox(input[type=checkbox]), multi-selection.
func (c *Controller) GetStrings(key string) []string {
r := c.Ctx.Request
if r.Form == nil {
f := c.Input()
if f == nil {
return []string{}
}
vs := r.Form[key]
vs := f[key]
if len(vs) > 0 {
return vs
}
return []string{}
}
// GetInt returns input value as int64.
func (c *Controller) GetInt(key string) (int64, error) {
return strconv.ParseInt(c.Input().Get(key), 10, 64)
// GetInt returns input as an int
func (c *Controller) GetInt(key string) (int, error) {
return strconv.Atoi(c.Ctx.Input.Query(key))
}
// GetInt8 return input as an int8
func (c *Controller) GetInt8(key string) (int8, error) {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 8)
i8 := int8(i64)
return i8, err
}
// GetInt16 returns input as an int16
func (c *Controller) GetInt16(key string) (int16, error) {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 16)
i16 := int16(i64)
return i16, err
}
// GetInt32 returns input as an int32
func (c *Controller) GetInt32(key string) (int32, error) {
i64, err := strconv.ParseInt(c.Ctx.Input.Query(key), 10, 32)
i32 := int32(i64)
return i32, err
}
// GetInt64 returns input value as int64.
func (c *Controller) GetInt64(key string) (int64, error) {
return strconv.ParseInt(c.Ctx.Input.Query(key), 10, 64)
}
// GetBool returns input value as bool.
func (c *Controller) GetBool(key string) (bool, error) {
return strconv.ParseBool(c.Input().Get(key))
return strconv.ParseBool(c.Ctx.Input.Query(key))
}
// GetFloat returns input value as float64.
func (c *Controller) GetFloat(key string) (float64, error) {
return strconv.ParseFloat(c.Input().Get(key), 64)
return strconv.ParseFloat(c.Ctx.Input.Query(key), 64)
}
// GetFile returns the file data in file upload field named as key.
@ -391,12 +484,16 @@ func (c *Controller) DelSession(name interface{}) {
// SessionRegenerateID regenerates session id for this session.
// the session data have no changes.
func (c *Controller) SessionRegenerateID() {
if c.CruSession != nil {
c.CruSession.SessionRelease(c.Ctx.ResponseWriter)
}
c.CruSession = GlobalSessions.SessionRegenerateId(c.Ctx.ResponseWriter, c.Ctx.Request)
c.Ctx.Input.CruSession = c.CruSession
}
// DestroySession cleans session data and session cookie.
func (c *Controller) DestroySession() {
c.Ctx.Input.CruSession.Flush()
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
}
@ -407,57 +504,24 @@ func (c *Controller) IsAjax() bool {
// GetSecureCookie returns decoded cookie value from encoded browser cookie values.
func (c *Controller) GetSecureCookie(Secret, key string) (string, bool) {
val := c.Ctx.GetCookie(key)
if val == "" {
return "", false
}
parts := strings.SplitN(val, "|", 3)
if len(parts) != 3 {
return "", false
}
vs := parts[0]
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
return "", false
}
res, _ := base64.URLEncoding.DecodeString(vs)
return string(res), true
return c.Ctx.GetSecureCookie(Secret, key)
}
// SetSecureCookie puts value into cookie after encoded the value.
func (c *Controller) SetSecureCookie(Secret, name, val string, age int64) {
vs := base64.URLEncoding.EncodeToString([]byte(val))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
c.Ctx.SetCookie(name, cookie, age, "/")
func (c *Controller) SetSecureCookie(Secret, name, value string, others ...interface{}) {
c.Ctx.SetSecureCookie(Secret, name, value, others...)
}
// XsrfToken creates a xsrf token string and returns.
func (c *Controller) XsrfToken() string {
if c._xsrf_token == "" {
token, ok := c.GetSecureCookie(XSRFKEY, "_xsrf")
if !ok {
var expire int64
if c.XSRFExpire > 0 {
expire = int64(c.XSRFExpire)
} else {
expire = int64(XSRFExpire)
}
token = getRandomString(15)
c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire)
var expire int64
if c.XSRFExpire > 0 {
expire = int64(c.XSRFExpire)
} else {
expire = int64(XSRFExpire)
}
c._xsrf_token = token
c._xsrf_token = c.Ctx.XsrfToken(XSRFKEY, expire)
}
return c._xsrf_token
}
@ -466,19 +530,10 @@ func (c *Controller) XsrfToken() string {
// the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
// or in form field value named as "_xsrf".
func (c *Controller) CheckXsrfCookie() bool {
token := c.GetString("_xsrf")
if token == "" {
token = c.Ctx.Request.Header.Get("X-Xsrftoken")
if !c.EnableXSRF {
return true
}
if token == "" {
token = c.Ctx.Request.Header.Get("X-Csrftoken")
}
if token == "" {
c.Ctx.Abort(403, "'_xsrf' argument missing from POST")
} else if c._xsrf_token != token {
c.Ctx.Abort(403, "XSRF cookie does not match POST argument")
}
return true
return c.Ctx.CheckXsrfCookie()
}
// XsrfFormHtml writes an input field contains xsrf token value.
@ -491,14 +546,3 @@ func (c *Controller) XsrfFormHtml() string {
func (c *Controller) GetControllerAndAction() (controllerName, actionName string) {
return c.controllerName, c.actionName
}
// getRandomString returns random string.
func getRandomString(n int) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
}
return string(bytes)
}

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
}

52
docs.go Normal file
View File

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

View File

@ -1,7 +1,14 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package controllers
import (
"encoding/json"
"github.com/astaxie/beego"
"github.com/astaxie/beego/example/beeapi/models"
)
@ -10,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

@ -1,3 +1,13 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package main
import (

View File

@ -1,3 +1,9 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package models
import (

View File

@ -1,3 +1,9 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors Unknwon
package controllers
import (
@ -8,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

@ -1,12 +1,19 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors Unknwon
package controllers
import (
"github.com/astaxie/beego"
"github.com/garyburd/go-websocket/websocket"
"io/ioutil"
"math/rand"
"net/http"
"time"
"github.com/astaxie/beego"
"github.com/gorilla/websocket"
)
const (
@ -53,9 +60,9 @@ func (c *connection) readPump() {
break
}
switch op {
case websocket.OpPong:
case websocket.PongMessage:
c.ws.SetReadDeadline(time.Now().Add(readWait))
case websocket.OpText:
case websocket.TextMessage:
message, err := ioutil.ReadAll(r)
if err != nil {
break
@ -82,14 +89,14 @@ func (c *connection) writePump() {
select {
case message, ok := <-c.send:
if !ok {
c.write(websocket.OpClose, []byte{})
c.write(websocket.CloseMessage, []byte{})
return
}
if err := c.write(websocket.OpText, message); err != nil {
if err := c.write(websocket.TextMessage, message); err != nil {
return
}
case <-ticker.C:
if err := c.write(websocket.OpPing, []byte{}); err != nil {
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
return
}
}
@ -142,10 +149,15 @@ type WSController struct {
beego.Controller
}
func (this *WSController) Get() {
ws, err := websocket.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request.Header, nil, 1024, 1024)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func (w *WSController) Get() {
ws, err := upgrader.Upgrade(w.Ctx.ResponseWriter, w.Ctx.Request, nil)
if _, ok := err.(websocket.HandshakeError); ok {
http.Error(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

@ -1,3 +1,8 @@
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors Unknwon
package main
import (

165
filter.go
View File

@ -1,148 +1,43 @@
// 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 (
"regexp"
"strings"
)
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 {
pattern string
regex *regexp.Regexp
filterFunc FilterFunc
hasregex bool
params map[int]string
parseParams map[string]string
filterFunc FilterFunc
tree *Tree
pattern string
returnOnOutput bool
}
// ValidRouter check current request is valid for this filter.
// if matched, returns parsed params in this request by defined filter router pattern.
func (mr *FilterRouter) ValidRouter(router string) (bool, map[string]string) {
if mr.pattern == "" {
return true, nil
func (f *FilterRouter) ValidRouter(router string) (bool, map[string]string) {
isok, params := f.tree.Match(router)
if isok == nil {
return false, nil
}
if mr.pattern == "*" {
return true, nil
if isok, ok := isok.(bool); ok {
return isok, params
} else {
return false, nil
}
if router == mr.pattern {
return true, nil
}
if mr.hasregex {
if !mr.regex.MatchString(router) {
return false, nil
}
matches := mr.regex.FindStringSubmatch(router)
if len(matches) > 0 {
if len(matches[0]) == len(router) {
params := make(map[string]string)
for i, match := range matches[1:] {
params[mr.params[i]] = match
}
return true, params
}
}
}
return false, nil
}
func buildFilter(pattern string, filter FilterFunc) *FilterRouter {
mr := new(FilterRouter)
mr.params = make(map[int]string)
mr.filterFunc = filter
parts := strings.Split(pattern, "/")
j := 0
for i, part := range parts {
if strings.HasPrefix(part, ":") {
expr := "(.+)"
//a user may choose to override the default expression
// similar to expressjs: /user/:id([0-9]+)
if index := strings.Index(part, "("); index != -1 {
expr = part[index:]
part = part[:index]
//match /user/:id:int ([0-9]+)
//match /post/:username:string ([\w]+)
} else if lindex := strings.LastIndex(part, ":"); lindex != 0 {
switch part[lindex:] {
case ":int":
expr = "([0-9]+)"
part = part[:lindex]
case ":string":
expr = `([\w]+)`
part = part[:lindex]
}
}
mr.params[j] = part
parts[i] = expr
j++
}
if strings.HasPrefix(part, "*") {
expr := "(.+)"
if part == "*.*" {
mr.params[j] = ":path"
parts[i] = "([^.]+).([^.]+)"
j++
mr.params[j] = ":ext"
j++
} else {
mr.params[j] = ":splat"
parts[i] = expr
j++
}
}
//url like someprefix:id(xxx).html
if strings.Contains(part, ":") && strings.Contains(part, "(") && strings.Contains(part, ")") {
var out []rune
var start bool
var startexp bool
var param []rune
var expt []rune
for _, v := range part {
if start {
if v != '(' {
param = append(param, v)
continue
}
}
if startexp {
if v != ')' {
expt = append(expt, v)
continue
}
}
if v == ':' {
param = make([]rune, 0)
param = append(param, ':')
start = true
} else if v == '(' {
startexp = true
start = false
mr.params[j] = string(param)
j++
expt = make([]rune, 0)
expt = append(expt, '(')
} else if v == ')' {
startexp = false
expt = append(expt, ')')
out = append(out, expt...)
} else {
out = append(out, v)
}
}
parts[i] = string(out)
}
}
if j != 0 {
pattern = strings.Join(parts, "/")
regex, regexErr := regexp.Compile(pattern)
if regexErr != nil {
//TODO add error handling here to avoid panic
panic(regexErr)
}
mr.regex = regex
mr.hasregex = true
}
mr.pattern = pattern
return mr
}

68
filter_test.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/astaxie/beego/context"
)
var FilterUser = func(ctx *context.Context) {
ctx.Output.Body([]byte("i am " + ctx.Input.Params[":last"] + ctx.Input.Params[":first"]))
}
func TestFilter(t *testing.T) {
r, _ := http.NewRequest("GET", "/person/asta/Xie", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.InsertFilter("/person/:last/:first", BeforeRouter, FilterUser)
handler.Add("/person/:last/:first", &TestController{})
handler.ServeHTTP(w, r)
if w.Body.String() != "i am astaXie" {
t.Errorf("user define func can't run")
}
}
var FilterAdminUser = func(ctx *context.Context) {
ctx.Output.Body([]byte("i am admin"))
}
// Filter pattern /admin/:all
// all url like /admin/ /admin/xie will all get filter
func TestPatternTwo(t *testing.T) {
r, _ := http.NewRequest("GET", "/admin/", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.InsertFilter("/admin/?:all", BeforeRouter, FilterAdminUser)
handler.ServeHTTP(w, r)
if w.Body.String() != "i am admin" {
t.Errorf("filter /admin/ can't run")
}
}
func TestPatternThree(t *testing.T) {
r, _ := http.NewRequest("GET", "/admin/astaxie", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.InsertFilter("/admin/:all", BeforeRouter, FilterAdminUser)
handler.ServeHTTP(w, r)
if w.Body.String() != "i am admin" {
t.Errorf("filter /admin/astaxie can't run")
}
}

View File

@ -1,25 +0,0 @@
package beego
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/astaxie/beego/context"
)
var FilterUser = func(ctx *context.Context) {
ctx.Output.Body([]byte("i am " + ctx.Input.Params[":last"] + ctx.Input.Params[":first"]))
}
func TestFilter(t *testing.T) {
r, _ := http.NewRequest("GET", "/person/asta/Xie", nil)
w := httptest.NewRecorder()
handler := NewControllerRegistor()
handler.AddFilter("/person/:last/:first", "AfterStatic", FilterUser)
handler.Add("/person/:last/:first", &TestController{})
handler.ServeHTTP(w, r)
if w.Body.String() != "i am astaXie" {
t.Errorf("user define func can't run")
}
}

View File

@ -1,3 +1,17 @@
// 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 (
@ -6,9 +20,6 @@ import (
"strings"
)
// the separation string when encoding flash data.
const BEEGO_FLASH_SEP = "#BEEGOFLASH#"
// FlashData is a tools to maintain data when using across request.
type FlashData struct {
Data map[string]string
@ -21,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 {
@ -54,29 +83,27 @@ func (fd *FlashData) Store(c *Controller) {
c.Data["flash"] = fd.Data
var flashValue string
for key, value := range fd.Data {
flashValue += "\x00" + key + BEEGO_FLASH_SEP + value + "\x00"
flashValue += "\x00" + key + "\x23" + FlashSeperator + "\x23" + value + "\x00"
}
c.Ctx.SetCookie("BEEGO_FLASH", url.QueryEscape(flashValue), 0, "/")
c.Ctx.SetCookie(FlashName, url.QueryEscape(flashValue), 0, "/")
}
// ReadFromRequest parsed flash data from encoded values in cookie.
func ReadFromRequest(c *Controller) *FlashData {
flash := &FlashData{
Data: make(map[string]string),
}
if cookie, err := c.Ctx.Request.Cookie("BEEGO_FLASH"); err == nil {
flash := NewFlash()
if cookie, err := c.Ctx.Request.Cookie(FlashName); err == nil {
v, _ := url.QueryUnescape(cookie.Value)
vals := strings.Split(v, "\x00")
for _, v := range vals {
if len(v) > 0 {
kv := strings.Split(v, BEEGO_FLASH_SEP)
kv := strings.Split(v, "\x23"+FlashSeperator+"\x23")
if len(kv) == 2 {
flash.Data[kv[0]] = kv[1]
}
}
}
//read one time then delete it
c.Ctx.SetCookie("BEEGO_FLASH", "", -1, "/")
c.Ctx.SetCookie(FlashName, "", -1, "/")
}
c.Data["flash"] = flash.Data
return flash

54
flash_test.go Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
type TestFlashController struct {
Controller
}
func (t *TestFlashController) TestWriteFlash() {
flash := NewFlash()
flash.Notice("TestFlashString")
flash.Store(&t.Controller)
// we choose to serve json because we don't want to load a template html file
t.ServeJson(true)
}
func TestFlashHeader(t *testing.T) {
// create fake GET request
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
// setup the handler
handler := NewControllerRegister()
handler.Add("/", &TestFlashController{}, "get:TestWriteFlash")
handler.ServeHTTP(w, r)
// get the Set-Cookie value
sc := w.Header().Get("Set-Cookie")
// match for the expected header
res := strings.Contains(sc, "BEEGO_FLASH=%00notice%23BEEGOFLASH%23TestFlashString%00")
// validate the assertion
if res != true {
t.Errorf("TestFlashHeader() unable to validate flash message")
}
}

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,3 +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 `req.PostFile()`
req := httplib.Post("http://beego.me/")
req.Param("username","astaxie")
req.PostFile("uploadfile1", "httplib.pdf")
str, err := req.String()
if err != nil {
// error
}
fmt.Println(str)
See godoc for further documentation and examples.
* [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib)

View File

@ -1,3 +1,33 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
//
// import "github.com/astaxie/beego/httplib"
//
// 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()
// if err != nil {
// t.Fatal(err)
// }
// fmt.Println(str)
//
// more docs http://beego.me/docs/module/httplib.md
package httplib
import (
@ -7,98 +37,208 @@ import (
"encoding/xml"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httputil"
"net/url"
"os"
"strings"
"sync"
"time"
)
var defaultUserAgent = "beegoServer"
var defaultSetting = BeegoHttpSettings{false, "beegoServer", 60 * time.Second, 60 * time.Second, nil, nil, nil, false}
var defaultCookieJar http.CookieJar
var settingMutex sync.Mutex
// createDefaultCookie creates a global cookiejar to store cookies.
func createDefaultCookie() {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultCookieJar, _ = cookiejar.New(nil)
}
// Overwrite default settings
func SetDefaultSetting(setting BeegoHttpSettings) {
settingMutex.Lock()
defer settingMutex.Unlock()
defaultSetting = setting
if defaultSetting.ConnectTimeout == 0 {
defaultSetting.ConnectTimeout = 60 * time.Second
}
if defaultSetting.ReadWriteTimeout == 0 {
defaultSetting.ReadWriteTimeout = 60 * time.Second
}
}
// return *BeegoHttpRequest with specific method
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{}
req.Header.Set("User-Agent", defaultUserAgent)
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
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{}
req.Header.Set("User-Agent", defaultUserAgent)
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
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{}
req.Header.Set("User-Agent", defaultUserAgent)
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
return newBeegoRequest(url, "PUT")
}
// Delete returns *BeegoHttpRequest DELETE method.
func Delete(url string) *BeegoHttpRequest {
var req http.Request
req.Method = "DELETE"
req.Header = http.Header{}
req.Header.Set("User-Agent", defaultUserAgent)
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
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{}
req.Header.Set("User-Agent", defaultUserAgent)
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil}
return newBeegoRequest(url, "HEAD")
}
// BeegoHttpSettings
type BeegoHttpSettings struct {
ShowDebug bool
UserAgent string
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
TlsClientConfig *tls.Config
Proxy func(*http.Request) (*url.URL, error)
Transport http.RoundTripper
EnableCookie bool
}
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
type BeegoHttpRequest struct {
url string
req *http.Request
params map[string]string
showdebug bool
connectTimeout time.Duration
readWriteTimeout time.Duration
tlsClientConfig *tls.Config
url string
req *http.Request
params map[string]string
files map[string]string
setting BeegoHttpSettings
resp *http.Response
body []byte
}
// Change request settings
func (b *BeegoHttpRequest) Setting(setting BeegoHttpSettings) *BeegoHttpRequest {
b.setting = setting
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
return b
}
// SetUserAgent sets User-Agent header field
func (b *BeegoHttpRequest) SetUserAgent(useragent string) *BeegoHttpRequest {
b.setting.UserAgent = useragent
return b
}
// Debug sets show debug or not when executing request.
func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
b.showdebug = isdebug
b.setting.ShowDebug = isdebug
return b
}
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
b.connectTimeout = connectTimeout
b.readWriteTimeout = readWriteTimeout
b.setting.ConnectTimeout = connectTimeout
b.setting.ReadWriteTimeout = readWriteTimeout
return b
}
// SetTLSClientConfig sets tls connection configurations if visiting https url.
func (b *BeegoHttpRequest) SetTLSClientConfig(config *tls.Config) *BeegoHttpRequest {
b.tlsClientConfig = config
b.setting.TlsClientConfig = config
return b
}
// Header add header item string in request.
func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest {
b.req.Header.Set(key, value)
return b
}
// Set the protocol version for incoming requests.
// Client requests always use HTTP/1.1.
func (b *BeegoHttpRequest) SetProtocolVersion(vers string) *BeegoHttpRequest {
if len(vers) == 0 {
vers = "HTTP/1.1"
}
major, minor, ok := http.ParseHTTPVersion(vers)
if ok {
b.req.Proto = vers
b.req.ProtoMajor = major
b.req.ProtoMinor = minor
}
return b
}
// SetCookie add cookie into request.
func (b *BeegoHttpRequest) SetCookie(cookie *http.Cookie) *BeegoHttpRequest {
b.req.Header.Add("Cookie", cookie.String())
return b
}
// Set transport to
func (b *BeegoHttpRequest) SetTransport(transport http.RoundTripper) *BeegoHttpRequest {
b.setting.Transport = transport
return b
}
// Set http proxy
// example:
//
// func(req *http.Request) (*url.URL, error) {
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
// return u, nil
// }
func (b *BeegoHttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHttpRequest {
b.setting.Proxy = proxy
return b
}
// Param adds query param in to request.
// params build query string as ?key1=value1&key2=value2...
func (b *BeegoHttpRequest) Param(key, value string) *BeegoHttpRequest {
b.params[key] = value
return b
}
func (b *BeegoHttpRequest) PostFile(formname, filename string) *BeegoHttpRequest {
b.files[formname] = filename
return b
}
// Body adds request raw body.
// it supports string and []byte.
func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
switch t := data.(type) {
case string:
@ -114,6 +254,9 @@ func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
}
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
@ -133,22 +276,92 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
} else {
b.url = b.url + "?" + paramBody
}
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 {
b.Header("Content-Type", "application/x-www-form-urlencoded")
b.Body(paramBody)
} else if b.req.Method == "POST" && b.req.Body == nil {
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)
} else if len(paramBody) > 0 {
b.Header("Content-Type", "application/x-www-form-urlencoded")
b.Body(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.showdebug {
trans := b.setting.Transport
if trans == nil {
// create default transport
trans = &http.Transport{
TLSClientConfig: b.setting.TlsClientConfig,
Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
}
} else {
// if b.transport is *http.Transport then set the settings.
if t, ok := trans.(*http.Transport); ok {
if t.TLSClientConfig == nil {
t.TLSClientConfig = b.setting.TlsClientConfig
}
if t.Proxy == nil {
t.Proxy = b.setting.Proxy
}
if t.Dial == nil {
t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout)
}
}
}
var jar http.CookieJar
if b.setting.EnableCookie {
if defaultCookieJar == nil {
createDefaultCookie()
}
jar = defaultCookieJar
} else {
jar = nil
}
client := &http.Client{
Transport: trans,
Jar: jar,
}
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())
@ -156,19 +369,16 @@ func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
println(string(dump))
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: b.tlsClientConfig,
Dial: TimeoutDialer(b.connectTimeout, b.readWriteTimeout),
},
}
resp, err := client.Do(b.req)
if err != nil {
return nil, err
}
b.resp = resp
return resp, nil
}
// String returns the body string in response.
// it calls Response inner.
func (b *BeegoHttpRequest) String() (string, error) {
data, err := b.Bytes()
if err != nil {
@ -178,7 +388,12 @@ func (b *BeegoHttpRequest) String() (string, error) {
return string(data), nil
}
// 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
@ -191,9 +406,12 @@ func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
if err != nil {
return nil, err
}
b.body = data
return data, nil
}
// ToFile saves the body data in response to one file.
// it calls Response inner.
func (b *BeegoHttpRequest) ToFile(filename string) error {
f, err := os.Create(filename)
if err != nil {
@ -210,40 +428,37 @@ func (b *BeegoHttpRequest) ToFile(filename string) error {
}
defer resp.Body.Close()
_, err = io.Copy(f, resp.Body)
if err != nil {
return err
}
return nil
return err
}
// ToJson returns the map that marshals from the body bytes as json in response .
// it calls Response inner.
func (b *BeegoHttpRequest) ToJson(v interface{}) error {
data, err := b.Bytes()
if err != nil {
return err
}
err = json.Unmarshal(data, v)
if err != nil {
return err
}
return nil
return err
}
func (b *BeegoHttpRequest) ToXML(v interface{}) error {
// ToXml returns the map that marshals from the body bytes as xml in response .
// it calls Response inner.
func (b *BeegoHttpRequest) ToXml(v interface{}) error {
data, err := b.Bytes()
if err != nil {
return err
}
err = xml.Unmarshal(data, v)
if err != nil {
return err
}
return nil
return err
}
// Response executes request client gets response mannually.
func (b *BeegoHttpRequest) Response() (*http.Response, error) {
return b.getResponse()
}
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
return func(netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, cTimeout)

View File

@ -1,32 +1,216 @@
// 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 httplib
import (
"io/ioutil"
"os"
"strings"
"testing"
)
func TestGetUrl(t *testing.T) {
resp, err := Get("http://beego.me/").Debug(true).Response()
func TestResponse(t *testing.T) {
req := Get("http://httpbin.org/get")
resp, err := req.Response()
if err != nil {
t.Fatal(err)
}
if resp.Body == nil {
t.Fatal("body is nil")
}
data, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
t.Fatal(err)
}
if len(data) == 0 {
t.Fatal("data is no")
}
t.Log(resp)
}
str, err := Get("http://beego.me/").String()
func TestGet(t *testing.T) {
req := Get("http://httpbin.org/get")
b, err := req.Bytes()
if err != nil {
t.Fatal(err)
}
if len(str) == 0 {
t.Fatal("has no info")
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.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)
}
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) {
v := "smallfish"
str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String()
if err != nil {
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()
if err != nil {
t.Fatal(err)
}
t.Log(str)
n := strings.Index(str, v)
if n == -1 {
t.Fatal(v + " not found in user-agent")
}
}
func TestWithSetting(t *testing.T) {
v := "beego"
var setting BeegoHttpSettings
setting.EnableCookie = true
setting.UserAgent = v
setting.Transport = nil
SetDefaultSetting(setting)
str, err := Get("http://httpbin.org/get").String()
if err != nil {
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)
}

104
log.go
View File

@ -1,3 +1,17 @@
// 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 (
@ -8,12 +22,14 @@ import (
// Log levels to control the logging output.
const (
LevelTrace = iota
LevelDebug
LevelInfo
LevelWarning
LevelError
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
LevelWarning
LevelNotice
LevelInformational
LevelDebug
)
// SetLogLevel sets the global log level used by the simple
@ -22,37 +38,29 @@ func SetLevel(l int) {
BeeLogger.SetLevel(l)
}
func SetLogFuncCall(b bool) {
BeeLogger.EnableFuncCallDepth(b)
BeeLogger.SetLogFuncCallDepth(3)
}
// logger references the used application logger.
var BeeLogger *logs.BeeLogger
// SetLogger sets a new logger.
func SetLogger(adaptername string, config string) {
BeeLogger.SetLogger(adaptername, config)
func SetLogger(adaptername string, config string) error {
err := BeeLogger.SetLogger(adaptername, config)
if err != nil {
return err
}
return nil
}
// Trace logs a message at trace level.
func Trace(v ...interface{}) {
BeeLogger.Trace(generateFmtStr(len(v)), v...)
func Emergency(v ...interface{}) {
BeeLogger.Emergency(generateFmtStr(len(v)), v...)
}
// Debug logs a message at debug level.
func Debug(v ...interface{}) {
BeeLogger.Debug(generateFmtStr(len(v)), v...)
}
// Info logs a message at info level.
func Info(v ...interface{}) {
BeeLogger.Info(generateFmtStr(len(v)), v...)
}
// Warning logs a message at warning level.
func Warn(v ...interface{}) {
BeeLogger.Warn(generateFmtStr(len(v)), v...)
}
// Error logs a message at error level.
func Error(v ...interface{}) {
BeeLogger.Error(generateFmtStr(len(v)), v...)
func Alert(v ...interface{}) {
BeeLogger.Alert(generateFmtStr(len(v)), v...)
}
// Critical logs a message at critical level.
@ -60,6 +68,46 @@ func Critical(v ...interface{}) {
BeeLogger.Critical(generateFmtStr(len(v)), v...)
}
// Error logs a message at error level.
func Error(v ...interface{}) {
BeeLogger.Error(generateFmtStr(len(v)), v...)
}
// Warning logs a message at warning level.
func Warning(v ...interface{}) {
BeeLogger.Warning(generateFmtStr(len(v)), v...)
}
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
func Warn(v ...interface{}) {
Warning(v...)
}
func Notice(v ...interface{}) {
BeeLogger.Notice(generateFmtStr(len(v)), v...)
}
// Info logs a message at info level.
func Informational(v ...interface{}) {
BeeLogger.Informational(generateFmtStr(len(v)), v...)
}
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
func Info(v ...interface{}) {
Informational(v...)
}
// Debug logs a message at debug level.
func Debug(v ...interface{}) {
BeeLogger.Debug(generateFmtStr(len(v)), v...)
}
// Trace logs a message at trace level.
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
func Trace(v ...interface{}) {
BeeLogger.Trace(generateFmtStr(len(v)), v...)
}
func generateFmtStr(n int) string {
return strings.Repeat("%v ", n)
}

View File

@ -1,3 +1,17 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
@ -7,6 +21,8 @@ import (
"net"
)
// ConnWriter implements LoggerInterface.
// it writes messages in keep-live tcp connection.
type ConnWriter struct {
lg *log.Logger
innerWriter io.WriteCloser
@ -17,12 +33,15 @@ type ConnWriter struct {
Level int `json:"level"`
}
// create new ConnWrite returning as LoggerInterface.
func NewConn() LoggerInterface {
conn := new(ConnWriter)
conn.Level = LevelTrace
return conn
}
// 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 {
@ -31,8 +50,10 @@ func (c *ConnWriter) Init(jsonconfig string) error {
return nil
}
// write message in connection.
// if connection is down, try to re-connect.
func (c *ConnWriter) WriteMsg(msg string, level int) error {
if level < c.Level {
if level > c.Level {
return nil
}
if c.neddedConnectOnMsg() {
@ -49,10 +70,12 @@ func (c *ConnWriter) WriteMsg(msg string, level int) error {
return nil
}
// implementing method. empty.
func (c *ConnWriter) Flush() {
}
// destroy connection writer and close tcp listener.
func (c *ConnWriter) Destroy() {
if c.innerWriter == nil {
return

View File

@ -1,3 +1,17 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
@ -7,5 +21,5 @@ import (
func TestConn(t *testing.T) {
log := NewLogger(1000)
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Info("info")
log.Informational("informational")
}

View File

@ -1,24 +1,67 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"encoding/json"
"log"
"os"
"runtime"
)
type Brush func(string) string
func NewBrush(color string) Brush {
pre := "\033["
reset := "\033[0m"
return func(text string) string {
return pre + color + "m" + text + reset
}
}
var colors = []Brush{
NewBrush("1;37"), // Emergency white
NewBrush("1;36"), // Alert cyan
NewBrush("1;35"), // Critical magenta
NewBrush("1;31"), // Error red
NewBrush("1;33"), // Warning yellow
NewBrush("1;32"), // Notice green
NewBrush("1;34"), // Informational blue
NewBrush("1;34"), // Debug blue
}
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
type ConsoleWriter struct {
lg *log.Logger
Level int `json:"level"`
}
// create ConsoleWriter returning as LoggerInterface.
func NewConsole() LoggerInterface {
cw := new(ConsoleWriter)
cw.lg = log.New(os.Stdout, "", log.Ldate|log.Ltime)
cw.Level = LevelTrace
cw.Level = LevelDebug
return cw
}
// init console logger.
// jsonconfig like '{"level":LevelTrace}'.
func (c *ConsoleWriter) Init(jsonconfig string) error {
if len(jsonconfig) == 0 {
return nil
}
err := json.Unmarshal([]byte(jsonconfig), c)
if err != nil {
return err
@ -26,18 +69,25 @@ func (c *ConsoleWriter) Init(jsonconfig string) error {
return nil
}
// write message in console.
func (c *ConsoleWriter) WriteMsg(msg string, level int) error {
if level < c.Level {
if level > c.Level {
return nil
}
c.lg.Println(msg)
if goos := runtime.GOOS; goos == "windows" {
c.lg.Println(msg)
} else {
c.lg.Println(colors[level](msg))
}
return nil
}
// implementing method. empty.
func (c *ConsoleWriter) Destroy() {
}
// implementing method. empty.
func (c *ConsoleWriter) Flush() {
}

View File

@ -1,30 +1,53 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"testing"
)
// Try each log level in decreasing order of priority.
func testConsoleCalls(bl *BeeLogger) {
bl.Emergency("emergency")
bl.Alert("alert")
bl.Critical("critical")
bl.Error("error")
bl.Warning("warning")
bl.Notice("notice")
bl.Informational("informational")
bl.Debug("debug")
}
// Test console logging by visually comparing the lines being output with and
// without a log level specification.
func TestConsole(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("console", "")
log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")
log1 := NewLogger(10000)
log1.EnableFuncCallDepth(true)
log1.SetLogger("console", "")
testConsoleCalls(log1)
log2 := NewLogger(100)
log2.SetLogger("console", `{"level":1}`)
log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")
log2.SetLogger("console", `{"level":3}`)
testConsoleCalls(log2)
}
func BenchmarkConsole(b *testing.B) {
log := NewLogger(10000)
log.EnableFuncCallDepth(true)
log.SetLogger("console", "")
for i := 0; i < b.N; i++ {
log.Trace("trace")
log.Debug("debug")
}
}

View File

@ -1,3 +1,17 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
@ -13,6 +27,8 @@ import (
"time"
)
// FileLogWriter implements LoggerInterface.
// It writes messages by lines limit, file size limit, or time frequency.
type FileLogWriter struct {
*log.Logger
mw *MuxWriter
@ -28,7 +44,7 @@ type FileLogWriter struct {
// Rotate daily
Daily bool `json:"daily"`
Maxdays int64 `json:"maxdays`
Maxdays int64 `json:"maxdays"`
daily_opendate int
Rotate bool `json:"rotate"`
@ -38,17 +54,20 @@ type FileLogWriter struct {
Level int `json:"level"`
}
// an *os.File writer with locker.
type MuxWriter struct {
sync.Mutex
fd *os.File
}
// write to os.File.
func (l *MuxWriter) Write(b []byte) (int, error) {
l.Lock()
defer l.Unlock()
return l.fd.Write(b)
}
// set os.File in writer.
func (l *MuxWriter) SetFd(fd *os.File) {
if l.fd != nil {
l.fd.Close()
@ -56,6 +75,7 @@ func (l *MuxWriter) SetFd(fd *os.File) {
l.fd = fd
}
// create a FileLogWriter returning as LoggerInterface.
func NewFileWriter() LoggerInterface {
w := &FileLogWriter{
Filename: "",
@ -73,15 +93,16 @@ func NewFileWriter() LoggerInterface {
return w
}
// jsonconfig like this
//{
// Init file logger with json config.
// jsonconfig like:
// {
// "filename":"logs/beego.log",
// "maxlines":10000,
// "maxsize":1<<30,
// "daily":true,
// "maxdays":15,
// "rotate":true
//}
// }
func (w *FileLogWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), w)
if err != nil {
@ -90,11 +111,12 @@ func (w *FileLogWriter) Init(jsonconfig string) error {
if len(w.Filename) == 0 {
return errors.New("jsonconfig must have filename")
}
err = w.StartLogger()
err = w.startLogger()
return err
}
func (w *FileLogWriter) StartLogger() error {
// start file logger. create log file and set to locker-inside file writer.
func (w *FileLogWriter) startLogger() error {
fd, err := w.createLogFile()
if err != nil {
return err
@ -110,9 +132,9 @@ func (w *FileLogWriter) StartLogger() error {
func (w *FileLogWriter) docheck(size int) {
w.startLock.Lock()
defer w.startLock.Unlock()
if (w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) ||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) ||
(w.Daily && time.Now().Day() != w.daily_opendate) {
(w.Daily && time.Now().Day() != w.daily_opendate)) {
if err := w.DoRotate(); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
return
@ -122,8 +144,9 @@ func (w *FileLogWriter) docheck(size int) {
w.maxsize_cursize += size
}
// write logger message into file.
func (w *FileLogWriter) WriteMsg(msg string, level int) error {
if level < w.Level {
if level > w.Level {
return nil
}
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
@ -158,6 +181,8 @@ func (w *FileLogWriter) initFd() error {
return 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 {
_, err := os.Lstat(w.Filename)
if err == nil { // file exists
@ -188,7 +213,7 @@ func (w *FileLogWriter) DoRotate() error {
}
// re-start logger
err = w.StartLogger()
err = w.startLogger()
if err != nil {
return fmt.Errorf("Rotate StartLogger: %s\n", err)
}
@ -201,20 +226,31 @@ 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
})
}
// destroy file logger, close file writer.
func (w *FileLogWriter) Destroy() {
w.mw.fd.Close()
}
// flush file logger.
// there are no buffering messages in file logger in memory.
// flush file means sync file from disk.
func (w *FileLogWriter) Flush() {
w.mw.fd.Sync()
}

View File

@ -1,9 +1,24 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"bufio"
"fmt"
"os"
"strconv"
"testing"
"time"
)
@ -11,12 +26,14 @@ import (
func TestFile(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test.log"}`)
log.Trace("test")
log.Info("info")
log.Debug("debug")
log.Warn("warning")
log.Informational("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
time.Sleep(time.Second * 4)
f, err := os.Open("test.log")
if err != nil {
@ -33,21 +50,24 @@ func TestFile(t *testing.T) {
linenum++
}
}
if linenum != 6 {
t.Fatal(linenum, "not line 6")
var expected = LevelDebug + 1
if linenum != expected {
t.Fatal(linenum, "not "+strconv.Itoa(expected)+" lines")
}
os.Remove("test.log")
}
func TestFile2(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test2.log","level":2}`)
log.Trace("test")
log.Info("info")
log.SetLogger("file", fmt.Sprintf(`{"filename":"test2.log","level":%d}`, LevelError))
log.Debug("debug")
log.Warn("warning")
log.Info("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
time.Sleep(time.Second * 4)
f, err := os.Open("test2.log")
if err != nil {
@ -64,8 +84,9 @@ func TestFile2(t *testing.T) {
linenum++
}
}
if linenum != 4 {
t.Fatal(linenum, "not line 4")
var expected = LevelError + 1
if linenum != expected {
t.Fatal(linenum, "not "+strconv.Itoa(expected)+" lines")
}
os.Remove("test2.log")
}
@ -73,17 +94,19 @@ func TestFile2(t *testing.T) {
func TestFileRotate(t *testing.T) {
log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test3.log","maxlines":4}`)
log.Trace("test")
log.Info("info")
log.Debug("debug")
log.Warn("warning")
log.Info("info")
log.Notice("notice")
log.Warning("warning")
log.Error("error")
log.Alert("alert")
log.Critical("critical")
log.Emergency("emergency")
time.Sleep(time.Second * 4)
rotatename := "test3.log" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1)
b, err := exists(rotatename)
if !b || err != nil {
t.Fatal("rotate not gen")
t.Fatal("rotate not generated")
}
os.Remove(rotatename)
os.Remove("test3.log")
@ -104,7 +127,7 @@ func BenchmarkFile(b *testing.B) {
log := NewLogger(100000)
log.SetLogger("file", `{"filename":"test4.log"}`)
for i := 0; i < b.N; i++ {
log.Trace("trace")
log.Debug("debug")
}
os.Remove("test4.log")
}

View File

@ -1,21 +1,68 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
//
// import "github.com/astaxie/beego/logs"
//
// log := NewLogger(10000)
// log.SetLogger("console", "")
//
// > the first params stand for how many channel
//
// Use it like this:
//
// log.Trace("trace")
// log.Info("info")
// log.Warn("warning")
// log.Debug("debug")
// log.Critical("critical")
//
// more docs http://beego.me/docs/module/logs.md
package logs
import (
"fmt"
"path"
"runtime"
"sync"
)
// RFC5424 log message levels.
const (
LevelTrace = iota
LevelDebug
LevelInfo
LevelWarn
LevelError
LevelEmergency = iota
LevelAlert
LevelCritical
LevelError
LevelWarning
LevelNotice
LevelInformational
LevelDebug
)
// Legacy loglevel constants to ensure backwards compatibility.
//
// Deprecated: will be removed in 1.5.0.
const (
LevelInfo = LevelInformational
LevelTrace = LevelDebug
LevelWarn = LevelWarning
)
type loggerType func() LoggerInterface
// LoggerInterface defines the behavior of a log provider.
type LoggerInterface interface {
Init(config string) error
WriteMsg(msg string, level int) error
@ -38,11 +85,15 @@ func Register(name string, log loggerType) {
adapters[name] = log
}
// BeeLogger is default logger in beego application.
// it can contain several providers and log message into all providers.
type BeeLogger struct {
lock sync.Mutex
level int
msg chan *logMsg
outputs map[string]LoggerInterface
lock sync.Mutex
level int
enableFuncCallDepth bool
loggerFuncCallDepth int
msg chan *logMsg
outputs map[string]LoggerInterface
}
type logMsg struct {
@ -50,29 +101,40 @@ type logMsg struct {
msg string
}
// config need to be correct JSON as string: {"interval":360}
// NewLogger returns a new BeeLogger.
// channellen means the number of messages in chan.
// if the buffering chan is full, logger adapters write to file or other way.
func NewLogger(channellen int64) *BeeLogger {
bl := new(BeeLogger)
bl.level = LevelDebug
bl.loggerFuncCallDepth = 2
bl.msg = make(chan *logMsg, channellen)
bl.outputs = make(map[string]LoggerInterface)
//bl.SetLogger("console", "") // default output to console
go bl.StartLogger()
go bl.startLogger()
return bl
}
// SetLogger provides a given logger adapter into BeeLogger with config string.
// config need to be correct JSON as string: {"interval":360}.
func (bl *BeeLogger) SetLogger(adaptername string, config string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
if log, ok := adapters[adaptername]; ok {
lg := log()
lg.Init(config)
err := lg.Init(config)
bl.outputs[adaptername] = lg
return nil
if err != nil {
fmt.Println("logs.BeeLogger.SetLogger: " + err.Error())
return err
}
} else {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adaptername)
}
return nil
}
// remove a logger adapter in BeeLogger.
func (bl *BeeLogger) DelLogger(adaptername string) error {
bl.lock.Lock()
defer bl.lock.Unlock()
@ -86,74 +148,149 @@ func (bl *BeeLogger) DelLogger(adaptername string) error {
}
func (bl *BeeLogger) writerMsg(loglevel int, msg string) error {
if bl.level > loglevel {
if loglevel > bl.level {
return nil
}
lm := new(logMsg)
lm.level = loglevel
lm.msg = msg
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)
} else {
lm.msg = msg
}
} else {
lm.msg = msg
}
bl.msg <- lm
return nil
}
// Set log message level.
//
// If message level (such as LevelDebug) is higher than logger level (such as LevelWarning),
// log providers will not even be sent the message.
func (bl *BeeLogger) SetLevel(l int) {
bl.level = l
}
func (bl *BeeLogger) StartLogger() {
// set log funcCallDepth
func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
bl.loggerFuncCallDepth = d
}
// enable log funcCallDepth
func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
bl.enableFuncCallDepth = b
}
// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
for {
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)
}
}
}
}
}
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
msg := fmt.Sprintf("[T] "+format, v...)
bl.writerMsg(LevelTrace, msg)
// Log EMERGENCY level message.
func (bl *BeeLogger) Emergency(format string, v ...interface{}) {
msg := fmt.Sprintf("[M] "+format, v...)
bl.writerMsg(LevelEmergency, msg)
}
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
msg := fmt.Sprintf("[D] "+format, v...)
bl.writerMsg(LevelDebug, msg)
}
func (bl *BeeLogger) Info(format string, v ...interface{}) {
msg := fmt.Sprintf("[I] "+format, v...)
bl.writerMsg(LevelInfo, msg)
}
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
msg := fmt.Sprintf("[W] "+format, v...)
bl.writerMsg(LevelWarn, msg)
}
func (bl *BeeLogger) Error(format string, v ...interface{}) {
msg := fmt.Sprintf("[E] "+format, v...)
bl.writerMsg(LevelError, msg)
// Log ALERT level message.
func (bl *BeeLogger) Alert(format string, v ...interface{}) {
msg := fmt.Sprintf("[A] "+format, v...)
bl.writerMsg(LevelAlert, msg)
}
// Log CRITICAL level message.
func (bl *BeeLogger) Critical(format string, v ...interface{}) {
msg := fmt.Sprintf("[C] "+format, v...)
bl.writerMsg(LevelCritical, msg)
}
//flush all chan data
// Log ERROR level message.
func (bl *BeeLogger) Error(format string, v ...interface{}) {
msg := fmt.Sprintf("[E] "+format, v...)
bl.writerMsg(LevelError, msg)
}
// Log WARNING level message.
func (bl *BeeLogger) Warning(format string, v ...interface{}) {
msg := fmt.Sprintf("[W] "+format, v...)
bl.writerMsg(LevelWarning, msg)
}
// Log NOTICE level message.
func (bl *BeeLogger) Notice(format string, v ...interface{}) {
msg := fmt.Sprintf("[N] "+format, v...)
bl.writerMsg(LevelNotice, msg)
}
// Log INFORMATIONAL level message.
func (bl *BeeLogger) Informational(format string, v ...interface{}) {
msg := fmt.Sprintf("[I] "+format, v...)
bl.writerMsg(LevelInformational, msg)
}
// Log DEBUG level message.
func (bl *BeeLogger) Debug(format string, v ...interface{}) {
msg := fmt.Sprintf("[D] "+format, v...)
bl.writerMsg(LevelDebug, msg)
}
// Log WARN level message.
//
// Deprecated: compatibility alias for Warning(), Will be removed in 1.5.0.
func (bl *BeeLogger) Warn(format string, v ...interface{}) {
bl.Warning(format, v...)
}
// Log INFO level message.
//
// Deprecated: compatibility alias for Informational(), Will be removed in 1.5.0.
func (bl *BeeLogger) Info(format string, v ...interface{}) {
bl.Informational(format, v...)
}
// Log TRACE level message.
//
// Deprecated: compatibility alias for Debug(), Will be removed in 1.5.0.
func (bl *BeeLogger) Trace(format string, v ...interface{}) {
bl.Debug(format, v...)
}
// flush all chan data.
func (bl *BeeLogger) Flush() {
for _, l := range bl.outputs {
l.Flush()
}
}
// close logger, flush all chan data and destroy all adapters in BeeLogger.
func (bl *BeeLogger) Close() {
for {
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

View File

@ -1,8 +1,24 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (
"crypto/tls"
"encoding/json"
"fmt"
"net"
"net/smtp"
"strings"
"time"
@ -12,20 +28,33 @@ const (
subjectPhrase = "Diagnostic message from server"
)
// smtpWriter is used to send emails via given SMTP-server.
// smtpWriter implements LoggerInterface and is used to send emails via given SMTP-server.
type SmtpWriter struct {
Username string `json:"Username"`
Password string `json:"password"`
Host string `json:"Host"`
Subject string `json:"subject"`
FromAddress string `json:"fromAddress"`
RecipientAddresses []string `json:"sendTos"`
Level int `json:"level"`
}
// create smtp writer.
func NewSmtpWriter() LoggerInterface {
return &SmtpWriter{Level: LevelTrace}
}
// init smtp writer with json config.
// config like:
// {
// "Username":"example@gmail.com",
// "password:"password",
// "host":"smtp.gmail.com:465",
// "subject":"email title",
// "fromAddress":"from@example.com",
// "sendTos":["email1","email2"],
// "level":LevelError
// }
func (s *SmtpWriter) Init(jsonconfig string) error {
err := json.Unmarshal([]byte(jsonconfig), s)
if err != nil {
@ -34,40 +63,100 @@ func (s *SmtpWriter) Init(jsonconfig string) error {
return nil
}
func (s *SmtpWriter) GetSmtpAuth(host string) smtp.Auth {
if len(strings.Trim(s.Username, " ")) == 0 && len(strings.Trim(s.Password, " ")) == 0 {
return nil
}
return smtp.PlainAuth(
"",
s.Username,
s.Password,
host,
)
}
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 {
if level < s.Level {
if level > s.Level {
return nil
}
hp := strings.Split(s.Host, ":")
// Set up authentication information.
auth := smtp.PlainAuth(
"",
s.Username,
s.Password,
hp[0],
)
auth := s.GetSmtpAuth(hp[0])
// 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,
)
err := s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
return err
}
// implementing method. empty.
func (s *SmtpWriter) Flush() {
return
}
// implementing method. empty.
func (s *SmtpWriter) Destroy() {
return
}

View File

@ -1,3 +1,17 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logs
import (

View File

@ -1,3 +1,17 @@
// 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 (
@ -5,20 +19,21 @@ import (
"compress/flate"
"compress/gzip"
"errors"
//"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"sync"
"time"
)
var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo)
var gmfim map[string]*memFileInfo = make(map[string]*memFileInfo)
var lock sync.RWMutex
// OpenMemZipFile returns MemFile object with a compressed static file.
// it's used for serve static file if gzip enable.
func OpenMemZipFile(path string, zip string) (*MemFile, error) {
func openMemZipFile(path string, zip string) (*memFile, error) {
osfile, e := os.Open(path)
if e != nil {
return nil, e
@ -32,15 +47,12 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) {
modtime := osfileinfo.ModTime()
fileSize := osfileinfo.Size()
lock.RLock()
cfi, ok := gmfim[zip+":"+path]
if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize {
//fmt.Printf("read %s file %s from cache\n", zip, path)
} else {
//fmt.Printf("NOT read %s file %s from cache\n", zip, path)
lock.RUnlock()
if !(ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize) {
var content []byte
if zip == "gzip" {
//将文件内容压缩到zipbuf中
var zipbuf bytes.Buffer
gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression)
if e != nil {
@ -51,13 +63,11 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) {
if e != nil {
return nil, e
}
//读zipbuf到content
content, e = ioutil.ReadAll(&zipbuf)
if e != nil {
return nil, e
}
} else if zip == "deflate" {
//将文件内容压缩到zipbuf中
var zipbuf bytes.Buffer
deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression)
if e != nil {
@ -68,7 +78,6 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) {
if e != nil {
return nil, e
}
//将zipbuf读入到content
content, e = ioutil.ReadAll(&zipbuf)
if e != nil {
return nil, e
@ -80,16 +89,17 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) {
}
}
cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
cfi = &memFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize}
lock.Lock()
defer lock.Unlock()
gmfim[zip+":"+path] = cfi
//fmt.Printf("%s file %s to %d, cache it\n", zip, path, len(content))
}
return &MemFile{fi: cfi, offset: 0}, nil
return &memFile{fi: cfi, offset: 0}, nil
}
// MemFileInfo contains a compressed file bytes and file information.
// it implements os.FileInfo interface.
type MemFileInfo struct {
type memFileInfo struct {
os.FileInfo
modTime time.Time
content []byte
@ -98,62 +108,62 @@ type MemFileInfo struct {
}
// Name returns the compressed filename.
func (fi *MemFileInfo) Name() string {
func (fi *memFileInfo) Name() string {
return fi.Name()
}
// Size returns the raw file content size, not compressed size.
func (fi *MemFileInfo) Size() int64 {
func (fi *memFileInfo) Size() int64 {
return fi.contentSize
}
// Mode returns file mode.
func (fi *MemFileInfo) Mode() os.FileMode {
func (fi *memFileInfo) Mode() os.FileMode {
return fi.Mode()
}
// ModTime returns the last modified time of raw file.
func (fi *MemFileInfo) ModTime() time.Time {
func (fi *memFileInfo) ModTime() time.Time {
return fi.modTime
}
// IsDir returns the compressing file is a directory or not.
func (fi *MemFileInfo) IsDir() bool {
func (fi *memFileInfo) IsDir() bool {
return fi.IsDir()
}
// return nil. implement the os.FileInfo interface method.
func (fi *MemFileInfo) Sys() interface{} {
func (fi *memFileInfo) Sys() interface{} {
return nil
}
// MemFile contains MemFileInfo and bytes offset when reading.
// it implements io.Reader,io.ReadCloser and io.Seeker.
type MemFile struct {
fi *MemFileInfo
type memFile struct {
fi *memFileInfo
offset int64
}
// Close memfile.
func (f *MemFile) Close() error {
func (f *memFile) Close() error {
return nil
}
// Get os.FileInfo of memfile.
func (f *MemFile) Stat() (os.FileInfo, error) {
func (f *memFile) Stat() (os.FileInfo, error) {
return f.fi, nil
}
// read os.FileInfo of files in directory of memfile.
// it returns empty slice.
func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) {
func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
infos := []os.FileInfo{}
return infos, nil
}
// Read bytes from the compressed file bytes.
func (f *MemFile) Read(p []byte) (n int, err error) {
func (f *memFile) Read(p []byte) (n int, err error) {
if len(f.fi.content)-int(f.offset) >= len(p) {
n = len(p)
} else {
@ -169,7 +179,7 @@ var errWhence = errors.New("Seek: invalid whence")
var errOffset = errors.New("Seek: invalid offset")
// Read bytes from the compressed file bytes by seeker.
func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
func (f *memFile) Seek(offset int64, whence int) (ret int64, err error) {
switch whence {
default:
return 0, errWhence
@ -189,7 +199,7 @@ func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) {
// GetAcceptEncodingZip returns accept encoding format in http header.
// zip is first, then deflate if both accepted.
// If no accepted, return empty string.
func GetAcceptEncodingZip(r *http.Request) string {
func getAcceptEncodingZip(r *http.Request) string {
ss := r.Header.Get("Accept-Encoding")
ss = strings.ToLower(ss)
if strings.Contains(ss, "gzip") {
@ -199,24 +209,4 @@ func GetAcceptEncodingZip(r *http.Request) string {
} else {
return ""
}
return ""
}
// CloseZWriter closes the io.Writer after compressing static file.
func CloseZWriter(zwriter io.Writer) {
if zwriter == nil {
return
}
switch zwriter.(type) {
case *gzip.Writer:
zwriter.(*gzip.Writer).Close()
case *flate.Writer:
zwriter.(*flate.Writer).Close()
//其他情况不close, 保持和默认(非压缩)行为一致
/*
case io.WriteCloser:
zwriter.(io.WriteCloser).Close()
*/
}
}

View File

@ -1,3 +1,17 @@
// 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 (
@ -61,6 +75,7 @@ var tpl = `
</html>
`
// render default application error page with error and stack string.
func ShowErr(err interface{}, rw http.ResponseWriter, r *http.Request, Stack string) {
t, _ := template.New("beegoerrortemp").Parse(tpl)
data := make(map[string]string)
@ -71,6 +86,7 @@ func ShowErr(err interface{}, rw http.ResponseWriter, r *http.Request, Stack str
data["Stack"] = Stack
data["BeegoVersion"] = VERSION
data["GoVersion"] = runtime.Version()
rw.WriteHeader(500)
t.Execute(rw, data)
}
@ -166,7 +182,7 @@ var errtpl = `
{{.Content}}
<a href="/" title="Home" class="button">Go Home</a><br />
<br>power by beego {{.BeegoVersion}}
<br>Powered by beego {{.BeegoVersion}}
</div>
</div>
</div>
@ -174,18 +190,19 @@ var errtpl = `
</html>
`
// map of http handlers for each error string.
var ErrorMaps map[string]http.HandlerFunc
func init() {
ErrorMaps = make(map[string]http.HandlerFunc)
}
//404
// show 404 notfound error.
func NotFound(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Page Not Found"
data["Content"] = template.HTML("<br>The Page You have requested flown the coop." +
data["Content"] = template.HTML("<br>The page you have requested has flown the coop." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>The page has moved" +
@ -198,28 +215,28 @@ func NotFound(rw http.ResponseWriter, r *http.Request) {
t.Execute(rw, data)
}
//401
// 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 authorized." +
data["Content"] = template.HTML("<br>The page you have requested can't be authorized." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br>Check the credentials that you supplied" +
"<br>Check the address for errors" +
"<br>The credentials you supplied are incorrect" +
"<br>There are errors in the website address" +
"</ul>")
data["BeegoVersion"] = VERSION
//rw.WriteHeader(http.StatusUnauthorized)
t.Execute(rw, data)
}
//403
// 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 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" +
@ -231,12 +248,12 @@ func Forbidden(rw http.ResponseWriter, r *http.Request) {
t.Execute(rw, data)
}
//503
// show 503 service unavailable error.
func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
t, _ := template.New("beegoerrortemp").Parse(errtpl)
data := make(map[string]interface{})
data["Title"] = "Service Unavailable"
data["Content"] = template.HTML("<br>The Page You have requested unavailable." +
data["Content"] = template.HTML("<br>The page you have requested is unavailable." +
"<br>Perhaps you are here because:" +
"<br><br><ul>" +
"<br><br>The page is overloaded" +
@ -247,30 +264,32 @@ func ServiceUnavailable(rw http.ResponseWriter, r *http.Request) {
t.Execute(rw, data)
}
//500
// 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"] = "Internal Server Error"
data["Content"] = template.HTML("<br>The Page You have requested has down now." +
data["Content"] = template.HTML("<br>The page you have requested is down right now." +
"<br><br><ul>" +
"<br>simply try again later" +
"<br>you should report the fault to the website administrator" +
"</ul>")
"<br>Please try again later and report the error to the website administrator" +
"<br></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
}
func RegisterErrorHander() {
// register default error http handlers, 404,401,403,500 and 503.
func RegisterErrorHandler() {
if _, ok := ErrorMaps["404"]; !ok {
ErrorMaps["404"] = NotFound
}
@ -292,12 +311,15 @@ func RegisterErrorHander() {
}
}
// 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) {
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)
return

View File

@ -1,17 +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 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 `status description`, e.g. `400 Bad Request`
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() {

View File

@ -1,84 +1,71 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Usage:
//
// import "github.com/astaxie/beego/middleware"
//
// I18N = middleware.NewLocale("conf/i18n.conf", beego.AppConfig.String("language"))
//
// more docs: http://beego.me/docs/module/i18n.md
package middleware
//import (
// "github.com/astaxie/beego/config"
// "os"
// "path"
//)
import (
"encoding/json"
"io/ioutil"
"os"
)
//type Translation struct {
// filetype string
// CurrentLocal string
// Locales map[string]map[string]string
//}
type Translation struct {
filepath string
CurrentLocal string
Locales map[string]map[string]string
}
//func NewLocale(filetype string) *Translation {
// return &Translation{
// filetype: filetype,
// CurrentLocal: "zh",
// Locales: make(map[string]map[string]string),
// }
//}
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())
}
data, err := ioutil.ReadAll(file)
if err != nil {
panic("read " + filepath + " err :" + err.Error())
}
err = json.Unmarshal(data, &i18n)
if err != nil {
panic("json.Unmarshal " + filepath + " err :" + err.Error())
}
return &Translation{
filepath: filepath,
CurrentLocal: defaultlocal,
Locales: i18n,
}
}
//func (t *Translation) loadTranslations(dirPath string) error {
// dir, err := os.Open(dirPath)
// if err != nil {
// return err
// }
// defer dir.Close()
func (t *Translation) SetLocale(local string) {
t.CurrentLocal = local
}
// names, err := dir.Readdirnames(-1)
// if err != nil {
// return err
// }
// for _, name := range names {
// fullPath := path.Join(dirPath, name)
// fi, err := os.Stat(fullPath)
// if err != nil {
// return err
// }
// if fi.IsDir() {
// continue
// } else {
// if err := t.loadTranslation(fullPath, name); err != nil {
// return err
// }
// }
// }
// return nil
//}
//func (t *Translation) loadTranslation(fullPath, locale string) error {
// sourceKey2Trans, ok := t.Locales[locale]
// if !ok {
// sourceKey2Trans = make(map[string]string)
// t.Locales[locale] = sourceKey2Trans
// }
// for _, m := range trf.Messages {
// if m.Translation != "" {
// sourceKey2Trans[sourceKey(m.Source, m.Context)] = m.Translation
// }
// }
// return nil
//}
//func (t *Translation) SetLocale(local string) {
// t.CurrentLocal = local
//}
//func (t *Translation) Translate(key string) string {
// if ct, ok := t.Locales[t.CurrentLocal]; ok {
// if v, o := ct[key]; o {
// return v
// }
// }
// return key
//}
func (t *Translation) Translate(key string, local string) string {
if local == "" {
local = t.CurrentLocal
}
if ct, ok := t.Locales[key]; ok {
if v, o := ct[local]; o {
return v
}
}
return key
}

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 ""
}

280
migration/migration.go Normal file
View File

@ -0,0 +1,280 @@
// 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.
// migration package for migration
//
// The table structure is as follow:
//
// CREATE TABLE `migrations` (
// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key',
// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique',
// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back',
// `statements` longtext COMMENT 'SQL statements for this migration',
// `rollback_statements` longtext,
// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back',
// PRIMARY KEY (`id_migration`)
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
package migration
import (
"errors"
"sort"
"strings"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
)
// const the data format for the bee generate migration datatype
const (
M_DATE_FORMAT = "20060102_150405"
M_DB_DATE_FORMAT = "2006-01-02 15:04:05"
)
// Migrationer is an interface for all Migration struct
type Migrationer interface {
Up()
Down()
Reset()
Exec(name, status string) error
GetCreated() int64
}
var (
migrationMap map[string]Migrationer
)
func init() {
migrationMap = make(map[string]Migrationer)
}
// the basic type which will implement the basic type
type Migration struct {
sqls []string
Created string
}
// implement in the Inheritance struct for upgrade
func (m *Migration) Up() {
}
// implement in the Inheritance struct for down
func (m *Migration) Down() {
}
// add sql want to execute
func (m *Migration) Sql(sql string) {
m.sqls = append(m.sqls, sql)
}
// Reset the sqls
func (m *Migration) Reset() {
m.sqls = make([]string, 0)
}
// execute the sql already add in the sql
func (m *Migration) Exec(name, status string) error {
o := orm.NewOrm()
for _, s := range m.sqls {
beego.Info("exec sql:", s)
r := o.Raw(s)
_, err := r.Exec()
if err != nil {
return err
}
}
return m.addOrUpdateRecord(name, status)
}
func (m *Migration) addOrUpdateRecord(name, status string) error {
o := orm.NewOrm()
if status == "down" {
status = "rollback"
p, err := o.Raw("update migrations set `status` = ?, `rollback_statements` = ?, `created_at` = ? where name = ?").Prepare()
if err != nil {
return nil
}
_, err = p.Exec(status, strings.Join(m.sqls, "; "), time.Now().Format(M_DB_DATE_FORMAT), name)
return err
} else {
status = "update"
p, err := o.Raw("insert into migrations(`name`, `created_at`, `statements`, `status`) values(?,?,?,?)").Prepare()
if err != nil {
return err
}
_, err = p.Exec(name, time.Now().Format(M_DB_DATE_FORMAT), strings.Join(m.sqls, "; "), status)
return err
}
}
// get the unixtime from the Created
func (m *Migration) GetCreated() int64 {
t, err := time.Parse(M_DATE_FORMAT, m.Created)
if err != nil {
return 0
}
return t.Unix()
}
// register the Migration in the map
func Register(name string, m Migrationer) error {
if _, ok := migrationMap[name]; ok {
return errors.New("already exist name:" + name)
}
migrationMap[name] = m
return nil
}
// upgrate the migration from lasttime
func Upgrade(lasttime int64) error {
sm := sortMap(migrationMap)
i := 0
for _, v := range sm {
if v.created > lasttime {
beego.Info("start upgrade", v.name)
v.m.Reset()
v.m.Up()
err := v.m.Exec(v.name, "up")
if err != nil {
beego.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
beego.Info("end upgrade:", v.name)
i++
}
}
beego.Info("total success upgrade:", i, " migration")
time.Sleep(2 * time.Second)
return nil
}
//rollback the migration by the name
func Rollback(name string) error {
if v, ok := migrationMap[name]; ok {
beego.Info("start rollback")
v.Reset()
v.Down()
err := v.Exec(name, "down")
if err != nil {
beego.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
beego.Info("end rollback")
time.Sleep(2 * time.Second)
return nil
} else {
beego.Error("not exist the migrationMap name:" + name)
time.Sleep(2 * time.Second)
return errors.New("not exist the migrationMap name:" + name)
}
}
// reset all migration
// run all migration's down function
func Reset() error {
sm := sortMap(migrationMap)
i := 0
for j := len(sm) - 1; j >= 0; j-- {
v := sm[j]
if isRollBack(v.name) {
beego.Info("skip the", v.name)
time.Sleep(1 * time.Second)
continue
}
beego.Info("start reset:", v.name)
v.m.Reset()
v.m.Down()
err := v.m.Exec(v.name, "down")
if err != nil {
beego.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
i++
beego.Info("end reset:", v.name)
}
beego.Info("total success reset:", i, " migration")
time.Sleep(2 * time.Second)
return nil
}
// first Reset, then Upgrade
func Refresh() error {
err := Reset()
if err != nil {
beego.Error("execute error:", err)
time.Sleep(2 * time.Second)
return err
}
err = Upgrade(0)
return err
}
type dataSlice []data
type data struct {
created int64
name string
m Migrationer
}
// Len is part of sort.Interface.
func (d dataSlice) Len() int {
return len(d)
}
// Swap is part of sort.Interface.
func (d dataSlice) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
// Less is part of sort.Interface. We use count as the value to sort by
func (d dataSlice) Less(i, j int) bool {
return d[i].created < d[j].created
}
func sortMap(m map[string]Migrationer) dataSlice {
s := make(dataSlice, 0, len(m))
for k, v := range m {
d := data{}
d.created = v.GetCreated()
d.name = k
d.m = v
s = append(s, d)
}
sort.Sort(s)
return s
}
func isRollBack(name string) bool {
o := orm.NewOrm()
var maps []orm.Params
num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps)
if err != nil {
beego.Info("get name has error", err)
return false
}
if num <= 0 {
return false
}
if maps[0]["status"] == "rollback" {
return true
}
return false
}

17
mime.go
View File

@ -1,3 +1,17 @@
// 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 (
@ -544,8 +558,9 @@ var mimemaps map[string]string = map[string]string{
".mustache": "text/html",
}
func initMime() {
func initMime() error {
for k, v := range mimemaps {
mime.AddExtensionType(k, v)
}
return nil
}

390
namespace.go Normal file
View File

@ -0,0 +1,390 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"net/http"
"strings"
beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
)
type namespaceCond func(*beecontext.Context) bool
type innnerNamespace func(*Namespace)
// Namespace is store all the info
type Namespace struct {
prefix string
handlers *ControllerRegistor
}
// get new Namespace
func NewNamespace(prefix string, params ...innnerNamespace) *Namespace {
ns := &Namespace{
prefix: prefix,
handlers: NewControllerRegister(),
}
for _, p := range params {
p(ns)
}
return ns
}
// set condtion function
// if cond return true can run this namespace, else can't
// usage:
// ns.Cond(func (ctx *context.Context) bool{
// if ctx.Input.Domain() == "api.beego.me" {
// return true
// }
// return false
// })
// Cond as the first filter
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")
}
}
if v, ok := n.handlers.filters[BeforeRouter]; ok {
mr := new(FilterRouter)
mr.tree = NewTree()
mr.pattern = "*"
mr.filterFunc = fn
mr.tree.AddRouter("*", true)
n.handlers.filters[BeforeRouter] = append([]*FilterRouter{mr}, v...)
} else {
n.handlers.InsertFilter("*", BeforeRouter, fn)
}
return n
}
// add filter in the Namespace
// action has before & after
// FilterFunc
// usage:
// Filter("before", func (ctx *context.Context){
// _, ok := ctx.Input.Session("uid").(int)
// if !ok && ctx.Request.RequestURI != "/login" {
// ctx.Redirect(302, "/login")
// }
// })
func (n *Namespace) Filter(action string, filter ...FilterFunc) *Namespace {
var a int
if action == "before" {
a = BeforeRouter
} else if action == "after" {
a = FinishRouter
}
for _, f := range filter {
n.handlers.InsertFilter("*", a, f)
}
return n
}
// same as beego.Rourer
// refer: https://godoc.org/github.com/astaxie/beego#Router
func (n *Namespace) Router(rootpath string, c ControllerInterface, mappingMethods ...string) *Namespace {
n.handlers.Add(rootpath, c, mappingMethods...)
return n
}
// same as beego.AutoRouter
// refer: https://godoc.org/github.com/astaxie/beego#AutoRouter
func (n *Namespace) AutoRouter(c ControllerInterface) *Namespace {
n.handlers.AddAuto(c)
return n
}
// same as beego.AutoPrefix
// refer: https://godoc.org/github.com/astaxie/beego#AutoPrefix
func (n *Namespace) AutoPrefix(prefix string, c ControllerInterface) *Namespace {
n.handlers.AddAutoPrefix(prefix, c)
return n
}
// same as beego.Get
// refer: https://godoc.org/github.com/astaxie/beego#Get
func (n *Namespace) Get(rootpath string, f FilterFunc) *Namespace {
n.handlers.Get(rootpath, f)
return n
}
// same as beego.Post
// refer: https://godoc.org/github.com/astaxie/beego#Post
func (n *Namespace) Post(rootpath string, f FilterFunc) *Namespace {
n.handlers.Post(rootpath, f)
return n
}
// same as beego.Delete
// refer: https://godoc.org/github.com/astaxie/beego#Delete
func (n *Namespace) Delete(rootpath string, f FilterFunc) *Namespace {
n.handlers.Delete(rootpath, f)
return n
}
// same as beego.Put
// refer: https://godoc.org/github.com/astaxie/beego#Put
func (n *Namespace) Put(rootpath string, f FilterFunc) *Namespace {
n.handlers.Put(rootpath, f)
return n
}
// same as beego.Head
// refer: https://godoc.org/github.com/astaxie/beego#Head
func (n *Namespace) Head(rootpath string, f FilterFunc) *Namespace {
n.handlers.Head(rootpath, f)
return n
}
// same as beego.Options
// refer: https://godoc.org/github.com/astaxie/beego#Options
func (n *Namespace) Options(rootpath string, f FilterFunc) *Namespace {
n.handlers.Options(rootpath, f)
return n
}
// same as beego.Patch
// refer: https://godoc.org/github.com/astaxie/beego#Patch
func (n *Namespace) Patch(rootpath string, f FilterFunc) *Namespace {
n.handlers.Patch(rootpath, f)
return n
}
// same as beego.Any
// refer: https://godoc.org/github.com/astaxie/beego#Any
func (n *Namespace) Any(rootpath string, f FilterFunc) *Namespace {
n.handlers.Any(rootpath, f)
return n
}
// same as beego.Handler
// refer: https://godoc.org/github.com/astaxie/beego#Handler
func (n *Namespace) Handler(rootpath string, h http.Handler) *Namespace {
n.handlers.Handler(rootpath, h)
return n
}
// add include class
// refer: https://godoc.org/github.com/astaxie/beego#Include
func (n *Namespace) Include(cList ...ControllerInterface) *Namespace {
n.handlers.Include(cList...)
return n
}
// nest Namespace
// usage:
//ns := beego.NewNamespace(“/v1”).
//Namespace(
// beego.NewNamespace("/shop").
// Get("/:id", func(ctx *context.Context) {
// ctx.Output.Body([]byte("shopinfo"))
// }),
// beego.NewNamespace("/order").
// Get("/:id", func(ctx *context.Context) {
// ctx.Output.Body([]byte("orderinfo"))
// }),
// beego.NewNamespace("/crm").
// Get("/:id", func(ctx *context.Context) {
// ctx.Output.Body([]byte("crminfo"))
// }),
//)
func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
for _, ni := range ns {
for k, v := range ni.handlers.routers {
if t, ok := n.handlers.routers[k]; ok {
addPrefix(v, ni.prefix)
n.handlers.routers[k].AddTree(ni.prefix, v)
} else {
t = NewTree()
t.AddTree(ni.prefix, v)
addPrefix(t, ni.prefix)
n.handlers.routers[k] = t
}
}
if ni.handlers.enableFilter {
for pos, filterList := range ni.handlers.filters {
for _, mr := range filterList {
t := NewTree()
t.AddTree(ni.prefix, mr.tree)
mr.tree = t
n.handlers.insertFilterRouter(pos, mr)
}
}
}
}
return n
}
// register Namespace into beego.Handler
// support multi Namespace
func AddNamespace(nl ...*Namespace) {
for _, n := range nl {
for k, v := range n.handlers.routers {
if t, ok := BeeApp.Handlers.routers[k]; ok {
addPrefix(v, n.prefix)
BeeApp.Handlers.routers[k].AddTree(n.prefix, v)
} else {
t = NewTree()
t.AddTree(n.prefix, v)
addPrefix(t, n.prefix)
BeeApp.Handlers.routers[k] = t
}
}
if n.handlers.enableFilter {
for pos, filterList := range n.handlers.filters {
for _, mr := range filterList {
t := NewTree()
t.AddTree(n.prefix, mr.tree)
mr.tree = t
BeeApp.Handlers.insertFilterRouter(pos, mr)
}
}
}
}
}
func addPrefix(t *Tree, prefix string) {
for _, v := range t.fixrouters {
addPrefix(v, prefix)
}
if t.wildcard != nil {
addPrefix(t.wildcard, prefix)
}
for _, l := range t.leaves {
if c, ok := l.runObject.(*controllerInfo); ok {
if !strings.HasPrefix(c.pattern, prefix) {
c.pattern = prefix + c.pattern
}
}
}
}
// Namespace Condition
func NSCond(cond namespaceCond) innnerNamespace {
return func(ns *Namespace) {
ns.Cond(cond)
}
}
// Namespace BeforeRouter filter
func NSBefore(filiterList ...FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Filter("before", filiterList...)
}
}
// Namespace FinishRouter filter
func NSAfter(filiterList ...FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Filter("after", filiterList...)
}
}
// Namespace Include ControllerInterface
func NSInclude(cList ...ControllerInterface) innnerNamespace {
return func(ns *Namespace) {
ns.Include(cList...)
}
}
// Namespace Router
func NSRouter(rootpath string, c ControllerInterface, mappingMethods ...string) innnerNamespace {
return func(ns *Namespace) {
ns.Router(rootpath, c, mappingMethods...)
}
}
// Namespace Get
func NSGet(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Get(rootpath, f)
}
}
// Namespace Post
func NSPost(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Post(rootpath, f)
}
}
// Namespace Head
func NSHead(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Head(rootpath, f)
}
}
// Namespace Put
func NSPut(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Put(rootpath, f)
}
}
// Namespace Delete
func NSDelete(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Delete(rootpath, f)
}
}
// Namespace Any
func NSAny(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Any(rootpath, f)
}
}
// Namespace Options
func NSOptions(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Options(rootpath, f)
}
}
// Namespace Patch
func NSPatch(rootpath string, f FilterFunc) innnerNamespace {
return func(ns *Namespace) {
ns.Patch(rootpath, f)
}
}
//Namespace AutoRouter
func NSAutoRouter(c ControllerInterface) innnerNamespace {
return func(ns *Namespace) {
ns.AutoRouter(c)
}
}
// Namespace AutoPrefix
func NSAutoPrefix(prefix string, c ControllerInterface) innnerNamespace {
return func(ns *Namespace) {
ns.AutoPrefix(prefix, c)
}
}
// Namespace add sub Namespace
func NSNamespace(prefix string, params ...innnerNamespace) innnerNamespace {
return func(ns *Namespace) {
n := NewNamespace(prefix, params...)
ns.Namespace(n)
}
}

171
namespace_test.go Normal file
View File

@ -0,0 +1,171 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package beego
import (
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/astaxie/beego/context"
)
func TestNamespaceGet(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/user", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Get("/user", func(ctx *context.Context) {
ctx.Output.Body([]byte("v1_user"))
})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "v1_user" {
t.Errorf("TestNamespaceGet can't run, get the response is " + w.Body.String())
}
}
func TestNamespacePost(t *testing.T) {
r, _ := http.NewRequest("POST", "/v1/user/123", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Post("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "123" {
t.Errorf("TestNamespacePost can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceNest(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/admin/order", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Namespace(
NewNamespace("/admin").
Get("/order", func(ctx *context.Context) {
ctx.Output.Body([]byte("order"))
}),
)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "order" {
t.Errorf("TestNamespaceNest can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceNestParam(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/admin/order/123", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Namespace(
NewNamespace("/admin").
Get("/order/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
}),
)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "123" {
t.Errorf("TestNamespaceNestParam can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceRouter(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/api/list", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Router("/api/list", &TestController{}, "*:List")
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "i am list" {
t.Errorf("TestNamespaceRouter can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceAutoFunc(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/test/list", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.AutoRouter(&TestController{})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "i am list" {
t.Errorf("user define func can't run")
}
}
func TestNamespaceFilter(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/user/123", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Filter("before", func(ctx *context.Context) {
ctx.Output.Body([]byte("this is Filter"))
}).
Get("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "this is Filter" {
t.Errorf("TestNamespaceFilter can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceCond(t *testing.T) {
r, _ := http.NewRequest("GET", "/v2/test/list", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v2")
ns.Cond(func(ctx *context.Context) bool {
if ctx.Input.Domain() == "beego.me" {
return true
}
return false
}).
AutoRouter(&TestController{})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Code != 405 {
t.Errorf("TestNamespaceCond can't run get the result " + strconv.Itoa(w.Code))
}
}
func TestNamespaceInside(t *testing.T) {
r, _ := http.NewRequest("GET", "/v3/shop/order/123", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v3",
NSAutoRouter(&TestController{}),
NSNamespace("/shop",
NSGet("/order/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
}),
),
)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "123" {
t.Errorf("TestNamespaceInside can't run, get the response is " + w.Body.String())
}
}

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)
@ -154,10 +152,5 @@ note: not recommend use this in product env.
more details and examples in docs and test
* [中文](http://beego.me/docs/Models_Overview?lang=zh)
* [English](http://beego.me/docs/Models_Overview?lang=en)
[documents](http://beego.me/docs/mvc/model/overview.md)
## TODO
- some unrealized api
- examples
- docs

View File

@ -1,3 +1,17 @@
// 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 (
@ -16,6 +30,7 @@ var (
commands = make(map[string]commander)
)
// print help.
func printHelp(errs ...string) {
content := `orm command usage:
@ -31,6 +46,7 @@ func printHelp(errs ...string) {
os.Exit(2)
}
// listen for orm command and then run it if command arguments passed.
func RunCommand() {
if len(os.Args) < 2 || os.Args[1] != "orm" {
return
@ -58,6 +74,7 @@ func RunCommand() {
}
}
// sync database struct command interface.
type commandSyncDb struct {
al *alias
force bool
@ -66,6 +83,7 @@ type commandSyncDb struct {
rtOnError bool
}
// parse orm command line arguments.
func (d *commandSyncDb) Parse(args []string) {
var name string
@ -78,6 +96,7 @@ func (d *commandSyncDb) Parse(args []string) {
d.al = getDbAlias(name)
}
// run orm line command.
func (d *commandSyncDb) Run() error {
var drops []string
if d.force {
@ -208,10 +227,12 @@ func (d *commandSyncDb) Run() error {
return nil
}
// database creation commander interface implement.
type commandSqlAll struct {
al *alias
}
// parse orm command line arguments.
func (d *commandSqlAll) Parse(args []string) {
var name string
@ -222,6 +243,7 @@ func (d *commandSqlAll) Parse(args []string) {
d.al = getDbAlias(name)
}
// run orm line command.
func (d *commandSqlAll) Run() error {
sqls, indexes := getDbCreateSql(d.al)
var all []string
@ -243,6 +265,10 @@ func init() {
commands["sqlall"] = new(commandSqlAll)
}
// run syncdb command line.
// name means table's alias name. default is "default".
// force means run next sql if the current is error.
// verbose means show all info when running command or not.
func RunSyncdb(name string, force bool, verbose bool) error {
BootStrap()

View File

@ -1,3 +1,17 @@
// 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 (
@ -12,6 +26,7 @@ type dbIndex struct {
Sql string
}
// create database drop sql.
func getDbDropSql(al *alias) (sqls []string) {
if len(modelCache.cache) == 0 {
fmt.Println("no Model found, need register your model")
@ -26,6 +41,7 @@ func getDbDropSql(al *alias) (sqls []string) {
return sqls
}
// get database column type string.
func getColumnTyp(al *alias, fi *fieldInfo) (col string) {
T := al.DbBaser.DbTypes()
fieldType := fi.fieldType
@ -79,6 +95,7 @@ checkColumn:
return
}
// create alter sql string.
func getColumnAddQuery(al *alias, fi *fieldInfo) string {
Q := al.DbBaser.TableQuote()
typ := getColumnTyp(al, fi)
@ -90,6 +107,7 @@ func getColumnAddQuery(al *alias, fi *fieldInfo) string {
return fmt.Sprintf("ALTER TABLE %s%s%s ADD COLUMN %s%s%s %s", Q, fi.mi.table, Q, Q, fi.column, Q, typ)
}
// create database creation string.
func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex) {
if len(modelCache.cache) == 0 {
fmt.Println("no Model found, need register your model")
@ -134,6 +152,10 @@ func getDbCreateSql(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
column += " " + "NOT NULL"
}
//if fi.initial.String() != "" {
// column += " DEFAULT " + fi.initial.String()
//}
if fi.unique {
column += " " + "UNIQUE"
}

434
orm/db.go
View File

@ -1,3 +1,17 @@
// 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 (
@ -15,7 +29,7 @@ const (
)
var (
ErrMissPK = errors.New("missed pk value")
ErrMissPK = errors.New("missed pk value") // missing pk error
)
var (
@ -35,7 +49,7 @@ var (
"istartswith": true,
"iendswith": true,
"in": true,
// "range": true,
"between": true,
// "year": true,
// "month": true,
// "day": true,
@ -45,13 +59,22 @@ var (
}
)
// an instance of dbBaser interface/
type dbBase struct {
ins dbBaser
}
// check dbBase implements dbBaser interface.
var _ dbBaser = new(dbBase)
func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string, skipAuto bool, insert bool, tz *time.Location) (columns []string, values []interface{}, err error) {
// get struct columns values as interface slice.
func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string, skipAuto bool, insert bool, names *[]string, tz *time.Location) (values []interface{}, err error) {
var columns []string
if names != nil {
columns = *names
}
for _, column := range cols {
var fi *fieldInfo
if fi, _ = mi.fields.GetByAny(column); fi != nil {
@ -64,14 +87,24 @@ func (d *dbBase) collectValues(mi *modelInfo, ind reflect.Value, cols []string,
}
value, err := d.collectFieldValue(mi, fi, ind, insert, tz)
if err != nil {
return nil, nil, err
return nil, err
}
columns = append(columns, column)
if names != nil {
columns = append(columns, column)
}
values = append(values, value)
}
if names != nil {
*names = columns
}
return
}
// get one field value in struct column as interface.
func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Value, insert bool, tz *time.Location) (interface{}, error) {
var value interface{}
if fi.pk {
@ -84,28 +117,92 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
} else {
switch fi.fieldType {
case TypeBooleanField:
value = field.Bool()
case TypeCharField, TypeTextField:
value = field.String()
case TypeFloatField, TypeDecimalField:
vu := field.Interface()
if _, ok := vu.(float32); ok {
value, _ = StrTo(ToStr(vu)).Float64()
if nb, ok := field.Interface().(sql.NullBool); ok {
value = nil
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.Float()
value = field.Bool()
}
case TypeCharField, TypeTextField:
if ns, ok := field.Interface().(sql.NullString); ok {
value = nil
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()
}
case TypeFloatField, TypeDecimalField:
if nf, ok := field.Interface().(sql.NullFloat64); ok {
value = nil
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 {
value, _ = StrTo(ToStr(vu)).Float64()
} else {
value = field.Float()
}
}
case TypeDateField, TypeDateTimeField:
value = field.Interface()
if t, ok := value.(time.Time); ok {
d.ins.TimeToDB(&t, tz)
value = t
if t.IsZero() {
value = nil
} else {
value = t
}
}
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:
value = field.Int()
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()
}
case fi.fieldType&IsRelField > 0:
if field.IsNil() {
value = nil
@ -125,6 +222,11 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
switch fi.fieldType {
case TypeDateField, TypeDateTimeField:
if fi.auto_now || fi.auto_now_add && insert {
if insert {
if t, ok := value.(time.Time); ok && !t.IsZero() {
break
}
}
tnow := time.Now()
d.ins.TimeToDB(&tnow, tz)
value = tnow
@ -140,6 +242,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
return value, nil
}
// create insert sql preparation statement object.
func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string, error) {
Q := d.ins.TableQuote()
@ -165,8 +268,9 @@ func (d *dbBase) PrepareInsert(q dbQuerier, mi *modelInfo) (stmtQuerier, string,
return stmt, query, err
}
// insert struct with prepared statement and given struct reflect value.
func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
_, values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, tz)
values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
if err != nil {
return 0, err
}
@ -185,6 +289,7 @@ func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value,
}
}
// query sql ,read records and persist in dbBaser.
func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) error {
var whereCols []string
var args []interface{}
@ -192,7 +297,8 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
// if specify cols length > 0, then use it for where condition.
if len(cols) > 0 {
var err error
whereCols, args, err = d.collectValues(mi, ind, cols, false, false, tz)
whereCols = make([]string, 0, len(cols))
args, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz)
if err != nil {
return err
}
@ -202,7 +308,7 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
if ok == false {
return ErrMissPK
}
whereCols = append(whereCols, pkColumn)
whereCols = []string{pkColumn}
args = append(args, pkValue)
}
@ -243,16 +349,77 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo
return nil
}
// execute insert sql dbQuerier with given struct reflect.Value.
func (d *dbBase) Insert(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
names, values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, tz)
names := make([]string, 0, len(mi.fields.dbcols)-1)
values, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz)
if err != nil {
return 0, err
}
return d.InsertValue(q, mi, names, values)
return d.InsertValue(q, mi, false, names, values)
}
func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, names []string, values []interface{}) (int64, error) {
// multi-insert sql with given slice struct reflect.Value.
func (d *dbBase) InsertMulti(q dbQuerier, mi *modelInfo, sind reflect.Value, bulk int, tz *time.Location) (int64, error) {
var (
cnt int64
nums int
values []interface{}
names []string
)
// typ := reflect.Indirect(mi.addrField).Type()
length := sind.Len()
for i := 1; i <= length; i++ {
ind := reflect.Indirect(sind.Index(i - 1))
// Is this needed ?
// if !ind.Type().AssignableTo(typ) {
// return cnt, ErrArgs
// }
if i == 1 {
vus, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz)
if err != nil {
return cnt, err
}
values = make([]interface{}, bulk*len(vus))
nums += copy(values, vus)
} else {
vus, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, nil, tz)
if err != nil {
return cnt, err
}
if len(vus) != len(names) {
return cnt, ErrArgs
}
nums += copy(values[nums:], vus)
}
if i > 1 && i%bulk == 0 || length == i {
num, err := d.InsertValue(q, mi, true, names, values[:nums])
if err != nil {
return cnt, err
}
cnt += num
nums = 0
}
}
return cnt, nil
}
// execute insert sql with given struct and given values.
// insert the given values, not the field values in struct.
func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []string, values []interface{}) (int64, error) {
Q := d.ins.TableQuote()
marks := make([]string, len(names))
@ -264,36 +431,51 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, names []string, values
qmarks := strings.Join(marks, ", ")
columns := strings.Join(names, sep)
multi := len(values) / len(names)
if isMulti {
qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks
}
query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s)", Q, mi.table, Q, Q, columns, Q, qmarks)
d.ins.ReplaceMarks(&query)
if d.ins.HasReturningID(mi, &query) {
row := q.QueryRow(query, values...)
var id int64
err := row.Scan(&id)
return id, err
} else {
if isMulti || !d.ins.HasReturningID(mi, &query) {
if res, err := q.Exec(query, values...); err == nil {
if isMulti {
return res.RowsAffected()
}
return res.LastInsertId()
} else {
return 0, err
}
} else {
row := q.QueryRow(query, values...)
var id int64
err := row.Scan(&id)
return id, err
}
}
// execute update sql dbQuerier with given struct reflect.Value.
func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
pkName, pkValue, ok := getExistPk(mi, ind)
if ok == false {
return 0, ErrMissPK
}
var setNames []string
// if specify cols length is zero, then commit all columns.
if len(cols) == 0 {
cols = mi.fields.dbcols
setNames = make([]string, 0, len(mi.fields.dbcols)-1)
} else {
setNames = make([]string, 0, len(cols))
}
setNames, setValues, err := d.collectValues(mi, ind, cols, true, false, tz)
setValues, err := d.collectValues(mi, ind, cols, true, false, &setNames, tz)
if err != nil {
return 0, err
}
@ -314,9 +496,10 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
} else {
return 0, err
}
return 0, nil
}
// execute delete sql dbQuerier with given struct reflect.Value.
// delete index is pk.
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
pkName, pkValue, ok := getExistPk(mi, ind)
if ok == false {
@ -355,9 +538,10 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
} else {
return 0, err
}
return 0, nil
}
// update table-related record by querySet.
// need querySet not struct reflect.Value to update related records.
func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, params Params, tz *time.Location) (int64, error) {
columns := make([]string, 0, len(params))
values := make([]interface{}, 0, len(params))
@ -430,9 +614,10 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
} else {
return 0, err
}
return 0, nil
}
// delete related records.
// do UpdateBanch or DeleteBanch by condition of tables' relationship.
func (d *dbBase) deleteRels(q dbQuerier, mi *modelInfo, args []interface{}, tz *time.Location) error {
for _, fi := range mi.fields.fieldsReverse {
fi = fi.reverseFieldInfo
@ -459,8 +644,11 @@ func (d *dbBase) deleteRels(q dbQuerier, mi *modelInfo, args []interface{}, tz *
return nil
}
// delete table-related records.
func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, tz *time.Location) (int64, error) {
tables := newDbTables(mi, d.ins)
tables.skipEnd = true
if qs != nil {
tables.parseRelated(qs.related, qs.relDepth)
}
@ -486,6 +674,8 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
rs = r
}
defer rs.Close()
var ref interface{}
args = make([]interface{}, 0)
@ -528,10 +718,9 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con
} else {
return 0, err
}
return 0, nil
}
// read related records.
func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, container interface{}, tz *time.Location, cols []string) (int64, error) {
val := reflect.ValueOf(container)
@ -640,6 +829,8 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
refs[i] = &ref
}
defer rs.Close()
slice := ind
var cnt int64
@ -739,6 +930,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
return cnt, nil
}
// excute count sql and return count result int64.
func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, tz *time.Location) (cnt int64, err error) {
tables := newDbTables(mi, d.ins)
tables.parseRelated(qs.related, qs.relDepth)
@ -759,6 +951,7 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition
return
}
// generate sql with replacing operator string placeholders and replaced values.
func (d *dbBase) GenerateOperatorSql(mi *modelInfo, fi *fieldInfo, operator string, args []interface{}, tz *time.Location) (string, []interface{}) {
sql := ""
params := getFlatParams(fi, args, tz)
@ -768,13 +961,19 @@ func (d *dbBase) GenerateOperatorSql(mi *modelInfo, fi *fieldInfo, operator stri
}
arg := params[0]
if operator == "in" {
switch operator {
case "in":
marks := make([]string, len(params))
for i, _ := range marks {
marks[i] = "?"
}
sql = fmt.Sprintf("IN (%s)", strings.Join(marks, ", "))
} else {
case "between":
if len(params) != 2 {
panic(fmt.Errorf("operator `%s` need 2 args not %d", operator, len(params)))
}
sql = "BETWEEN ? AND ?"
default:
if len(params) > 1 {
panic(fmt.Errorf("operator `%s` need 1 args not %d", operator, len(params)))
}
@ -812,10 +1011,12 @@ func (d *dbBase) GenerateOperatorSql(mi *modelInfo, fi *fieldInfo, operator stri
return sql, params
}
// gernerate sql string with inner function, such as UPPER(text).
func (d *dbBase) GenerateOperatorLeftCol(*fieldInfo, string, *string) {
// default not use
}
// set values to struct column.
func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string, values []interface{}, tz *time.Location) {
for i, column := range cols {
val := reflect.Indirect(reflect.ValueOf(values[i])).Interface()
@ -837,6 +1038,7 @@ func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string,
}
}
// convert value from database result to value following in field type.
func (d *dbBase) convertValueFromDB(fi *fieldInfo, val interface{}, tz *time.Location) (interface{}, error) {
if val == nil {
return nil, nil
@ -989,6 +1191,7 @@ end:
}
// set one value to struct column field.
func (d *dbBase) setFieldValue(fi *fieldInfo, value interface{}, field reflect.Value) (interface{}, error) {
fieldType := fi.fieldType
@ -998,17 +1201,47 @@ setValue:
switch {
case fieldType == TypeBooleanField:
if isNative {
if value == nil {
value = false
if nb, ok := field.Interface().(sql.NullBool); ok {
if value == nil {
nb.Valid = false
} else {
nb.Bool = value.(bool)
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
}
field.SetBool(value.(bool))
}
field.SetBool(value.(bool))
}
case fieldType == TypeCharField || fieldType == TypeTextField:
if isNative {
if value == nil {
value = ""
if ns, ok := field.Interface().(sql.NullString); ok {
if value == nil {
ns.Valid = false
} else {
ns.String = value.(string)
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 = ""
}
field.SetString(value.(string))
}
field.SetString(value.(string))
}
case fieldType == TypeDateField || fieldType == TypeDateTimeField:
if isNative {
@ -1017,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 {
@ -1027,18 +1310,49 @@ setValue:
}
} else {
if isNative {
if value == nil {
value = int64(0)
if ni, ok := field.Interface().(sql.NullInt64); ok {
if value == nil {
ni.Valid = false
} else {
ni.Int64 = value.(int64)
ni.Valid = true
}
field.Set(reflect.ValueOf(ni))
} else {
if value == nil {
value = int64(0)
}
field.SetInt(value.(int64))
}
field.SetInt(value.(int64))
}
}
case fieldType == TypeFloatField || fieldType == TypeDecimalField:
if isNative {
if value == nil {
value = float64(0)
if nf, ok := field.Interface().(sql.NullFloat64); ok {
if value == nil {
nf.Valid = false
} else {
nf.Float64 = value.(float64)
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 {
value = float64(0)
}
field.SetFloat(value.(float64))
}
field.SetFloat(value.(float64))
}
case fieldType&IsRelField > 0:
if value != nil {
@ -1063,6 +1377,7 @@ setValue:
return value, nil
}
// query sql, read values , save to *[]ParamList.
func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition, exprs []string, container interface{}, tz *time.Location) (int64, error) {
var (
@ -1150,6 +1465,8 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
refs[i] = &ref
}
defer rs.Close()
var (
cnt int64
columns []string
@ -1228,6 +1545,11 @@ func (d *dbBase) ReadValues(q dbQuerier, qs *querySet, mi *modelInfo, cond *Cond
return cnt, nil
}
func (d *dbBase) RowsTo(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, string, string, *time.Location) (int64, error) {
return 0, nil
}
// flag of update joined record.
func (d *dbBase) SupportUpdateJoin() bool {
return true
}
@ -1236,30 +1558,37 @@ func (d *dbBase) MaxLimit() uint64 {
return 18446744073709551615
}
// return quote.
func (d *dbBase) TableQuote() string {
return "`"
}
// replace value placeholer in parametered sql string.
func (d *dbBase) ReplaceMarks(query *string) {
// default use `?` as mark, do nothing
}
// flag of RETURNING sql.
func (d *dbBase) HasReturningID(*modelInfo, *string) bool {
return false
}
// convert time from db.
func (d *dbBase) TimeFromDB(t *time.Time, tz *time.Location) {
*t = t.In(tz)
}
// convert time to db.
func (d *dbBase) TimeToDB(t *time.Time, tz *time.Location) {
*t = t.In(tz)
}
// get database types.
func (d *dbBase) DbTypes() map[string]string {
return nil
}
// gt all tables.
func (d *dbBase) GetTables(db dbQuerier) (map[string]bool, error) {
tables := make(map[string]bool)
query := d.ins.ShowTablesQuery()
@ -1268,6 +1597,8 @@ func (d *dbBase) GetTables(db dbQuerier) (map[string]bool, error) {
return tables, err
}
defer rows.Close()
for rows.Next() {
var table string
err := rows.Scan(&table)
@ -1282,6 +1613,7 @@ func (d *dbBase) GetTables(db dbQuerier) (map[string]bool, error) {
return tables, nil
}
// get all cloumns in table.
func (d *dbBase) GetColumns(db dbQuerier, table string) (map[string][3]string, error) {
columns := make(map[string][3]string)
query := d.ins.ShowColumnsQuery(table)
@ -1290,6 +1622,8 @@ func (d *dbBase) GetColumns(db dbQuerier, table string) (map[string][3]string, e
return columns, err
}
defer rows.Close()
for rows.Next() {
var (
name string
@ -1306,18 +1640,22 @@ func (d *dbBase) GetColumns(db dbQuerier, table string) (map[string][3]string, e
return columns, nil
}
// not implement.
func (d *dbBase) OperatorSql(operator string) string {
panic(ErrNotImplement)
}
// not implement.
func (d *dbBase) ShowTablesQuery() string {
panic(ErrNotImplement)
}
// not implement.
func (d *dbBase) ShowColumnsQuery(table string) string {
panic(ErrNotImplement)
}
// not implement.
func (d *dbBase) IndexExists(dbQuerier, string, string) bool {
panic(ErrNotImplement)
}

View File

@ -1,35 +1,53 @@
// 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 (
"database/sql"
"fmt"
"os"
"reflect"
"sync"
"time"
)
// database driver constant int.
type DriverType int
const (
_ DriverType = iota
DR_MySQL
DR_Sqlite
DR_Oracle
DR_Postgres
_ DriverType = iota // int enum type
DR_MySQL // mysql
DR_Sqlite // sqlite
DR_Oracle // oracle
DR_Postgres // pgsql
)
// database driver string.
type driver string
// get type constant int of current driver..
func (d driver) Type() DriverType {
a, _ := dataBaseCache.get(string(d))
return a.Driver
}
// get name of current driver
func (d driver) Name() string {
return string(d)
}
// check driver iis implemented Driver interface or not.
var _ Driver = new(driver)
var (
@ -47,11 +65,13 @@ var (
}
)
// database alias cacher.
type _dbCache struct {
mux sync.RWMutex
cache map[string]*alias
}
// add database alias with original name.
func (ac *_dbCache) add(name string, al *alias) (added bool) {
ac.mux.Lock()
defer ac.mux.Unlock()
@ -62,6 +82,7 @@ func (ac *_dbCache) add(name string, al *alias) (added bool) {
return
}
// get database alias if cached.
func (ac *_dbCache) get(name string) (al *alias, ok bool) {
ac.mux.RLock()
defer ac.mux.RUnlock()
@ -69,6 +90,7 @@ func (ac *_dbCache) get(name string) (al *alias, ok bool) {
return
}
// get default alias.
func (ac *_dbCache) getDefault() (al *alias) {
al, _ = ac.get("default")
return
@ -87,57 +109,29 @@ type alias struct {
Engine string
}
// Setting the database connect params. Use the database driver self dataSource args.
func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) {
al := new(alias)
al.Name = aliasName
al.DriverName = driverName
al.DataSource = dataSource
var (
err error
)
if dr, ok := drivers[driverName]; ok {
al.DbBaser = dbBasers[dr]
al.Driver = dr
} else {
err = fmt.Errorf("driver name `%s` have not registered", driverName)
goto end
}
if dataBaseCache.add(aliasName, al) == false {
err = fmt.Errorf("db name `%s` already registered, cannot reuse", aliasName)
goto end
}
al.DB, err = sql.Open(driverName, dataSource)
if err != nil {
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
goto end
}
func detectTZ(al *alias) {
// orm timezone system match database
// default use Local
al.TZ = time.Local
if al.DriverName == "sphinx" {
return
}
switch al.Driver {
case DR_MySQL:
row := al.DB.QueryRow("SELECT @@session.time_zone")
row := al.DB.QueryRow("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP)")
var tz string
row.Scan(&tz)
if tz == "SYSTEM" {
tz = ""
row = al.DB.QueryRow("SELECT @@system_time_zone")
row.Scan(&tz)
t, err := time.Parse("MST", tz)
if err == nil {
al.TZ = t.Location()
if len(tz) >= 8 {
if tz[0] != '-' {
tz = "+" + tz
}
} else {
t, err := time.Parse("-07:00", tz)
t, err := time.Parse("-07:00:00", tz)
if err == nil {
al.TZ = t.Location()
} else {
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
}
}
@ -150,7 +144,7 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) {
if engine != "" {
al.Engine = engine
} else {
engine = "INNODB"
al.Engine = "INNODB"
}
case DR_Sqlite:
@ -163,8 +157,64 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) {
loc, err := time.LoadLocation(tz)
if err == nil {
al.TZ = loc
} else {
DebugLog.Printf("Detect DB timezone: %s %s\n", tz, err.Error())
}
}
}
func addAliasWthDB(aliasName, driverName string, db *sql.DB) (*alias, error) {
al := new(alias)
al.Name = aliasName
al.DriverName = driverName
al.DB = db
if dr, ok := drivers[driverName]; ok {
al.DbBaser = dbBasers[dr]
al.Driver = dr
} else {
return nil, fmt.Errorf("driver name `%s` have not registered", driverName)
}
err := db.Ping()
if err != nil {
return nil, fmt.Errorf("register db Ping `%s`, %s", aliasName, err.Error())
}
if dataBaseCache.add(aliasName, al) == false {
return nil, fmt.Errorf("DataBase alias name `%s` already registered, cannot reuse", aliasName)
}
return al, nil
}
func AddAliasWthDB(aliasName, driverName string, db *sql.DB) error {
_, err := addAliasWthDB(aliasName, driverName, db)
return err
}
// Setting the database connect params. Use the database driver self dataSource args.
func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) error {
var (
err error
db *sql.DB
al *alias
)
db, err = sql.Open(driverName, dataSource)
if err != nil {
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
goto end
}
al, err = addAliasWthDB(aliasName, driverName, db)
if err != nil {
goto end
}
al.DataSource = dataSource
detectTZ(al)
for i, v := range params {
switch i {
@ -175,39 +225,37 @@ func RegisterDataBase(aliasName, driverName, dataSource string, params ...int) {
}
}
err = al.DB.Ping()
if err != nil {
err = fmt.Errorf("register db `%s`, %s", aliasName, err.Error())
goto end
}
end:
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
if db != nil {
db.Close()
}
DebugLog.Println(err.Error())
}
return err
}
// Register a database driver use specify driver name, this can be definition the driver is which database type.
func RegisterDriver(driverName string, typ DriverType) {
func RegisterDriver(driverName string, typ DriverType) error {
if t, ok := drivers[driverName]; ok == false {
drivers[driverName] = typ
} else {
if t != typ {
fmt.Sprintf("driverName `%s` db driver already registered and is other type\n", driverName)
os.Exit(2)
return fmt.Errorf("driverName `%s` db driver already registered and is other type\n", driverName)
}
}
return nil
}
// Change the database default used timezone
func SetDataBaseTZ(aliasName string, tz *time.Location) {
func SetDataBaseTZ(aliasName string, tz *time.Location) error {
if al, ok := dataBaseCache.get(aliasName); ok {
al.TZ = tz
} else {
fmt.Sprintf("DataBase name `%s` not registered\n", aliasName)
os.Exit(2)
return fmt.Errorf("DataBase alias name `%s` not registered\n", aliasName)
}
return nil
}
// Change the max idle conns for *sql.DB, use specify database alias name
@ -226,3 +274,19 @@ func SetMaxOpenConns(aliasName string, maxOpenConns int) {
fun.Call([]reflect.Value{reflect.ValueOf(maxOpenConns)})
}
}
// Get *sql.DB from registered database by db alias name.
// Use "default" as alias name if you not set.
func GetDB(aliasNames ...string) (*sql.DB, error) {
var name string
if len(aliasNames) > 0 {
name = aliasNames[0]
} else {
name = "default"
}
if al, ok := dataBaseCache.get(name); ok {
return al.DB, nil
} else {
return nil, fmt.Errorf("DataBase of alias name `%s` not found\n", name)
}
}

View File

@ -1,9 +1,24 @@
// 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"
)
// mysql operators.
var mysqlOperators = map[string]string{
"exact": "= ?",
"iexact": "LIKE ?",
@ -21,6 +36,7 @@ var mysqlOperators = map[string]string{
"iendswith": "LIKE ?",
}
// mysql column field types.
var mysqlTypes = map[string]string{
"auto": "AUTO_INCREMENT NOT NULL PRIMARY KEY",
"pk": "NOT NULL PRIMARY KEY",
@ -41,29 +57,35 @@ var mysqlTypes = map[string]string{
"float64-decimal": "numeric(%d, %d)",
}
// mysql dbBaser implementation.
type dbBaseMysql struct {
dbBase
}
var _ dbBaser = new(dbBaseMysql)
// get mysql operator.
func (d *dbBaseMysql) OperatorSql(operator string) string {
return mysqlOperators[operator]
}
// get mysql table field types.
func (d *dbBaseMysql) DbTypes() map[string]string {
return mysqlTypes
}
// show table sql for mysql.
func (d *dbBaseMysql) ShowTablesQuery() string {
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = DATABASE()"
}
// show columns sql of table for mysql.
func (d *dbBaseMysql) ShowColumnsQuery(table string) string {
return fmt.Sprintf("SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE FROM information_schema.columns "+
"WHERE table_schema = DATABASE() AND table_name = '%s'", table)
}
// execute sql to check index exist.
func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool {
row := db.QueryRow("SELECT count(*) FROM information_schema.statistics "+
"WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?", table, name)
@ -72,6 +94,7 @@ func (d *dbBaseMysql) IndexExists(db dbQuerier, table string, name string) bool
return cnt > 0
}
// create new mysql dbBaser.
func newdbBaseMysql() dbBaser {
b := new(dbBaseMysql)
b.ins = b

View File

@ -1,11 +1,27 @@
// 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
// oracle dbBaser
type dbBaseOracle struct {
dbBase
}
var _ dbBaser = new(dbBaseOracle)
// create oracle dbBaser.
func newdbBaseOracle() dbBaser {
b := new(dbBaseOracle)
b.ins = b

View File

@ -1,3 +1,17 @@
// 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 (
@ -5,6 +19,7 @@ import (
"strconv"
)
// postgresql operators.
var postgresOperators = map[string]string{
"exact": "= ?",
"iexact": "= UPPER(?)",
@ -20,6 +35,7 @@ var postgresOperators = map[string]string{
"iendswith": "LIKE UPPER(?)",
}
// postgresql column field types.
var postgresTypes = map[string]string{
"auto": "serial NOT NULL PRIMARY KEY",
"pk": "NOT NULL PRIMARY KEY",
@ -40,16 +56,19 @@ var postgresTypes = map[string]string{
"float64-decimal": "numeric(%d, %d)",
}
// postgresql dbBaser.
type dbBasePostgres struct {
dbBase
}
var _ dbBaser = new(dbBasePostgres)
// get postgresql operator.
func (d *dbBasePostgres) OperatorSql(operator string) string {
return postgresOperators[operator]
}
// generate functioned sql string, such as contains(text).
func (d *dbBasePostgres) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
switch operator {
case "contains", "startswith", "endswith":
@ -59,6 +78,7 @@ func (d *dbBasePostgres) GenerateOperatorLeftCol(fi *fieldInfo, operator string,
}
}
// postgresql unsupports updating joined record.
func (d *dbBasePostgres) SupportUpdateJoin() bool {
return false
}
@ -67,10 +87,13 @@ func (d *dbBasePostgres) MaxLimit() uint64 {
return 0
}
// postgresql quote is ".
func (d *dbBasePostgres) TableQuote() string {
return `"`
}
// postgresql value placeholder is $n.
// replace default ? to $n.
func (d *dbBasePostgres) ReplaceMarks(query *string) {
q := *query
num := 0
@ -97,6 +120,7 @@ func (d *dbBasePostgres) ReplaceMarks(query *string) {
*query = string(data)
}
// make returning sql support for postgresql.
func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) (has bool) {
if mi.fields.pk.auto {
if query != nil {
@ -107,18 +131,22 @@ func (d *dbBasePostgres) HasReturningID(mi *modelInfo, query *string) (has bool)
return
}
// show table sql for postgresql.
func (d *dbBasePostgres) ShowTablesQuery() string {
return "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog', 'information_schema')"
}
// show table columns sql for postgresql.
func (d *dbBasePostgres) ShowColumnsQuery(table string) string {
return fmt.Sprintf("SELECT column_name, data_type, is_nullable FROM information_schema.columns where table_schema NOT IN ('pg_catalog', 'information_schema') and table_name = '%s'", table)
}
// get column types of postgresql.
func (d *dbBasePostgres) DbTypes() map[string]string {
return postgresTypes
}
// check index exist in postgresql.
func (d *dbBasePostgres) IndexExists(db dbQuerier, table string, name string) bool {
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_indexes WHERE tablename = '%s' AND indexname = '%s'", table, name)
row := db.QueryRow(query)
@ -127,6 +155,7 @@ func (d *dbBasePostgres) IndexExists(db dbQuerier, table string, name string) bo
return cnt > 0
}
// create new postgresql dbBaser.
func newdbBasePostgres() dbBaser {
b := new(dbBasePostgres)
b.ins = b

View File

@ -1,3 +1,17 @@
// 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 (
@ -5,6 +19,7 @@ import (
"fmt"
)
// sqlite operators.
var sqliteOperators = map[string]string{
"exact": "= ?",
"iexact": "LIKE ? ESCAPE '\\'",
@ -20,6 +35,7 @@ var sqliteOperators = map[string]string{
"iendswith": "LIKE ? ESCAPE '\\'",
}
// sqlite column types.
var sqliteTypes = map[string]string{
"auto": "integer NOT NULL PRIMARY KEY AUTOINCREMENT",
"pk": "NOT NULL PRIMARY KEY",
@ -40,38 +56,47 @@ var sqliteTypes = map[string]string{
"float64-decimal": "decimal",
}
// sqlite dbBaser.
type dbBaseSqlite struct {
dbBase
}
var _ dbBaser = new(dbBaseSqlite)
// get sqlite operator.
func (d *dbBaseSqlite) OperatorSql(operator string) string {
return sqliteOperators[operator]
}
// generate functioned sql for sqlite.
// only support DATE(text).
func (d *dbBaseSqlite) GenerateOperatorLeftCol(fi *fieldInfo, operator string, leftCol *string) {
if fi.fieldType == TypeDateField {
*leftCol = fmt.Sprintf("DATE(%s)", *leftCol)
}
}
// unable updating joined record in sqlite.
func (d *dbBaseSqlite) SupportUpdateJoin() bool {
return false
}
// max int in sqlite.
func (d *dbBaseSqlite) MaxLimit() uint64 {
return 9223372036854775807
}
// get column types in sqlite.
func (d *dbBaseSqlite) DbTypes() map[string]string {
return sqliteTypes
}
// get show tables sql in sqlite.
func (d *dbBaseSqlite) ShowTablesQuery() string {
return "SELECT name FROM sqlite_master WHERE type = 'table'"
}
// get columns in sqlite.
func (d *dbBaseSqlite) GetColumns(db dbQuerier, table string) (map[string][3]string, error) {
query := d.ins.ShowColumnsQuery(table)
rows, err := db.Query(query)
@ -92,10 +117,12 @@ func (d *dbBaseSqlite) GetColumns(db dbQuerier, table string) (map[string][3]str
return columns, nil
}
// get show columns sql in sqlite.
func (d *dbBaseSqlite) ShowColumnsQuery(table string) string {
return fmt.Sprintf("pragma table_info('%s')", table)
}
// check index exist in sqlite.
func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool {
query := fmt.Sprintf("PRAGMA index_list('%s')", table)
rows, err := db.Query(query)
@ -113,6 +140,7 @@ func (d *dbBaseSqlite) IndexExists(db dbQuerier, table string, name string) bool
return false
}
// create new sqlite dbBaser.
func newdbBaseSqlite() dbBaser {
b := new(dbBaseSqlite)
b.ins = b

View File

@ -1,3 +1,17 @@
// 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 (
@ -6,6 +20,7 @@ import (
"time"
)
// table info struct.
type dbTable struct {
id int
index string
@ -18,13 +33,17 @@ type dbTable struct {
jtl *dbTable
}
// tables collection struct, contains some tables.
type dbTables struct {
tablesM map[string]*dbTable
tables []*dbTable
mi *modelInfo
base dbBaser
skipEnd bool
}
// set table info to collection.
// if not exist, create new.
func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool) *dbTable {
name := strings.Join(names, ExprSep)
if j, ok := t.tablesM[name]; ok {
@ -41,6 +60,7 @@ func (t *dbTables) set(names []string, mi *modelInfo, fi *fieldInfo, inner bool)
return t.tablesM[name]
}
// add table info to collection.
func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool) (*dbTable, bool) {
name := strings.Join(names, ExprSep)
if _, ok := t.tablesM[name]; ok == false {
@ -53,11 +73,14 @@ func (t *dbTables) add(names []string, mi *modelInfo, fi *fieldInfo, inner bool)
return t.tablesM[name], false
}
// get table info in collection.
func (t *dbTables) get(name string) (*dbTable, bool) {
j, ok := t.tablesM[name]
return j, ok
}
// get related fields info in recursive depth loop.
// loop once, depth decreases one.
func (t *dbTables) loopDepth(depth int, prefix string, fi *fieldInfo, related []string) []string {
if depth < 0 || fi.fieldType == RelManyToMany {
return related
@ -78,6 +101,7 @@ func (t *dbTables) loopDepth(depth int, prefix string, fi *fieldInfo, related []
return related
}
// parse related fields.
func (t *dbTables) parseRelated(rels []string, depth int) {
relsNum := len(rels)
@ -111,7 +135,7 @@ func (t *dbTables) parseRelated(rels []string, depth int) {
names = append(names, fi.name)
mmi = fi.relModelInfo
if fi.null {
if fi.null || t.skipEnd {
inner = false
}
@ -139,6 +163,7 @@ func (t *dbTables) parseRelated(rels []string, depth int) {
}
}
// generate join string.
func (t *dbTables) getJoinSql() (join string) {
Q := t.base.TableQuote()
@ -185,9 +210,12 @@ func (t *dbTables) getJoinSql() (join string) {
return
}
// parse orm model struct field tag expression.
func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string, info *fieldInfo, success bool) {
var (
jtl *dbTable
fi *fieldInfo
fiN *fieldInfo
mmi = mi
)
@ -196,9 +224,22 @@ func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string
inner := true
loopFor:
for i, ex := range exprs {
fi, ok := mmi.fields.GetByAny(ex)
var ok, okN bool
if fiN != nil {
fi = fiN
ok = true
fiN = nil
}
if i == 0 {
fi, ok = mmi.fields.GetByAny(ex)
}
_ = okN
if ok {
@ -216,44 +257,61 @@ func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string
mmi = fi.reverseFieldInfo.mi
}
if i < num {
fiN, okN = mmi.fields.GetByAny(exprs[i+1])
}
if isRel && (fi.mi.isThrough == false || num != i) {
if fi.null {
if fi.null || t.skipEnd {
inner = false
}
jt, _ := t.add(names, mmi, fi, inner)
jt.jtl = jtl
jtl = jt
}
if num == i {
if i == 0 || jtl == nil {
index = "T0"
} else {
index = jtl.index
}
info = fi
if jtl == nil {
name = fi.name
} else {
name = jtl.name + ExprSep + fi.name
}
switch {
case fi.rel:
case fi.reverse:
switch fi.reverseFieldInfo.fieldType {
case RelOneToOne, RelForeignKey:
index = jtl.index
info = fi.reverseFieldInfo.mi.fields.pk
name = info.name
if t.skipEnd && okN || !t.skipEnd {
if t.skipEnd && okN && fiN.pk {
goto loopEnd
}
jt, _ := t.add(names, mmi, fi, inner)
jt.jtl = jtl
jtl = jt
}
}
if num != i {
continue
}
loopEnd:
if i == 0 || jtl == nil {
index = "T0"
} else {
index = jtl.index
}
info = fi
if jtl == nil {
name = fi.name
} else {
name = jtl.name + ExprSep + fi.name
}
switch {
case fi.rel:
case fi.reverse:
switch fi.reverseFieldInfo.fieldType {
case RelOneToOne, RelForeignKey:
index = jtl.index
info = fi.reverseFieldInfo.mi.fields.pk
name = info.name
}
}
break loopFor
} else {
index = ""
name = ""
@ -267,6 +325,7 @@ func (t *dbTables) parseExprs(mi *modelInfo, exprs []string) (index, name string
return
}
// generate condition sql.
func (t *dbTables) getCondSql(cond *Condition, sub bool, tz *time.Location) (where string, params []interface{}) {
if cond == nil || cond.IsEmpty() {
return
@ -331,6 +390,7 @@ func (t *dbTables) getCondSql(cond *Condition, sub bool, tz *time.Location) (whe
return
}
// generate order sql.
func (t *dbTables) getOrderSql(orders []string) (orderSql string) {
if len(orders) == 0 {
return
@ -359,6 +419,7 @@ func (t *dbTables) getOrderSql(orders []string) (orderSql string) {
return
}
// generate limit sql.
func (t *dbTables) getLimitSql(mi *modelInfo, offset int64, limit int64) (limits string) {
if limit == 0 {
limit = int64(DefaultRowsLimit)
@ -381,6 +442,7 @@ func (t *dbTables) getLimitSql(mi *modelInfo, offset int64, limit int64) (limits
return
}
// crete new tables collection.
func newDbTables(mi *modelInfo, base dbBaser) *dbTables {
tables := &dbTables{}
tables.tablesM = make(map[string]*dbTable)

View File

@ -1,3 +1,17 @@
// 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 (
@ -6,15 +20,16 @@ import (
"time"
)
// get table alias.
func getDbAlias(name string) *alias {
if al, ok := dataBaseCache.get(name); ok {
return al
} else {
panic(fmt.Errorf("unknown DataBase alias name %s", name))
}
return nil
}
// get pk column info.
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
fi := mi.fields.pk
@ -37,6 +52,7 @@ func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interfac
return
}
// get fields description as flatted string.
func getFlatParams(fi *fieldInfo, args []interface{}, tz *time.Location) (params []interface{}) {
outFor:
@ -48,9 +64,16 @@ outFor:
continue
}
switch v := arg.(type) {
case []byte:
case string:
kind := val.Kind()
if kind == reflect.Ptr {
val = val.Elem()
kind = val.Kind()
arg = val.Interface()
}
switch kind {
case reflect.String:
v := val.String()
if fi != nil {
if fi.fieldType == TypeDateField || fi.fieldType == TypeDateTimeField {
var t time.Time
@ -75,61 +98,66 @@ outFor:
}
}
arg = v
case time.Time:
if fi != nil && fi.fieldType == TypeDateField {
arg = v.In(tz).Format(format_Date)
} else {
arg = v.In(tz).Format(format_DateTime)
}
default:
kind := val.Kind()
switch kind {
case reflect.Slice, reflect.Array:
var args []interface{}
for i := 0; i < val.Len(); i++ {
v := val.Index(i)
var vu interface{}
if v.CanInterface() {
vu = v.Interface()
}
if vu == nil {
continue
}
args = append(args, vu)
}
if len(args) > 0 {
p := getFlatParams(fi, args, tz)
params = append(params, p...)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
arg = val.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
arg = val.Uint()
case reflect.Float32:
arg, _ = StrTo(ToStr(arg)).Float64()
case reflect.Float64:
arg = val.Float()
case reflect.Bool:
arg = val.Bool()
case reflect.Slice, reflect.Array:
if _, ok := arg.([]byte); ok {
continue outFor
}
case reflect.Ptr, reflect.Struct:
ind := reflect.Indirect(val)
var args []interface{}
for i := 0; i < val.Len(); i++ {
v := val.Index(i)
if ind.Kind() == reflect.Struct {
typ := ind.Type()
name := getFullName(typ)
var value interface{}
if mmi, ok := modelCache.getByFN(name); ok {
if _, vu, exist := getExistPk(mmi, ind); exist {
value = vu
}
}
arg = value
var vu interface{}
if v.CanInterface() {
vu = v.Interface()
}
if arg == nil {
panic(fmt.Errorf("need a valid args value, unknown table or value `%s`", name))
}
if vu == nil {
continue
}
args = append(args, vu)
}
if len(args) > 0 {
p := getFlatParams(fi, args, tz)
params = append(params, p...)
}
continue outFor
case reflect.Struct:
if v, ok := arg.(time.Time); ok {
if fi != nil && fi.fieldType == TypeDateField {
arg = v.In(tz).Format(format_Date)
} else {
arg = ind.Interface()
arg = v.In(tz).Format(format_DateTime)
}
} else {
typ := val.Type()
name := getFullName(typ)
var value interface{}
if mmi, ok := modelCache.getByFN(name); ok {
if _, vu, exist := getExistPk(mmi, val); exist {
value = vu
}
}
arg = value
if arg == nil {
panic(fmt.Errorf("need a valid args value, unknown table or value `%s`", name))
}
}
}
params = append(params, arg)
}
return

View File

@ -1,43 +0,0 @@
## 命令模式
注册模型与数据库以后,调用 RunCommand 执行 orm 命令
```go
func main() {
// orm.RegisterModel...
// orm.RegisterDataBase...
...
orm.RunCommand()
}
```
```bash
go build main.go
./main orm
# 直接执行可以显示帮助
# 如果你的程序可以支持的话,直接运行 go run main.go orm 也是一样的效果
```
## 自动建表
```bash
./main orm syncdb -h
Usage of orm command: syncdb:
-db="default": DataBase alias name
-force=false: drop tables before create
-v=false: verbose info
```
使用 `-force=1` 可以 drop table 后再建表
使用 `-v` 可以查看执行的 sql 语句
## 打印建表SQL
```bash
./main orm sqlall -h
Usage of orm command: syncdb:
-db="default": DataBase alias name
```
默认使用别名为 default 的数据库

View File

@ -1,38 +0,0 @@
## Custom Fields
TypeBooleanField = 1 << iota
// string
TypeCharField
// string
TypeTextField
// time.Time
TypeDateField
// time.Time
TypeDateTimeField
// int16
TypeSmallIntegerField
// int32
TypeIntegerField
// int64
TypeBigIntegerField
// uint16
TypePositiveSmallIntegerField
// uint32
TypePositiveIntegerField
// uint64
TypePositiveBigIntegerField
// float64
TypeFloatField
// float64
TypeDecimalField
RelForeignKey
RelOneToOne
RelManyToMany
RelReverseOne
RelReverseMany

View File

@ -1,288 +0,0 @@
## 模型定义
复杂的模型定义不是必须的,此功能用作数据库数据转换和[自动建表](Cmd.md#自动建表)
默认的表名使用驼峰转蛇形,比如 AuthUser -> auth_user
**自定义表名**
```go
type User struct {
Id int
Name string
}
func (u *User) TableName() string {
return "auth_user"
}
```
如果[前缀设置](Orm.md#registermodelwithprefix)为`prefix_`那么表名为prefix_auth_user
## Struct Tag 设置参数
```go
orm:"null;rel(fk)"
```
多个设置间使用 `;` 分隔,设置的值如果是多个,使用 `,` 分隔。
#### 忽略字段
设置 `-` 即可忽略 struct 中的字段
```go
type User struct {
...
AnyField string `orm:"-"`
...
```
#### auto
当 Field 类型为 int, int32, int64 时,可以设置字段为自增健
当模型定义里没有主键时,符合上述类型且名称为 `Id` 的 Field 将被视为自增健。
#### pk
设置为主键,适用于自定义其他类型为主键
#### null
数据库表默认为 `NOT NULL`,设置 null 代表 `ALLOW NULL`
#### blank
设置 string 类型的字段允许为空,否则 clean 会返回错误
#### index
为字段增加索引
#### unique
为字段增加 unique 键
#### column
为字段设置 db 字段的名称
```go
Name `orm:"column(user_name)"`
```
#### default
为字段设置默认值,类型必须符合
```go
type User struct {
...
Status int `orm:"default(1)"`
```
#### size
string 类型字段默认为 varchar(255)
设置 size 以后db type 将使用 varchar(size)
```go
Title string `orm:"size(60)"`
```
#### digits / decimals
设置 float32, float64 类型的浮点精度
```go
Money float64 `orm:"digits(12);decimals(4)"`
```
总长度 12 小数点后 4 位 eg: `99999999.9999`
#### auto_now / auto_now_add
```go
Created time.Time `auto_now_add`
Updated time.Time `auto_now`
```
* auto_now 每次 model 保存时都会对时间自动更新
* auto_now_add 第一次保存时才设置时间
对于批量的 update 此设置是不生效的
#### type
设置为 date 时time.Time 字段的对应 db 类型使用 date
```go
Created time.Time `orm:"auto_now_add;type(date)"`
```
设置为 text 时string 字段对应的 db 类型使用 text
```go
Content string `orm:"type(text)"`
```
## 表关系设置
#### rel / reverse
**RelOneToOne**:
```go
type User struct {
...
Profile *Profile `orm:"null;rel(one);on_delete(set_null)"`
```
对应的反向关系 **RelReverseOne**:
```go
type Profile struct {
...
User *User `orm:"reverse(one)" json:"-"`
```
**RelForeignKey**:
```go
type Post struct {
...
User*User `orm:"rel(fk)"` // RelForeignKey relation
```
对应的反向关系 **RelReverseMany**:
```go
type User struct {
...
Posts []*Post `orm:"reverse(many)" json:"-"` // fk 的反向关系
```
**RelManyToMany**:
```go
type Post struct {
...
Tags []*Tag `orm:"rel(m2m)"` // ManyToMany relation
```
对应的反向关系 **RelReverseMany**:
```go
type Tag struct {
...
Posts []*Post `orm:"reverse(many)" json:"-"`
```
#### rel_table / rel_through
此设置针对 `orm:"rel(m2m)"` 的关系字段
rel_table 设置自动生成的 m2m 关系表的名称
rel_through 如果要在 m2m 关系中使用自定义的 m2m 关系表
通过这个设置其名称,格式为 pkg.path.ModelName
eg: app.models.PostTagRel
PostTagRel 表需要有到 Post 和 Tag 的关系
当设置 rel_table 时会忽略 rel_through
#### on_delete
设置对应的 rel 关系删除时,如何处理关系字段。
cascade 级联删除(默认值)
set_null 设置为 NULL需要设置 null = true
set_default 设置为默认值,需要设置 default 值
do_nothing 什么也不做,忽略
```go
type User struct {
...
Profile *Profile `orm:"null;rel(one);on_delete(set_null)"`
...
type Profile struct {
...
User *User `orm:"reverse(one)" json:"-"`
// 删除 Profile 时将设置 User.Profile 的数据库字段为 NULL
```
## 模型字段与数据库类型的对应
在此列出 orm 推荐的对应数据库类型,自动建表功能也会以此为标准。
默认所有的字段都是 **NOT NULL**
#### MySQL
| go |mysql
| :--- | :---
| int, int32, int64 - 设置 auto 或者名称为 `Id` 时 | integer AUTO_INCREMENT
| bool | bool
| string - 默认为 size 255 | varchar(size)
| string - 设置 type(text) 时 | longtext
| time.Time - 设置 type 为 date 时 | date
| time.TIme | datetime
| byte | tinyint unsigned
| rune | integer
| int | integer
| int8 | tinyint
| int16 | smallint
| int32 | integer
| int64 | bigint
| uint | integer unsigned
| uint8 | tinyint unsigned
| uint16 | smallint unsigned
| uint32 | integer unsigned
| uint64 | bigint unsigned
| float32 | double precision
| float64 | double precision
| float64 - 设置 digits, decimals 时 | numeric(digits, decimals)
#### Sqlite3
| go | sqlite3
| :--- | :---
| int, int32, int64 - 设置 auto 或者名称为 `Id` 时 | integer AUTOINCREMENT
| bool | bool
| string - 默认为 size 255 | varchar(size)
| string - 设置 type(text) 时 | text
| time.Time - 设置 type 为 date 时 | date
| time.TIme | datetime
| byte | tinyint unsigned
| rune | integer
| int | integer
| int8 | tinyint
| int16 | smallint
| int32 | integer
| int64 | bigint
| uint | integer unsigned
| uint8 | tinyint unsigned
| uint16 | smallint unsigned
| uint32 | integer unsigned
| uint64 | bigint unsigned
| float32 | real
| float64 | real
| float64 - 设置 digits, decimals 时 | decimal
#### PostgreSQL
| go | postgres
| :--- | :---
| int, int32, int64 - 设置 auto 或者名称为 `Id` 时 | serial
| bool | bool
| string - 默认为 size 255 | varchar(size)
| string - 设置 type(text) 时 | text
| time.Time - 设置 type 为 date 时 | date
| time.TIme | timestamp with time zone
| byte | smallint CHECK("column" >= 0 AND "column" <= 255)
| rune | integer
| int | integer
| int8 | smallint CHECK("column" >= -127 AND "column" <= 128)
| int16 | smallint
| int32 | integer
| int64 | bigint
| uint | bigint CHECK("column" >= 0)
| uint8 | smallint CHECK("column" >= 0 AND "column" <= 255)
| uint16 | integer CHECK("column" >= 0)
| uint32 | bigint CHECK("column" >= 0)
| uint64 | bigint CHECK("column" >= 0)
| float32 | double precision
| float64 | double precision
| float64 - 设置 digits, decimals 时 | numeric(digits, decimals)
## 关系型字段
其字段类型取决于对应的主键。
* RelForeignKey
* RelOneToOne
* RelManyToMany
* RelReverseOne
* RelReverseMany

View File

@ -1,83 +0,0 @@
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for `comment`
-- ----------------------------
DROP TABLE IF EXISTS `comment`;
CREATE TABLE `comment` (
`id` int(11) NOT NULL,
`post_id` bigint(200) NOT NULL,
`content` longtext NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`status` smallint(4) NOT NULL,
`created` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `post`
-- ----------------------------
DROP TABLE IF EXISTS `post`;
CREATE TABLE `post` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`title` varchar(60) NOT NULL,
`content` longtext NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `post_tag_rel`
-- ----------------------------
DROP TABLE IF EXISTS `post_tag_rel`;
CREATE TABLE `post_tag_rel` (
`id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `tag`
-- ----------------------------
DROP TABLE IF EXISTS `tag`;
CREATE TABLE `tag` (
`id` int(11) NOT NULL,
`name` varchar(30) NOT NULL,
`status` smallint(4) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(30) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(30) NOT NULL,
`status` smallint(4) NOT NULL,
`is_staff` tinyint(1) NOT NULL,
`is_active` tinyint(1) NOT NULL,
`created` date NOT NULL,
`updated` datetime NOT NULL,
`profile_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for `profile`
-- ----------------------------
DROP TABLE IF EXISTS `profile`;
CREATE TABLE `profile` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` smallint(4) NOT NULL,
`money` double NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -1,59 +0,0 @@
## 对象的CRUD操作
对 object 操作简单的三个方法 Read / Insert / Update / Delete
```go
o := orm.NewOrm()
user := new(User)
user.Name = "slene"
fmt.Println(o.Insert(user))
user.Name = "Your"
fmt.Println(o.Update(user))
fmt.Println(o.Read(user))
fmt.Println(o.Delete(user))
```
### Read
```go
o := orm.NewOrm()
user := User{Id: 1}
err = o.Read(&user)
if err == sql.ErrNoRows {
fmt.Println("查询不到")
} else if err == orm.ErrMissPK {
fmt.Println("找不到主键")
} else {
fmt.Println(user.Id, user.Name)
}
```
### Insert
```go
o := orm.NewOrm()
var user User
user.Name = "slene"
user.IsActive = true
fmt.Println(o.Insert(&user))
fmt.Println(user.Id)
```
创建后会自动对 auto 的 field 赋值
### Update
```go
o := orm.NewOrm()
user := User{Id: 1}
if o.Read(&user) == nil {
user.Name = "MyName"
o.Update(&user)
}
```
### Delete
```go
o := orm.NewOrm()
o.Delete(&User{Id: 1})
```
Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 User 的外键。删除 User 的时候。如果 on_delete 设置为默认的级联操作,将删除对应的 Post
删除以后会清除 auto field 的值

View File

@ -1,316 +0,0 @@
## Orm 使用方法
beego/orm 的使用例子
后文例子如无特殊说明都以这个为基础。
##### models.go:
```go
package main
import (
"github.com/astaxie/beego/orm"
)
type User struct {
Id int
Name string
Profile *Profile `orm:"rel(one)"` // OneToOne relation
}
type Profile struct {
Id int
Age int16
User *User `orm:"reverse(one)"` // 设置反向关系(可选)
}
func init() {
// 需要在init中注册定义的model
orm.RegisterModel(new(User), new(Profile))
}
```
##### main.go
```go
package main
import (
"fmt"
"github.com/astaxie/beego/orm"
_ "github.com/go-sql-driver/mysql"
)
func init() {
orm.RegisterDriver("mysql", orm.DR_MySQL)
orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8", 30)
}
func main() {
o := orm.NewOrm()
o.Using("default") // 默认使用 default你可以指定为其他数据库
profile := new(Profile)
profile.Age = 30
user := new(User)
user.Profile = profile
user.Name = "slene"
fmt.Println(o.Insert(profile))
fmt.Println(o.Insert(user))
}
```
## 数据库的设置
目前 orm 支持三种数据库,以下为测试过的 driver
将你需要使用的 driver 加入 import 中
```go
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
)
```
#### RegisterDriver
三种默认数据库类型
```go
orm.DR_MySQL
orm.DR_Sqlite
orm.DR_Postgres
```
```go
// 参数1 driverName
// 参数2 数据库类型
// 这个用来设置 driverName 对应的数据库类型
// mysql / sqlite3 / postgres 这三种是默认已经注册过的,所以可以无需设置
orm.RegisterDriver("mymysql", orm.DR_MySQL)
```
#### RegisterDataBase
orm 必须注册一个别名为 `default` 的数据库,作为默认使用。
```go
// 参数1 数据库的别名用来在orm中切换数据库使用
// 参数2 driverName
// 参数3 对应的链接字符串
// 参数4 设置最大的空闲连接数,使用 golang 自己的连接池
orm.RegisterDataBase("default", "mysql", "root:root@/orm_test?charset=utf8", 30)
```
#### 时区设置
orm 默认使用 time.Local 本地时区
* 作用于 orm 自动创建的时间
* 从数据库中取回的时间转换成 orm 本地时间
如果需要的话,你也可以进行更改
```go
// 设置为 UTC 时间
orm.DefaultTimeLoc = time.UTC
```
orm 在进行 RegisterDataBase 的同时,会获取数据库使用的时区,然后在 time.Time 类型存取的时做相应转换,以匹配时间系统,从而保证时间不会出错。
**注意:** 鉴于 Sqlite3 的设计,存取默认都为 UTC 时间
## 注册模型
如果使用 orm.QuerySeter 进行高级查询的话,这个是必须的。
反之,如果只使用 Raw 查询和 map struct是无需这一步的。您可以去查看 [Raw SQL 查询](Raw.md)
#### RegisterModel
将你定义的 Model 进行注册,最佳设计是有单独的 models.go 文件,在他的 init 函数中进行注册。
迷你版 models.go
```go
package main
import "github.com/astaxie/beego/orm"
type User struct {
Id int
Name string
}
func init(){
orm.RegisterModel(new(User))
}
```
RegisterModel 也可以同时注册多个 model
```go
orm.RegisterModel(new(User), new(Profile), new(Post))
```
详细的 struct 定义请查看文档 [模型定义](Models.md)
#### RegisterModelWithPrefix
使用表名前缀
```go
orm.RegisterModelWithPrefix("prefix_", new(User))
```
创建后的表名为 prefix_user
## ORM 接口使用
使用 orm 必然接触的 Ormer 接口,我们来熟悉一下
```go
var o Ormer
o = orm.NewOrm() // 创建一个 Ormer
// NewOrm 的同时会执行 orm.BootStrap (整个 app 只执行一次),用以验证模型之间的定义并缓存。
```
* type Ormer interface {
* [Read(Modeler) error](Object.md#read)
* [Insert(Modeler) (int64, error)](Object.md#insert)
* [Update(Modeler) (int64, error)](Object.md#update)
* [Delete(Modeler) (int64, error)](Object.md#delete)
* [M2mAdd(Modeler, string, ...interface{}) (int64, error)](Object.md#m2madd)
* [M2mDel(Modeler, string, ...interface{}) (int64, error)](Object.md#m2mdel)
* [LoadRel(Modeler, string) (int64, error)](Object.md#loadRel)
* [QueryTable(interface{}) QuerySeter](#querytable)
* [Using(string) error](#using)
* [Begin() error](Transaction.md)
* [Commit() error](Transaction.md)
* [Rollback() error](Transaction.md)
* [Raw(string, ...interface{}) RawSeter](#raw)
* [Driver() Driver](#driver)
* }
#### QueryTable
传入表名,或者 Modeler 对象,返回一个 [QuerySeter](Query.md#queryseter)
```go
o := orm.NewOrm()
var qs QuerySeter
qs = o.QueryTable("user")
// 如果表没有定义过,会立刻 panic
```
#### Using
切换为其他数据库
```go
orm.RegisterDataBase("db1", "mysql", "root:root@/orm_db2?charset=utf8", 30)
orm.RegisterDataBase("db2", "sqlite3", "data.db", 30)
o1 := orm.NewOrm()
o1.Using("db1")
o2 := orm.NewOrm()
o2.Using("db2")
// 切换为其他数据库以后
// 这个 Ormer 对象的其下的 api 调用都将使用这个数据库
```
默认使用 `default` 数据库,无需调用 Using
#### Raw
使用 sql 语句直接进行操作
Raw 函数,返回一个 [RawSeter](Raw.md) 用以对设置的 sql 语句和参数进行操作
```go
o := NewOrm()
var r RawSeter
r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene")
```
#### Driver
返回当前 orm 使用的 db 信息
```go
type Driver interface {
Name() string
Type() DriverType
}
```
```go
orm.RegisterDataBase("db1", "mysql", "root:root@/orm_db2?charset=utf8", 30)
orm.RegisterDataBase("db2", "sqlite3", "data.db", 30)
o1 := orm.NewOrm()
o1.Using("db1")
dr := o1.Driver()
fmt.Println(dr.Name() == "db1") // true
fmt.Println(dr.Type() == orm.DR_MySQL) // true
o2 := orm.NewOrm()
o2.Using("db2")
dr = o2.Driver()
fmt.Println(dr.Name() == "db2") // true
fmt.Println(dr.Type() == orm.DR_Sqlite) // true
```
## 调试模式打印查询语句
简单的设置 Debug 为 true 打印查询的语句
可能存在性能问题,不建议使用在产品模式
```go
func main() {
orm.Debug = true
...
```
默认使用 os.Stderr 输出日志信息
改变输出到你自己的 io.Writer
```go
var w io.Writer
...
// 设置为你的 io.Writer
...
orm.DebugLog = orm.NewLog(w)
```
日志格式
```go
[ORM] - 时间 - [Queries/数据库名] - [执行操作/执行时间] - [SQL语句] - 使用标点 `,` 分隔的参数列表 - 打印遇到的错误
```
```go
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Exec / 0.4ms] - [INSERT INTO `user` (`name`) VALUES (?)] - `slene`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Exec / 0.5ms] - [UPDATE `user` SET `name` = ? WHERE `id` = ?] - `astaxie`, `14`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [db.QueryRow / 0.4ms] - [SELECT `id`, `name` FROM `user` WHERE `id` = ?] - `14`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Exec / 0.4ms] - [INSERT INTO `post` (`user_id`,`title`,`content`) VALUES (?, ?, ?)] - `14`, `beego orm`, `powerful amazing`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Query / 0.4ms] - [SELECT T1.`name` `User__Name`, T0.`user_id` `User`, T1.`id` `User__Id` FROM `post` T0 INNER JOIN `user` T1 ON T1.`id` = T0.`user_id` WHERE T0.`id` = ? LIMIT 1000] - `68`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Exec / 0.4ms] - [DELETE FROM `user` WHERE `id` = ?] - `14`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Query / 0.3ms] - [SELECT T0.`id` FROM `post` T0 WHERE T0.`user_id` IN (?) ] - `14`
[ORM] - 2013-08-09 13:18:16 - [Queries/default] - [ db.Exec / 0.4ms] - [DELETE FROM `post` WHERE `id` IN (?)] - `68`
```
日志内容包括 **所有的数据库操作**事务Prepare

View File

@ -1,411 +0,0 @@
## 高级查询
orm 以 **QuerySeter** 来组织查询,每个返回 **QuerySeter** 的方法都会获得一个新的 **QuerySeter** 对象。
基本使用方法:
```go
o := orm.NewOrm()
// 获取 QuerySeter 对象user 为表名
qs := o.QueryTable("user")
// 也可以直接使用对象作为表名
user := new(User)
qs = o.QueryTable(user) // 返回 QuerySeter
```
## expr
QuerySeter 中用于描述字段和 sql 操作符,使用简单的 expr 查询方法
字段组合的前后顺序依照表的关系,比如 User 表拥有 Profile 的外键,那么对 User 表查询对应的 Profile.Age 为条件,则使用 `Profile__Age` 注意,字段的分隔符号使用双下划线 `__`,除了描述字段, expr 的尾部可以增加操作符以执行对应的 sql 操作。比如 `Profile__Age__gt` 代表 Profile.Age > 18 的条件查询。
注释后面将描述对应的 sql 语句,仅仅是描述 expr 的类似结果,并不代表实际生成的语句。
```go
qs.Filter("id", 1) // WHERE id = 1
qs.Filter("profile__age", 18) // WHERE profile.age = 18
qs.Filter("Profile__Age", 18) // 使用字段名和Field名都是允许的
qs.Filter("profile__age", 18) // WHERE profile.age = 18
qs.Filter("profile__age__gt", 18) // WHERE profile.age > 18
qs.Filter("profile__age__gte", 18) // WHERE profile.age >= 18
qs.Filter("profile__age__in", 18, 20) // WHERE profile.age IN (18, 20)
qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000
```
## Operators
当前支持的操作符号:
* [exact](#exact) / [iexact](#iexact) 等于
* [contains](#contains) / [icontains](#icontains) 包含
* [gt / gte](#gt / gte) 大于 / 大于等于
* [lt / lte](#lt / lte) 小于 / 小于等于
* [startswith](#startswith) / [istartswith](#istartswith) 以...起始
* [endswith](#endswith) / [iendswith](#iendswith) 以...结束
* [in](#in)
* [isnull](#isnull)
后面以 `i` 开头的表示:大小写不敏感
#### exact
Filter / Exclude / Condition expr 的默认值
```go
qs.Filter("name", "slene") // WHERE name = 'slene'
qs.Filter("name__exact", "slene") // WHERE name = 'slene'
// 使用 = 匹配,大小写是否敏感取决于数据表使用的 collation
qs.Filter("profile", nil) // WHERE profile_id IS NULL
```
#### iexact
```go
qs.Filter("name__iexact", "slene")
// WHERE name LIKE 'slene'
// 大小写不敏感,匹配任意 'Slene' 'sLENE'
```
#### contains
```go
qs.Filter("name__contains", "slene")
// WHERE name LIKE BINARY '%slene%'
// 大小写敏感, 匹配包含 slene 的字符
```
#### icontains
```go
qs.Filter("name__icontains", "slene")
// WHERE name LIKE '%slene%'
// 大小写不敏感, 匹配任意 'im Slene', 'im sLENE'
```
#### in
```go
qs.Filter("profile__age__in", 17, 18, 19, 20)
// WHERE profile.age IN (17, 18, 19, 20)
```
#### gt / gte
```go
qs.Filter("profile__age__gt", 17)
// WHERE profile.age > 17
qs.Filter("profile__age__gte", 18)
// WHERE profile.age >= 18
```
#### lt / lte
```go
qs.Filter("profile__age__lt", 17)
// WHERE profile.age < 17
qs.Filter("profile__age__lte", 18)
// WHERE profile.age <= 18
```
#### startswith
```go
qs.Filter("name__startswith", "slene")
// WHERE name LIKE BINARY 'slene%'
// 大小写敏感, 匹配以 'slene' 起始的字符串
```
#### istartswith
```go
qs.Filter("name__istartswith", "slene")
// WHERE name LIKE 'slene%'
// 大小写不敏感, 匹配任意以 'slene', 'Slene' 起始的字符串
```
#### endswith
```go
qs.Filter("name__endswith", "slene")
// WHERE name LIKE BINARY '%slene'
// 大小写敏感, 匹配以 'slene' 结束的字符串
```
#### iendswith
```go
qs.Filter("name__startswith", "slene")
// WHERE name LIKE '%slene'
// 大小写不敏感, 匹配任意以 'slene', 'Slene' 结束的字符串
```
#### isnull
```go
qs.Filter("profile__isnull", true)
qs.Filter("profile_id__isnull", true)
// WHERE profile_id IS NULL
qs.Filter("profile__isnull", false)
// WHERE profile_id IS NOT NULL
```
## 高级查询接口使用
QuerySeter 是高级查询使用的接口,我们来熟悉下他的接口方法
* type QuerySeter interface {
* [Filter(string, ...interface{}) QuerySeter](#filter)
* [Exclude(string, ...interface{}) QuerySeter](#exclude)
* [SetCond(*Condition) QuerySeter](#setcond)
* [Limit(int, ...int64) QuerySeter](#limit)
* [Offset(int64) QuerySeter](#offset)
* [OrderBy(...string) QuerySeter](#orderby)
* [RelatedSel(...interface{}) QuerySeter](#relatedsel)
* [Count() (int64, error)](#count)
* [Update(Params) (int64, error)](#update)
* [Delete() (int64, error)](#delete)
* [PrepareInsert() (Inserter, error)](#prepareinsert)
* [All(interface{}) (int64, error)](#all)
* [One(Modeler) error](#one)
* [Values(*[]Params, ...string) (int64, error)](#values)
* [ValuesList(*[]ParamsList, ...string) (int64, error)](#valueslist)
* [ValuesFlat(*ParamsList, string) (int64, error)](#valuesflat)
* }
* 每个返回 QuerySeter 的 api 调用时都会新建一个 QuerySeter不影响之前创建的。
* 高级查询使用 Filter 和 Exclude 来做常用的条件查询。囊括两种清晰的过滤规则:包含, 排除
#### Filter
用来过滤查询结果,起到 **包含条件** 的作用
多个 Filter 之间使用 `AND` 连接
```go
qs.Filter("profile__isnull", true).Filter("name", "slene")
// WHERE profile_id IS NULL AND name = 'slene'
```
#### Exclude
用来过滤查询结果,起到 **排除条件** 的作用
使用 `NOT` 排除条件
多个 Exclude 之间使用 `AND` 连接
```go
qs.Exclude("profile__isnull", true).Filter("name", "slene")
// WHERE NOT profile_id IS NULL AND name = 'slene'
```
#### SetCond
自定义条件表达式
```go
cond := NewCondition()
cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000)
qs := orm.QueryTable("user")
qs = qs.SetCond(cond1)
// WHERE ... AND ... AND NOT ... OR ...
cond2 := cond.AndCond(cond1).OrCond(cond.And("name", "slene"))
qs = qs.SetCond(cond2).Count()
// WHERE (... AND ... AND NOT ... OR ...) OR ( ... )
```
#### Limit
限制最大返回数据行数,第二个参数可以设置 `Offset`
```go
var DefaultRowsLimit = 1000 // orm 默认的 limit 值为 1000
// 默认情况下 select 查询的最大行数为 1000
// LIMIT 1000
qs.Limit(10)
// LIMIT 10
qs.Limit(10, 20)
// LIMIT 10 OFFSET 20
qs.Limit(-1)
// no limit
qs.Limit(-1, 100)
// LIMIT 18446744073709551615 OFFSET 100
// 18446744073709551615 是 1<<64 - 1 用来指定无 limit 限制 但有 offset 偏移的情况
```
#### Offset
设置 偏移行数
```go
qs.Offset(20)
// LIMIT 1000 OFFSET 20
```
#### OrderBy
参数使用 **expr**
在 expr 前使用减号 `-` 表示 `DESC` 的排列
```go
qs.OrderBy("id", "-profile__age")
// ORDER BY id ASC, profile.age DESC
qs.OrderBy("-profile__age", "profile")
// ORDER BY profile.age DESC, profile_id ASC
```
#### RelatedSel
关系查询,参数使用 **expr**
```go
var DefaultRelsDepth = 5 // 默认情况下直接调用 RelatedSel 将进行最大 5 层的关系查询
qs := o.QueryTable("post")
qs.RelateSel()
// INNER JOIN user ... LEFT OUTER JOIN profile ...
qs.RelateSel("user")
// INNER JOIN user ...
// 设置 expr 只对设置的字段进行关系查询
// 对设置 null 属性的 Field 将使用 LEFT OUTER JOIN
```
#### Count
依据当前的查询条件,返回结果行数
```go
cnt, err := o.QueryTable("user").Count() // SELECT COUNT(*) FROM USER
fmt.Printf("Count Num: %s, %s", cnt, err)
```
#### Update
依据当前查询条件,进行批量更新操作
```go
num, err := o.QueryTable("user").Filter("name", "slene").Update(orm.Params{
"name": "astaxie",
})
fmt.Printf("Affected Num: %s, %s", num, err)
// SET name = "astaixe" WHERE name = "slene"
```
#### Delete
依据当前查询条件,进行批量删除操作
```go
num, err := o.QueryTable("user").Filter("name", "slene").Delete()
fmt.Printf("Affected Num: %s, %s", num, err)
// DELETE FROM user WHERE name = "slene"
```
#### PrepareInsert
用于一次 prepare 多次 insert 插入,以提高批量插入的速度。
```go
var users []*User
...
qs := o.QueryTable("user")
i, _ := qs.PrepareInsert()
for _, user := range users {
id, err := i.Insert(user)
if err != nil {
...
}
}
// PREPARE INSERT INTO user (`name`, ...) VALUES (?, ...)
// EXECUTE INSERT INTO user (`name`, ...) VALUES ("slene", ...)
// EXECUTE ...
// ...
i.Close() // 别忘记关闭 statement
```
#### All
返回对应的结果集对象
```go
var users []*User
num, err := o.QueryTable("user").Filter("name", "slene").All(&users)
fmt.Printf("Returned Rows Num: %s, %s", num, err)
```
#### One
尝试返回单条记录
```go
var user *User
err := o.QueryTable("user").Filter("name", "slene").One(&user)
if err == orm.ErrMultiRows {
// 多条的时候报错
fmt.Printf("Returned Multi Rows Not One")
}
if err == orm.ErrNoRows {
// 没有找到记录
fmt.Printf("Not row found")
}
```
#### Values
返回结果集的 key => value 值
key 为 Model 里的 Field namevalue 的值 以 string 保存
```go
var maps []orm.Params
num, err := o.QueryTable("user").Values(&maps)
if err != nil {
fmt.Printf("Result Nums: %d\n", num)
for _, m := range maps {
fmt.Println(m["Id"], m["Name"])
}
}
```
返回指定的 Field 数据
**TODO**: 暂不支持级联查询 **RelatedSel** 直接返回 Values
但可以直接指定 expr 级联返回需要的数据
```go
var maps []orm.Params
num, err := o.QueryTable("user").Values(&maps, "id", "name", "profile", "profile__age")
if err != nil {
fmt.Printf("Result Nums: %d\n", num)
for _, m := range maps {
fmt.Println(m["Id"], m["Name"], m["Profile"], m["Profile__Age"])
// map 中的数据都是展开的,没有复杂的嵌套
}
}
```
#### ValuesList
顾名思义返回的结果集以slice存储
结果的排列与 Model 中定义的 Field 顺序一致
返回的每个元素值以 string 保存
```go
var lists []orm.ParamsList
num, err := o.QueryTable("user").ValuesList(&lists)
if err != nil {
fmt.Printf("Result Nums: %d\n", num)
for _, row := range lists {
fmt.Println(row)
}
}
```
当然也可以指定 expr 返回指定的 Field
```go
var lists []orm.ParamsList
num, err := o.QueryTable("user").ValuesList(&lists, "name", "profile__age")
if err != nil {
fmt.Printf("Result Nums: %d\n", num)
for _, row := range lists {
fmt.Printf("Name: %s, Age: %s\m", row[0], row[1])
}
}
```
#### ValuesFlat
只返回特定的 Field 值,讲结果集展开到单个 slice 里
```go
var list orm.ParamsList
num, err := o.QueryTable("user").ValuesFlat(&list, "name")
if err != nil {
fmt.Printf("Result Nums: %d\n", num)
fmt.Printf("All User Names: %s", strings.Join(list, ", ")
}
```

View File

@ -1,40 +0,0 @@
最新文档请查看 beedoc
* [中文](http://beego.me/docs/Models_Overview?lang=zh)
* [English](http://beego.me/docs/Models_Overview?lang=en)
## 文档目录
1. [Orm 使用方法](Orm.md)
- [数据库的设置](Orm.md#数据库的设置)
* [驱动类型设置](Orm.md#registerdriver)
* [参数设置](Orm.md#registerdatabase)
* [时区设置](Orm.md#时区设置)
- [注册模型](Orm.md#注册模型)
- [ORM 接口使用](Orm.md#orm-接口使用)
- [调试模式打印查询语句](Orm.md#调试模式打印查询语句)
2. [对象的CRUD操作](Object.md)
3. [高级查询](Query.md)
- [使用的表达式语法](Query.md#expr)
- [支持的操作符号](Query.md#operators)
- [高级查询接口使用](Query.md#高级查询接口使用)
4. [使用SQL语句进行查询](Raw.md)
5. [事务处理](Transaction.md)
6. [模型定义](Models.md)
- [Struct Tag 设置参数](Models.md#struct-tag-设置参数)
- [表关系设置](Models.md#表关系设置)
- [模型字段与数据库类型的对应](Models.md#模型字段与数据库类型的对应)
7. [命令模式](Cmd.md)
- [自动建表](Cmd.md#自动建表)
- [打印建表SQL](Cmd.md#打印建表sql)
8. [Test ORM](Test.md)
9. Custom Fields
10. Faq
### 文档更新记录
* 2013-08-20: 这里不再更新,最新文档在 beedoc, [中文](http://beego.me/docs/Models_Overview?lang=zh) / [English](http://beego.me/docs/Models_Overview?lang=en)
* 2013-08-19: 增加[自动建表](Cmd.md#自动建表)功能
* 2013-08-13: ORM 的 [时区设置](Orm.md#时区设置)
* 2013-08-13: [模型字段与数据库类型的对应](Models.md#模型字段与数据库类型的对应) 推荐的数据库对应使用的类型

View File

@ -1,116 +0,0 @@
## 使用SQL语句进行查询
* 使用 Raw SQL 查询,无需使用 ORM 表定义
* 多数据库,都可直接使用占位符号 `?`,自动转换
* 查询时的参数,支持使用 Model Struct 和 Slice, Array
```go
ids := []int{1, 2, 3}
p.Raw("SELECT name FROM user WHERE id IN (?, ?, ?)", ids)
```
创建一个 **RawSeter**
```go
o := NewOrm()
var r RawSeter
r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene")
```
* type RawSeter interface {
* [Exec() (int64, error)](#exec)
* [QueryRow(...interface{}) error](#queryrow)
* [QueryRows(...interface{}) (int64, error)](#queryrows)
* [SetArgs(...interface{}) RawSeter](#setargs)
* [Values(*[]Params) (int64, error)](#values)
* [ValuesList(*[]ParamsList) (int64, error)](#valueslist)
* [ValuesFlat(*ParamsList) (int64, error)](#valuesflat)
* [Prepare() (RawPreparer, error)](#prepare)
* }
#### Exec
执行sql语句
```go
num, err := r.Exec()
```
#### QueryRow
TODO
#### QueryRows
TODO
#### SetArgs
改变 Raw(sql, args...) 中的 args 参数,返回一个新的 RawSeter
用于单条 sql 语句,重复利用,替换参数然后执行。
```go
num, err := r.SetArgs("arg1", "arg2").Exec()
num, err := r.SetArgs("arg1", "arg2").Exec()
...
```
#### Values / ValuesList / ValuesFlat
Raw SQL 查询获得的结果集 Value 为 `string` 类型NULL 字段的值为空 ``
#### Values
返回结果集的 key => value 值
```go
var maps []orm.Params
num, err = o.Raw("SELECT user_name FROM user WHERE status = ?", 1).Values(&maps)
if err == nil && num > 0 {
fmt.Println(maps[0]["user_name"]) // slene
}
```
#### ValuesList
返回结果集 slice
```go
var lists []orm.ParamsList
num, err = o.Raw("SELECT user_name FROM user WHERE status = ?", 1).ValuesList(&lists)
if err == nil && num > 0 {
fmt.Println(lists[0][0]) // slene
}
```
#### ValuesFlat
返回单一字段的平铺 slice 数据
```go
var list orm.ParamsList
num, err = o.Raw("SELECT id FROM user WHERE id < ?", 10).ValuesList(&list)
if err == nil && num > 0 {
fmt.Println(list) // []{"1","2","3",...}
}
```
#### Prepare
用于一次 prepare 多次 exec以提高批量执行的速度。
```go
p, err := o.Raw("UPDATE user SET name = ? WHERE name = ?").Prepare()
num, err := p.Exec("testing", "slene")
num, err = p.Exec("testing", "astaxie")
...
...
p.Close() // 别忘记关闭 statement
```
## FAQ
1. 我的 app 需要支持多类型数据库,如何在使用 Raw SQL 的时候判断当前使用的数据库类型。
使用 Ormer 的 [Driver方法](Orm.md#driver) 可以进行判断

View File

@ -1,34 +0,0 @@
## Test ORM
测试代码参见
```bash
models_test.go // 表定义
orm_test.go // 测试用例
```
#### MySQL
```bash
mysql -u root -e 'create database orm_test;'
export ORM_DRIVER=mysql
export ORM_SOURCE="root:@/orm_test?charset=utf8"
go test -v github.com/astaxie/beego/orm
```
#### Sqlite3
```bash
touch /path/to/orm_test.db
export ORM_DRIVER=sqlite3
export ORM_SOURCE=/path/to/orm_test.db
go test -v github.com/astaxie/beego/orm
```
#### PostgreSQL
```bash
psql -c 'create database orm_test;' -U postgres
export ORM_DRIVER=postgres
export ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
go test -v github.com/astaxie/beego/orm
```

View File

@ -1,17 +0,0 @@
## 事务处理
orm 可以简单的进行事务操作
```go
o := NewOrm()
err := o.Begin()
// 事务处理过程
...
...
// 此过程中的所有使用 o Ormer 对象的查询都在事务处理范围内
if SomeError {
err = o.Rollback()
} else {
err = o.Commit()
}
```

View File

@ -1,3 +1,17 @@
// 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 (
@ -41,6 +55,7 @@ var (
}
)
// model info collection
type _modelCache struct {
sync.RWMutex
orders []string
@ -49,6 +64,7 @@ type _modelCache struct {
done bool
}
// get all model info
func (mc *_modelCache) all() map[string]*modelInfo {
m := make(map[string]*modelInfo, len(mc.cache))
for k, v := range mc.cache {
@ -57,6 +73,7 @@ func (mc *_modelCache) all() map[string]*modelInfo {
return m
}
// get orderd model info
func (mc *_modelCache) allOrdered() []*modelInfo {
m := make([]*modelInfo, 0, len(mc.orders))
for _, table := range mc.orders {
@ -65,16 +82,19 @@ func (mc *_modelCache) allOrdered() []*modelInfo {
return m
}
// get model info by table name
func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) {
mi, ok = mc.cache[table]
return
}
// get model info by field name
func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
mi, ok = mc.cacheByFN[name]
return
}
// set model info to collection
func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
mii := mc.cache[table]
mc.cache[table] = mi
@ -85,9 +105,16 @@ func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
return mii
}
// clean all model info.
func (mc *_modelCache) clean() {
mc.orders = make([]string, 0)
mc.cache = make(map[string]*modelInfo)
mc.cacheByFN = make(map[string]*modelInfo)
mc.done = false
}
// Clean model cache. Then you can re-RegisterModel.
// Common use this api for test case.
func ResetModelCache() {
modelCache.clean()
}

View File

@ -1,3 +1,17 @@
// 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 (
@ -8,7 +22,9 @@ import (
"strings"
)
func registerModel(model interface{}, prefix string) {
// register models.
// prefix means table name prefix.
func registerModel(prefix string, model interface{}) {
val := reflect.ValueOf(model)
ind := reflect.Indirect(val)
typ := ind.Type()
@ -67,6 +83,7 @@ func registerModel(model interface{}, prefix string) {
modelCache.set(table, info)
}
// boostrap models
func bootStrap() {
if modelCache.done {
return
@ -281,27 +298,24 @@ end:
}
}
// register models
func RegisterModel(models ...interface{}) {
if modelCache.done {
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
}
for _, model := range models {
registerModel(model, "")
}
RegisterModelWithPrefix("", models...)
}
// register model with a prefix
// register models with a prefix
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
if modelCache.done {
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
}
for _, model := range models {
registerModel(model, prefix)
registerModel(prefix, model)
}
}
// bootrap models.
// make all model parsed and can not add more models
func BootStrap() {
if modelCache.done {
return

View File

@ -1,3 +1,17 @@
// 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 (

View File

@ -1,3 +1,17 @@
// 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 (
@ -9,6 +23,7 @@ import (
var errSkipField = errors.New("skip field")
// field info collection
type fields struct {
pk *fieldInfo
columns map[string]*fieldInfo
@ -23,6 +38,7 @@ type fields struct {
dbcols []string
}
// add field info
func (f *fields) Add(fi *fieldInfo) (added bool) {
if f.fields[fi.name] == nil && f.columns[fi.column] == nil {
f.columns[fi.column] = fi
@ -49,14 +65,17 @@ func (f *fields) Add(fi *fieldInfo) (added bool) {
return true
}
// get field info by name
func (f *fields) GetByName(name string) *fieldInfo {
return f.fields[name]
}
// get field info by column name
func (f *fields) GetByColumn(column string) *fieldInfo {
return f.columns[column]
}
// get field info by string, name is prior
func (f *fields) GetByAny(name string) (*fieldInfo, bool) {
if fi, ok := f.fields[name]; ok {
return fi, ok
@ -70,6 +89,7 @@ func (f *fields) GetByAny(name string) (*fieldInfo, bool) {
return nil, false
}
// create new field info collection
func newFields() *fields {
f := new(fields)
f.fields = make(map[string]*fieldInfo)
@ -79,6 +99,7 @@ func newFields() *fields {
return f
}
// single field info
type fieldInfo struct {
mi *modelInfo
fieldIndex int
@ -115,6 +136,7 @@ type fieldInfo struct {
onDelete string
}
// new field info
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) {
var (
tag string

View File

@ -1,3 +1,17 @@
// 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 (
@ -7,6 +21,7 @@ import (
"reflect"
)
// single model info
type modelInfo struct {
pkg string
name string
@ -20,6 +35,7 @@ type modelInfo struct {
isThrough bool
}
// new model info
func newModelInfo(val reflect.Value) (info *modelInfo) {
var (
err error
@ -41,6 +57,9 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
for i := 0; i < ind.NumField(); i++ {
field := ind.Field(i)
sf = ind.Type().Field(i)
if sf.PkgPath != "" {
continue
}
fi, err = newFieldInfo(info, field, sf)
if err != nil {
@ -79,6 +98,8 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
return
}
// combine related model info to new model info.
// prepare for relation models query.
func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
info = new(modelInfo)
info.fields = newFields()

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