1
0
mirror of https://github.com/astaxie/beego.git synced 2024-12-26 00:20:49 +00:00

Merge branch 'astaxie/develop' into environmentVar

# Conflicts:
#	config/fake.go
#	config/xml/xml_test.go
#	config/yaml/yaml_test.go
This commit is contained in:
ysqi 2016-03-10 19:57:16 +08:00
commit 86c7f1db9e
52 changed files with 1205 additions and 336 deletions

View File

@ -1,9 +1,10 @@
language: go
go:
- tip
- 1.6.0
- 1.5.3
- 1.4.3
- 1.3.3
services:
- redis-server
- mysql
@ -13,6 +14,11 @@ env:
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
- 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:
- go get github.com/lib/pq
- go get github.com/go-sql-driver/mysql
@ -20,18 +26,26 @@ install:
- go get github.com/bradfitz/gomemcache/memcache
- go get github.com/garyburd/redigo/redis
- go get github.com/beego/x2j
- go get github.com/couchbase/go-couchbase
- go get github.com/beego/goyaml2
- 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/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:
- 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"
- 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
* [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()
}

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

127
config.go
View File

@ -15,7 +15,7 @@
package beego
import (
"html/template"
"fmt"
"os"
"path/filepath"
"strings"
@ -105,22 +105,19 @@ var (
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 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)
os.Chdir(AppPath)
BConfig = &Config{
AppName: "beego",
@ -170,7 +167,7 @@ func init() {
SessionName: "beegosessionID",
SessionGCMaxLifetime: 3600,
SessionProviderConfig: "",
SessionCookieLifeTime: 0, //set cookie default is the brower life
SessionCookieLifeTime: 0, //set cookie default is the browser life
SessionAutoSetCookie: true,
SessionDomain: "",
},
@ -181,34 +178,29 @@ func init() {
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.
func ParseConfig() (err error) {
if AppConfigPath == "" {
// initialize default configurations
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
if !utils.FileExists(AppConfigPath) {
AppConfig = &beegoAppConfig{config.NewFakeConfig()}
return
}
}
if workPath != AppPath {
os.Chdir(AppPath)
}
AppConfig, err = newAppConfig(AppConfigProvider, AppConfigPath)
func parseConfig(appConfigPath string) (err error) {
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
if err != nil {
return err
}
// set the runmode first
// set the run mode first
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
BConfig.RunMode = envRunMode
} else if runmode := AppConfig.String("RunMode"); runmode != "" {
BConfig.RunMode = runmode
} else if runMode := AppConfig.String("RunMode"); runMode != "" {
BConfig.RunMode = runMode
}
BConfig.AppName = AppConfig.DefaultString("AppName", BConfig.AppName)
@ -254,6 +246,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 {
@ -286,15 +280,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.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
}
// 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
}
@ -350,46 +387,46 @@ func (b *beegoAppConfig) Float(key string) (float64, error) {
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 != "" {
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 {
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 {
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 {
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 {
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 {
return v
}
return defaultval
return defaultVal
}
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {

View File

@ -54,12 +54,16 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin
}
func (c *fakeConfigContainer) Strings(key string) []string {
return strings.Split(c.String(key), ";")
v := c.String(key)
if v == "" {
return nil
}
return strings.Split(v, ";")
}
func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v

View File

@ -269,15 +269,20 @@ func (c *IniConfigContainer) DefaultString(key string, defaultval string) string
}
// 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 {
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.
// if err != nil return defaltval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v

View File

@ -75,6 +75,7 @@ password = $ENV_GOROOT
"null": "",
"demo2::key1": "",
"error": "",
"emptystrings": []string{},
}
)

View File

@ -173,7 +173,7 @@ func (c *JSONConfigContainer) DefaultString(key string, defaultval string) strin
func (c *JSONConfigContainer) Strings(key string) []string {
stringVal := c.String(key)
if stringVal == "" {
return []string{}
return nil
}
return strings.Split(c.String(key), ";")
}
@ -181,7 +181,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); len(v) > 0 {
if v := c.Strings(key); v != nil {
return v
}
return defaultval

View File

@ -174,14 +174,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
// Strings returns the []string value for a given key.
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.
// if err != nil return defaltval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v

View File

@ -99,7 +99,6 @@ func TestXML(t *testing.T) {
if xmlconf.String("name") != "astaxie" {
t.Fatal("get name error")
}
if xmlconf.String("path") != os.Getenv("GOROOT") {
t.Fatal("get path error")
}
@ -111,4 +110,7 @@ func TestXML(t *testing.T) {
} else if strings.Contains(dbinfo["detail"], os.Getenv("GOROOT")) == false {
t.Fatal("get goroot path error")
}
if xmlconf.Strings("emptystrings") != nil {
t.Fatal("get emtpy strings error")
}
}

View File

@ -227,14 +227,18 @@ func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
// Strings returns the []string value for a given key.
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.
// if err != nil return defaltval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key)
if len(v) == 0 {
if v == nil {
return defaultval
}
return v

View File

@ -105,4 +105,8 @@ func TestYaml(t *testing.T) {
} else if strings.Contains(dbinfo["detail"], os.Getenv("GOROOT")) == false {
t.Fatal("get GOROOT path error")
}
if yamlconf.Strings("emptystrings") != nil {
t.Fatal("get emtpy strings error")
}
}

View File

@ -24,11 +24,13 @@ package context
import (
"bufio"
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
@ -59,7 +61,10 @@ type Context struct {
// Reset init Context, BeegoInput and BeegoOutput
func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
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.Output.Reset(ctx)
}
@ -176,6 +181,12 @@ type Response struct {
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,
// and sets `started` to true.
// started means the response has sent out.
@ -184,9 +195,21 @@ func (w *Response) Write(p []byte) (int, error) {
return w.ResponseWriter.Write(p)
}
// Write 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 (w *Response) Copy(buf *bytes.Buffer) (int64, error) {
w.Started = true
return io.Copy(w.ResponseWriter, buf)
}
// WriteHeader sends an HTTP response header with status code,
// and sets `started` to true.
func (w *Response) WriteHeader(code int) {
if w.Status > 0 {
//prevent multiple response.WriteHeader calls
return
}
w.Status = code
w.Started = true
w.ResponseWriter.WriteHeader(code)

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

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

View File

@ -185,8 +185,7 @@ func (c *Controller) Render() error {
return err
}
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
c.Ctx.Output.Body(rb)
return nil
return c.Ctx.Output.Body(rb)
}
// 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.
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
var buf bytes.Buffer
if 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
}
buf, err := c.renderTemplate()
//if the controller has set layout, then first get the tplName's content set the content to the layout
if err == nil && c.Layout != "" {
c.Data["LayoutContent"] = template.HTML(buf.String())
if c.LayoutSections != nil {
@ -232,11 +207,9 @@ func (c *Controller) RenderBytes() ([]byte, error) {
c.Data[sectionName] = ""
continue
}
buf.Reset()
err = BeeTemplates[sectionTpl].ExecuteTemplate(&buf, sectionTpl, c.Data)
err = executeTemplate(&buf, sectionTpl, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
c.Data[sectionName] = template.HTML(buf.String())
@ -244,30 +217,32 @@ func (c *Controller) RenderBytes() ([]byte, error) {
}
buf.Reset()
err = BeeTemplates[c.Layout].ExecuteTemplate(&buf, c.Layout, c.Data)
if err != nil {
Trace("template Execute err:", err)
return nil, err
}
return buf.Bytes(), nil
executeTemplate(&buf, c.Layout, c.Data)
}
return buf.Bytes(), err
}
func (c *Controller) renderTemplate() (bytes.Buffer, error) {
var buf bytes.Buffer
if c.TplName == "" {
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
}
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 {
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
return buf, executeTemplate(&buf, c.TplName, c.Data)
}
// 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.
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.
if _, ok := ErrorMaps[body]; ok {
panic(body)

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

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

View File

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

View File

@ -16,7 +16,6 @@ package logs
import (
"encoding/json"
"log"
"os"
"runtime"
"time"
@ -47,15 +46,17 @@ var colors = []brush{
// consoleWriter implements LoggerInterface and writes messages to terminal.
type consoleWriter struct {
lg *log.Logger
Level int `json:"level"`
lg *logWriter
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.
func NewConsole() Logger {
cw := &consoleWriter{
lg: log.New(os.Stdout, "", 0),
Level: LevelDebug,
lg: newLogWriter(os.Stdout),
Level: LevelDebug,
Colorful: true,
}
return cw
}
@ -66,7 +67,11 @@ func (c *consoleWriter) Init(jsonConfig string) error {
if len(jsonConfig) == 0 {
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.
@ -74,13 +79,10 @@ func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > c.Level {
return nil
}
msg = formatLogTime(when) + msg
if runtime.GOOS == "windows" {
c.lg.Println(msg)
return nil
if c.Colorful {
msg = colors[level](msg)
}
c.lg.Println(colors[level](msg))
c.lg.println(when, 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

@ -53,9 +53,11 @@ type fileLogWriter struct {
Level int `json:"level"`
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 {
w := &fileLogWriter{
Filename: "",
@ -89,6 +91,11 @@ func (w *fileLogWriter) Init(jsonConfig string) error {
if len(w.Filename) == 0 {
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()
return err
}
@ -118,10 +125,9 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
if level > w.Level {
return nil
}
msg = formatLogTime(when) + msg + "\n"
h, d := formatTimeHeader(when)
msg = string(h) + msg + "\n"
if w.Rotate {
d := when.Day()
if w.needRotate(len(msg), d) {
w.Lock()
if w.needRotate(len(msg), d) {
@ -196,7 +202,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
// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size)
func (w *fileLogWriter) doRotate(logTime time.Time) error {
_, err := os.Lstat(w.Filename)
if err != nil {
@ -206,13 +212,13 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
// Find the next available number
num := 1
fName := ""
suffix := filepath.Ext(w.Filename)
filenameOnly := strings.TrimSuffix(w.Filename, suffix)
if suffix == "" {
suffix = ".log"
}
for ; err == nil && num <= 999; num++ {
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, suffix)
if w.MaxLines > 0 || w.MaxSize > 0 {
for ; err == nil && num <= 999; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
}
} else {
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix)
_, err = os.Lstat(fName)
}
// return error if the last file checked still existed
@ -250,7 +256,8 @@ func (w *fileLogWriter) deleteOldLog() {
}()
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)
}
}

View File

@ -98,6 +98,8 @@ type BeeLogger struct {
loggerFuncCallDepth int
asynchronous bool
msgChan chan *logMsg
signalChan chan string
wg sync.WaitGroup
outputs []*nameLogger
}
@ -109,7 +111,7 @@ type nameLogger struct {
type logMsg struct {
level int
msg string
when time.Time
when time.Time
}
var logMsgPool *sync.Pool
@ -122,6 +124,7 @@ func NewLogger(channelLen int64) *BeeLogger {
bl.level = LevelDebug
bl.loggerFuncCallDepth = 2
bl.msgChan = make(chan *logMsg, channelLen)
bl.signalChan = make(chan string, 1)
return bl
}
@ -133,6 +136,7 @@ func (bl *BeeLogger) Async() *BeeLogger {
return &logMsg{}
},
}
bl.wg.Add(1)
go bl.startLogger()
return bl
}
@ -232,11 +236,26 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
// start logger chan reading.
// when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() {
gameOver := false
for {
select {
case bm := <-bl.msgChan:
bl.writeToLoggers(bm.when, bm.msg, bm.level)
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
}
}
}
@ -345,13 +364,41 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) {
// Flush flush all chan data.
func (bl *BeeLogger) Flush() {
for _, l := range bl.outputs {
l.Flush()
if bl.asynchronous {
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.
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 {
if len(bl.msgChan) > 0 {
bm := <-bl.msgChan
@ -363,48 +410,5 @@ func (bl *BeeLogger) Close() {
}
for _, l := range bl.outputs {
l.Flush()
l.Destroy()
}
}
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:])
}

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

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

View File

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

View File

@ -102,7 +102,7 @@ func newFields() *fields {
// single field info
type fieldInfo struct {
mi *modelInfo
fieldIndex int
fieldIndex []int
fieldType int
dbcol bool
inModel bool
@ -138,7 +138,7 @@ type fieldInfo struct {
}
// 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 (
tag string
tagValue string
@ -278,7 +278,7 @@ checkType:
fi.column = getColumnName(fieldType, addrField, sf, tags["column"])
fi.addrValue = addrField
fi.sf = sf
fi.fullName = mi.fullName + "." + sf.Name
fi.fullName = mi.fullName + mName + "." + sf.Name
fi.null = attrs["null"]
fi.index = attrs["index"]

View File

@ -36,11 +36,6 @@ type modelInfo struct {
// new model info
func newModelInfo(val reflect.Value) (info *modelInfo) {
var (
err error
fi *fieldInfo
sf reflect.StructField
)
info = &modelInfo{}
info.fields = newFields()
@ -53,13 +48,31 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
info.name = typ.Name()
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++ {
field := ind.Field(i)
sf = ind.Type().Field(i)
if sf.PkgPath != "" {
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 == errSkipField {
@ -84,7 +97,7 @@ func newModelInfo(val reflect.Value) (info *modelInfo) {
}
}
fi.fieldIndex = i
fi.fieldIndex = append(index, i)
fi.mi = info
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))
os.Exit(2)
}
return
}
// combine related model info to new model info.

View File

@ -25,7 +25,6 @@ import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
// As tidb can't use go get, so disable the tidb testing now
// _ "github.com/pingcap/tidb"
)
@ -352,6 +351,30 @@ type GroupPermissions struct {
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 {
Driver 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 false, ind.Field(mi.fields.pk.fieldIndex).Int(), err
return false, ind.FieldByIndex(mi.fields.pk.fieldIndex).Int(), err
}
// 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) {
if mi.fields.pk.auto {
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 {
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}
}
find := ind.Field(fi.fieldIndex)
find := ind.FieldByIndex(fi.fieldIndex)
var nums int64
var err error

View File

@ -51,9 +51,9 @@ func (o *insertSet) Insert(md interface{}) (int64, error) {
if id > 0 {
if o.mi.fields.pk.auto {
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 {
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 {
if fi := sMi.fields.GetByColumn(col); fi != nil {
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value)
o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value)
}
}
} else {
@ -480,7 +480,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
for _, col := range columns {
if fi := sMi.fields.GetByColumn(col); fi != nil {
value := reflect.ValueOf(columnsMp[col]).Elem().Interface()
o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value)
o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value)
}
}
} else {

View File

@ -187,6 +187,7 @@ func TestSyncDb(t *testing.T) {
RegisterModel(new(Group))
RegisterModel(new(Permission))
RegisterModel(new(GroupPermissions))
RegisterModel(new(InLine))
err := RunSyncdb("default", true, Debug)
throwFail(t, err)
@ -206,6 +207,7 @@ func TestRegisterModels(t *testing.T) {
RegisterModel(new(Group))
RegisterModel(new(Permission))
RegisterModel(new(GroupPermissions))
RegisterModel(new(InLine))
BootStrap()
@ -1928,3 +1930,25 @@ func TestReadOrCreate(t *testing.T) {
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
// 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:

View File

@ -607,6 +607,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
)
context := p.pool.Get().(*beecontext.Context)
context.Reset(rw, r)
defer p.pool.Put(context)
defer p.recoverPanic(context)

View File

@ -65,6 +65,11 @@ func (tc *TestController) GetManyRouter() {
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 {
Code int
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) {
r, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()

View File

@ -102,7 +102,7 @@ func (pder *MemProvider) SessionRead(sid string) (Store, error) {
pder.lock.RUnlock()
pder.lock.Lock()
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.lock.Unlock()
return newsess, nil
@ -134,7 +134,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
pder.lock.RUnlock()
pder.lock.Lock()
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.lock.Unlock()
return newsess, nil

View File

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

View File

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

View File

@ -7,10 +7,12 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
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

@ -18,23 +18,41 @@ import (
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"github.com/astaxie/beego/utils"
)
var (
beegoTplFuncMap = make(template.FuncMap)
// BeeTemplates caching map and supported template file extensions.
BeeTemplates = make(map[string]*template.Template)
// BeeTemplateExt stores the template extension which will build
BeeTemplateExt = []string{"tpl", "html"}
// beeTemplates caching map and supported template file extensions.
beeTemplates = make(map[string]*template.Template)
templatesLock sync.RWMutex
// 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() {
beegoTplFuncMap["dateformat"] = DateFormat
beegoTplFuncMap["date"] = Date
@ -53,7 +71,6 @@ func init() {
beegoTplFuncMap["config"] = GetConfig
beegoTplFuncMap["map_get"] = MapGet
// go1.2 added template funcs
// Comparisons
beegoTplFuncMap["eq"] = eq // ==
beegoTplFuncMap["ge"] = ge // >=
@ -66,17 +83,21 @@ func init() {
}
// AddFuncMap let user to register a func in the template.
func AddFuncMap(key string, funname interface{}) error {
beegoTplFuncMap[key] = funname
func AddFuncMap(key string, fn interface{}) error {
beegoTplFuncMap[key] = fn
return nil
}
type templatefile struct {
type templateFile struct {
root 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 {
return err
}
@ -88,24 +109,16 @@ func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error {
}
replace := strings.NewReplacer("\\", "/")
a := []byte(paths)
a = a[len([]byte(tf.root)):]
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
}
file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/")
subDir := filepath.Dir(file)
tf.files[subDir] = append(tf.files[subDir], file)
return nil
}
// HasTemplateExt return this path contains supported template extension of beego or not.
func HasTemplateExt(paths string) bool {
for _, v := range BeeTemplateExt {
for _, v := range beeTemplateExt {
if strings.HasSuffix(paths, "."+v) {
return true
}
@ -115,12 +128,12 @@ func HasTemplateExt(paths string) bool {
// AddTemplateExt add new extension for template.
func AddTemplateExt(ext string) {
for _, v := range BeeTemplateExt {
for _, v := range beeTemplateExt {
if v == ext {
return
}
}
BeeTemplateExt = append(BeeTemplateExt, ext)
beeTemplateExt = append(beeTemplateExt, ext)
}
// 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")
}
self := &templatefile{
self := &templateFile{
root: dir,
files: make(map[string][]string),
}
@ -146,12 +159,14 @@ func BuildTemplate(dir string, files ...string) error {
for _, v := range self.files {
for _, file := range v {
if len(files) == 0 || utils.InSlice(file, files) {
templatesLock.Lock()
t, err := getTemplate(self.root, file, v...)
if err != nil {
Trace("parse template err:", file, err)
} 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) {
var fileabspath string
var fileAbsPath string
if filepath.HasPrefix(file, "../") {
fileabspath = filepath.Join(root, filepath.Dir(parent), file)
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
} 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)
}
data, err := ioutil.ReadFile(fileabspath)
data, err := ioutil.ReadFile(fileAbsPath)
if err != nil {
return nil, [][]string{}, err
}
@ -177,11 +192,11 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp
return nil, [][]string{}, err
}
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"")
allsub := reg.FindAllStringSubmatch(string(data), -1)
for _, m := range allsub {
allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, m := range allSub {
if len(m) == 2 {
tlook := t.Lookup(m[1])
if tlook != nil {
tl := t.Lookup(m[1])
if tl != nil {
continue
}
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) {
t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap)
var submods [][]string
t, submods, err = getTplDeep(root, file, "", t)
var subMods [][]string
t, subMods, err = getTplDeep(root, file, "", t)
if err != nil {
return nil, err
}
t, err = _getTemplate(t, root, submods, others...)
t, err = _getTemplate(t, root, subMods, others...)
if err != nil {
return nil, err
@ -211,44 +226,44 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err
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
for _, m := range submods {
for _, m := range subMods {
if len(m) == 2 {
templ := t.Lookup(m[1])
if templ != nil {
tpl := t.Lookup(m[1])
if tpl != nil {
continue
}
//first check filename
for _, otherfile := range others {
if otherfile == m[1] {
var submods1 [][]string
t, submods1, err = getTplDeep(root, otherfile, "", t)
for _, otherFile := range others {
if otherFile == m[1] {
var subMods1 [][]string
t, subMods1, err = getTplDeep(root, otherFile, "", t)
if err != nil {
Trace("template parse file err:", err)
} else if submods1 != nil && len(submods1) > 0 {
t, err = _getTemplate(t, root, submods1, others...)
} else if subMods1 != nil && len(subMods1) > 0 {
t, err = _getTemplate(t, root, subMods1, others...)
}
break
}
}
//second check define
for _, otherfile := range others {
fileabspath := filepath.Join(root, otherfile)
data, err := ioutil.ReadFile(fileabspath)
for _, otherFile := range others {
fileAbsPath := filepath.Join(root, otherFile)
data, err := ioutil.ReadFile(fileAbsPath)
if err != nil {
continue
}
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"")
allsub := reg.FindAllStringSubmatch(string(data), -1)
for _, sub := range allsub {
allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, sub := range allSub {
if len(sub) == 2 && sub[1] == m[1] {
var submods1 [][]string
t, submods1, err = getTplDeep(root, otherfile, "", t)
var subMods1 [][]string
t, subMods1, err = getTplDeep(root, otherFile, "", t)
if err != nil {
Trace("template parse file err:", err)
} else if submods1 != nil && len(submods1) > 0 {
t, err = _getTemplate(t, root, submods1, others...)
} else if subMods1 != nil && len(subMods1) > 0 {
t, err = _getTemplate(t, root, subMods1, others...)
}
break
}
@ -272,7 +287,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 +299,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
}

View File

@ -70,10 +70,10 @@ func TestTemplate(t *testing.T) {
if err := BuildTemplate(dir); err != nil {
t.Fatal(err)
}
if len(BeeTemplates) != 3 {
t.Fatalf("should be 3 but got %v", len(BeeTemplates))
if len(beeTemplates) != 3 {
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)
}
for _, name := range files {
@ -126,7 +126,7 @@ func TestRelativeTemplate(t *testing.T) {
if err := BuildTemplate(dir, files[1]); err != nil {
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)
}
for _, name := range files {

View File

@ -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)
} else {
var ok bool
var subTree *Tree
for _, subTree = range t.fixrouters {
if t.prefix == seg {
ok = true
for _, sub := range t.fixrouters {
if sub.prefix == seg {
subTree = sub
break
}
}
if !ok {
if subTree == nil {
subTree = NewTree()
subTree.prefix = seg
t.fixrouters = append(t.fixrouters, subTree)

View File

@ -221,6 +221,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) {
a := splitPath("")
if len(a) != 0 {

View File

@ -588,7 +588,7 @@ func (b Base64) GetLimitValue() interface{} {
}
// 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
type Mobile struct {