diff --git a/admin.go b/admin.go index 08f8d089..1cab32e0 100644 --- a/admin.go +++ b/admin.go @@ -9,8 +9,8 @@ import ( "github.com/astaxie/beego/utils" ) -// BeeAdminApp is the default AdminApp used by admin module. -var BeeAdminApp *AdminApp +// BeeAdminApp is the default adminApp used by admin module. +var beeAdminApp *adminApp // FilterMonitorFunc is default monitor filter when admin module is enable. // if this func returns, admin module records qbs for this request by condition of this function logic. @@ -31,22 +31,22 @@ var BeeAdminApp *AdminApp var FilterMonitorFunc func(string, string, time.Duration) bool func init() { - BeeAdminApp = &AdminApp{ + beeAdminApp = &adminApp{ routers: make(map[string]http.HandlerFunc), } - BeeAdminApp.Route("/", AdminIndex) - BeeAdminApp.Route("/qps", QpsIndex) - BeeAdminApp.Route("/prof", ProfIndex) - BeeAdminApp.Route("/healthcheck", Healthcheck) - BeeAdminApp.Route("/task", TaskStatus) - BeeAdminApp.Route("/runtask", RunTask) - BeeAdminApp.Route("/listconf", ListConf) + beeAdminApp.Route("/", adminIndex) + beeAdminApp.Route("/qps", qpsIndex) + beeAdminApp.Route("/prof", profIndex) + beeAdminApp.Route("/healthcheck", healthcheck) + beeAdminApp.Route("/task", taskStatus) + beeAdminApp.Route("/runtask", runTask) + beeAdminApp.Route("/listconf", listConf) FilterMonitorFunc = func(string, string, time.Duration) bool { return true } } // AdminIndex is the default http.Handler for admin module. // it matches url pattern "/". -func AdminIndex(rw http.ResponseWriter, r *http.Request) { +func adminIndex(rw http.ResponseWriter, r *http.Request) { rw.Write([]byte("Welcome to Admin Dashboard\n")) rw.Write([]byte("There are servral functions:\n")) rw.Write([]byte("1. Record all request and request time, http://localhost:8088/qps\n")) @@ -60,13 +60,13 @@ func AdminIndex(rw http.ResponseWriter, r *http.Request) { // QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. // it's registered with url pattern "/qbs" in admin module. -func QpsIndex(rw http.ResponseWriter, r *http.Request) { +func qpsIndex(rw http.ResponseWriter, r *http.Request) { toolbox.StatisticsMap.GetMap(rw) } // ListConf is the http.Handler of displaying all beego configuration values as key/value pair. // it's registered with url pattern "/listconf" in admin module. -func ListConf(rw http.ResponseWriter, r *http.Request) { +func listConf(rw http.ResponseWriter, r *http.Request) { r.ParseForm() command := r.Form.Get("command") if command != "" { @@ -183,7 +183,7 @@ func ListConf(rw http.ResponseWriter, r *http.Request) { // ProfIndex is a http.Handler for showing profile command. // it's in url pattern "/prof" in admin module. -func ProfIndex(rw http.ResponseWriter, r *http.Request) { +func profIndex(rw http.ResponseWriter, r *http.Request) { r.ParseForm() command := r.Form.Get("command") if command != "" { @@ -204,7 +204,7 @@ func ProfIndex(rw http.ResponseWriter, r *http.Request) { // Healthcheck is a http.Handler calling health checking and showing the result. // it's in "/healthcheck" pattern in admin module. -func Healthcheck(rw http.ResponseWriter, req *http.Request) { +func healthcheck(rw http.ResponseWriter, req *http.Request) { for name, h := range toolbox.AdminCheckList { if err := h.Check(); err != nil { fmt.Fprintf(rw, "%s : ok\n", name) @@ -216,7 +216,7 @@ func Healthcheck(rw http.ResponseWriter, req *http.Request) { // TaskStatus is a http.Handler with running task status (task name, status and the last execution). // it's in "/task" pattern in admin module. -func TaskStatus(rw http.ResponseWriter, req *http.Request) { +func taskStatus(rw http.ResponseWriter, req *http.Request) { for tname, tk := range toolbox.AdminTaskList { fmt.Fprintf(rw, "%s:%s:%s", tname, tk.GetStatus(), tk.GetPrev().String()) } @@ -224,7 +224,7 @@ func TaskStatus(rw http.ResponseWriter, req *http.Request) { // RunTask is a http.Handler to run a Task from the "query string. // the request url likes /runtask?taskname=sendmail. -func RunTask(rw http.ResponseWriter, req *http.Request) { +func runTask(rw http.ResponseWriter, req *http.Request) { req.ParseForm() taskname := req.Form.Get("taskname") if t, ok := toolbox.AdminTaskList[taskname]; ok { @@ -238,19 +238,19 @@ func RunTask(rw http.ResponseWriter, req *http.Request) { } } -// AdminApp is an http.HandlerFunc map used as BeeAdminApp. -type AdminApp struct { +// adminApp is an http.HandlerFunc map used as beeAdminApp. +type adminApp struct { routers map[string]http.HandlerFunc } -// Route adds http.HandlerFunc to AdminApp with url pattern. -func (admin *AdminApp) Route(pattern string, f http.HandlerFunc) { +// Route adds http.HandlerFunc to adminApp with url pattern. +func (admin *adminApp) Route(pattern string, f http.HandlerFunc) { admin.routers[pattern] = f } -// Run AdminApp http server. +// Run adminApp http server. // Its addr is defined in configuration file as adminhttpaddr and adminhttpport. -func (admin *AdminApp) Run() { +func (admin *adminApp) Run() { if len(toolbox.AdminTaskList) > 0 { toolbox.StartTask() } diff --git a/beego.go b/beego.go index 439add44..32bfe5e4 100644 --- a/beego.go +++ b/beego.go @@ -1,3 +1,4 @@ +// beego is an open-source, high-performance web framework for the Go programming language package beego import ( @@ -13,7 +14,7 @@ import ( ) // beego web framework version. -const VERSION = "1.1.3" +const VERSION = "1.1.4" type hookfunc func() error //hook function to run var hooks []hookfunc //hook function slice to store the hookfunc @@ -137,6 +138,7 @@ func SetStaticPath(url string, path string) *App { if !strings.HasPrefix(url, "/") { url = "/" + url } + url = strings.TrimRight(url, "/") StaticDir[url] = path return BeeApp } @@ -178,7 +180,7 @@ func Run() { initBeforeHttpRun() if EnableAdmin { - go BeeAdminApp.Run() + go beeAdminApp.Run() } BeeApp.Run() diff --git a/cache/memcache.go b/cache/memcache/memcache.go similarity index 97% rename from cache/memcache.go rename to cache/memcache/memcache.go index 365c5de7..130405c4 100644 --- a/cache/memcache.go +++ b/cache/memcache/memcache.go @@ -5,6 +5,8 @@ import ( "errors" "github.com/beego/memcache" + + "github.com/astaxie/beego/cache" ) // Memcache adapter. @@ -147,5 +149,5 @@ func (rc *MemcacheCache) connectInit() (*memcache.Connection, error) { } func init() { - Register("memcache", NewMemCache()) + cache.Register("memcache", NewMemCache()) } diff --git a/cache/redis.go b/cache/redis/redis.go similarity index 97% rename from cache/redis.go rename to cache/redis/redis.go index ba1d4d49..71c7d67e 100644 --- a/cache/redis.go +++ b/cache/redis/redis.go @@ -6,6 +6,8 @@ import ( "time" "github.com/beego/redigo/redis" + + "github.com/astaxie/beego/cache" ) var ( @@ -130,5 +132,5 @@ func (rc *RedisCache) connectInit() { } func init() { - Register("redis", NewRedisCache()) + cache.Register("redis", NewRedisCache()) } diff --git a/config.go b/config.go index 6abc1b68..9b722712 100644 --- a/config.go +++ b/config.go @@ -306,9 +306,9 @@ func ParseConfig() (err error) { sds := strings.Fields(sd) for _, v := range sds { if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 { - StaticDir["/"+url2fsmap[0]] = url2fsmap[1] + StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1] } else { - StaticDir["/"+url2fsmap[0]] = url2fsmap[0] + StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0] } } } diff --git a/config/xml.go b/config/xml/xml.go similarity index 100% rename from config/xml.go rename to config/xml/xml.go diff --git a/config/xml_test.go b/config/xml/xml_test.go similarity index 100% rename from config/xml_test.go rename to config/xml/xml_test.go diff --git a/config/yaml.go b/config/yaml/yaml.go similarity index 100% rename from config/yaml.go rename to config/yaml/yaml.go diff --git a/config/yaml_test.go b/config/yaml/yaml_test.go similarity index 100% rename from config/yaml_test.go rename to config/yaml/yaml_test.go diff --git a/context/input.go b/context/input.go index 0a3c8535..df0680e5 100644 --- a/context/input.go +++ b/context/input.go @@ -2,6 +2,7 @@ package context import ( "bytes" + "errors" "io/ioutil" "net/http" "reflect" @@ -92,6 +93,41 @@ func (input *BeegoInput) Is(method string) bool { return input.Method() == method } +// Is this a GET method request? +func (input *BeegoInput) IsGet() bool { + return input.Is("GET") +} + +// Is this a POST method request? +func (input *BeegoInput) IsPost() bool { + return input.Is("POST") +} + +// Is this a Head method request? +func (input *BeegoInput) IsHead() bool { + return input.Is("HEAD") +} + +// Is this a OPTIONS method request? +func (input *BeegoInput) IsOptions() bool { + return input.Is("OPTIONS") +} + +// Is this a PUT method request? +func (input *BeegoInput) IsPut() bool { + return input.Is("PUT") +} + +// Is this a DELETE method request? +func (input *BeegoInput) IsDelete() bool { + return input.Is("DELETE") +} + +// Is this a PATCH method request? +func (input *BeegoInput) IsPatch() bool { + return input.Is("PATCH") +} + // IsAjax returns boolean of this request is generated by ajax. func (input *BeegoInput) IsAjax() bool { return input.Header("X-Requested-With") == "XMLHttpRequest" @@ -109,7 +145,7 @@ func (input *BeegoInput) IsWebsocket() bool { // IsSecure returns boolean of whether file uploads in this request or not.. func (input *BeegoInput) IsUpload() bool { - return input.Request.MultipartForm != nil + return input.Header("Content-Type") == "multipart/form-data" } // IP returns request client ip. @@ -175,7 +211,9 @@ func (input *BeegoInput) Param(key string) string { // Query returns input data item string by a given string. func (input *BeegoInput) Query(key string) string { - input.Request.ParseForm() + if input.Request.Form == nil { + input.Request.ParseForm() + } return input.Request.Form.Get(key) } @@ -200,7 +238,7 @@ func (input *BeegoInput) Session(key interface{}) interface{} { } // Body returns the raw request body data as bytes. -func (input *BeegoInput) Body() []byte { +func (input *BeegoInput) CopyBody() []byte { requestbody, _ := ioutil.ReadAll(input.Request.Body) input.Request.Body.Close() bf := bytes.NewBuffer(requestbody) @@ -222,3 +260,21 @@ func (input *BeegoInput) GetData(key interface{}) interface{} { func (input *BeegoInput) SetData(key, val interface{}) { input.Data[key] = val } + +func (input *BeegoInput) ParseFormOrMulitForm(maxMemory int64) error { + // Parse the body depending on the content type. + switch input.Header("Content-Type") { + case "application/x-www-form-urlencoded": + // Typical form. + if err := input.Request.ParseForm(); err != nil { + return errors.New("Error parsing request body:" + err.Error()) + } + + case "multipart/form-data": + if err := input.Request.ParseMultipartForm(maxMemory); err != nil { + return errors.New("Error parsing request body:" + err.Error()) + } + } + + return nil +} diff --git a/memzipfile.go b/memzipfile.go index 8d3edd9c..c62e2bc4 100644 --- a/memzipfile.go +++ b/memzipfile.go @@ -14,12 +14,12 @@ import ( "time" ) -var gmfim map[string]*MemFileInfo = make(map[string]*MemFileInfo) +var gmfim map[string]*memFileInfo = make(map[string]*memFileInfo) var lock sync.RWMutex // OpenMemZipFile returns MemFile object with a compressed static file. // it's used for serve static file if gzip enable. -func OpenMemZipFile(path string, zip string) (*MemFile, error) { +func openMemZipFile(path string, zip string) (*memFile, error) { osfile, e := os.Open(path) if e != nil { return nil, e @@ -36,12 +36,9 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) { lock.RLock() cfi, ok := gmfim[zip+":"+path] lock.RUnlock() - if ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize { - - } else { + if !(ok && cfi.ModTime() == modtime && cfi.fileSize == fileSize) { var content []byte if zip == "gzip" { - //将文件内容压缩到zipbuf中 var zipbuf bytes.Buffer gzipwriter, e := gzip.NewWriterLevel(&zipbuf, gzip.BestCompression) if e != nil { @@ -52,13 +49,11 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) { if e != nil { return nil, e } - //读zipbuf到content content, e = ioutil.ReadAll(&zipbuf) if e != nil { return nil, e } } else if zip == "deflate" { - //将文件内容压缩到zipbuf中 var zipbuf bytes.Buffer deflatewriter, e := flate.NewWriter(&zipbuf, flate.BestCompression) if e != nil { @@ -69,7 +64,6 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) { if e != nil { return nil, e } - //将zipbuf读入到content content, e = ioutil.ReadAll(&zipbuf) if e != nil { return nil, e @@ -81,17 +75,17 @@ func OpenMemZipFile(path string, zip string) (*MemFile, error) { } } - cfi = &MemFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize} + cfi = &memFileInfo{osfileinfo, modtime, content, int64(len(content)), fileSize} lock.Lock() defer lock.Unlock() gmfim[zip+":"+path] = cfi } - return &MemFile{fi: cfi, offset: 0}, nil + return &memFile{fi: cfi, offset: 0}, nil } // MemFileInfo contains a compressed file bytes and file information. // it implements os.FileInfo interface. -type MemFileInfo struct { +type memFileInfo struct { os.FileInfo modTime time.Time content []byte @@ -100,62 +94,62 @@ type MemFileInfo struct { } // Name returns the compressed filename. -func (fi *MemFileInfo) Name() string { +func (fi *memFileInfo) Name() string { return fi.Name() } // Size returns the raw file content size, not compressed size. -func (fi *MemFileInfo) Size() int64 { +func (fi *memFileInfo) Size() int64 { return fi.contentSize } // Mode returns file mode. -func (fi *MemFileInfo) Mode() os.FileMode { +func (fi *memFileInfo) Mode() os.FileMode { return fi.Mode() } // ModTime returns the last modified time of raw file. -func (fi *MemFileInfo) ModTime() time.Time { +func (fi *memFileInfo) ModTime() time.Time { return fi.modTime } // IsDir returns the compressing file is a directory or not. -func (fi *MemFileInfo) IsDir() bool { +func (fi *memFileInfo) IsDir() bool { return fi.IsDir() } // return nil. implement the os.FileInfo interface method. -func (fi *MemFileInfo) Sys() interface{} { +func (fi *memFileInfo) Sys() interface{} { return nil } // MemFile contains MemFileInfo and bytes offset when reading. // it implements io.Reader,io.ReadCloser and io.Seeker. -type MemFile struct { - fi *MemFileInfo +type memFile struct { + fi *memFileInfo offset int64 } // Close memfile. -func (f *MemFile) Close() error { +func (f *memFile) Close() error { return nil } // Get os.FileInfo of memfile. -func (f *MemFile) Stat() (os.FileInfo, error) { +func (f *memFile) Stat() (os.FileInfo, error) { return f.fi, nil } // read os.FileInfo of files in directory of memfile. // it returns empty slice. -func (f *MemFile) Readdir(count int) ([]os.FileInfo, error) { +func (f *memFile) Readdir(count int) ([]os.FileInfo, error) { infos := []os.FileInfo{} return infos, nil } // Read bytes from the compressed file bytes. -func (f *MemFile) Read(p []byte) (n int, err error) { +func (f *memFile) Read(p []byte) (n int, err error) { if len(f.fi.content)-int(f.offset) >= len(p) { n = len(p) } else { @@ -171,7 +165,7 @@ var errWhence = errors.New("Seek: invalid whence") var errOffset = errors.New("Seek: invalid offset") // Read bytes from the compressed file bytes by seeker. -func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) { +func (f *memFile) Seek(offset int64, whence int) (ret int64, err error) { switch whence { default: return 0, errWhence @@ -191,7 +185,7 @@ func (f *MemFile) Seek(offset int64, whence int) (ret int64, err error) { // GetAcceptEncodingZip returns accept encoding format in http header. // zip is first, then deflate if both accepted. // If no accepted, return empty string. -func GetAcceptEncodingZip(r *http.Request) string { +func getAcceptEncodingZip(r *http.Request) string { ss := r.Header.Get("Accept-Encoding") ss = strings.ToLower(ss) if strings.Contains(ss, "gzip") { @@ -203,22 +197,3 @@ func GetAcceptEncodingZip(r *http.Request) string { } return "" } - -// CloseZWriter closes the io.Writer after compressing static file. -func CloseZWriter(zwriter io.Writer) { - if zwriter == nil { - return - } - - switch zwriter.(type) { - case *gzip.Writer: - zwriter.(*gzip.Writer).Close() - case *flate.Writer: - zwriter.(*flate.Writer).Close() - //其他情况不close, 保持和默认(非压缩)行为一致 - /* - case io.WriteCloser: - zwriter.(io.WriteCloser).Close() - */ - } -} diff --git a/reload.go b/reload.go index ad8ad8e7..05a24f86 100644 --- a/reload.go +++ b/reload.go @@ -1,4 +1,3 @@ -// Zero-downtime restarts in Go. package beego import ( diff --git a/router.go b/router.go index e21750f4..e123ec55 100644 --- a/router.go +++ b/router.go @@ -7,8 +7,6 @@ import ( "net" "net/http" "net/url" - "os" - "path" "reflect" "regexp" "runtime" @@ -33,7 +31,7 @@ const ( var ( // supported http methods. - HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head"} + HTTPMETHOD = []string{"get", "post", "put", "delete", "patch", "options", "head", "trace", "connect"} // these beego.Controller's methods shouldn't reflect to AutoRouter exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString", "RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJson", "ServeJsonp", @@ -544,88 +542,26 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) http.Error(w, "Method Not Allowed", 405) goto Admin } + //static file server + if serverStaticRouter(context) { + goto Admin + } + + if context.Input.IsPost() { + if CopyRequestBody && !context.Input.IsUpload() { + context.Input.CopyBody() + } + context.Input.ParseFormOrMulitForm(MaxMemory) + } if do_filter(BeforeRouter) { goto Admin } - //static file server - for prefix, staticDir := range StaticDir { - if len(prefix) == 0 { - continue - } - if r.URL.Path == "/favicon.ico" { - file := path.Join(staticDir, r.URL.Path) - if utils.FileExists(file) { - http.ServeFile(w, r, file) - w.started = true - goto Admin - } - } - if strings.HasPrefix(r.URL.Path, prefix) { - if len(r.URL.Path) > len(prefix) && r.URL.Path[len(prefix)] != '/' { - continue - } - if r.URL.Path == prefix && prefix[len(prefix)-1] != '/' { - http.Redirect(rw, r, r.URL.Path+"/", 302) - goto Admin - } - file := path.Join(staticDir, r.URL.Path[len(prefix):]) - finfo, err := os.Stat(file) - if err != nil { - if RunMode == "dev" { - Warn(err) - } - http.NotFound(w, r) - goto Admin - } - //if the request is dir and DirectoryIndex is false then - if finfo.IsDir() && !DirectoryIndex { - middleware.Exception("403", rw, r, "403 Forbidden") - goto Admin - } - - //This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request - isStaticFileToCompress := false - if StaticExtensionsToGzip != nil && len(StaticExtensionsToGzip) > 0 { - for _, statExtension := range StaticExtensionsToGzip { - if strings.HasSuffix(strings.ToLower(file), strings.ToLower(statExtension)) { - isStaticFileToCompress = true - break - } - } - } - - if isStaticFileToCompress { - if EnableGzip { - w.contentEncoding = GetAcceptEncodingZip(r) - } - - memzipfile, err := OpenMemZipFile(file, w.contentEncoding) - if err != nil { - return - } - - w.InitHeadContent(finfo.Size()) - - http.ServeContent(w, r, file, finfo.ModTime(), memzipfile) - } else { - http.ServeFile(w, r, file) - } - - w.started = true - goto Admin - } - } - if do_filter(AfterStatic) { goto Admin } - if CopyRequestBody { - context.Input.Body() - } - if context.Input.RunController != nil && context.Input.RunMethod != "" { findrouter = true runMethod = context.Input.RunMethod @@ -757,9 +693,6 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) } if findrouter { - if r.Method == "POST" { - r.ParseMultipartForm(MaxMemory) - } //execute middleware filters if do_filter(BeforeExec) { goto Admin @@ -830,9 +763,8 @@ func (p *ControllerRegistor) ServeHTTP(rw http.ResponseWriter, r *http.Request) } } -Admin: do_filter(FinishRouter) - +Admin: //admin module record QPS if EnableAdmin { timeend := time.Since(starttime) @@ -891,10 +823,9 @@ func (p *ControllerRegistor) getRunMethod(method string, context *beecontext.Con //responseWriter is a wrapper for the http.ResponseWriter //started set to true if response was written to then don't execute other handler type responseWriter struct { - writer http.ResponseWriter - started bool - status int - contentEncoding string + writer http.ResponseWriter + started bool + status int } // Header returns the header map that will be sent by WriteHeader. @@ -902,17 +833,6 @@ func (w *responseWriter) Header() http.Header { return w.writer.Header() } -// Init content-length header. -func (w *responseWriter) InitHeadContent(contentlength int64) { - if w.contentEncoding == "gzip" { - w.Header().Set("Content-Encoding", "gzip") - } else if w.contentEncoding == "deflate" { - w.Header().Set("Content-Encoding", "deflate") - } else { - w.Header().Set("Content-Length", strconv.FormatInt(contentlength, 10)) - } -} - // 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. diff --git a/session/sess_couchbase.go b/session/couchbase/sess_couchbase.go similarity index 92% rename from session/sess_couchbase.go rename to session/couchbase/sess_couchbase.go index 74b2242c..29661b1a 100644 --- a/session/sess_couchbase.go +++ b/session/couchbase/sess_couchbase.go @@ -1,10 +1,13 @@ package session import ( - "github.com/couchbaselabs/go-couchbase" "net/http" "strings" "sync" + + "github.com/couchbaselabs/go-couchbase" + + "github.com/astaxie/beego/session" ) var couchbpder = &CouchbaseProvider{} @@ -70,7 +73,7 @@ func (cs *CouchbaseSessionStore) SessionRelease(w http.ResponseWriter) { return } - bo, err := encodeGob(cs.values) + bo, err := session.EncodeGob(cs.values) if err != nil { return } @@ -117,7 +120,7 @@ func (cp *CouchbaseProvider) SessionInit(maxlifetime int64, savePath string) err } // read couchbase session by sid -func (cp *CouchbaseProvider) SessionRead(sid string) (SessionStore, error) { +func (cp *CouchbaseProvider) SessionRead(sid string) (session.SessionStore, error) { cp.b = cp.getBucket() var doc []byte @@ -127,7 +130,7 @@ func (cp *CouchbaseProvider) SessionRead(sid string) (SessionStore, error) { if doc == nil { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(doc) + kv, err = session.DecodeGob(doc) if err != nil { return nil, err } @@ -150,7 +153,7 @@ func (cp *CouchbaseProvider) SessionExist(sid string) bool { } } -func (cp *CouchbaseProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { +func (cp *CouchbaseProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) { cp.b = cp.getBucket() var doc []byte @@ -172,7 +175,7 @@ func (cp *CouchbaseProvider) SessionRegenerate(oldsid, sid string) (SessionStore if doc == nil { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(doc) + kv, err = session.DecodeGob(doc) if err != nil { return nil, err } @@ -199,5 +202,5 @@ func (cp *CouchbaseProvider) SessionAll() int { } func init() { - Register("couchbase", couchbpder) + session.Register("couchbase", couchbpder) } diff --git a/session/sess_mysql.go b/session/mysql/sess_mysql.go similarity index 93% rename from session/sess_mysql.go rename to session/mysql/sess_mysql.go index b471c6c0..2e88aec3 100644 --- a/session/sess_mysql.go +++ b/session/mysql/sess_mysql.go @@ -14,6 +14,8 @@ import ( "sync" "time" + "github.com/astaxie/beego/session" + _ "github.com/go-sql-driver/mysql" ) @@ -73,7 +75,7 @@ func (st *MysqlSessionStore) SessionID() string { // must call this method to save values to database. func (st *MysqlSessionStore) SessionRelease(w http.ResponseWriter) { defer st.c.Close() - b, err := encodeGob(st.values) + b, err := session.EncodeGob(st.values) if err != nil { return } @@ -106,7 +108,7 @@ func (mp *MysqlProvider) SessionInit(maxlifetime int64, savePath string) error { } // get mysql session by sid -func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) { +func (mp *MysqlProvider) SessionRead(sid string) (session.SessionStore, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=?", sid) var sessiondata []byte @@ -119,7 +121,7 @@ func (mp *MysqlProvider) SessionRead(sid string) (SessionStore, error) { if len(sessiondata) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(sessiondata) + kv, err = session.DecodeGob(sessiondata) if err != nil { return nil, err } @@ -143,7 +145,7 @@ func (mp *MysqlProvider) SessionExist(sid string) bool { } // generate new sid for mysql session -func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { +func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=?", oldsid) var sessiondata []byte @@ -156,7 +158,7 @@ func (mp *MysqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er if len(sessiondata) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(sessiondata) + kv, err = session.DecodeGob(sessiondata) if err != nil { return nil, err } @@ -194,5 +196,5 @@ func (mp *MysqlProvider) SessionAll() int { } func init() { - Register("mysql", mysqlpder) + session.Register("mysql", mysqlpder) } diff --git a/session/sess_postgresql.go b/session/postgres/sess_postgresql.go similarity index 92% rename from session/sess_postgresql.go rename to session/postgres/sess_postgresql.go index 585a864d..2838a80d 100644 --- a/session/sess_postgresql.go +++ b/session/postgres/sess_postgresql.go @@ -34,6 +34,9 @@ import ( "net/http" "sync" "time" + + "github.com/astaxie/beego/session" + _ "github.com/lib/pq" ) @@ -93,7 +96,7 @@ func (st *PostgresqlSessionStore) SessionID() string { // must call this method to save values to database. func (st *PostgresqlSessionStore) SessionRelease(w http.ResponseWriter) { defer st.c.Close() - b, err := encodeGob(st.values) + b, err := session.EncodeGob(st.values) if err != nil { return } @@ -126,7 +129,7 @@ func (mp *PostgresqlProvider) SessionInit(maxlifetime int64, savePath string) er } // get postgresql session by sid -func (mp *PostgresqlProvider) SessionRead(sid string) (SessionStore, error) { +func (mp *PostgresqlProvider) SessionRead(sid string) (session.SessionStore, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=$1", sid) var sessiondata []byte @@ -138,7 +141,7 @@ func (mp *PostgresqlProvider) SessionRead(sid string) (SessionStore, error) { if err != nil { return nil, err } - } else if err != nil { + } else if err != nil { return nil, err } @@ -146,7 +149,7 @@ func (mp *PostgresqlProvider) SessionRead(sid string) (SessionStore, error) { if len(sessiondata) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(sessiondata) + kv, err = session.DecodeGob(sessiondata) if err != nil { return nil, err } @@ -162,7 +165,7 @@ func (mp *PostgresqlProvider) SessionExist(sid string) bool { row := c.QueryRow("select session_data from session where session_key=$1", sid) var sessiondata []byte err := row.Scan(&sessiondata) - + if err == sql.ErrNoRows { return false } else { @@ -171,21 +174,21 @@ func (mp *PostgresqlProvider) SessionExist(sid string) bool { } // generate new sid for postgresql session -func (mp *PostgresqlProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { +func (mp *PostgresqlProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) { c := mp.connectInit() row := c.QueryRow("select session_data from session where session_key=$1", oldsid) var sessiondata []byte err := row.Scan(&sessiondata) if err == sql.ErrNoRows { c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)", - oldsid, "", time.Now().Format(time.RFC3339)) + oldsid, "", time.Now().Format(time.RFC3339)) } c.Exec("update session set session_key=$1 where session_key=$2", sid, oldsid) var kv map[interface{}]interface{} if len(sessiondata) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(sessiondata) + kv, err = session.DecodeGob(sessiondata) if err != nil { return nil, err } @@ -223,5 +226,5 @@ func (mp *PostgresqlProvider) SessionAll() int { } func init() { - Register("postgresql", postgresqlpder) + session.Register("postgresql", postgresqlpder) } diff --git a/session/sess_redis.go b/session/redis/sess_redis.go similarity index 93% rename from session/sess_redis.go rename to session/redis/sess_redis.go index e64d4c90..00ef4a63 100644 --- a/session/sess_redis.go +++ b/session/redis/sess_redis.go @@ -6,6 +6,8 @@ import ( "strings" "sync" + "github.com/astaxie/beego/session" + "github.com/beego/redigo/redis" ) @@ -77,7 +79,7 @@ func (rs *RedisSessionStore) SessionRelease(w http.ResponseWriter) { return } - b, err := encodeGob(rs.values) + b, err := session.EncodeGob(rs.values) if err != nil { return } @@ -134,7 +136,7 @@ func (rp *RedisProvider) SessionInit(maxlifetime int64, savePath string) error { } // read redis session by sid -func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { +func (rp *RedisProvider) SessionRead(sid string) (session.SessionStore, error) { c := rp.poollist.Get() defer c.Close() @@ -143,7 +145,7 @@ func (rp *RedisProvider) SessionRead(sid string) (SessionStore, error) { if len(kvs) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob([]byte(kvs)) + kv, err = session.DecodeGob([]byte(kvs)) if err != nil { return nil, err } @@ -166,7 +168,7 @@ func (rp *RedisProvider) SessionExist(sid string) bool { } // generate new sid for redis session -func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, error) { +func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (session.SessionStore, error) { c := rp.poollist.Get() defer c.Close() @@ -185,7 +187,7 @@ func (rp *RedisProvider) SessionRegenerate(oldsid, sid string) (SessionStore, er if len(kvs) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob([]byte(kvs)) + kv, err = session.DecodeGob([]byte(kvs)) if err != nil { return nil, err } @@ -215,5 +217,5 @@ func (rp *RedisProvider) SessionAll() int { } func init() { - Register("redis", redispder) + session.Register("redis", redispder) } diff --git a/session/sess_file.go b/session/sess_file.go index 7e9e2229..6ac22b24 100644 --- a/session/sess_file.go +++ b/session/sess_file.go @@ -70,7 +70,7 @@ func (fs *FileSessionStore) SessionID() string { // Write file session to local file with Gob string func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { defer fs.f.Close() - b, err := encodeGob(fs.values) + b, err := EncodeGob(fs.values) if err != nil { return } @@ -123,7 +123,7 @@ func (fp *FileProvider) SessionRead(sid string) (SessionStore, error) { if len(b) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(b) + kv, err = DecodeGob(b) if err != nil { return nil, err } @@ -222,7 +222,7 @@ func (fp *FileProvider) SessionRegenerate(oldsid, sid string) (SessionStore, err if len(b) == 0 { kv = make(map[interface{}]interface{}) } else { - kv, err = decodeGob(b) + kv, err = DecodeGob(b) if err != nil { return nil, err } diff --git a/session/sess_test.go b/session/sess_test.go index d754b526..b903dca6 100644 --- a/session/sess_test.go +++ b/session/sess_test.go @@ -10,11 +10,11 @@ func Test_gob(t *testing.T) { a := make(map[interface{}]interface{}) a["username"] = "astaxie" a[12] = 234 - b, err := encodeGob(a) + b, err := EncodeGob(a) if err != nil { t.Error(err) } - c, err := decodeGob(b) + c, err := DecodeGob(b) if err != nil { t.Error(err) } diff --git a/session/sess_utils.go b/session/sess_utils.go index 73f96630..bea19dba 100644 --- a/session/sess_utils.go +++ b/session/sess_utils.go @@ -27,7 +27,7 @@ func init() { gob.Register(map[int]int64{}) } -func encodeGob(obj map[interface{}]interface{}) ([]byte, error) { +func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { buf := bytes.NewBuffer(nil) enc := gob.NewEncoder(buf) err := enc.Encode(obj) @@ -37,7 +37,7 @@ func encodeGob(obj map[interface{}]interface{}) ([]byte, error) { return buf.Bytes(), nil } -func decodeGob(encoded []byte) (map[interface{}]interface{}, error) { +func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) { buf := bytes.NewBuffer(encoded) dec := gob.NewDecoder(buf) var out map[interface{}]interface{} @@ -97,8 +97,8 @@ func decrypt(block cipher.Block, value []byte) ([]byte, error) { func encodeCookie(block cipher.Block, hashKey, name string, value map[interface{}]interface{}) (string, error) { var err error var b []byte - // 1. encodeGob. - if b, err = encodeGob(value); err != nil { + // 1. EncodeGob. + if b, err = EncodeGob(value); err != nil { return "", err } // 2. Encrypt (optional). @@ -158,8 +158,8 @@ func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime if b, err = decrypt(block, b); err != nil { return nil, err } - // 5. decodeGob. - if dst, err := decodeGob(b); err != nil { + // 5. DecodeGob. + if dst, err := DecodeGob(b); err != nil { return nil, err } else { return dst, nil diff --git a/staticfile.go b/staticfile.go new file mode 100644 index 00000000..e140ed38 --- /dev/null +++ b/staticfile.go @@ -0,0 +1,90 @@ +package beego + +import ( + "net/http" + "os" + "path" + "strconv" + "strings" + + "github.com/astaxie/beego/context" + "github.com/astaxie/beego/middleware" + "github.com/astaxie/beego/utils" +) + +func serverStaticRouter(ctx *context.Context) bool { + requestPath := path.Clean(ctx.Input.Request.URL.Path) + for prefix, staticDir := range StaticDir { + if len(prefix) == 0 { + continue + } + if requestPath == "/favicon.ico" { + file := path.Join(staticDir, requestPath) + if utils.FileExists(file) { + http.ServeFile(ctx.ResponseWriter, ctx.Request, file) + return true + } + } + if strings.HasPrefix(requestPath, prefix) { + if len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { + continue + } + if requestPath == prefix && prefix[len(prefix)-1] != '/' { + http.Redirect(ctx.ResponseWriter, ctx.Request, requestPath+"/", 302) + return true + } + file := path.Join(staticDir, requestPath[len(prefix):]) + finfo, err := os.Stat(file) + if err != nil { + if RunMode == "dev" { + Warn(err) + } + http.NotFound(ctx.ResponseWriter, ctx.Request) + return true + } + //if the request is dir and DirectoryIndex is false then + if finfo.IsDir() && !DirectoryIndex { + middleware.Exception("403", ctx.ResponseWriter, ctx.Request, "403 Forbidden") + return true + } + + //This block obtained from (https://github.com/smithfox/beego) - it should probably get merged into astaxie/beego after a pull request + isStaticFileToCompress := false + if StaticExtensionsToGzip != nil && len(StaticExtensionsToGzip) > 0 { + for _, statExtension := range StaticExtensionsToGzip { + if strings.HasSuffix(strings.ToLower(file), strings.ToLower(statExtension)) { + isStaticFileToCompress = true + break + } + } + } + + if isStaticFileToCompress { + var contentEncoding string + if EnableGzip { + contentEncoding = getAcceptEncodingZip(ctx.Request) + } + + memzipfile, err := openMemZipFile(file, contentEncoding) + if err != nil { + return true + } + + if contentEncoding == "gzip" { + ctx.Output.Header("Content-Encoding", "gzip") + } else if contentEncoding == "deflate" { + ctx.Output.Header("Content-Encoding", "deflate") + } else { + ctx.Output.Header("Content-Length", strconv.FormatInt(finfo.Size(), 10)) + } + + http.ServeContent(ctx.ResponseWriter, ctx.Request, file, finfo.ModTime(), memzipfile) + + } else { + http.ServeFile(ctx.ResponseWriter, ctx.Request, file) + } + return true + } + } + return false +} diff --git a/toolbox/debug.go b/toolbox/debug.go index c22af04f..86638d05 100644 --- a/toolbox/debug.go +++ b/toolbox/debug.go @@ -1,4 +1,4 @@ -// most reference from github.com/realint/dbgutil +// Here are the features: healthcheck, profile, statistics and task. package toolbox import ( @@ -34,7 +34,6 @@ func Display(data ...interface{}) { display(true, data...) } - // return data print string func GetDisplayString(data ...interface{}) string { return display(false, data...)