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

184 Commits

Author SHA1 Message Date
1aeb3d9051 release 1.6.1 2016-03-11 11:35:24 +08:00
778a5a11ac Merge pull request #1781 from JessonChan/develop
duplicate adapter logger  bug fixed
2016-03-11 11:19:07 +08:00
4801099675 duplicate adapter logger bug fixed 2016-03-11 10:12:17 +08:00
420cd507b2 update output information 2016-03-11 10:07:44 +08:00
22196d7841 add mis function NSHandler 2016-03-11 09:21:13 +08:00
571f9b4b65 Merge pull request #1780 from goodloop/develop
fix static pattern match for leaf
2016-03-11 09:07:53 +08:00
1f0a65f0a2 fix the orm test 2016-03-10 22:21:21 +08:00
90e7d252a7 fix static pattern match for leaf 2016-03-10 22:16:41 +08:00
31f7524dae fix the golint travis 2016-03-10 21:47:50 +08:00
3a12e238cc support oracle 2016-03-10 21:23:13 +08:00
f45b271b96 Merge pull request #1723 from miraclesu/feature/orm_inline_struct
orm: inline struct support
2016-03-10 14:31:44 +08:00
589616b303 Merge pull request #1768 from aolu11/master
fix json extra newline
2016-03-09 21:11:20 +08:00
65b13eddad Merge pull request #1719 from JessonChan/err_ctrler
multiple response.WriteHeader calls
2016-03-09 19:18:09 +08:00
686d2e834e Merge pull request #1765 from saturn4er/fix_layout_rebuild_in_dev
Add layout rebuilding on each request in dev mode
2016-03-09 19:02:46 +08:00
adbae18e8c Fix formatting with gofmt 2016-03-09 10:47:09 +02:00
f21cff0166 some typo fixed 2016-03-09 16:00:52 +08:00
3dd9020249 Merge remote-tracking branch 'remotes/upstream/develop' into err_ctrler 2016-03-09 15:59:13 +08:00
9a2696d216 accept asta's idea see the talk
https://github.com/astaxie/beego/pull/1719
2016-03-09 15:56:18 +08:00
64e0858d44 orm: add inline struct test case 2016-03-08 22:24:38 +08:00
d86ab2ed31 Merge pull request #1721 from JessonChan/log_enhancement
Log enhancement
2016-03-08 21:35:31 +08:00
b2f071395b rename files to mulitfile 2016-03-08 18:44:39 +08:00
54b5120a64 rename files to mulitfile 2016-03-08 18:43:09 +08:00
5e2384e95a fix json extra newline 2016-03-08 17:04:14 +08:00
adb41eb299 Merge pull request #1761 from lcbluestorm/develop
add ssdb cache adapter
2016-03-08 15:47:45 +08:00
bd04be4470 move time to the top 2016-03-08 14:44:37 +08:00
ef59a0ed63 fix travis.yml 2016-03-08 14:03:33 +08:00
14be252d2a fix travis.yml 2016-03-08 14:01:33 +08:00
5698b5dc92 Merge pull request #1709 from mlgd/develop
Fix cookies in accordance with the "net / http" and Flash usage
2016-03-08 13:53:43 +08:00
0a0fc351e7 fix ssdb_test 2016-03-08 12:59:19 +08:00
662bea352f modify travis 2016-03-08 12:45:54 +08:00
2f18b9103b Merge pull request #1679 from ysqi/emptybodyfix
fix #1669 and return IO error
2016-03-08 09:48:17 +08:00
2c5ef8ccc8 Add layout rebuilding on each request in dev mode 2016-03-07 23:24:52 +02:00
1ddb1ce2fe add ssdb travis 2016-03-07 15:50:13 +08:00
9ddc2f5474 fix panic err 2016-03-07 15:00:03 +08:00
e29f4b57a3 fix travis.yml 2016-03-07 14:53:36 +08:00
0caadb9b66 rename vars 2016-03-07 14:45:45 +08:00
22e3900403 add .travis.yml 2016-03-07 14:34:40 +08:00
b39830dff3 add .travis.yml 2016-03-07 10:31:56 +08:00
be23c42674 Merge branches 'master' and 'develop' of lcbluestorm.github.com:lcbluestorm/beego into develop 2016-03-07 10:19:55 +08:00
90344a7b8f fix conflicts 2016-03-06 21:25:43 +08:00
920862884d Merge branch 'astaxie/develop' into emptybodyfix
# Conflicts:
#	config.go
2016-03-06 21:19:04 +08:00
48ec7f736e fix GetMulti bug 2016-03-06 14:46:13 +08:00
292d8f2c00 add ssdb cache adapter 2016-03-06 13:17:16 +08:00
f6f34306ee Merge pull request #1740 from ysqi/configer
Fixed #1735 Return nil if config value does not exist or is empty
2016-03-05 22:05:43 +08:00
a40c0dd156 Merge pull request #1750 from JessonChan/staticfile_map_race
static file map race bug fixed
2016-03-05 20:43:00 +08:00
795092bdd2 Merge pull request #1751 from FlamingTree/develop
Update phone regexp
2016-03-05 20:41:52 +08:00
524446c857 Merge pull request #1752 from JessonChan/ab_lock_race
fix template  read-write lock race
2016-03-05 20:40:14 +08:00
f5adec31c6 improve the template reader function 2016-03-04 14:49:16 +08:00
6747c55a81 remove unused cache 2016-03-04 12:01:04 +08:00
1f46c1d231 add template read lock when dev mode 2016-03-04 12:00:43 +08:00
8bd1be8e29 Update validators.go
增加178号段
2016-03-04 11:16:47 +08:00
226e54e0d8 static file map race bug fixed 2016-03-04 10:54:54 +08:00
3379a2b7ed remove file bug fixed
remove file by filename and file suffix
2016-03-04 10:43:57 +08:00
19d921d3f5 Return nil not empty []string{}
Return nil if config value does not exist or is empty
2016-03-03 20:03:23 +08:00
4b99e41880 Merge pull request #1688 from ysqi/configIssue
fixed handle config issue
2016-03-03 09:50:14 +08:00
8ff74e71cb Fixed #1735 Return empty []string
Need return empty []string  if config value is empty.

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

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

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

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

View File

@ -1,8 +1,10 @@
language: go language: go
go: go:
- 1.5.1 - tip
- 1.6.0
- 1.5.3
- 1.4.3
services: services:
- redis-server - redis-server
- mysql - mysql
@ -12,6 +14,11 @@ env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db - ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8" - ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable" - ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
before_install:
- git clone git://github.com/ideawu/ssdb.git
- cd ssdb
- make
- cd ..
install: install:
- go get github.com/lib/pq - go get github.com/lib/pq
- go get github.com/go-sql-driver/mysql - go get github.com/go-sql-driver/mysql
@ -19,12 +26,26 @@ install:
- go get github.com/bradfitz/gomemcache/memcache - go get github.com/bradfitz/gomemcache/memcache
- go get github.com/garyburd/redigo/redis - go get github.com/garyburd/redigo/redis
- go get github.com/beego/x2j - go get github.com/beego/x2j
- go get github.com/couchbase/go-couchbase
- go get github.com/beego/goyaml2 - go get github.com/beego/goyaml2
- go get github.com/belogik/goes - go get github.com/belogik/goes
- go get github.com/couchbase/go-couchbase
- go get github.com/siddontang/ledisdb/config - go get github.com/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis - go get github.com/siddontang/ledisdb/ledis
- go get golang.org/x/tools/cmd/vet
- go get github.com/golang/lint/golint
- go get github.com/ssdb/gossdb/ssdb
before_script: before_script:
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
- mkdir -p res/var
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
after_script:
-killall -w ssdb-server
- rm -rf ./res/var/*
script:
- go vet -x ./...
- $HOME/gopath/bin/golint ./...
- go test -v ./...
notifications:
webhooks: https://hooks.pubu.im/services/z7m9bvybl3rgtg9

View File

@ -53,6 +53,7 @@ Please see [Documentation](http://beego.me/docs) for more.
## Community ## Community
* [http://beego.me/community](http://beego.me/community) * [http://beego.me/community](http://beego.me/community)
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
## LICENSE ## LICENSE

View File

@ -90,8 +90,8 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
switch command { switch command {
case "conf": case "conf":
m := make(map[string]interface{}) m := make(map[string]interface{})
m["AppConfigPath"] = AppConfigPath m["AppConfigPath"] = appConfigPath
m["AppConfigProvider"] = AppConfigProvider m["AppConfigProvider"] = appConfigProvider
m["BConfig.AppName"] = BConfig.AppName m["BConfig.AppName"] = BConfig.AppName
m["BConfig.RunMode"] = BConfig.RunMode m["BConfig.RunMode"] = BConfig.RunMode
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive

View File

@ -15,7 +15,6 @@
package beego package beego
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -24,7 +23,7 @@ import (
const ( const (
// VERSION represent beego web framework version. // VERSION represent beego web framework version.
VERSION = "1.6.0" VERSION = "1.6.1"
// DEV is for develop // DEV is for develop
DEV = "dev" DEV = "dev"
@ -68,21 +67,6 @@ func Run(params ...string) {
} }
func initBeforeHTTPRun() { func initBeforeHTTPRun() {
// if AppConfigPath is setted or conf/app.conf exist
err := ParseConfig()
if err != nil {
panic(err)
}
//init log
for adaptor, config := range BConfig.Log.Outputs {
err = BeeLogger.SetLogger(adaptor, config)
if err != nil {
fmt.Printf("%s with the config `%s` got err:%s\n", adaptor, config, err)
}
}
SetLogFuncCall(BConfig.Log.FileLineNum)
//init hooks //init hooks
AddAPPStartHook(registerMime) AddAPPStartHook(registerMime)
AddAPPStartHook(registerDefaultErrorHandler) AddAPPStartHook(registerDefaultErrorHandler)
@ -101,7 +85,7 @@ func initBeforeHTTPRun() {
// TestBeegoInit is for test package init // TestBeegoInit is for test package init
func TestBeegoInit(ap string) { func TestBeegoInit(ap string) {
os.Setenv("BEEGO_RUNMODE", "test") os.Setenv("BEEGO_RUNMODE", "test")
AppConfigPath = filepath.Join(ap, "conf", "app.conf") appConfigPath = filepath.Join(ap, "conf", "app.conf")
os.Chdir(ap) os.Chdir(ap)
initBeforeHTTPRun() initBeforeHTTPRun()
} }

View File

@ -17,10 +17,11 @@ package memcache
import ( import (
_ "github.com/bradfitz/gomemcache/memcache" _ "github.com/bradfitz/gomemcache/memcache"
"github.com/astaxie/beego/cache"
"strconv" "strconv"
"testing" "testing"
"time" "time"
"github.com/astaxie/beego/cache"
) )
func TestMemcacheCache(t *testing.T) { func TestMemcacheCache(t *testing.T) {
@ -36,7 +37,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("check err") t.Error("check err")
} }
time.Sleep(10 * time.Second) time.Sleep(11 * time.Second)
if bm.IsExist("astaxie") { if bm.IsExist("astaxie") {
t.Error("check err") t.Error("check err")
@ -93,10 +94,10 @@ func TestMemcacheCache(t *testing.T) {
if len(vv) != 2 { if len(vv) != 2 {
t.Error("GetMulti ERROR") t.Error("GetMulti ERROR")
} }
if vv[0].(string) != "author" { if vv[0].(string) != "author" && vv[0].(string) != "author1" {
t.Error("GetMulti ERROR") t.Error("GetMulti ERROR")
} }
if vv[1].(string) != "author1" { if vv[1].(string) != "author1" && vv[1].(string) != "author" {
t.Error("GetMulti ERROR") t.Error("GetMulti ERROR")
} }

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

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

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

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

124
config.go
View File

@ -15,7 +15,7 @@
package beego package beego
import ( import (
"html/template" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -103,17 +103,22 @@ var (
BConfig *Config BConfig *Config
// AppConfig is the instance of Config, store the config information from file // AppConfig is the instance of Config, store the config information from file
AppConfig *beegoAppConfig AppConfig *beegoAppConfig
// AppConfigPath is the path to the config files // AppPath is the absolute path to the app
AppConfigPath string AppPath string
// AppConfigProvider is the provider for the config, default is ini
AppConfigProvider = "ini"
// TemplateCache stores template caching
TemplateCache map[string]*template.Template
// GlobalSessions is the instance for the session manager // GlobalSessions is the instance for the session manager
GlobalSessions *session.Manager GlobalSessions *session.Manager
// appConfigPath is the path to the config files
appConfigPath string
// appConfigProvider is the provider for the config, default is ini
appConfigProvider = "ini"
) )
func init() { func init() {
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
os.Chdir(AppPath)
BConfig = &Config{ BConfig = &Config{
AppName: "beego", AppName: "beego",
RunMode: DEV, RunMode: DEV,
@ -162,7 +167,7 @@ func init() {
SessionName: "beegosessionID", SessionName: "beegosessionID",
SessionGCMaxLifetime: 3600, SessionGCMaxLifetime: 3600,
SessionProviderConfig: "", SessionProviderConfig: "",
SessionCookieLifeTime: 0, //set cookie default is the brower life SessionCookieLifeTime: 0, //set cookie default is the browser life
SessionAutoSetCookie: true, SessionAutoSetCookie: true,
SessionDomain: "", SessionDomain: "",
}, },
@ -173,29 +178,29 @@ func init() {
Outputs: map[string]string{"console": ""}, Outputs: map[string]string{"console": ""},
}, },
} }
ParseConfig()
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if !utils.FileExists(appConfigPath) {
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
return
}
if err := parseConfig(appConfigPath); err != nil {
panic(err)
}
} }
// ParseConfig parsed default config file.
// now only support ini, next will support json. // now only support ini, next will support json.
func ParseConfig() (err error) { func parseConfig(appConfigPath string) (err error) {
if AppConfigPath == "" { AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
if utils.FileExists(filepath.Join("conf", "app.conf")) {
AppConfigPath = filepath.Join("conf", "app.conf")
} else {
AppConfig = &beegoAppConfig{config.NewFakeConfig()}
return
}
}
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
if err != nil { if err != nil {
return err return err
} }
// set the runmode first // set the run mode first
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" { if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
BConfig.RunMode = envRunMode BConfig.RunMode = envRunMode
} else if runmode := AppConfig.String("RunMode"); runmode != "" { } else if runMode := AppConfig.String("RunMode"); runMode != "" {
BConfig.RunMode = runmode BConfig.RunMode = runMode
} }
BConfig.AppName = AppConfig.DefaultString("AppName", BConfig.AppName) BConfig.AppName = AppConfig.DefaultString("AppName", BConfig.AppName)
@ -241,6 +246,8 @@ func ParseConfig() (err error) {
BConfig.WebConfig.Session.SessionCookieLifeTime = AppConfig.DefaultInt("SessionCookieLifeTime", BConfig.WebConfig.Session.SessionCookieLifeTime) BConfig.WebConfig.Session.SessionCookieLifeTime = AppConfig.DefaultInt("SessionCookieLifeTime", BConfig.WebConfig.Session.SessionCookieLifeTime)
BConfig.WebConfig.Session.SessionAutoSetCookie = AppConfig.DefaultBool("SessionAutoSetCookie", BConfig.WebConfig.Session.SessionAutoSetCookie) BConfig.WebConfig.Session.SessionAutoSetCookie = AppConfig.DefaultBool("SessionAutoSetCookie", BConfig.WebConfig.Session.SessionAutoSetCookie)
BConfig.WebConfig.Session.SessionDomain = AppConfig.DefaultString("SessionDomain", BConfig.WebConfig.Session.SessionDomain) BConfig.WebConfig.Session.SessionDomain = AppConfig.DefaultString("SessionDomain", BConfig.WebConfig.Session.SessionDomain)
BConfig.Log.AccessLogs = AppConfig.DefaultBool("LogAccessLogs", BConfig.Log.AccessLogs)
BConfig.Log.FileLineNum = AppConfig.DefaultBool("LogFileLineNum", BConfig.Log.FileLineNum)
if sd := AppConfig.String("StaticDir"); sd != "" { if sd := AppConfig.String("StaticDir"); sd != "" {
for k := range BConfig.WebConfig.StaticDir { for k := range BConfig.WebConfig.StaticDir {
@ -273,15 +280,58 @@ func ParseConfig() (err error) {
BConfig.WebConfig.StaticExtensionsToGzip = fileExts BConfig.WebConfig.StaticExtensionsToGzip = fileExts
} }
} }
if lo := AppConfig.String("LogOutputs"); lo != "" {
los := strings.Split(lo, ";")
for _, v := range los {
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1]
} else {
continue
}
}
}
//init log
BeeLogger.Reset()
for adaptor, config := range BConfig.Log.Outputs {
err = BeeLogger.SetLogger(adaptor, config)
if err != nil {
fmt.Printf("%s with the config `%s` got err:%s\n", adaptor, config, err)
}
}
SetLogFuncCall(BConfig.Log.FileLineNum)
return nil return nil
} }
// LoadAppConfig allow developer to apply a config file
func LoadAppConfig(adapterName, configPath string) error {
absConfigPath, err := filepath.Abs(configPath)
if err != nil {
return err
}
if !utils.FileExists(absConfigPath) {
return fmt.Errorf("the target config file: %s don't exist", configPath)
}
if absConfigPath == appConfigPath {
return nil
}
appConfigPath = absConfigPath
appConfigProvider = adapterName
return parseConfig(appConfigPath)
}
type beegoAppConfig struct { type beegoAppConfig struct {
innerConfig config.Configer innerConfig config.Configer
} }
func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, error) { func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
ac, err := config.NewConfig(AppConfigProvider, AppConfigPath) ac, err := config.NewConfig(appConfigProvider, appConfigPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -337,46 +387,46 @@ func (b *beegoAppConfig) Float(key string) (float64, error) {
return b.innerConfig.Float(key) return b.innerConfig.Float(key)
} }
func (b *beegoAppConfig) DefaultString(key string, defaultval string) string { func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
if v := b.String(key); v != "" { if v := b.String(key); v != "" {
return v return v
} }
return defaultval return defaultVal
} }
func (b *beegoAppConfig) DefaultStrings(key string, defaultval []string) []string { func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
if v := b.Strings(key); len(v) != 0 { if v := b.Strings(key); len(v) != 0 {
return v return v
} }
return defaultval return defaultVal
} }
func (b *beegoAppConfig) DefaultInt(key string, defaultval int) int { func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
if v, err := b.Int(key); err == nil { if v, err := b.Int(key); err == nil {
return v return v
} }
return defaultval return defaultVal
} }
func (b *beegoAppConfig) DefaultInt64(key string, defaultval int64) int64 { func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
if v, err := b.Int64(key); err == nil { if v, err := b.Int64(key); err == nil {
return v return v
} }
return defaultval return defaultVal
} }
func (b *beegoAppConfig) DefaultBool(key string, defaultval bool) bool { func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
if v, err := b.Bool(key); err == nil { if v, err := b.Bool(key); err == nil {
return v return v
} }
return defaultval return defaultVal
} }
func (b *beegoAppConfig) DefaultFloat(key string, defaultval float64) float64 { func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
if v, err := b.Float(key); err == nil { if v, err := b.Float(key); err == nil {
return v return v
} }
return defaultval return defaultVal
} }
func (b *beegoAppConfig) DIY(key string) (interface{}, error) { func (b *beegoAppConfig) DIY(key string) (interface{}, error) {

View File

@ -106,3 +106,39 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
} }
return adapter.ParseData(data) return adapter.ParseData(data)
} }
// ParseBool returns the boolean value represented by the string.
//
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
// Any other value returns an error.
func ParseBool(val interface{}) (value bool, err error) {
if val != nil {
switch v := val.(type) {
case bool:
return v, nil
case string:
switch v {
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
return true, nil
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
return false, nil
}
case int8, int32, int64:
strV := fmt.Sprintf("%s", v)
if strV == "1" {
return true, nil
} else if strV == "0" {
return false, nil
}
case float64:
if v == 1 {
return true, nil
} else if v == 0 {
return false, nil
}
}
return false, fmt.Errorf("parsing %q: invalid syntax", val)
}
return false, fmt.Errorf("parsing <nil>: invalid syntax")
}

View File

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

View File

@ -27,7 +27,6 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"unicode"
) )
var ( var (
@ -97,9 +96,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
} }
if bComment != nil { if bComment != nil {
line = bytes.TrimLeft(line, string(bComment)) line = bytes.TrimLeft(line, string(bComment))
line = bytes.TrimLeftFunc(line, unicode.IsSpace) // Need append to a new line if multi-line comments.
if comment.Len() > 0 {
comment.WriteByte('\n')
}
comment.Write(line) comment.Write(line)
comment.WriteByte('\n')
continue continue
} }
@ -194,7 +195,7 @@ type IniConfigContainer struct {
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) { func (c *IniConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.getdata(key)) return ParseBool(c.getdata(key))
} }
// DefaultBool returns the boolean value for a given key. // DefaultBool returns the boolean value for a given key.
@ -268,15 +269,20 @@ func (c *IniConfigContainer) DefaultString(key string, defaultval string) string
} }
// Strings returns the []string value for a given key. // Strings returns the []string value for a given key.
// Return nil if config value does not exist or is empty.
func (c *IniConfigContainer) Strings(key string) []string { func (c *IniConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";") v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if len(v) == 0 { if v == nil {
return defaultval return defaultval
} }
return v return v
@ -299,14 +305,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
} }
defer f.Close() defer f.Close()
// Get section or key comments. Fixed #1607
getCommentStr := func(section, key string) string {
comment, ok := "", false
if len(key) == 0 {
comment, ok = c.sectionComment[section]
} else {
comment, ok = c.keyComment[section+"."+key]
}
if ok {
// Empty comment
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
return string(bNumComment)
}
prefix := string(bNumComment)
// Add the line head character "#"
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
}
return ""
}
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
// Save default section at first place // Save default section at first place
if dt, ok := c.data[defaultSection]; ok { if dt, ok := c.data[defaultSection]; ok {
for key, val := range dt { for key, val := range dt {
if key != " " { if key != " " {
// Write key comments. // Write key comments.
if v, ok := c.keyComment[key]; ok { if v := getCommentStr(defaultSection, key); len(v) > 0 {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { if _, err = buf.WriteString(v + lineBreak); err != nil {
return err return err
} }
} }
@ -327,8 +354,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
for section, dt := range c.data { for section, dt := range c.data {
if section != defaultSection { if section != defaultSection {
// Write section comments. // Write section comments.
if v, ok := c.sectionComment[section]; ok { if v := getCommentStr(section, ""); len(v) > 0 {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { if _, err = buf.WriteString(v + lineBreak); err != nil {
return err return err
} }
} }
@ -341,8 +368,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
for key, val := range dt { for key, val := range dt {
if key != " " { if key != " " {
// Write key comments. // Write key comments.
if v, ok := c.keyComment[key]; ok { if v := getCommentStr(section, key); len(v) > 0 {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { if _, err = buf.WriteString(v + lineBreak); err != nil {
return err return err
} }
} }

View File

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

View File

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

View File

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

View File

@ -92,7 +92,10 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) { func (c *ConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.data[key].(string)) if v, ok := c.data[key]; ok {
return config.ParseBool(v)
}
return false, fmt.Errorf("not exist key: %q", key)
} }
// DefaultBool return the bool value if has no error // DefaultBool return the bool value if has no error
@ -171,14 +174,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
// Strings returns the []string value for a given key. // Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(key string) []string { func (c *ConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";") v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if len(v) == 0 { if v == nil {
return defaultval return defaultval
} }
return v return v

View File

@ -82,4 +82,7 @@ func TestXML(t *testing.T) {
if xmlconf.String("name") != "astaxie" { if xmlconf.String("name") != "astaxie" {
t.Fatal("get name error") t.Fatal("get name error")
} }
if xmlconf.Strings("emptystrings") != nil {
t.Fatal("get emtpy strings error")
}
} }

View File

@ -121,10 +121,10 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key. // Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) { func (c *ConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key].(bool); ok { if v, ok := c.data[key]; ok {
return v, nil return config.ParseBool(v)
} }
return false, errors.New("not bool value") return false, fmt.Errorf("not exist key: %q", key)
} }
// DefaultBool return the bool value if has no error // DefaultBool return the bool value if has no error
@ -211,14 +211,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
// Strings returns the []string value for a given key. // Strings returns the []string value for a given key.
func (c *ConfigContainer) Strings(key string) []string { func (c *ConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";") v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaltval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if len(v) == 0 { if v == nil {
return defaultval return defaultval
} }
return v return v

View File

@ -79,4 +79,8 @@ func TestYaml(t *testing.T) {
if yamlconf.String("name") != "astaxie" { if yamlconf.String("name") != "astaxie" {
t.Fatal("get name error") t.Fatal("get name error")
} }
if yamlconf.Strings("emptystrings") != nil {
t.Fatal("get emtpy strings error")
}
} }

View File

@ -24,11 +24,13 @@ package context
import ( import (
"bufio" "bufio"
"bytes"
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@ -59,7 +61,10 @@ type Context struct {
// Reset init Context, BeegoInput and BeegoOutput // Reset init Context, BeegoInput and BeegoOutput
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
ctx.Request = r ctx.Request = r
ctx.ResponseWriter = &Response{rw, false, 0} if ctx.ResponseWriter == nil {
ctx.ResponseWriter = &Response{}
}
ctx.ResponseWriter.reset(rw)
ctx.Input.Reset(ctx) ctx.Input.Reset(ctx)
ctx.Output.Reset(ctx) ctx.Output.Reset(ctx)
} }
@ -176,25 +181,43 @@ type Response struct {
Status int Status int
} }
func (r *Response) reset(rw http.ResponseWriter) {
r.ResponseWriter = rw
r.Status = 0
r.Started = false
}
// Write writes the data to the connection as part of an HTTP reply, // Write writes the data to the connection as part of an HTTP reply,
// and sets `started` to true. // and sets `started` to true.
// started means the response has sent out. // started means the response has sent out.
func (w *Response) Write(p []byte) (int, error) { func (r *Response) Write(p []byte) (int, error) {
w.Started = true r.Started = true
return w.ResponseWriter.Write(p) return r.ResponseWriter.Write(p)
}
// Copy writes the data to the connection as part of an HTTP reply,
// and sets `started` to true.
// started means the response has sent out.
func (r *Response) Copy(buf *bytes.Buffer) (int64, error) {
r.Started = true
return io.Copy(r.ResponseWriter, buf)
} }
// WriteHeader sends an HTTP response header with status code, // WriteHeader sends an HTTP response header with status code,
// and sets `started` to true. // and sets `started` to true.
func (w *Response) WriteHeader(code int) { func (r *Response) WriteHeader(code int) {
w.Status = code if r.Status > 0 {
w.Started = true //prevent multiple response.WriteHeader calls
w.ResponseWriter.WriteHeader(code) return
}
r.Status = code
r.Started = true
r.ResponseWriter.WriteHeader(code)
} }
// Hijack hijacker for http // Hijack hijacker for http
func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := w.ResponseWriter.(http.Hijacker) hj, ok := r.ResponseWriter.(http.Hijacker)
if !ok { if !ok {
return nil, nil, errors.New("webserver doesn't support hijacking") return nil, nil, errors.New("webserver doesn't support hijacking")
} }
@ -202,15 +225,15 @@ func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
} }
// Flush http.Flusher // Flush http.Flusher
func (w *Response) Flush() { func (r *Response) Flush() {
if f, ok := w.ResponseWriter.(http.Flusher); ok { if f, ok := r.ResponseWriter.(http.Flusher); ok {
f.Flush() f.Flush()
} }
} }
// CloseNotify http.CloseNotifier // CloseNotify http.CloseNotifier
func (w *Response) CloseNotify() <-chan bool { func (r *Response) CloseNotify() <-chan bool {
if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok {
return cn.CloseNotify() return cn.CloseNotify()
} }
return nil return nil

View File

@ -287,6 +287,13 @@ func (input *BeegoInput) Params() map[string]string {
// SetParam will set the param with key and value // SetParam will set the param with key and value
func (input *BeegoInput) SetParam(key, val string) { func (input *BeegoInput) SetParam(key, val string) {
// check if already exists
for i, v := range input.pnames {
if v == key && i <= len(input.pvalues) {
input.pvalues[i] = val
return
}
}
input.pvalues = append(input.pvalues, val) input.pvalues = append(input.pvalues, val)
input.pnames = append(input.pnames, key) input.pnames = append(input.pnames, key)
} }

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect"
"testing" "testing"
) )
@ -117,3 +118,56 @@ func TestSubDomain(t *testing.T) {
t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains()) t.Fatal("Subdomain parse error, got " + beegoInput.SubDomains())
} }
} }
func TestParams(t *testing.T) {
inp := NewInput()
inp.SetParam("p1", "val1_ver1")
inp.SetParam("p2", "val2_ver1")
inp.SetParam("p3", "val3_ver1")
if l := inp.ParamsLen(); l != 3 {
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
}
if val := inp.Param("p1"); val != "val1_ver1" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver1")
}
if val := inp.Param("p3"); val != "val3_ver1" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val3_ver1")
}
vals := inp.Params()
expected := map[string]string{
"p1": "val1_ver1",
"p2": "val2_ver1",
"p3": "val3_ver1",
}
if !reflect.DeepEqual(vals, expected) {
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
}
// overwriting existing params
inp.SetParam("p1", "val1_ver2")
inp.SetParam("p2", "val2_ver2")
expected = map[string]string{
"p1": "val1_ver2",
"p2": "val2_ver2",
"p3": "val3_ver1",
}
vals = inp.Params()
if !reflect.DeepEqual(vals, expected) {
t.Fatalf("Input.Params wrong value: %s, expected %s", vals, expected)
}
if l := inp.ParamsLen(); l != 3 {
t.Fatalf("Input.ParamsLen wrong value: %d, expected %d", l, 3)
}
if val := inp.Param("p1"); val != "val1_ver2" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
}
if val := inp.Param("p2"); val != "val2_ver2" {
t.Fatalf("Input.Param wrong value: %s, expected %s", val, "val1_ver2")
}
}

View File

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
"io"
"mime" "mime"
"net/http" "net/http"
"path/filepath" "path/filepath"
@ -57,7 +56,7 @@ func (output *BeegoOutput) Header(key, val string) {
// Body sets response body content. // Body sets response body content.
// if EnableGzip, compress content string. // if EnableGzip, compress content string.
// it sends out response body directly. // it sends out response body directly.
func (output *BeegoOutput) Body(content []byte) { func (output *BeegoOutput) Body(content []byte) error {
var encoding string var encoding string
var buf = &bytes.Buffer{} var buf = &bytes.Buffer{}
if output.EnableGzip { if output.EnableGzip {
@ -75,7 +74,8 @@ func (output *BeegoOutput) Body(content []byte) {
output.Status = 0 output.Status = 0
} }
io.Copy(output.Context.ResponseWriter, buf) _, err := output.Context.ResponseWriter.Copy(buf)
return err
} }
// Cookie sets cookie value via given key. // Cookie sets cookie value via given key.
@ -97,9 +97,10 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface
maxAge = v maxAge = v
} }
if maxAge > 0 { switch {
case maxAge > 0:
fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge) fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge)
} else { case maxAge < 0:
fmt.Fprintf(&b, "; Max-Age=0") fmt.Fprintf(&b, "; Max-Age=0")
} }
} }
@ -186,8 +187,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
if coding { if coding {
content = []byte(stringsToJSON(string(content))) content = []byte(stringsToJSON(string(content)))
} }
output.Body(content) return output.Body(content)
return nil
} }
// JSONP writes jsonp to response body. // JSONP writes jsonp to response body.
@ -212,8 +212,7 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error {
callbackContent.WriteString("(") callbackContent.WriteString("(")
callbackContent.Write(content) callbackContent.Write(content)
callbackContent.WriteString(");\r\n") callbackContent.WriteString(");\r\n")
output.Body(callbackContent.Bytes()) return output.Body(callbackContent.Bytes())
return nil
} }
// XML writes xml string to response body. // XML writes xml string to response body.
@ -230,8 +229,7 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error {
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err return err
} }
output.Body(content) return output.Body(content)
return nil
} }
// Download forces response for download file. // Download forces response for download file.

View File

@ -185,8 +185,7 @@ func (c *Controller) Render() error {
return err return err
} }
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
c.Ctx.Output.Body(rb) return c.Ctx.Output.Body(rb)
return nil
} }
// RenderString returns the rendered template string. Do not send out response. // RenderString returns the rendered template string. Do not send out response.
@ -197,33 +196,9 @@ func (c *Controller) RenderString() (string, error) {
// RenderBytes returns the bytes of rendered template string. Do not send out response. // RenderBytes returns the bytes of rendered template string. Do not send out response.
func (c *Controller) RenderBytes() ([]byte, error) { func (c *Controller) RenderBytes() ([]byte, error) {
//if the controller has set layout, then first get the tplname's content set the content to the layout buf, err := c.renderTemplate()
var buf bytes.Buffer //if the controller has set layout, then first get the tplName's content set the content to the layout
if c.Layout != "" { if err == nil && c.Layout != "" {
if c.TplName == "" {
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
}
if BConfig.RunMode == DEV {
buildFiles := []string{c.TplName}
if c.LayoutSections != nil {
for _, sectionTpl := range c.LayoutSections {
if sectionTpl == "" {
continue
}
buildFiles = append(buildFiles, sectionTpl)
}
}
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
}
if _, ok := BeeTemplates[c.TplName]; !ok {
panic("can't find templatefile in the path:" + c.TplName)
}
err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
c.Data["LayoutContent"] = template.HTML(buf.String()) c.Data["LayoutContent"] = template.HTML(buf.String())
if c.LayoutSections != nil { if c.LayoutSections != nil {
@ -232,11 +207,9 @@ func (c *Controller) RenderBytes() ([]byte, error) {
c.Data[sectionName] = "" c.Data[sectionName] = ""
continue continue
} }
buf.Reset() buf.Reset()
err = BeeTemplates[sectionTpl].ExecuteTemplate(&buf, sectionTpl, c.Data) err = executeTemplate(&buf, sectionTpl, c.Data)
if err != nil { if err != nil {
Trace("template Execute err:", err)
return nil, err return nil, err
} }
c.Data[sectionName] = template.HTML(buf.String()) c.Data[sectionName] = template.HTML(buf.String())
@ -244,30 +217,32 @@ func (c *Controller) RenderBytes() ([]byte, error) {
} }
buf.Reset() buf.Reset()
err = BeeTemplates[c.Layout].ExecuteTemplate(&buf, c.Layout, c.Data) executeTemplate(&buf, c.Layout, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
return buf.Bytes(), nil
} }
return buf.Bytes(), err
}
func (c *Controller) renderTemplate() (bytes.Buffer, error) {
var buf bytes.Buffer
if c.TplName == "" { if c.TplName == "" {
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
} }
if BConfig.RunMode == DEV { if BConfig.RunMode == DEV {
BuildTemplate(BConfig.WebConfig.ViewsPath, c.TplName) buildFiles := []string{c.TplName}
if c.Layout != "" {
buildFiles = append(buildFiles, c.Layout)
if c.LayoutSections != nil {
for _, sectionTpl := range c.LayoutSections {
if sectionTpl == "" {
continue
}
buildFiles = append(buildFiles, sectionTpl)
}
}
}
BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...)
} }
if _, ok := BeeTemplates[c.TplName]; !ok { return buf, executeTemplate(&buf, c.TplName, c.Data)
panic("can't find templatefile in the path:" + c.TplName)
}
buf.Reset()
err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
return buf.Bytes(), nil
} }
// Redirect sends the redirection response to url with status code. // Redirect sends the redirection response to url with status code.
@ -286,7 +261,7 @@ func (c *Controller) Abort(code string) {
// CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. // CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body.
func (c *Controller) CustomAbort(status int, body string) { func (c *Controller) CustomAbort(status int, body string) {
c.Ctx.ResponseWriter.WriteHeader(status) c.Ctx.Output.Status = status
// first panic from ErrorMaps, is is user defined error functions. // first panic from ErrorMaps, is is user defined error functions.
if _, ok := ErrorMaps[body]; ok { if _, ok := ErrorMaps[body]; ok {
panic(body) panic(body)
@ -568,6 +543,7 @@ func (c *Controller) SessionRegenerateID() {
// DestroySession cleans session data and session cookie. // DestroySession cleans session data and session cookie.
func (c *Controller) DestroySession() { func (c *Controller) DestroySession() {
c.Ctx.Input.CruSession.Flush() c.Ctx.Input.CruSession.Flush()
c.Ctx.Input.CruSession = nil
GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request) GlobalSessions.SessionDestroy(c.Ctx.ResponseWriter, c.Ctx.Request)
} }

View File

@ -424,6 +424,7 @@ func exception(errCode string, ctx *context.Context) {
func executeError(err *errorInfo, ctx *context.Context, code int) { func executeError(err *errorInfo, ctx *context.Context, code int) {
if err.errorType == errorTypeHandler { if err.errorType == errorTypeHandler {
ctx.ResponseWriter.WriteHeader(code)
err.handler(ctx.ResponseWriter, ctx.Request) err.handler(ctx.ResponseWriter, ctx.Request)
return return
} }

View File

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

View File

@ -68,13 +68,11 @@ func registerSession() error {
} }
func registerTemplate() error { func registerTemplate() error {
if BConfig.WebConfig.AutoRender { if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil { if BConfig.RunMode == DEV {
if BConfig.RunMode == DEV { Warn(err)
Warn(err)
}
return err
} }
return err
} }
return nil return nil
} }

View File

@ -301,13 +301,12 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
// JSONBody adds request raw body encoding by JSON. // JSONBody adds request raw body encoding by JSON.
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil { if b.req.Body == nil && obj != nil {
buf := bytes.NewBuffer(nil) byts, err := json.Marshal(obj)
enc := json.NewEncoder(buf) if err != nil {
if err := enc.Encode(obj); err != nil {
return b, err return b, err
} }
b.req.Body = ioutil.NopCloser(buf) b.req.Body = ioutil.NopCloser(bytes.NewReader(byts))
b.req.ContentLength = int64(buf.Len()) b.req.ContentLength = int64(len(byts))
b.req.Header.Set("Content-Type", "application/json") b.req.Header.Set("Content-Type", "application/json")
} }
return b, nil return b, nil

View File

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

View File

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

View File

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

View File

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

View File

@ -53,9 +53,11 @@ type fileLogWriter struct {
Level int `json:"level"` Level int `json:"level"`
Perm os.FileMode `json:"perm"` Perm os.FileMode `json:"perm"`
fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix
} }
// NewFileWriter create a FileLogWriter returning as LoggerInterface. // newFileWriter create a FileLogWriter returning as LoggerInterface.
func newFileWriter() Logger { func newFileWriter() Logger {
w := &fileLogWriter{ w := &fileLogWriter{
Filename: "", Filename: "",
@ -89,6 +91,11 @@ func (w *fileLogWriter) Init(jsonConfig string) error {
if len(w.Filename) == 0 { if len(w.Filename) == 0 {
return errors.New("jsonconfig must have filename") return errors.New("jsonconfig must have filename")
} }
w.suffix = filepath.Ext(w.Filename)
w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix)
if w.suffix == "" {
w.suffix = ".log"
}
err = w.startLogger() err = w.startLogger()
return err return err
} }
@ -114,59 +121,19 @@ func (w *fileLogWriter) needRotate(size int, day int) bool {
} }
// WriteMsg write logger message into file. // WriteMsg write logger message into file.
func (w *fileLogWriter) WriteMsg(msg string, level int) error { func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level { if level > w.Level {
return nil return nil
} }
//2016/01/12 21:34:33 h, d := formatTimeHeader(when)
now := time.Now() msg = string(h) + msg + "\n"
y, mo, d := now.Date()
h, mi, s := now.Clock()
//len(2006/01/02 15:03:04)==19
var buf [20]byte
t := 3
for y >= 10 {
p := y / 10
buf[t] = byte('0' + y - p*10)
y = p
t--
}
buf[0] = byte('0' + y)
buf[4] = '/'
if mo > 9 {
buf[5] = '1'
buf[6] = byte('0' + mo - 9)
} else {
buf[5] = '0'
buf[6] = byte('0' + mo)
}
buf[7] = '/'
t = d / 10
buf[8] = byte('0' + t)
buf[9] = byte('0' + d - t*10)
buf[10] = ' '
t = h / 10
buf[11] = byte('0' + t)
buf[12] = byte('0' + h - t*10)
buf[13] = ':'
t = mi / 10
buf[14] = byte('0' + t)
buf[15] = byte('0' + mi - t*10)
buf[16] = ':'
t = s / 10
buf[17] = byte('0' + t)
buf[18] = byte('0' + s - t*10)
buf[19] = ' '
msg = string(buf[0:]) + msg + "\n"
if w.Rotate { if w.Rotate {
if w.needRotate(len(msg), d) { if w.needRotate(len(msg), d) {
w.Lock() w.Lock()
if w.needRotate(len(msg), d) { if w.needRotate(len(msg), d) {
if err := w.doRotate(); err != nil { if err := w.doRotate(when); err != nil {
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
} }
} }
w.Unlock() w.Unlock()
} }
@ -235,8 +202,8 @@ func (w *fileLogWriter) lines() (int, error) {
} }
// DoRotate means it need to write file in new file. // DoRotate means it need to write file in new file.
// new file name like xx.2013-01-01.2.log // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
func (w *fileLogWriter) doRotate() error { func (w *fileLogWriter) doRotate(logTime time.Time) error {
_, err := os.Lstat(w.Filename) _, err := os.Lstat(w.Filename)
if err != nil { if err != nil {
return err return err
@ -245,13 +212,13 @@ func (w *fileLogWriter) doRotate() error {
// Find the next available number // Find the next available number
num := 1 num := 1
fName := "" fName := ""
suffix := filepath.Ext(w.Filename) if w.MaxLines > 0 || w.MaxSize > 0 {
filenameOnly := strings.TrimSuffix(w.Filename, suffix) for ; err == nil && num <= 999; num++ {
if suffix == "" { fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
suffix = ".log" _, err = os.Lstat(fName)
} }
for ; err == nil && num <= 999; num++ { } else {
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", time.Now().Format("2006-01-02"), num, suffix) fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix)
_, err = os.Lstat(fName) _, err = os.Lstat(fName)
} }
// return error if the last file checked still existed // return error if the last file checked still existed
@ -289,7 +256,8 @@ func (w *fileLogWriter) deleteOldLog() {
}() }()
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) { if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) &&
strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path) os.Remove(path)
} }
} }

View File

@ -40,6 +40,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"sync" "sync"
"time"
) )
// RFC5424 log message levels. // RFC5424 log message levels.
@ -68,7 +69,7 @@ type loggerType func() Logger
// Logger defines the behavior of a log provider. // Logger defines the behavior of a log provider.
type Logger interface { type Logger interface {
Init(config string) error Init(config string) error
WriteMsg(msg string, level int) error WriteMsg(when time.Time, msg string, level int) error
Destroy() Destroy()
Flush() Flush()
} }
@ -97,6 +98,8 @@ type BeeLogger struct {
loggerFuncCallDepth int loggerFuncCallDepth int
asynchronous bool asynchronous bool
msgChan chan *logMsg msgChan chan *logMsg
signalChan chan string
wg sync.WaitGroup
outputs []*nameLogger outputs []*nameLogger
} }
@ -108,6 +111,7 @@ type nameLogger struct {
type logMsg struct { type logMsg struct {
level int level int
msg string msg string
when time.Time
} }
var logMsgPool *sync.Pool var logMsgPool *sync.Pool
@ -120,6 +124,7 @@ func NewLogger(channelLen int64) *BeeLogger {
bl.level = LevelDebug bl.level = LevelDebug
bl.loggerFuncCallDepth = 2 bl.loggerFuncCallDepth = 2
bl.msgChan = make(chan *logMsg, channelLen) bl.msgChan = make(chan *logMsg, channelLen)
bl.signalChan = make(chan string, 1)
return bl return bl
} }
@ -131,6 +136,7 @@ func (bl *BeeLogger) Async() *BeeLogger {
return &logMsg{} return &logMsg{}
}, },
} }
bl.wg.Add(1)
go bl.startLogger() go bl.startLogger()
return bl return bl
} }
@ -140,17 +146,25 @@ func (bl *BeeLogger) Async() *BeeLogger {
func (bl *BeeLogger) SetLogger(adapterName string, config string) error { func (bl *BeeLogger) SetLogger(adapterName string, config string) error {
bl.lock.Lock() bl.lock.Lock()
defer bl.lock.Unlock() defer bl.lock.Unlock()
if log, ok := adapters[adapterName]; ok {
lg := log() for _, l := range bl.outputs {
err := lg.Init(config) if l.name == adapterName {
if err != nil { return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName)
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
return err
} }
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) }
} else {
log, ok := adapters[adapterName]
if !ok {
return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName) return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName)
} }
lg := log()
err := lg.Init(config)
if err != nil {
fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error())
return err
}
bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg})
return nil return nil
} }
@ -173,9 +187,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error {
return nil return nil
} }
func (bl *BeeLogger) writeToLoggers(msg string, level int) { func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
for _, l := range bl.outputs { for _, l := range bl.outputs {
err := l.WriteMsg(msg, level) err := l.WriteMsg(when, msg, level)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
} }
@ -183,6 +197,7 @@ func (bl *BeeLogger) writeToLoggers(msg string, level int) {
} }
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error { func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
when := time.Now()
if bl.enableFuncCallDepth { if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if !ok { if !ok {
@ -196,9 +211,10 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
lm := logMsgPool.Get().(*logMsg) lm := logMsgPool.Get().(*logMsg)
lm.level = logLevel lm.level = logLevel
lm.msg = msg lm.msg = msg
lm.when = when
bl.msgChan <- lm bl.msgChan <- lm
} else { } else {
bl.writeToLoggers(msg, logLevel) bl.writeToLoggers(when, msg, logLevel)
} }
return nil return nil
} }
@ -228,11 +244,26 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
// start logger chan reading. // start logger chan reading.
// when chan is not empty, write logs. // when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() { func (bl *BeeLogger) startLogger() {
gameOver := false
for { for {
select { select {
case bm := <-bl.msgChan: case bm := <-bl.msgChan:
bl.writeToLoggers(bm.msg, bm.level) bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm) logMsgPool.Put(bm)
case sg := <-bl.signalChan:
// Now should only send "flush" or "close" to bl.signalChan
bl.flush()
if sg == "close" {
for _, l := range bl.outputs {
l.Destroy()
}
bl.outputs = nil
gameOver = true
}
bl.wg.Done()
}
if gameOver {
break
} }
} }
} }
@ -341,17 +372,45 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) {
// Flush flush all chan data. // Flush flush all chan data.
func (bl *BeeLogger) Flush() { func (bl *BeeLogger) Flush() {
for _, l := range bl.outputs { if bl.asynchronous {
l.Flush() bl.signalChan <- "flush"
bl.wg.Wait()
bl.wg.Add(1)
return
} }
bl.flush()
} }
// Close close logger, flush all chan data and destroy all adapters in BeeLogger. // Close close logger, flush all chan data and destroy all adapters in BeeLogger.
func (bl *BeeLogger) Close() { func (bl *BeeLogger) Close() {
if bl.asynchronous {
bl.signalChan <- "close"
bl.wg.Wait()
} else {
bl.flush()
for _, l := range bl.outputs {
l.Destroy()
}
bl.outputs = nil
}
close(bl.msgChan)
close(bl.signalChan)
}
// Reset close all outputs, and set bl.outputs to nil
func (bl *BeeLogger) Reset() {
bl.Flush()
for _, l := range bl.outputs {
l.Destroy()
}
bl.outputs = nil
}
func (bl *BeeLogger) flush() {
for { for {
if len(bl.msgChan) > 0 { if len(bl.msgChan) > 0 {
bm := <-bl.msgChan bm := <-bl.msgChan
bl.writeToLoggers(bm.msg, bm.level) bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm) logMsgPool.Put(bm)
continue continue
} }
@ -359,6 +418,5 @@ func (bl *BeeLogger) Close() {
} }
for _, l := range bl.outputs { for _, l := range bl.outputs {
l.Flush() l.Flush()
l.Destroy()
} }
} }

80
logs/logger.go Normal file
View File

@ -0,0 +1,80 @@
// 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 (
"io"
"sync"
"time"
)
type logWriter struct {
sync.Mutex
writer io.Writer
}
func newLogWriter(wr io.Writer) *logWriter {
return &logWriter{writer: wr}
}
func (lg *logWriter) println(when time.Time, msg string) {
lg.Lock()
h, _ := formatTimeHeader(when)
lg.writer.Write(append(append(h, msg...), '\n'))
lg.Unlock()
}
func formatTimeHeader(when time.Time) ([]byte, int) {
y, mo, d := when.Date()
h, mi, s := when.Clock()
//len(2006/01/02 15:03:04)==19
var buf [20]byte
t := 3
for y >= 10 {
p := y / 10
buf[t] = byte('0' + y - p*10)
y = p
t--
}
buf[0] = byte('0' + y)
buf[4] = '/'
if mo > 9 {
buf[5] = '1'
buf[6] = byte('0' + mo - 9)
} else {
buf[5] = '0'
buf[6] = byte('0' + mo)
}
buf[7] = '/'
t = d / 10
buf[8] = byte('0' + t)
buf[9] = byte('0' + d - t*10)
buf[10] = ' '
t = h / 10
buf[11] = byte('0' + t)
buf[12] = byte('0' + h - t*10)
buf[13] = ':'
t = mi / 10
buf[14] = byte('0' + t)
buf[15] = byte('0' + mi - t*10)
buf[16] = ':'
t = s / 10
buf[17] = byte('0' + t)
buf[18] = byte('0' + s - t*10)
buf[19] = ' '
return buf[0:], d
}

116
logs/multifile.go Normal file
View File

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

78
logs/multifile_test.go Normal file
View File

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

View File

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

View File

@ -388,3 +388,10 @@ func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace {
ns.Namespace(n) ns.Namespace(n)
} }
} }
// NSHandler add handler
func NSHandler(rootpath string, h http.Handler) LinkNamespace {
return func(ns *Namespace) {
ns.Handler(rootpath, h)
}
}

View File

@ -113,7 +113,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val
if fi.pk { if fi.pk {
_, value, _ = getExistPk(mi, ind) _, value, _ = getExistPk(mi, ind)
} else { } else {
field := ind.Field(fi.fieldIndex) field := ind.FieldByIndex(fi.fieldIndex)
if fi.isFielder { if fi.isFielder {
f := field.Addr().Interface().(Fielder) f := field.Addr().Interface().(Fielder)
value = f.RawValue() value = f.RawValue()
@ -517,9 +517,9 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
if num > 0 { if num > 0 {
if mi.fields.pk.auto { if mi.fields.pk.auto {
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
ind.Field(mi.fields.pk.fieldIndex).SetUint(0) ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0)
} else { } else {
ind.Field(mi.fields.pk.fieldIndex).SetInt(0) ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
} }
} }
err := d.deleteRels(q, mi, []interface{}{pkValue}, tz) err := d.deleteRels(q, mi, []interface{}{pkValue}, tz)
@ -859,13 +859,13 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
mmi = fi.relModelInfo mmi = fi.relModelInfo
field := last field := last
if last.Kind() != reflect.Invalid { if last.Kind() != reflect.Invalid {
field = reflect.Indirect(last.Field(fi.fieldIndex)) field = reflect.Indirect(last.FieldByIndex(fi.fieldIndex))
if field.IsValid() { if field.IsValid() {
d.setColsValues(mmi, &field, mmi.fields.dbcols, trefs[:len(mmi.fields.dbcols)], tz) d.setColsValues(mmi, &field, mmi.fields.dbcols, trefs[:len(mmi.fields.dbcols)], tz)
for _, fi := range mmi.fields.fieldsReverse { for _, fi := range mmi.fields.fieldsReverse {
if fi.inModel && fi.reverseFieldInfo.mi == lastm { if fi.inModel && fi.reverseFieldInfo.mi == lastm {
if fi.reverseFieldInfo != nil { if fi.reverseFieldInfo != nil {
f := field.Field(fi.fieldIndex) f := field.FieldByIndex(fi.fieldIndex)
if f.Kind() == reflect.Ptr { if f.Kind() == reflect.Ptr {
f.Set(last.Addr()) f.Set(last.Addr())
} }
@ -1014,7 +1014,7 @@ func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string,
fi := mi.fields.GetByColumn(column) fi := mi.fields.GetByColumn(column)
field := ind.Field(fi.fieldIndex) field := ind.FieldByIndex(fi.fieldIndex)
value, err := d.convertValueFromDB(fi, val, tz) value, err := d.convertValueFromDB(fi, val, tz)
if err != nil { if err != nil {
@ -1350,7 +1350,7 @@ setValue:
fieldType = fi.relModelInfo.fields.pk.fieldType fieldType = fi.relModelInfo.fields.pk.fieldType
mf := reflect.New(fi.relModelInfo.addrField.Elem().Type()) mf := reflect.New(fi.relModelInfo.addrField.Elem().Type())
field.Set(mf) field.Set(mf)
f := mf.Elem().Field(fi.relModelInfo.fields.pk.fieldIndex) f := mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex)
field = f field = f
goto setValue goto setValue
} }

View File

@ -59,6 +59,7 @@ var (
"postgres": DRPostgres, "postgres": DRPostgres,
"sqlite3": DRSqlite, "sqlite3": DRSqlite,
"tidb": DRTiDB, "tidb": DRTiDB,
"oracle": DROracle,
} }
dbBasers = map[DriverType]dbBaser{ dbBasers = map[DriverType]dbBaser{
DRMySQL: newdbBaseMysql(), DRMySQL: newdbBaseMysql(),
@ -151,7 +152,7 @@ func detectTZ(al *alias) {
al.Engine = "INNODB" al.Engine = "INNODB"
} }
case DRSqlite: case DRSqlite, DROracle:
al.TZ = time.UTC al.TZ = time.UTC
case DRPostgres: case DRPostgres:

View File

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

View File

@ -32,7 +32,7 @@ func getDbAlias(name string) *alias {
func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) { func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) {
fi := mi.fields.pk fi := mi.fields.pk
v := ind.Field(fi.fieldIndex) v := ind.FieldByIndex(fi.fieldIndex)
if fi.fieldType&IsPostiveIntegerField > 0 { if fi.fieldType&IsPostiveIntegerField > 0 {
vu := v.Uint() vu := v.Uint()
exist = vu > 0 exist = vu > 0

View File

@ -102,7 +102,7 @@ func newFields() *fields {
// single field info // single field info
type fieldInfo struct { type fieldInfo struct {
mi *modelInfo mi *modelInfo
fieldIndex int fieldIndex []int
fieldType int fieldType int
dbcol bool dbcol bool
inModel bool inModel bool
@ -138,7 +138,7 @@ type fieldInfo struct {
} }
// new field info // new field info
func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) { func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) {
var ( var (
tag string tag string
tagValue string tagValue string
@ -278,7 +278,7 @@ checkType:
fi.column = getColumnName(fieldType, addrField, sf, tags["column"]) fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
fi.addrValue = addrField fi.addrValue = addrField
fi.sf = sf fi.sf = sf
fi.fullName = mi.fullName + "." + sf.Name fi.fullName = mi.fullName + mName + "." + sf.Name
fi.null = attrs["null"] fi.null = attrs["null"]
fi.index = attrs["index"] fi.index = attrs["index"]

View File

@ -36,11 +36,6 @@ type modelInfo struct {
// new model info // new model info
func newModelInfo(val reflect.Value) (info *modelInfo) { func newModelInfo(val reflect.Value) (info *modelInfo) {
var (
err error
fi *fieldInfo
sf reflect.StructField
)
info = &modelInfo{} info = &modelInfo{}
info.fields = newFields() info.fields = newFields()
@ -53,13 +48,31 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
info.name = typ.Name() info.name = typ.Name()
info.fullName = getFullName(typ) info.fullName = getFullName(typ)
addModelFields(info, ind, "", []int{})
return
}
func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []int) {
var (
err error
fi *fieldInfo
sf reflect.StructField
)
for i := 0; i < ind.NumField(); i++ { for i := 0; i < ind.NumField(); i++ {
field := ind.Field(i) field := ind.Field(i)
sf = ind.Type().Field(i) sf = ind.Type().Field(i)
if sf.PkgPath != "" { if sf.PkgPath != "" {
continue continue
} }
fi, err = newFieldInfo(info, field, sf) // add anonymous struct fields
if sf.Anonymous {
addModelFields(info, field, mName+"."+sf.Name, append(index, i))
continue
}
fi, err = newFieldInfo(info, field, sf, mName)
if err != nil { if err != nil {
if err == errSkipField { if err == errSkipField {
@ -84,7 +97,7 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
} }
} }
fi.fieldIndex = i fi.fieldIndex = append(index, i)
fi.mi = info fi.mi = info
fi.inModel = true fi.inModel = true
} }
@ -93,8 +106,6 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err)) fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err))
os.Exit(2) os.Exit(2)
} }
return
} }
// combine related model info to new model info. // combine related model info to new model info.

View File

@ -25,7 +25,6 @@ import (
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq" _ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
// As tidb can't use go get, so disable the tidb testing now // As tidb can't use go get, so disable the tidb testing now
// _ "github.com/pingcap/tidb" // _ "github.com/pingcap/tidb"
) )
@ -352,6 +351,30 @@ type GroupPermissions struct {
Permission *Permission `orm:"rel(fk)"` Permission *Permission `orm:"rel(fk)"`
} }
type ModelID struct {
ID int64
}
type ModelBase struct {
ModelID
Created time.Time `orm:"auto_now_add;type(datetime)"`
Updated time.Time `orm:"auto_now;type(datetime)"`
}
type InLine struct {
// Common Fields
ModelBase
// Other Fields
Name string `orm:"unique"`
Email string
}
func NewInLine() *InLine {
return new(InLine)
}
var DBARGS = struct { var DBARGS = struct {
Driver string Driver string
Source string Source string

View File

@ -140,7 +140,7 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i
return (err == nil), id, err return (err == nil), id, err
} }
return false, ind.Field(mi.fields.pk.fieldIndex).Int(), err return false, ind.FieldByIndex(mi.fields.pk.fieldIndex).Int(), err
} }
// insert model data to database // insert model data to database
@ -160,9 +160,9 @@ func (o *orm) Insert(md interface{}) (int64, error) {
func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) { func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) {
if mi.fields.pk.auto { if mi.fields.pk.auto {
if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 {
ind.Field(mi.fields.pk.fieldIndex).SetUint(uint64(id)) ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
} else { } else {
ind.Field(mi.fields.pk.fieldIndex).SetInt(id) ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id)
} }
} }
} }
@ -290,7 +290,7 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int
qs.orders = []string{order} qs.orders = []string{order}
} }
find := ind.Field(fi.fieldIndex) find := ind.FieldByIndex(fi.fieldIndex)
var nums int64 var nums int64
var err error var err error

View File

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

View File

@ -342,7 +342,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
for _, col := range columns { for _, col := range columns {
if fi := sMi.fields.GetByColumn(col); fi != nil { if fi := sMi.fields.GetByColumn(col); fi != nil {
value := reflect.ValueOf(columnsMp[col]).Elem().Interface() value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value) o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value)
} }
} }
} else { } else {
@ -480,7 +480,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
for _, col := range columns { for _, col := range columns {
if fi := sMi.fields.GetByColumn(col); fi != nil { if fi := sMi.fields.GetByColumn(col); fi != nil {
value := reflect.ValueOf(columnsMp[col]).Elem().Interface() value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value) o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value)
} }
} }
} else { } else {

View File

@ -187,6 +187,7 @@ func TestSyncDb(t *testing.T) {
RegisterModel(new(Group)) RegisterModel(new(Group))
RegisterModel(new(Permission)) RegisterModel(new(Permission))
RegisterModel(new(GroupPermissions)) RegisterModel(new(GroupPermissions))
RegisterModel(new(InLine))
err := RunSyncdb("default", true, Debug) err := RunSyncdb("default", true, Debug)
throwFail(t, err) throwFail(t, err)
@ -206,6 +207,7 @@ func TestRegisterModels(t *testing.T) {
RegisterModel(new(Group)) RegisterModel(new(Group))
RegisterModel(new(Permission)) RegisterModel(new(Permission))
RegisterModel(new(GroupPermissions)) RegisterModel(new(GroupPermissions))
RegisterModel(new(InLine))
BootStrap() BootStrap()
@ -1928,3 +1930,25 @@ func TestReadOrCreate(t *testing.T) {
dORM.Delete(u) dORM.Delete(u)
} }
func TestInLine(t *testing.T) {
name := "inline"
email := "hello@go.com"
inline := NewInLine()
inline.Name = name
inline.Email = email
id, err := dORM.Insert(inline)
throwFail(t, err)
throwFail(t, AssertIs(id, 1))
il := NewInLine()
il.ID = 1
err = dORM.Read(il)
throwFail(t, err)
throwFail(t, AssertIs(il.Name, name))
throwFail(t, AssertIs(il.Email, email))
throwFail(t, AssertIs(il.Created.In(DefaultTimeLoc), inline.Created.In(DefaultTimeLoc), testDate))
throwFail(t, AssertIs(il.Updated.In(DefaultTimeLoc), inline.Updated.In(DefaultTimeLoc), testDateTime))
}

View File

@ -148,6 +148,10 @@ type QuerySeter interface {
// add OFFSET value // add OFFSET value
// same as Limit function's args[0] // same as Limit function's args[0]
Offset(offset interface{}) QuerySeter Offset(offset interface{}) QuerySeter
// add GROUP BY expression
// for example:
// qs.GroupBy("id")
GroupBy(exprs ...string) QuerySeter
// add ORDER expression. // add ORDER expression.
// "column" means ASC, "-column" means DESC. // "column" means ASC, "-column" means DESC.
// for example: // for example:
@ -162,6 +166,12 @@ type QuerySeter interface {
// qs.RelatedSel("profile").One(&user) // qs.RelatedSel("profile").One(&user)
// user.Profile.Age = 32 // user.Profile.Age = 32
RelatedSel(params ...interface{}) QuerySeter RelatedSel(params ...interface{}) QuerySeter
// Set Distinct
// for example:
// o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
// Distinct().
// All(&permissions)
Distinct() QuerySeter
// return QuerySeter execution result number // return QuerySeter execution result number
// for example: // for example:
// num, err = qs.Filter("profile__age__gt", 28).Count() // num, err = qs.Filter("profile__age__gt", 28).Count()

View File

@ -58,7 +58,7 @@ func parserPkg(pkgRealpath, pkgpath string) error {
rep := strings.NewReplacer("/", "_", ".", "_") rep := strings.NewReplacer("/", "_", ".", "_")
commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go" commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go"
if !compareFile(pkgRealpath) { if !compareFile(pkgRealpath) {
Info(pkgRealpath + " has not changed, not reloading") Info(pkgRealpath + " no changed")
return nil return nil
} }
genInfoList = make(map[string][]ControllerComments) genInfoList = make(map[string][]ControllerComments)
@ -130,7 +130,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
} }
func genRouterCode() { func genRouterCode() {
os.Mkdir("routers", 0755) os.Mkdir(path.Join(AppPath, "routers"), 0755)
Info("generate router from comments") Info("generate router from comments")
var ( var (
globalinfo string globalinfo string
@ -172,7 +172,7 @@ func genRouterCode() {
} }
} }
if globalinfo != "" { if globalinfo != "" {
f, err := os.Create(path.Join("routers", commentFilename)) f, err := os.Create(path.Join(AppPath, "routers", commentFilename))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -182,7 +182,7 @@ func genRouterCode() {
} }
func compareFile(pkgRealpath string) bool { func compareFile(pkgRealpath string) bool {
if !utils.FileExists(path.Join("routers", commentFilename)) { if !utils.FileExists(path.Join(AppPath, "routers", commentFilename)) {
return true return true
} }
if utils.FileExists(lastupdateFilename) { if utils.FileExists(lastupdateFilename) {

View File

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

View File

@ -62,12 +62,12 @@ var (
} }
// these beego.Controller's methods shouldn't reflect to AutoRouter // these beego.Controller's methods shouldn't reflect to AutoRouter
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString", exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJson", "ServeJsonp", "RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
"ServeXml", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool", "ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession", "GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie", "DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml", "SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
"GetControllerAndAction"} "GetControllerAndAction", "ServeFormatted"}
urlPlaceholder = "{{placeholder}}" urlPlaceholder = "{{placeholder}}"
// DefaultAccessLogFilter will skip the accesslog if return true // DefaultAccessLogFilter will skip the accesslog if return true
@ -321,7 +321,8 @@ func (p *ControllerRegister) Any(pattern string, f FilterFunc) {
// ctx.Output.Body("hello world") // ctx.Output.Body("hello world")
// }) // })
func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) { func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
if _, ok := HTTPMETHOD[strings.ToUpper(method)]; method != "*" && !ok { method = strings.ToUpper(method)
if _, ok := HTTPMETHOD[method]; method != "*" && !ok {
panic("not support http method: " + method) panic("not support http method: " + method)
} }
route := &controllerInfo{} route := &controllerInfo{}
@ -334,7 +335,7 @@ func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
methods[val] = val methods[val] = val
} }
} else { } else {
methods[strings.ToUpper(method)] = strings.ToUpper(method) methods[method] = method
} }
route.methods = methods route.methods = methods
for k := range methods { for k := range methods {
@ -606,6 +607,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
) )
context := p.pool.Get().(*beecontext.Context) context := p.pool.Get().(*beecontext.Context)
context.Reset(rw, r) context.Reset(rw, r)
defer p.pool.Put(context) defer p.pool.Put(context)
defer p.recoverPanic(context) defer p.recoverPanic(context)
@ -639,6 +641,13 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
goto Admin goto Admin
} }
if r.Method != "GET" && r.Method != "HEAD" {
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
context.Input.CopyBody(BConfig.MaxMemory)
}
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
}
// session init // session init
if BConfig.WebConfig.Session.SessionOn { if BConfig.WebConfig.Session.SessionOn {
var err error var err error
@ -649,17 +658,12 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
return return
} }
defer func() { defer func() {
context.Input.CruSession.SessionRelease(rw) if context.Input.CruSession != nil {
context.Input.CruSession.SessionRelease(rw)
}
}() }()
} }
if r.Method != "GET" && r.Method != "HEAD" {
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
context.Input.CopyBody(BConfig.MaxMemory)
}
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
}
if p.execFilter(context, BeforeRouter, urlPath) { if p.execFilter(context, BeforeRouter, urlPath) {
goto Admin goto Admin
} }

View File

@ -65,6 +65,11 @@ func (tc *TestController) GetManyRouter() {
tc.Ctx.WriteString(tc.Ctx.Input.Query(":id") + tc.Ctx.Input.Query(":page")) tc.Ctx.WriteString(tc.Ctx.Input.Query(":id") + tc.Ctx.Input.Query(":page"))
} }
func (tc *TestController) GetEmptyBody() {
var res []byte
tc.Ctx.Output.Body(res)
}
type ResStatus struct { type ResStatus struct {
Code int Code int
Msg string Msg string
@ -239,6 +244,21 @@ func TestManyRoute(t *testing.T) {
} }
} }
// Test for issue #1669
func TestEmptyResponse(t *testing.T) {
r, _ := http.NewRequest("GET", "/beego-empty.html", nil)
w := httptest.NewRecorder()
handler := NewControllerRegister()
handler.Add("/beego-empty.html", &TestController{}, "get:GetEmptyBody")
handler.ServeHTTP(w, r)
if body := w.Body.String(); body != "" {
t.Error("want empty body")
}
}
func TestNotFound(t *testing.T) { func TestNotFound(t *testing.T) {
r, _ := http.NewRequest("GET", "/", nil) r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()

View File

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

View File

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

View File

@ -102,7 +102,7 @@ func (pder *MemProvider) SessionRead(sid string) (Store, error) {
pder.lock.RUnlock() pder.lock.RUnlock()
pder.lock.Lock() pder.lock.Lock()
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})} newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
element := pder.list.PushBack(newsess) element := pder.list.PushFront(newsess)
pder.sessions[sid] = element pder.sessions[sid] = element
pder.lock.Unlock() pder.lock.Unlock()
return newsess, nil return newsess, nil
@ -134,7 +134,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
pder.lock.RUnlock() pder.lock.RUnlock()
pder.lock.Lock() pder.lock.Lock()
newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})} newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})}
element := pder.list.PushBack(newsess) element := pder.list.PushFront(newsess)
pder.sessions[sid] = element pder.sessions[sid] = element
pder.lock.Unlock() pder.lock.Unlock()
return newsess, nil return newsess, nil

View File

@ -201,7 +201,9 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
if err != nil || cookie.Value == "" { if err != nil || cookie.Value == "" {
return return
} }
manager.provider.SessionDestroy(cookie.Value)
sid, _ := url.QueryUnescape(cookie.Value)
manager.provider.SessionDestroy(sid)
if manager.config.EnableSetCookie { if manager.config.EnableSetCookie {
expiration := time.Now() expiration := time.Now()
cookie = &http.Cookie{Name: manager.config.CookieName, cookie = &http.Cookie{Name: manager.config.CookieName,

View File

@ -93,12 +93,14 @@ type serveContentHolder struct {
var ( var (
staticFileMap = make(map[string]*serveContentHolder) staticFileMap = make(map[string]*serveContentHolder)
mapLock sync.Mutex mapLock sync.RWMutex
) )
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) { func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) {
mapKey := acceptEncoding + ":" + filePath mapKey := acceptEncoding + ":" + filePath
mapLock.RLock()
mapFile, _ := staticFileMap[mapKey] mapFile, _ := staticFileMap[mapKey]
mapLock.RUnlock()
if isOk(mapFile, fi) { if isOk(mapFile, fi) {
return mapFile.encoding != "", mapFile.encoding, mapFile, nil return mapFile.encoding != "", mapFile.encoding, mapFile, nil
} }

View File

@ -7,10 +7,12 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
) )
const licenseFile = "./LICENSE" var currentWorkDir, _ = os.Getwd()
var licenseFile = filepath.Join(currentWorkDir, "LICENSE")
func testOpenFile(encoding string, content []byte, t *testing.T) { func testOpenFile(encoding string, content []byte, t *testing.T) {
fi, _ := os.Stat(licenseFile) fi, _ := os.Stat(licenseFile)

View File

@ -18,23 +18,41 @@ import (
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync"
"github.com/astaxie/beego/utils" "github.com/astaxie/beego/utils"
) )
var ( var (
beegoTplFuncMap = make(template.FuncMap) beegoTplFuncMap = make(template.FuncMap)
// BeeTemplates caching map and supported template file extensions. // beeTemplates caching map and supported template file extensions.
BeeTemplates = make(map[string]*template.Template) beeTemplates = make(map[string]*template.Template)
// BeeTemplateExt stores the template extension which will build templatesLock sync.RWMutex
BeeTemplateExt = []string{"tpl", "html"} // beeTemplateExt stores the template extension which will build
beeTemplateExt = []string{"tpl", "html"}
) )
func executeTemplate(wr io.Writer, name string, data interface{}) error {
if BConfig.RunMode == DEV {
templatesLock.RLock()
defer templatesLock.RUnlock()
}
if t, ok := beeTemplates[name]; ok {
err := t.ExecuteTemplate(wr, name, data)
if err != nil {
Trace("template Execute err:", err)
}
return err
}
panic("can't find templatefile in the path:" + name)
}
func init() { func init() {
beegoTplFuncMap["dateformat"] = DateFormat beegoTplFuncMap["dateformat"] = DateFormat
beegoTplFuncMap["date"] = Date beegoTplFuncMap["date"] = Date
@ -53,7 +71,6 @@ func init() {
beegoTplFuncMap["config"] = GetConfig beegoTplFuncMap["config"] = GetConfig
beegoTplFuncMap["map_get"] = MapGet beegoTplFuncMap["map_get"] = MapGet
// go1.2 added template funcs
// Comparisons // Comparisons
beegoTplFuncMap["eq"] = eq // == beegoTplFuncMap["eq"] = eq // ==
beegoTplFuncMap["ge"] = ge // >= beegoTplFuncMap["ge"] = ge // >=
@ -62,21 +79,25 @@ func init() {
beegoTplFuncMap["lt"] = lt // < beegoTplFuncMap["lt"] = lt // <
beegoTplFuncMap["ne"] = ne // != beegoTplFuncMap["ne"] = ne // !=
beegoTplFuncMap["urlfor"] = URLFor // != beegoTplFuncMap["urlfor"] = URLFor // build a URL to match a Controller and it's method
} }
// AddFuncMap let user to register a func in the template. // AddFuncMap let user to register a func in the template.
func AddFuncMap(key string, funname interface{}) error { func AddFuncMap(key string, fn interface{}) error {
beegoTplFuncMap[key] = funname beegoTplFuncMap[key] = fn
return nil return nil
} }
type templatefile struct { type templateFile struct {
root string root string
files map[string][]string files map[string][]string
} }
func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error { // visit will make the paths into two part,the first is subDir (without tf.root),the second is full path(without tf.root).
// if tf.root="views" and
// paths is "views/errors/404.html",the subDir will be "errors",the file will be "errors/404.html"
// paths is "views/admin/errors/404.html",the subDir will be "admin/errors",the file will be "admin/errors/404.html"
func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error {
if f == nil { if f == nil {
return err return err
} }
@ -88,24 +109,16 @@ func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error {
} }
replace := strings.NewReplacer("\\", "/") replace := strings.NewReplacer("\\", "/")
a := []byte(paths) file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/")
a = a[len([]byte(tf.root)):] subDir := filepath.Dir(file)
file := strings.TrimLeft(replace.Replace(string(a)), "/")
subdir := filepath.Dir(file)
if _, ok := tf.files[subdir]; ok {
tf.files[subdir] = append(tf.files[subdir], file)
} else {
m := make([]string, 1)
m[0] = file
tf.files[subdir] = m
}
tf.files[subDir] = append(tf.files[subDir], file)
return nil return nil
} }
// HasTemplateExt return this path contains supported template extension of beego or not. // HasTemplateExt return this path contains supported template extension of beego or not.
func HasTemplateExt(paths string) bool { func HasTemplateExt(paths string) bool {
for _, v := range BeeTemplateExt { for _, v := range beeTemplateExt {
if strings.HasSuffix(paths, "."+v) { if strings.HasSuffix(paths, "."+v) {
return true return true
} }
@ -115,12 +128,12 @@ func HasTemplateExt(paths string) bool {
// AddTemplateExt add new extension for template. // AddTemplateExt add new extension for template.
func AddTemplateExt(ext string) { func AddTemplateExt(ext string) {
for _, v := range BeeTemplateExt { for _, v := range beeTemplateExt {
if v == ext { if v == ext {
return return
} }
} }
BeeTemplateExt = append(BeeTemplateExt, ext) beeTemplateExt = append(beeTemplateExt, ext)
} }
// BuildTemplate will build all template files in a directory. // BuildTemplate will build all template files in a directory.
@ -132,7 +145,7 @@ func BuildTemplate(dir string, files ...string) error {
} }
return errors.New("dir open err") return errors.New("dir open err")
} }
self := &templatefile{ self := &templateFile{
root: dir, root: dir,
files: make(map[string][]string), files: make(map[string][]string),
} }
@ -146,12 +159,14 @@ func BuildTemplate(dir string, files ...string) error {
for _, v := range self.files { for _, v := range self.files {
for _, file := range v { for _, file := range v {
if len(files) == 0 || utils.InSlice(file, files) { if len(files) == 0 || utils.InSlice(file, files) {
templatesLock.Lock()
t, err := getTemplate(self.root, file, v...) t, err := getTemplate(self.root, file, v...)
if err != nil { if err != nil {
Trace("parse template err:", file, err) Trace("parse template err:", file, err)
} else { } else {
BeeTemplates[file] = t beeTemplates[file] = t
} }
templatesLock.Unlock()
} }
} }
} }
@ -159,16 +174,16 @@ func BuildTemplate(dir string, files ...string) error {
} }
func getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) { func getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) {
var fileabspath string var fileAbsPath string
if filepath.HasPrefix(file, "../") { if filepath.HasPrefix(file, "../") {
fileabspath = filepath.Join(root, filepath.Dir(parent), file) fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
} else { } else {
fileabspath = filepath.Join(root, file) fileAbsPath = filepath.Join(root, file)
} }
if e := utils.FileExists(fileabspath); !e { if e := utils.FileExists(fileAbsPath); !e {
panic("can't find template file:" + file) panic("can't find template file:" + file)
} }
data, err := ioutil.ReadFile(fileabspath) data, err := ioutil.ReadFile(fileAbsPath)
if err != nil { if err != nil {
return nil, [][]string{}, err return nil, [][]string{}, err
} }
@ -177,11 +192,11 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
return nil, [][]string{}, err return nil, [][]string{}, err
} }
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"") reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"")
allsub := reg.FindAllStringSubmatch(string(data), -1) allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, m := range allsub { for _, m := range allSub {
if len(m) == 2 { if len(m) == 2 {
tlook := t.Lookup(m[1]) tl := t.Lookup(m[1])
if tlook != nil { if tl != nil {
continue continue
} }
if !HasTemplateExt(m[1]) { if !HasTemplateExt(m[1]) {
@ -193,17 +208,17 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
} }
} }
} }
return t, allsub, nil return t, allSub, nil
} }
func getTemplate(root, file string, others ...string) (t *template.Template, err error) { func getTemplate(root, file string, others ...string) (t *template.Template, err error) {
t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap) t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap)
var submods [][]string var subMods [][]string
t, submods, err = getTplDeep(root, file, "", t) t, subMods, err = getTplDeep(root, file, "", t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t, err = _getTemplate(t, root, submods, others...) t, err = _getTemplate(t, root, subMods, others...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -211,44 +226,44 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err
return return
} }
func _getTemplate(t0 *template.Template, root string, submods [][]string, others ...string) (t *template.Template, err error) { func _getTemplate(t0 *template.Template, root string, subMods [][]string, others ...string) (t *template.Template, err error) {
t = t0 t = t0
for _, m := range submods { for _, m := range subMods {
if len(m) == 2 { if len(m) == 2 {
templ := t.Lookup(m[1]) tpl := t.Lookup(m[1])
if templ != nil { if tpl != nil {
continue continue
} }
//first check filename //first check filename
for _, otherfile := range others { for _, otherFile := range others {
if otherfile == m[1] { if otherFile == m[1] {
var submods1 [][]string var subMods1 [][]string
t, submods1, err = getTplDeep(root, otherfile, "", t) t, subMods1, err = getTplDeep(root, otherFile, "", t)
if err != nil { if err != nil {
Trace("template parse file err:", err) Trace("template parse file err:", err)
} else if submods1 != nil && len(submods1) > 0 { } else if subMods1 != nil && len(subMods1) > 0 {
t, err = _getTemplate(t, root, submods1, others...) t, err = _getTemplate(t, root, subMods1, others...)
} }
break break
} }
} }
//second check define //second check define
for _, otherfile := range others { for _, otherFile := range others {
fileabspath := filepath.Join(root, otherfile) fileAbsPath := filepath.Join(root, otherFile)
data, err := ioutil.ReadFile(fileabspath) data, err := ioutil.ReadFile(fileAbsPath)
if err != nil { if err != nil {
continue continue
} }
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"") reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"")
allsub := reg.FindAllStringSubmatch(string(data), -1) allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, sub := range allsub { for _, sub := range allSub {
if len(sub) == 2 && sub[1] == m[1] { if len(sub) == 2 && sub[1] == m[1] {
var submods1 [][]string var subMods1 [][]string
t, submods1, err = getTplDeep(root, otherfile, "", t) t, subMods1, err = getTplDeep(root, otherFile, "", t)
if err != nil { if err != nil {
Trace("template parse file err:", err) Trace("template parse file err:", err)
} else if submods1 != nil && len(submods1) > 0 { } else if subMods1 != nil && len(subMods1) > 0 {
t, err = _getTemplate(t, root, submods1, others...) t, err = _getTemplate(t, root, subMods1, others...)
} }
break break
} }
@ -272,7 +287,9 @@ func SetStaticPath(url string, path string) *App {
if !strings.HasPrefix(url, "/") { if !strings.HasPrefix(url, "/") {
url = "/" + url url = "/" + url
} }
url = strings.TrimRight(url, "/") if url != "/" {
url = strings.TrimRight(url, "/")
}
BConfig.WebConfig.StaticDir[url] = path BConfig.WebConfig.StaticDir[url] = path
return BeeApp return BeeApp
} }
@ -282,7 +299,9 @@ func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") { if !strings.HasPrefix(url, "/") {
url = "/" + url url = "/" + url
} }
url = strings.TrimRight(url, "/") if url != "/" {
url = strings.TrimRight(url, "/")
}
delete(BConfig.WebConfig.StaticDir, url) delete(BConfig.WebConfig.StaticDir, url)
return BeeApp return BeeApp
} }

View File

@ -70,10 +70,10 @@ func TestTemplate(t *testing.T) {
if err := BuildTemplate(dir); err != nil { if err := BuildTemplate(dir); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(BeeTemplates) != 3 { if len(beeTemplates) != 3 {
t.Fatalf("should be 3 but got %v", len(BeeTemplates)) t.Fatalf("should be 3 but got %v", len(beeTemplates))
} }
if err := BeeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", nil); err != nil { if err := beeTemplates["index.tpl"].ExecuteTemplate(os.Stdout, "index.tpl", nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, name := range files { for _, name := range files {
@ -126,7 +126,7 @@ func TestRelativeTemplate(t *testing.T) {
if err := BuildTemplate(dir, files[1]); err != nil { if err := BuildTemplate(dir, files[1]); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := BeeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil { if err := beeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
for _, name := range files { for _, name := range files {

45
tree.go
View File

@ -141,7 +141,7 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st
regexpStr = "([^.]+).(.+)" regexpStr = "([^.]+).(.+)"
params = params[1:] params = params[1:]
} else { } else {
for range params { for _ = range params {
regexpStr = "([^/]+)/" + regexpStr regexpStr = "([^/]+)/" + regexpStr
} }
} }
@ -254,7 +254,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
regexpStr = "/([^.]+).(.+)" regexpStr = "/([^.]+).(.+)"
params = params[1:] params = params[1:]
} else { } else {
for range params { for _ = range params {
regexpStr = "/([^/]+)" + regexpStr regexpStr = "/([^/]+)" + regexpStr
} }
} }
@ -265,15 +265,14 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
} }
t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr) t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr)
} else { } else {
var ok bool
var subTree *Tree var subTree *Tree
for _, subTree = range t.fixrouters { for _, sub := range t.fixrouters {
if t.prefix == seg { if sub.prefix == seg {
ok = true subTree = sub
break break
} }
} }
if !ok { if subTree == nil {
subTree = NewTree() subTree = NewTree()
subTree.prefix = seg subTree.prefix = seg
t.fixrouters = append(t.fixrouters, subTree) t.fixrouters = append(t.fixrouters, subTree)
@ -390,7 +389,7 @@ type leafInfo struct {
func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok bool) { func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok bool) {
//fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) //fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps)
if leaf.regexps == nil { if leaf.regexps == nil {
if len(wildcardValues) == 0 { // static path if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path
return true return true
} }
// match * // match *
@ -420,7 +419,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
if len(strs) == 2 { if len(strs) == 2 {
ctx.Input.SetParam(":ext", strs[1]) ctx.Input.SetParam(":ext", strs[1])
} }
ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0])) if index > (len(wildcardValues) - 1) {
ctx.Input.SetParam(":path", "")
} else {
ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
}
return true return true
} }
// match :id // match :id
@ -438,7 +441,9 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
} }
matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...)) matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
for i, match := range matches[1:] { for i, match := range matches[1:] {
ctx.Input.SetParam(leaf.wildcards[i], match) if i < len(leaf.wildcards) {
ctx.Input.SetParam(leaf.wildcards[i], match)
}
} }
return true return true
} }
@ -448,17 +453,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
// "/admin/" -> ["admin"] // "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"] // "/admin/users" -> ["admin", "users"]
func splitPath(key string) []string { func splitPath(key string) []string {
key = strings.Trim(key, "/ ")
if key == "" { if key == "" {
return []string{} return []string{}
} }
elements := strings.Split(key, "/") return strings.Split(key, "/")
if elements[0] == "" {
elements = elements[1:]
}
if elements[len(elements)-1] == "" {
elements = elements[:len(elements)-1]
}
return elements
} }
// "admin" -> false, nil, "" // "admin" -> false, nil, ""
@ -542,13 +541,19 @@ func splitSegment(key string) (bool, []string, string) {
continue continue
} }
} }
if v == ':' { // Escape Sequence '\'
if i > 0 && key[i-1] == '\\' {
out = append(out, v)
} else if v == ':' {
param = make([]rune, 0) param = make([]rune, 0)
start = true start = true
} else if v == '(' { } else if v == '(' {
startexp = true startexp = true
start = false start = false
params = append(params, ":"+string(param)) if len(param) > 0 {
params = append(params, ":"+string(param))
param = make([]rune, 0)
}
paramsNum++ paramsNum++
expt = make([]rune, 0) expt = make([]rune, 0)
expt = append(expt, '(') expt = append(expt, '(')

View File

@ -15,6 +15,7 @@
package beego package beego
import ( import (
"strings"
"testing" "testing"
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
@ -57,6 +58,9 @@ func init() {
"/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg", "/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg",
map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}}) map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}})
routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}}) routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(a)", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(b)", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(c)", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}}) routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}})
routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}}) routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}})
routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}}) routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}})
@ -93,6 +97,21 @@ func TestTreeRouters(t *testing.T) {
} }
} }
func TestStaticPath(t *testing.T) {
tr := NewTree()
tr.AddRouter("/topic/:id", "wildcard")
tr.AddRouter("/topic", "static")
ctx := context.NewContext()
obj := tr.Match("/topic", ctx)
if obj == nil || obj.(string) != "static" {
t.Fatal("/topic is a static route")
}
obj = tr.Match("/topic/1", ctx)
if obj == nil || obj.(string) != "wildcard" {
t.Fatal("/topic/1 is a wildcard route")
}
}
func TestAddTree(t *testing.T) { func TestAddTree(t *testing.T) {
tr := NewTree() tr := NewTree()
tr.AddRouter("/shop/:id/account", "astaxie") tr.AddRouter("/shop/:id/account", "astaxie")
@ -217,6 +236,18 @@ func TestAddTree4(t *testing.T) {
} }
} }
// Test for issue #1595
func TestAddTree5(t *testing.T) {
tr := NewTree()
tr.AddRouter("/v1/shop/:id", "shopdetail")
tr.AddRouter("/v1/shop/", "shophome")
ctx := context.NewContext()
obj := tr.Match("/v1/shop/", ctx)
if obj == nil || obj.(string) != "shophome" {
t.Fatal("url /v1/shop/ need match router /v1/shop/ ")
}
}
func TestSplitPath(t *testing.T) { func TestSplitPath(t *testing.T) {
a := splitPath("") a := splitPath("")
if len(a) != 0 { if len(a) != 0 {
@ -245,48 +276,31 @@ func TestSplitPath(t *testing.T) {
} }
func TestSplitSegment(t *testing.T) { func TestSplitSegment(t *testing.T) {
b, w, r := splitSegment("admin")
if b || len(w) != 0 || r != "" { items := map[string]struct {
t.Fatal("admin should return false, nil, ''") isReg bool
params []string
regStr string
}{
"admin": {false, nil, ""},
"*": {true, []string{":splat"}, ""},
"*.*": {true, []string{".", ":path", ":ext"}, ""},
":id": {true, []string{":id"}, ""},
"?:id": {true, []string{":", ":id"}, ""},
":id:int": {true, []string{":id"}, "([0-9]+)"},
":name:string": {true, []string{":name"}, `([\w]+)`},
":id([0-9]+)": {true, []string{":id"}, `([0-9]+)`},
":id([0-9]+)_:name": {true, []string{":id", ":name"}, `([0-9]+)_(.+)`},
":id(.+)_cms.html": {true, []string{":id"}, `(.+)_cms.html`},
"cms_:id(.+)_:page(.+).html": {true, []string{":id", ":page"}, `cms_(.+)_(.+).html`},
`:app(a|b|c)`: {true, []string{":app"}, `(a|b|c)`},
`:app\((a|b|c)\)`: {true, []string{":app"}, `(.+)\((a|b|c)\)`},
} }
b, w, r = splitSegment("*")
if !b || len(w) != 1 || w[0] != ":splat" || r != "" { for pattern, v := range items {
t.Fatal("* should return true, [:splat], ''") b, w, r := splitSegment(pattern)
} if b != v.isReg || r != v.regStr || strings.Join(w, ",") != strings.Join(v.params, ",") {
b, w, r = splitSegment("*.*") t.Fatalf("%s should return %t,%s,%q, got %t,%s,%q", pattern, v.isReg, v.params, v.regStr, b, w, r)
if !b || len(w) != 3 || w[1] != ":path" || w[2] != ":ext" || w[0] != "." || r != "" { }
t.Fatal("admin should return true,[. :path :ext], ''")
}
b, w, r = splitSegment(":id")
if !b || len(w) != 1 || w[0] != ":id" || r != "" {
t.Fatal(":id should return true, [:id], ''")
}
b, w, r = splitSegment("?:id")
if !b || len(w) != 2 || w[0] != ":" || w[1] != ":id" || r != "" {
t.Fatal("?:id should return true, [: :id], ''")
}
b, w, r = splitSegment(":id:int")
if !b || len(w) != 1 || w[0] != ":id" || r != "([0-9]+)" {
t.Fatal(":id:int should return true, [:id], '([0-9]+)'")
}
b, w, r = splitSegment(":name:string")
if !b || len(w) != 1 || w[0] != ":name" || r != `([\w]+)` {
t.Fatal(`:name:string should return true, [:name], '([\w]+)'`)
}
b, w, r = splitSegment(":id([0-9]+)")
if !b || len(w) != 1 || w[0] != ":id" || r != `([0-9]+)` {
t.Fatal(`:id([0-9]+) should return true, [:id], '([0-9]+)'`)
}
b, w, r = splitSegment(":id([0-9]+)_:name")
if !b || len(w) != 2 || w[0] != ":id" || w[1] != ":name" || r != `([0-9]+)_(.+)` {
t.Fatal(`:id([0-9]+)_:name should return true, [:id :name], '([0-9]+)_(.+)'`)
}
b, w, r = splitSegment(":id(.+)_cms.html")
if !b || len(w) != 1 || w[0] != ":id" || r != `(.+)_cms.html` {
t.Fatal(":id_cms.html should return true, [:id], '(.+)_cms.html'")
}
b, w, r = splitSegment("cms_:id(.+)_:page(.+).html")
if !b || len(w) != 2 || w[0] != ":id" || w[1] != ":page" || r != `cms_(.+)_(.+).html` {
t.Fatal(":id_cms.html should return true, [:id :page], cms_(.+)_(.+).html")
} }
} }

View File

@ -263,7 +263,7 @@ func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha {
beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler) beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler)
// add to template func map // add to template func map
beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML()) beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML)
return cpt return cpt
} }

View File

@ -31,10 +31,13 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync"
) )
const ( const (
maxLineLength = 76 maxLineLength = 76
upperhex = "0123456789ABCDEF"
) )
// Email is the type used for email messages // Email is the type used for email messages
@ -74,9 +77,6 @@ func NewEMail(config string) *Email {
if err != nil { if err != nil {
return nil return nil
} }
if e.From == "" {
e.From = e.Username
}
return e return e
} }
@ -228,14 +228,21 @@ func (e *Email) Send() error {
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc)) to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...) to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
// Check to make sure there is at least one recipient and one "From" address // Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 { if len(to) == 0 {
return errors.New("Must specify at least one From address and one To address") return errors.New("Must specify at least one To address")
} }
from, err := mail.ParseAddress(e.From)
from, err := mail.ParseAddress(e.Username)
if err != nil { if err != nil {
return err return err
} }
e.From = from.String()
if len(e.From) == 0 {
e.From = e.Username
}
// use mail's RFC 2047 to encode any string
e.Subject = qEncode("utf-8", e.Subject)
raw, err := e.Bytes() raw, err := e.Bytes()
if err != nil { if err != nil {
return err return err
@ -342,3 +349,73 @@ func base64Wrap(w io.Writer, b []byte) {
w.Write(out) w.Write(out)
} }
} }
// Encode returns the encoded-word form of s. If s is ASCII without special
// characters, it is returned unchanged. The provided charset is the IANA
// charset name of s. It is case insensitive.
// RFC 2047 encoded-word
func qEncode(charset, s string) string {
if !needsEncoding(s) {
return s
}
return encodeWord(charset, s)
}
func needsEncoding(s string) bool {
for _, b := range s {
if (b < ' ' || b > '~') && b != '\t' {
return true
}
}
return false
}
// encodeWord encodes a string into an encoded-word.
func encodeWord(charset, s string) string {
buf := getBuffer()
buf.WriteString("=?")
buf.WriteString(charset)
buf.WriteByte('?')
buf.WriteByte('q')
buf.WriteByte('?')
enc := make([]byte, 3)
for i := 0; i < len(s); i++ {
b := s[i]
switch {
case b == ' ':
buf.WriteByte('_')
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
buf.WriteByte(b)
default:
enc[0] = '='
enc[1] = upperhex[b>>4]
enc[2] = upperhex[b&0x0f]
buf.Write(enc)
}
}
buf.WriteString("?=")
es := buf.String()
putBuffer(buf)
return es
}
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func getBuffer() *bytes.Buffer {
return bufPool.Get().(*bytes.Buffer)
}
func putBuffer(buf *bytes.Buffer) {
if buf.Len() > 1024 {
return
}
buf.Reset()
bufPool.Put(buf)
}

View File

@ -588,7 +588,7 @@ func (b Base64) GetLimitValue() interface{} {
} }
// just for chinese mobile phone number // just for chinese mobile phone number
var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][0679]|[4][579]))\\d{8}$") var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\\d{8}$")
// Mobile check struct // Mobile check struct
type Mobile struct { type Mobile struct {