diff --git a/cache/file.go b/cache/file.go index 4b030980..691ce7cd 100644 --- a/cache/file.go +++ b/cache/file.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "path/filepath" "reflect" @@ -222,33 +223,13 @@ func exists(path string) (bool, error) { // FileGetContents Get bytes to file. // if non-exist, create this file. func FileGetContents(filename string) (data []byte, e error) { - f, e := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) - if e != nil { - return - } - defer f.Close() - stat, e := f.Stat() - if e != nil { - return - } - data = make([]byte, stat.Size()) - result, e := f.Read(data) - if e != nil || int64(result) != stat.Size() { - return nil, e - } - return + return ioutil.ReadFile(filename) } // FilePutContents Put bytes to file. // if non-exist, create this file. func FilePutContents(filename string, content []byte) error { - fp, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) - if err != nil { - return err - } - defer fp.Close() - _, err = fp.Write(content) - return err + return ioutil.WriteFile(filename, content, os.ModePerm) } // GobEncode Gob encodes file cache item. diff --git a/config.go b/config.go index 42b7c70c..51ee08a1 100644 --- a/config.go +++ b/config.go @@ -41,6 +41,7 @@ type Config struct { EnableGzip bool MaxMemory int64 EnableErrorsShow bool + EnableErrorsRender bool Listen Listen WebConfig WebConfig Log LogConfig @@ -171,7 +172,7 @@ func recoverPanic(ctx *context.Context) { logs.Critical(fmt.Sprintf("%s:%d", file, line)) stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) } - if BConfig.RunMode == DEV { + if BConfig.RunMode == DEV && BConfig.EnableErrorsRender { showErr(err, ctx, stack) } } @@ -189,6 +190,7 @@ func newBConfig() *Config { EnableGzip: false, MaxMemory: 1 << 26, //64MB EnableErrorsShow: true, + EnableErrorsRender: true, Listen: Listen{ Graceful: false, ServerTimeOut: 0, diff --git a/config/env/env.go b/config/env/env.go new file mode 100644 index 00000000..a819e51a --- /dev/null +++ b/config/env/env.go @@ -0,0 +1,85 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// Copyright 2017 Faissal Elamraoui. 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 env + +import ( + "fmt" + "os" + "strings" + + "github.com/astaxie/beego/utils" +) + +var env *utils.BeeMap + +func init() { + env = utils.NewBeeMap() + for _, e := range os.Environ() { + splits := strings.Split(e, "=") + env.Set(splits[0], os.Getenv(splits[0])) + } +} + +// Get returns a value by key. +// If the key does not exist, the default value will be returned. +func Get(key string, defVal string) string { + if val := env.Get(key); val != nil { + return val.(string) + } + return defVal +} + +// MustGet returns a value by key. +// If the key does not exist, it will return an error. +func MustGet(key string) (string, error) { + if val := env.Get(key); val != nil { + return val.(string), nil + } + return "", fmt.Errorf("no env variable with %s", key) +} + +// Set sets a value in the ENV copy. +// This does not affect the child process environment. +func Set(key string, value string) { + env.Set(key, value) +} + +// MustSet sets a value in the ENV copy and the child process environment. +// It returns an error in case the set operation failed. +func MustSet(key string, value string) error { + err := os.Setenv(key, value) + if err != nil { + return err + } + env.Set(key, value) + return nil +} + +// GetAll returns all keys/values in the current child process environment. +func GetAll() map[string]string { + items := env.Items() + envs := make(map[string]string, env.Count()) + + for key, val := range items { + switch key := key.(type) { + case string: + switch val := val.(type) { + case string: + envs[key] = val + } + } + } + return envs +} diff --git a/config/env/env_test.go b/config/env/env_test.go new file mode 100644 index 00000000..3f1d4dba --- /dev/null +++ b/config/env/env_test.go @@ -0,0 +1,75 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// Copyright 2017 Faissal Elamraoui. 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 env + +import ( + "os" + "testing" +) + +func TestEnvGet(t *testing.T) { + gopath := Get("GOPATH", "") + if gopath != os.Getenv("GOPATH") { + t.Error("expected GOPATH not empty.") + } + + noExistVar := Get("NOEXISTVAR", "foo") + if noExistVar != "foo" { + t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar) + } +} + +func TestEnvMustGet(t *testing.T) { + gopath, err := MustGet("GOPATH") + if err != nil { + t.Error(err) + } + + if gopath != os.Getenv("GOPATH") { + t.Errorf("expected GOPATH to be the same, got %s.", gopath) + } + + _, err = MustGet("NOEXISTVAR") + if err == nil { + t.Error("expected error to be non-nil") + } +} + +func TestEnvSet(t *testing.T) { + Set("MYVAR", "foo") + myVar := Get("MYVAR", "bar") + if myVar != "foo" { + t.Errorf("expected MYVAR to equal foo, got %s.", myVar) + } +} + +func TestEnvMustSet(t *testing.T) { + err := MustSet("FOO", "bar") + if err != nil { + t.Error(err) + } + + fooVar := os.Getenv("FOO") + if fooVar != "bar" { + t.Errorf("expected FOO variable to equal bar, got %s.", fooVar) + } +} + +func TestEnvGetAll(t *testing.T) { + envMap := GetAll() + if len(envMap) == 0 { + t.Error("expected environment not empty.") + } +} diff --git a/config/ini.go b/config/ini.go index b3332bd8..6b78f02a 100644 --- a/config/ini.go +++ b/config/ini.go @@ -18,16 +18,13 @@ import ( "bufio" "bytes" "errors" - "fmt" "io" "io/ioutil" "os" - "path" "path/filepath" "strconv" "strings" "sync" - "time" ) var ( @@ -52,24 +49,26 @@ func (ini *IniConfig) Parse(name string) (Configer, error) { } func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { - file, err := os.Open(name) + data, err := ioutil.ReadFile(name) if err != nil { return nil, err } + return ini.parseData(data) +} + +func (ini *IniConfig) parseData(data []byte) (*IniConfigContainer, error) { cfg := &IniConfigContainer{ - file.Name(), - make(map[string]map[string]string), - make(map[string]string), - make(map[string]string), - sync.RWMutex{}, + data: make(map[string]map[string]string), + sectionComment: make(map[string]string), + keyComment: make(map[string]string), + RWMutex: sync.RWMutex{}, } cfg.Lock() defer cfg.Unlock() - defer file.Close() var comment bytes.Buffer - buf := bufio.NewReader(file) + buf := bufio.NewReader(bytes.NewBuffer(data)) // check the BOM head, err := buf.Peek(3) if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 { @@ -130,16 +129,24 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { // handle include "other.conf" if len(keyValue) == 1 && strings.HasPrefix(key, "include") { + includefiles := strings.Fields(key) if includefiles[0] == "include" && len(includefiles) == 2 { + otherfile := strings.Trim(includefiles[1], "\"") if !filepath.IsAbs(otherfile) { - otherfile = filepath.Join(filepath.Dir(name), otherfile) + dir, err := os.Getwd() + if err != nil { + return nil, err + } + otherfile = filepath.Join(dir, otherfile) } + i, err := ini.parseFile(otherfile) if err != nil { return nil, err } + for sec, dt := range i.data { if _, ok := cfg.data[sec]; !ok { cfg.data[sec] = make(map[string]string) @@ -148,12 +155,15 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { cfg.data[sec][k] = v } } + for sec, comm := range i.sectionComment { cfg.sectionComment[sec] = comm } + for k, comm := range i.keyComment { cfg.keyComment[k] = comm } + continue } } @@ -178,19 +188,12 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { // ParseData parse ini the data func (ini *IniConfig) ParseData(data []byte) (Configer, error) { - // Save memory data to temporary file - tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) - os.MkdirAll(path.Dir(tmpName), os.ModePerm) - if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { - return nil, err - } - return ini.Parse(tmpName) + return ini.parseData(data) } // IniConfigContainer A Config represents the ini configuration. // When set and get value, support key as section:name type. type IniConfigContainer struct { - filename string data map[string]map[string]string // section=> key:val sectionComment map[string]string // section : comment keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment. diff --git a/config/xml/xml.go b/config/xml/xml.go index 66115714..b82bf403 100644 --- a/config/xml/xml.go +++ b/config/xml/xml.go @@ -35,11 +35,9 @@ import ( "fmt" "io/ioutil" "os" - "path" "strconv" "strings" "sync" - "time" "github.com/astaxie/beego/config" "github.com/beego/x2j" @@ -52,36 +50,26 @@ type Config struct{} // Parse returns a ConfigContainer with parsed xml config map. func (xc *Config) Parse(filename string) (config.Configer, error) { - file, err := os.Open(filename) + context, err := ioutil.ReadFile(filename) if err != nil { return nil, err } - defer file.Close() + return xc.ParseData(context) +} + +// ParseData xml data +func (xc *Config) ParseData(data []byte) (config.Configer, error) { x := &ConfigContainer{data: make(map[string]interface{})} - content, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - d, err := x2j.DocToMap(string(content)) + d, err := x2j.DocToMap(string(data)) if err != nil { return nil, err } x.data = config.ExpandValueEnvForMap(d["config"].(map[string]interface{})) - return x, nil -} -// ParseData xml data -func (xc *Config) ParseData(data []byte) (config.Configer, error) { - // Save memory data to temporary file - tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) - os.MkdirAll(path.Dir(tmpName), os.ModePerm) - if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { - return nil, err - } - return xc.Parse(tmpName) + return x, nil } // ConfigContainer A Config represents the xml configuration. diff --git a/config/yaml/yaml.go b/config/yaml/yaml.go index e3260215..bcef4a20 100644 --- a/config/yaml/yaml.go +++ b/config/yaml/yaml.go @@ -37,10 +37,8 @@ import ( "io/ioutil" "log" "os" - "path" "strings" "sync" - "time" "github.com/astaxie/beego/config" "github.com/beego/goyaml2" @@ -63,26 +61,30 @@ func (yaml *Config) Parse(filename string) (y config.Configer, err error) { // ParseData parse yaml data func (yaml *Config) ParseData(data []byte) (config.Configer, error) { - // Save memory data to temporary file - tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) - os.MkdirAll(path.Dir(tmpName), os.ModePerm) - if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { + cnf, err := parseYML(data) + if err != nil { return nil, err } - return yaml.Parse(tmpName) + + return &ConfigContainer{ + data: cnf, + }, nil } // ReadYmlReader Read yaml file to map. // if json like, use json package, unless goyaml2 package. func ReadYmlReader(path string) (cnf map[string]interface{}, err error) { - f, err := os.Open(path) + buf, err := ioutil.ReadFile(path) if err != nil { return } - defer f.Close() - buf, err := ioutil.ReadAll(f) - if err != nil || len(buf) < 3 { + return parseYML(buf) +} + +// parseYML parse yaml formatted []byte to map. +func parseYML(buf []byte) (cnf map[string]interface{}, err error) { + if len(buf) < 3 { return } diff --git a/context/output.go b/context/output.go index 4b513dd8..564ef96d 100644 --- a/context/output.go +++ b/context/output.go @@ -331,16 +331,17 @@ func (output *BeegoOutput) IsServerError() bool { func stringsToJSON(str string) string { rs := []rune(str) - jsons := "" + var jsons bytes.Buffer for _, r := range rs { rint := int(r) if rint < 128 { - jsons += string(r) + jsons.WriteRune(r) } else { - jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json + jsons.WriteString("\\u") + jsons.WriteString(strconv.FormatInt(int64(rint), 16)) } } - return jsons + return jsons.String() } // Session sets session item value with given key. diff --git a/controller.go b/controller.go index a6fbdd23..488ffcda 100644 --- a/controller.go +++ b/controller.go @@ -69,6 +69,7 @@ type Controller struct { // template data TplName string + ViewPath string Layout string LayoutSections map[string]string // the key is the section name and the value is the template name TplPrefix string @@ -213,7 +214,7 @@ func (c *Controller) RenderBytes() ([]byte, error) { continue } buf.Reset() - err = ExecuteTemplate(&buf, sectionTpl, c.Data) + err = ExecuteViewPathTemplate(&buf, sectionTpl, c.viewPath(), c.Data) if err != nil { return nil, err } @@ -222,7 +223,7 @@ func (c *Controller) RenderBytes() ([]byte, error) { } buf.Reset() - ExecuteTemplate(&buf, c.Layout, c.Data) + ExecuteViewPathTemplate(&buf, c.Layout, c.viewPath() ,c.Data) } return buf.Bytes(), err } @@ -248,9 +249,16 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) { } } } - BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...) + BuildTemplate(c.viewPath() , buildFiles...) } - return buf, ExecuteTemplate(&buf, c.TplName, c.Data) + return buf, ExecuteViewPathTemplate(&buf, c.TplName, c.viewPath(), c.Data) +} + +func (c *Controller) viewPath() string { + if c.ViewPath == "" { + return BConfig.WebConfig.ViewsPath + } + return c.ViewPath } // Redirect sends the redirection response to url with status code. diff --git a/controller_test.go b/controller_test.go index 132971a1..c2025860 100644 --- a/controller_test.go +++ b/controller_test.go @@ -20,6 +20,8 @@ import ( "testing" "github.com/astaxie/beego/context" + "os" + "path/filepath" ) func TestGetInt(t *testing.T) { @@ -121,3 +123,59 @@ func TestGetUint64(t *testing.T) { t.Errorf("TestGetUint64 expect %v,get %T,%v", uint64(math.MaxUint64), val, val) } } + +func TestAdditionalViewPaths(t *testing.T) { + dir1 := "_beeTmp" + dir2 := "_beeTmp2" + defer os.RemoveAll(dir1) + defer os.RemoveAll(dir2) + + dir1file := "file1.tpl" + dir2file := "file2.tpl" + + genFile := func(dir string, name string, content string) { + os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) + if f, err := os.Create(filepath.Join(dir, name)); err != nil { + t.Fatal(err) + } else { + defer f.Close() + f.WriteString(content) + f.Close() + } + + } + genFile(dir1, dir1file, `