1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-22 07:00:55 +00:00

Merge branches 'master' and 'develop' of lcbluestorm.github.com:lcbluestorm/beego into develop

This commit is contained in:
liuchun 2016-03-07 10:19:55 +08:00
commit be23c42674
34 changed files with 710 additions and 329 deletions

View File

@ -1,8 +1,10 @@
language: go
go:
- 1.5.1
- tip
- 1.5.3
- 1.4.3
- 1.3.3
services:
- redis-server
- mysql
@ -24,7 +26,15 @@ install:
- go get github.com/couchbase/go-couchbase
- go get github.com/siddontang/ledisdb/config
- go get github.com/siddontang/ledisdb/ledis
- go get golang.org/x/tools/cmd/vet
- go get github.com/golang/lint/golint
before_script:
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
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
* [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

View File

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

View File

@ -15,7 +15,6 @@
package beego
import (
"fmt"
"os"
"path/filepath"
"strconv"
@ -68,21 +67,6 @@ func Run(params ...string) {
}
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
AddAPPStartHook(registerMime)
AddAPPStartHook(registerDefaultErrorHandler)
@ -101,7 +85,7 @@ func initBeforeHTTPRun() {
// TestBeegoInit is for test package init
func TestBeegoInit(ap string) {
os.Setenv("BEEGO_RUNMODE", "test")
AppConfigPath = filepath.Join(ap, "conf", "app.conf")
appConfigPath = filepath.Join(ap, "conf", "app.conf")
os.Chdir(ap)
initBeforeHTTPRun()
}

View File

@ -37,7 +37,7 @@ func TestMemcacheCache(t *testing.T) {
t.Error("check err")
}
time.Sleep(10 * time.Second)
time.Sleep(11 * time.Second)
if bm.IsExist("astaxie") {
t.Error("check err")

View File

@ -19,6 +19,7 @@ import (
"os"
"path/filepath"
"strings"
"fmt"
"github.com/astaxie/beego/config"
"github.com/astaxie/beego/session"
@ -103,17 +104,29 @@ var (
BConfig *Config
// AppConfig is the instance of Config, store the config information from file
AppConfig *beegoAppConfig
// AppConfigPath is the path to the config files
AppConfigPath string
// AppConfigProvider is the provider for the config, default is ini
AppConfigProvider = "ini"
// AppPath is the absolute path to the app
AppPath string
// TemplateCache stores template caching
TemplateCache map[string]*template.Template
// GlobalSessions is the instance for the session manager
GlobalSessions *session.Manager
workPath string
// appConfigPath is the path to the config files
appConfigPath string
// appConfigProvider is the provider for the config, default is ini
appConfigProvider = "ini"
)
func init() {
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
workPath, _ = os.Getwd()
workPath, _ = filepath.Abs(workPath)
if workPath != AppPath {
os.Chdir(AppPath)
}
BConfig = &Config{
AppName: "beego",
RunMode: DEV,
@ -173,21 +186,19 @@ func init() {
Outputs: map[string]string{"console": ""},
},
}
ParseConfig()
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if !utils.FileExists(appConfigPath) {
AppConfig = &beegoAppConfig{config.NewFakeConfig()}
return
}
parseConfig(appConfigPath)
}
// ParseConfig parsed default config file.
// now only support ini, next will support json.
func ParseConfig() (err error) {
if 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)
func parseConfig(appConfigPath string) (err error) {
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
if err != nil {
return err
}
@ -241,6 +252,8 @@ func ParseConfig() (err error) {
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.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 != "" {
for k := range BConfig.WebConfig.StaticDir {
@ -273,15 +286,58 @@ func ParseConfig() (err error) {
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.Close()
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
}
// 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 {
innerConfig config.Configer
}
func newAppConfig(AppConfigProvider, AppConfigPath string) (*beegoAppConfig, error) {
ac, err := config.NewConfig(AppConfigProvider, AppConfigPath)
func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
ac, err := config.NewConfig(appConfigProvider, appConfigPath)
if err != nil {
return nil, err
}

View File

@ -106,3 +106,39 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
}
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

@ -82,7 +82,7 @@ func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
}
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 {

View File

@ -27,7 +27,6 @@ import (
"strings"
"sync"
"time"
"unicode"
)
var (
@ -97,9 +96,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
}
if bComment != nil {
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.WriteByte('\n')
continue
}
@ -194,7 +195,7 @@ type IniConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) {
return strconv.ParseBool(c.getdata(key))
return ParseBool(c.getdata(key))
}
// DefaultBool returns the boolean value for a given key.
@ -299,14 +300,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
}
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)
// Save default section at first place
if dt, ok := c.data[defaultSection]; ok {
for key, val := range dt {
if key != " " {
// Write key comments.
if v, ok := c.keyComment[key]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
if v := getCommentStr(defaultSection, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
@ -327,8 +349,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
for section, dt := range c.data {
if section != defaultSection {
// Write section comments.
if v, ok := c.sectionComment[section]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
if v := getCommentStr(section, ""); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}
@ -341,8 +363,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
for key, val := range dt {
if key != " " {
// Write key comments.
if v, ok := c.keyComment[key]; ok {
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
if v := getCommentStr(section, key); len(v) > 0 {
if _, err = buf.WriteString(v + lineBreak); err != nil {
return err
}
}

View File

@ -15,11 +15,17 @@
package config
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
)
var inicontext = `
func TestIni(t *testing.T) {
var (
inicontext = `
;comment one
#comment two
appname = beeapi
@ -29,6 +35,13 @@ PI = 3.1415976
runmode = "dev"
autorender = false
copyrequestbody = true
session= on
cookieon= off
newreg = OFF
needlogin = ON
enableSession = Y
enableCookie = N
flag = 1
[demo]
key1="asta"
key2 = "xie"
@ -36,7 +49,31 @@ CaseInsensitive = true
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": "",
}
)
f, err := os.Create("testini.conf")
if err != nil {
t.Fatal(err)
@ -52,31 +89,31 @@ func TestIni(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if iniconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
if port, err := iniconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port)
t.Fatal(err)
}
if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port)
t.Fatal(err)
}
if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 {
t.Error(pi)
t.Fatal(err)
}
if iniconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v, err := iniconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
}
if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true {
t.Error(v)
t.Fatal(err)
for k, v := range keyValue {
var err error
var value interface{}
switch v.(type) {
case int:
value, err = iniconf.Int(k)
case int64:
value, err = iniconf.Int64(k)
case float64:
value, err = iniconf.Float(k)
case bool:
value, err = iniconf.Bool(k)
case []string:
value = iniconf.Strings(k)
case string:
value = iniconf.String(k)
default:
value, err = iniconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fail,err %s", k, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = iniconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
@ -84,20 +121,63 @@ func TestIni(t *testing.T) {
if iniconf.String("name") != "astaxie" {
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 (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
@ -70,12 +71,9 @@ type JSONConfigContainer struct {
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
val := c.getData(key)
if val != nil {
if v, ok := val.(bool); ok {
return v, nil
}
return false, errors.New("not bool value")
return ParseBool(val)
}
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

View File

@ -15,34 +15,14 @@
package config
import (
"fmt"
"os"
"testing"
)
var jsoncontext = `{
"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"
}
}
}`
func TestJsonStartsWithArray(t *testing.T) {
var jsoncontextwitharray = `[
const jsoncontextwitharray = `[
{
"url": "user",
"serviceAPI": "http://www.test.com/user"
@ -52,8 +32,6 @@ var jsoncontextwitharray = `[
"serviceAPI": "http://www.test.com/employee"
}
]`
func TestJsonStartsWithArray(t *testing.T) {
f, err := os.Create("testjsonWithArray.conf")
if err != nil {
t.Fatal(err)
@ -90,6 +68,64 @@ func TestJsonStartsWithArray(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")
if err != nil {
t.Fatal(err)
@ -105,37 +141,32 @@ func TestJson(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if jsonconf.String("appname") != "beeapi" {
t.Fatal("appname not equal to beeapi")
}
if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 {
t.Error(port)
t.Fatal(err)
}
if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 {
t.Error(port)
t.Fatal(err)
}
if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 {
t.Error(pi)
t.Fatal(err)
}
if jsonconf.String("runmode") != "dev" {
t.Fatal("runmode not equal to dev")
}
if v := jsonconf.Strings("unknown"); len(v) > 0 {
t.Fatal("unknown strings, the length should be 0")
}
if v := jsonconf.Strings("testnames"); len(v) != 2 {
t.Fatal("testnames length should be 2")
}
if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
t.Error(v)
t.Fatal(err)
}
if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true {
t.Error(v)
t.Fatal(err)
for k, v := range keyValue {
var err error
var value interface{}
switch v.(type) {
case int:
value, err = jsonconf.Int(k)
case int64:
value, err = jsonconf.Int64(k)
case float64:
value, err = jsonconf.Float(k)
case bool:
value, err = jsonconf.Bool(k)
case []string:
value = jsonconf.Strings(k)
case string:
value = jsonconf.String(k)
default:
value, err = jsonconf.DIY(k)
}
if err != nil {
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
}
}
if err = jsonconf.Set("name", "astaxie"); err != nil {
t.Fatal(err)
@ -143,15 +174,7 @@ func TestJson(t *testing.T) {
if jsonconf.String("name") != "astaxie" {
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 {
t.Fatal(err)
} 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.
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

View File

@ -121,10 +121,10 @@ type ConfigContainer struct {
// Bool returns the boolean value for a given key.
func (c *ConfigContainer) Bool(key string) (bool, error) {
if v, ok := c.data[key].(bool); ok {
return v, nil
if v, ok := c.data[key]; ok {
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

View File

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

View File

@ -18,6 +18,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
)
@ -117,3 +118,56 @@ func TestSubDomain(t *testing.T) {
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

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ import (
"io"
"log"
"net"
"time"
)
// connWriter implements LoggerInterface.
@ -48,7 +49,7 @@ func (c *connWriter) Init(jsonconfig string) error {
// WriteMsg write message in connection.
// 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 {
return nil
}
@ -62,6 +63,9 @@ func (c *connWriter) WriteMsg(msg string, level int) error {
if c.ReconnectOnMsg {
defer c.innerWriter.Close()
}
msg = formatLogTime(when) + msg
c.lg.Println(msg)
return nil
}
@ -94,7 +98,7 @@ func (c *connWriter) connect() error {
}
c.innerWriter = conn
c.lg = log.New(conn, "", log.Ldate|log.Ltime)
c.lg = log.New(conn, "", 0)
return nil
}

View File

@ -19,6 +19,7 @@ import (
"log"
"os"
"runtime"
"time"
)
// brush is a color join function
@ -34,46 +35,49 @@ func newBrush(color string) brush {
}
var colors = []brush{
newBrush("1;37"), // Emergency white
newBrush("1;36"), // Alert cyan
newBrush("1;35"), // Critical magenta
newBrush("1;31"), // Error red
newBrush("1;33"), // Warning yellow
newBrush("1;32"), // Notice green
newBrush("1;34"), // Informational blue
newBrush("1;34"), // Debug blue
newBrush("1;37"), // Emergency white
newBrush("1;36"), // Alert cyan
newBrush("1;35"), // Critical magenta
newBrush("1;31"), // Error red
newBrush("1;33"), // Warning yellow
newBrush("1;32"), // Notice green
newBrush("1;34"), // Informational blue
newBrush("1;34"), // Debug blue
}
// consoleWriter implements LoggerInterface and writes messages to terminal.
type consoleWriter struct {
lg *log.Logger
Level int `json:"level"`
Level int `json:"level"`
Color bool `json:"color"`
}
// NewConsole create ConsoleWriter returning as LoggerInterface.
func NewConsole() Logger {
cw := &consoleWriter{
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
lg: log.New(os.Stdout, "", 0),
Level: LevelDebug,
Color: true,
}
return cw
}
// Init init console logger.
// jsonconfig like '{"level":LevelTrace}'.
func (c *consoleWriter) Init(jsonconfig string) error {
if len(jsonconfig) == 0 {
// jsonConfig like '{"level":LevelTrace}'.
func (c *consoleWriter) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
return nil
}
return json.Unmarshal([]byte(jsonconfig), c)
return json.Unmarshal([]byte(jsonConfig), c)
}
// 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 {
return nil
}
if goos := runtime.GOOS; goos == "windows" {
msg = formatLogTime(when) + msg
if runtime.GOOS == "windows" || !c.Color {
c.lg.Println(msg)
return nil
}

View File

@ -42,3 +42,10 @@ func TestConsole(t *testing.T) {
log2.SetLogger("console", `{"level":3}`)
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
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 {
return nil
}
t := time.Now()
vals := make(map[string]interface{})
vals["@timestamp"] = t.Format(time.RFC3339)
vals["@timestamp"] = when.Format(time.RFC3339)
vals["@msg"] = msg
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",
Fields: vals,
}

View File

@ -114,59 +114,20 @@ func (w *fileLogWriter) needRotate(size int, day int) bool {
}
// 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 {
return nil
}
//2016/01/12 21:34:33
now := time.Now()
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"
msg = formatLogTime(when) + msg + "\n"
if w.Rotate {
d := when.Day()
if w.needRotate(len(msg), d) {
w.Lock()
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)
}
}
w.Unlock()
}
@ -236,7 +197,7 @@ func (w *fileLogWriter) lines() (int, error) {
// DoRotate means it need to write file in new file.
// new file name like xx.2013-01-01.2.log
func (w *fileLogWriter) doRotate() error {
func (w *fileLogWriter) doRotate(logTime time.Time) error {
_, err := os.Lstat(w.Filename)
if err != nil {
return err
@ -251,7 +212,7 @@ func (w *fileLogWriter) doRotate() error {
suffix = ".log"
}
for ; err == nil && num <= 999; num++ {
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", time.Now().Format("2006-01-02"), num, suffix)
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, suffix)
_, err = os.Lstat(fName)
}
// return error if the last file checked still existed

View File

@ -40,6 +40,7 @@ import (
"runtime"
"strconv"
"sync"
"time"
)
// RFC5424 log message levels.
@ -68,7 +69,7 @@ type loggerType func() Logger
// Logger defines the behavior of a log provider.
type Logger interface {
Init(config string) error
WriteMsg(msg string, level int) error
WriteMsg(when time.Time, msg string, level int) error
Destroy()
Flush()
}
@ -108,6 +109,7 @@ type nameLogger struct {
type logMsg struct {
level int
msg string
when time.Time
}
var logMsgPool *sync.Pool
@ -173,9 +175,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error {
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 {
err := l.WriteMsg(msg, level)
err := l.WriteMsg(when, msg, level)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
}
@ -183,6 +185,7 @@ func (bl *BeeLogger) writeToLoggers(msg string, level int) {
}
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
when := time.Now()
if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
if !ok {
@ -196,9 +199,10 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
lm := logMsgPool.Get().(*logMsg)
lm.level = logLevel
lm.msg = msg
lm.when = when
bl.msgChan <- lm
} else {
bl.writeToLoggers(msg, logLevel)
bl.writeToLoggers(when, msg, logLevel)
}
return nil
}
@ -231,7 +235,7 @@ func (bl *BeeLogger) startLogger() {
for {
select {
case bm := <-bl.msgChan:
bl.writeToLoggers(bm.msg, bm.level)
bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm)
}
}
@ -351,7 +355,7 @@ func (bl *BeeLogger) Close() {
for {
if len(bl.msgChan) > 0 {
bm := <-bl.msgChan
bl.writeToLoggers(bm.msg, bm.level)
bl.writeToLoggers(bm.when, bm.msg, bm.level)
logMsgPool.Put(bm)
continue
}
@ -361,4 +365,47 @@ func (bl *BeeLogger) Close() {
l.Flush()
l.Destroy()
}
bl.outputs = nil
}
func formatLogTime(when time.Time) string {
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 string(buf[0:])
}

View File

@ -126,7 +126,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
// WriteMsg write message in smtp writer.
// it will send an email with subject and only this message.
func (s *SMTPWriter) WriteMsg(msg string, level int) error {
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > s.Level {
return nil
}
@ -140,7 +140,7 @@ func (s *SMTPWriter) WriteMsg(msg string, level int) error {
// and send the email all in one step.
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
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)
}

View File

@ -148,6 +148,10 @@ type QuerySeter interface {
// add OFFSET value
// same as Limit function's args[0]
Offset(offset interface{}) QuerySeter
// add GROUP BY expression
// for example:
// qs.GroupBy("id")
GroupBy(exprs ...string) QuerySeter
// add ORDER expression.
// "column" means ASC, "-column" means DESC.
// for example:
@ -162,6 +166,12 @@ type QuerySeter interface {
// qs.RelatedSel("profile").One(&user)
// user.Profile.Age = 32
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
// for example:
// num, err = qs.Filter("profile__age__gt", 28).Count()

View File

@ -130,7 +130,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
}
func genRouterCode() {
os.Mkdir("routers", 0755)
os.Mkdir(path.Join(AppPath, "routers"), 0755)
Info("generate router from comments")
var (
globalinfo string
@ -172,7 +172,7 @@ func genRouterCode() {
}
}
if globalinfo != "" {
f, err := os.Create(path.Join("routers", commentFilename))
f, err := os.Create(path.Join(AppPath, "routers", commentFilename))
if err != nil {
panic(err)
}
@ -182,7 +182,7 @@ func genRouterCode() {
}
func compareFile(pkgRealpath string) bool {
if !utils.FileExists(path.Join("routers", commentFilename)) {
if !utils.FileExists(path.Join(AppPath, "routers", commentFilename)) {
return true
}
if utils.FileExists(lastupdateFilename) {

View File

@ -62,12 +62,12 @@ var (
}
// these beego.Controller's methods shouldn't reflect to AutoRouter
exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString",
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJson", "ServeJsonp",
"ServeXml", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP",
"ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool",
"GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession",
"DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie",
"SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml",
"GetControllerAndAction"}
"GetControllerAndAction", "ServeFormatted"}
urlPlaceholder = "{{placeholder}}"
// DefaultAccessLogFilter will skip the accesslog if return true

View File

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

View File

@ -62,7 +62,7 @@ func init() {
beegoTplFuncMap["lt"] = lt // <
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.
@ -272,7 +272,9 @@ func SetStaticPath(url string, path string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
if url != "/" {
url = strings.TrimRight(url, "/")
}
BConfig.WebConfig.StaticDir[url] = path
return BeeApp
}
@ -282,7 +284,9 @@ func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
url = strings.TrimRight(url, "/")
if url != "/" {
url = strings.TrimRight(url, "/")
}
delete(BConfig.WebConfig.StaticDir, url)
return BeeApp
}

34
tree.go
View File

@ -141,7 +141,7 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st
regexpStr = "([^.]+).(.+)"
params = params[1:]
} else {
for range params {
for _ = range params {
regexpStr = "([^/]+)/" + regexpStr
}
}
@ -254,7 +254,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
regexpStr = "/([^.]+).(.+)"
params = params[1:]
} else {
for range params {
for _ = range params {
regexpStr = "/([^/]+)" + regexpStr
}
}
@ -420,7 +420,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
if len(strs) == 2 {
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
}
// match :id
@ -438,7 +442,9 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
}
matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
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
}
@ -448,17 +454,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
// "/admin/" -> ["admin"]
// "/admin/users" -> ["admin", "users"]
func splitPath(key string) []string {
key = strings.Trim(key, "/ ")
if key == "" {
return []string{}
}
elements := strings.Split(key, "/")
if elements[0] == "" {
elements = elements[1:]
}
if elements[len(elements)-1] == "" {
elements = elements[:len(elements)-1]
}
return elements
return strings.Split(key, "/")
}
// "admin" -> false, nil, ""
@ -542,13 +542,19 @@ func splitSegment(key string) (bool, []string, string) {
continue
}
}
if v == ':' {
// Escape Sequence '\'
if i > 0 && key[i-1] == '\\' {
out = append(out, v)
} else if v == ':' {
param = make([]rune, 0)
start = true
} else if v == '(' {
startexp = true
start = false
params = append(params, ":"+string(param))
if len(param) > 0 {
params = append(params, ":"+string(param))
param = make([]rune, 0)
}
paramsNum++
expt = make([]rune, 0)
expt = append(expt, '(')

View File

@ -15,6 +15,7 @@
package beego
import (
"strings"
"testing"
"github.com/astaxie/beego/context"
@ -57,6 +58,9 @@ func init() {
"/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg",
map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}})
routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/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{"/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"}})
@ -245,48 +249,31 @@ func TestSplitPath(t *testing.T) {
}
func TestSplitSegment(t *testing.T) {
b, w, r := splitSegment("admin")
if b || len(w) != 0 || r != "" {
t.Fatal("admin should return false, nil, ''")
items := map[string]struct {
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 != "" {
t.Fatal("* should return true, [:splat], ''")
}
b, w, r = splitSegment("*.*")
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")
for pattern, v := range items {
b, w, r := splitSegment(pattern)
if b != v.isReg || r != v.regStr || strings.Join(w, ",") != strings.Join(v.params, ",") {
t.Fatalf("%s should return %t,%s,%q, got %t,%s,%q", pattern, v.isReg, v.params, v.regStr, b, w, r)
}
}
}

View File

@ -31,10 +31,13 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
)
const (
maxLineLength = 76
upperhex = "0123456789ABCDEF"
)
// Email is the type used for email messages
@ -74,9 +77,6 @@ func NewEMail(config string) *Email {
if err != nil {
return nil
}
if e.From == "" {
e.From = e.Username
}
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 = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
// Check to make sure there is at least one recipient and one "From" address
if e.From == "" || len(to) == 0 {
return errors.New("Must specify at least one From address and one To address")
if len(to) == 0 {
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 {
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()
if err != nil {
return err
@ -342,3 +349,73 @@ func base64Wrap(w io.Writer, b []byte) {
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)
}