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

Merge branch 'develop' into config-logic

This commit is contained in:
youngsterxyf 2016-01-27 10:58:38 +08:00
commit c59a029ce7
24 changed files with 548 additions and 280 deletions

View File

@ -1,8 +1,9 @@
language: go language: go
go: go:
- 1.5.1 - 1.5.3
- 1.4.3
- 1.3.3
services: services:
- redis-server - redis-server
- mysql - mysql
@ -24,7 +25,13 @@ install:
- go get github.com/couchbase/go-couchbase - 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
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"
script:
- go vet -x ./...
- $HOME/gopath/bin/golint ./...
- go test -v ./...

View File

@ -37,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")

View File

@ -105,18 +105,25 @@ 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
// AppPath is the absolute path to the app
AppPath string
// AppConfigPath is the path to the config files
AppConfigPath string
// AppConfigProvider is the provider for the config, default is ini
AppConfigProvider = "ini"
// TemplateCache stores template caching // TemplateCache stores template caching
TemplateCache map[string]*template.Template 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 workPath string
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]))
workPath, _ = os.Getwd()
workPath, _ = filepath.Abs(workPath)
BConfig = &Config{ BConfig = &Config{
AppName: "beego", AppName: "beego",
RunMode: DEV, RunMode: DEV,
@ -177,8 +184,7 @@ func init() {
}, },
} }
AppConfigPath = getDefaultAppConfigPath() AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if !utils.FileExists(AppConfigPath) { if !utils.FileExists(AppConfigPath) {
AppConfig = &beegoAppConfig{config.NewFakeConfig()} AppConfig = &beegoAppConfig{config.NewFakeConfig()}
return return
@ -187,14 +193,12 @@ func init() {
parseConfig(AppConfigPath) parseConfig(AppConfigPath)
} }
func getDefaultAppConfigPath() string {
// default config path
AppPath, _ := filepath.Abs(filepath.Dir(os.Args[0]))
return filepath.Join(AppPath, "conf", "app.conf")
}
// now only support ini, next will support json. // now only support ini, next will support json.
func parseConfig(appConfigPath string) (err error) { func parseConfig(appConfigPath string) (err error) {
if workPath != AppPath {
os.Chdir(AppPath)
}
AppConfig, err = newAppConfig(AppConfigProvider, appConfigPath) AppConfig, err = newAppConfig(AppConfigProvider, appConfigPath)
if err != nil { if err != nil {
return err return err

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

@ -82,7 +82,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.
comment.Write(line) if comment.Len() > 0 {
comment.WriteByte('\n') comment.WriteByte('\n')
}
comment.Write(line)
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.
@ -299,14 +300,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 +349,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 +363,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,31 @@ 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": "",
}
)
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 +89,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{}
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 port, err := iniconf.Int("httpport"); err != nil || port != 8080 { if err != nil {
t.Error(port) t.Fatalf("get key %q value fail,err %s", k, err)
t.Fatal(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 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)
} }
if err = iniconf.Set("name", "astaxie"); err != nil { if err = iniconf.Set("name", "astaxie"); err != nil {
t.Fatal(err) t.Fatal(err)
@ -84,20 +121,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 { func TestIniSave(t *testing.T) {
t.Fatal("get strings error", data)
} else if data[0] != "one" { const (
t.Fatal("get first params error not equat to one") 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, fmt.Errorf("not exist key: %q", key)
}
return false, errors.New("not exist key:" + key)
} }
// DefaultBool return the bool value if has no error // DefaultBool return the bool value if has no error

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
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 port, err := jsonconf.Int("httpport"); err != nil || port != 8080 { if err != nil {
t.Error(port) t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
t.Fatal(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 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)
} }
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

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

View File

@ -68,14 +68,12 @@ 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

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

View File

@ -19,6 +19,7 @@ import (
"log" "log"
"os" "os"
"runtime" "runtime"
"time"
) )
// brush is a color join function // brush is a color join function
@ -53,27 +54,28 @@ type consoleWriter struct {
// 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: log.New(os.Stdout, "", 0),
Level: LevelDebug, Level: LevelDebug,
} }
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) return json.Unmarshal([]byte(jsonConfig), c)
} }
// 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" { msg = formatLogTime(when) + msg
if runtime.GOOS == "windows" {
c.lg.Println(msg) c.lg.Println(msg)
return nil return nil
} }

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

@ -114,59 +114,20 @@ 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 msg = formatLogTime(when) + msg + "\n"
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"
if w.Rotate { if w.Rotate {
d := when.Day()
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()
} }
@ -236,7 +197,7 @@ 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.2.log
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
@ -251,7 +212,7 @@ func (w *fileLogWriter) doRotate() error {
suffix = ".log" suffix = ".log"
} }
for ; err == nil && num <= 999; num++ { 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) _, err = os.Lstat(fName)
} }
// return error if the last file checked still existed // return error if the last file checked still existed

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()
} }
@ -108,6 +109,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
@ -173,9 +175,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 +185,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 +199,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
} }
@ -231,7 +235,7 @@ func (bl *BeeLogger) startLogger() {
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)
} }
} }
@ -351,7 +355,7 @@ func (bl *BeeLogger) Close() {
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
} }
@ -363,3 +367,45 @@ func (bl *BeeLogger) Close() {
} }
bl.outputs = nil 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. // 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

@ -162,6 +162,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

@ -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)

18
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
} }
} }
@ -420,7 +420,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])
} }
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])) ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
}
return true return true
} }
// match :id // match :id
@ -438,8 +442,10 @@ 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:] {
if i < len(leaf.wildcards) {
ctx.Input.SetParam(leaf.wildcards[i], match) ctx.Input.SetParam(leaf.wildcards[i], match)
} }
}
return true return true
} }
@ -536,13 +542,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
if len(param) > 0 {
params = append(params, ":"+string(param)) 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"}})
@ -245,48 +249,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)\)`},
}
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)
} }
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")
} }
} }

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