diff --git a/internal/utils/utils_str.go b/internal/utils/utils_str.go index ff56aa0e0..5f6a878a9 100644 --- a/internal/utils/utils_str.go +++ b/internal/utils/utils_str.go @@ -8,7 +8,7 @@ package utils import "strings" -// IsLetterUpper tests whether the given byte b is in upper case. +// IsLetterUpper checks whether the given byte b is in upper case. func IsLetterUpper(b byte) bool { if b >= byte('A') && b <= byte('Z') { return true @@ -16,7 +16,7 @@ func IsLetterUpper(b byte) bool { return false } -// IsLetterLower tests whether the given byte b is in lower case. +// IsLetterLower checks whether the given byte b is in lower case. func IsLetterLower(b byte) bool { if b >= byte('a') && b <= byte('z') { return true @@ -24,7 +24,12 @@ func IsLetterLower(b byte) bool { return false } -// IsNumeric tests whether the given string s is numeric. +// IsLetter checks whether the given byte b is a letter. +func IsLetter(b byte) bool { + return IsLetterUpper(b) || IsLetterLower(b) +} + +// IsNumeric checks whether the given string s is numeric. // Note that float string like "123.456" is also numeric. func IsNumeric(s string) bool { length := len(s) diff --git a/net/ghttp/ghttp_server_router_group.go b/net/ghttp/ghttp_server_router_group.go index 53480e5e6..57fa47751 100644 --- a/net/ghttp/ghttp_server_router_group.go +++ b/net/ghttp/ghttp_server_router_group.go @@ -38,12 +38,13 @@ type ( pattern string object interface{} // Can be handler, controller or object. params []interface{} // Extra parameters for route registering depending on the type. - source string // // Handler is register at certain source file path:line. + source string // Handler is register at certain source file path:line. + bound bool // Is this item bound to server. } ) var ( - preBindItems = make([]preBindItem, 0, 64) + preBindItems = make([]*preBindItem, 0, 64) ) // handlePreBindItems is called when server starts, which does really route registering to the server. @@ -52,6 +53,9 @@ func (s *Server) handlePreBindItems() { return } for _, item := range preBindItems { + if item.bound { + continue + } // Handle the items of current server. if item.group.server != nil && item.group.server != s { continue @@ -60,8 +64,8 @@ func (s *Server) handlePreBindItems() { continue } item.group.doBindRoutersToServer(item) + item.bound = true } - preBindItems = preBindItems[:0] } // Group creates and returns a RouterGroup object. @@ -238,7 +242,7 @@ func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup { // preBindToLocalArray adds the route registering parameters to internal variable array for lazily registering feature. func (g *RouterGroup) preBindToLocalArray(bindType string, pattern string, object interface{}, params ...interface{}) *RouterGroup { _, file, line := gdebug.CallerWithFilter(gFILTER_KEY) - preBindItems = append(preBindItems, preBindItem{ + preBindItems = append(preBindItems, &preBindItem{ group: g, bindType: bindType, pattern: pattern, @@ -261,7 +265,7 @@ func (g *RouterGroup) getPrefix() string { } // doBindRoutersToServer does really registering for the group. -func (g *RouterGroup) doBindRoutersToServer(item preBindItem) *RouterGroup { +func (g *RouterGroup) doBindRoutersToServer(item *preBindItem) *RouterGroup { var ( bindType = item.bindType pattern = item.pattern diff --git a/net/ghttp/ghttp_unit_router_group_test.go b/net/ghttp/ghttp_unit_router_group_test.go index f447560da..46ffd4f9d 100644 --- a/net/ghttp/ghttp_unit_router_group_test.go +++ b/net/ghttp/ghttp_unit_router_group_test.go @@ -214,3 +214,38 @@ func Test_Router_Group_Mthods(t *testing.T) { t.Assert(client.GetContent("/obj/delete"), "1Object Delete2") }) } + +func Test_Router_Group_MultiServer(t *testing.T) { + p1 := ports.PopRand() + p2 := ports.PopRand() + s1 := g.Server(p1) + s2 := g.Server(p2) + s1.Group("/", func(group *ghttp.RouterGroup) { + group.POST("/post", func(r *ghttp.Request) { + r.Response.Write("post1") + }) + }) + s2.Group("/", func(group *ghttp.RouterGroup) { + group.POST("/post", func(r *ghttp.Request) { + r.Response.Write("post2") + }) + }) + s1.SetPort(p1) + s2.SetPort(p2) + s1.SetDumpRouterMap(false) + s2.SetDumpRouterMap(false) + gtest.Assert(s1.Start(), nil) + gtest.Assert(s2.Start(), nil) + defer s1.Shutdown() + defer s2.Shutdown() + + time.Sleep(100 * time.Millisecond) + gtest.C(t, func(t *gtest.T) { + c1 := ghttp.NewClient() + c1.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p1)) + c2 := ghttp.NewClient() + c2.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p2)) + t.Assert(c1.PostContent("/post"), "post1") + t.Assert(c2.PostContent("/post"), "post2") + }) +} diff --git a/os/gfile/gfile_size.go b/os/gfile/gfile_size.go index 048c0be8a..9b4a2bf19 100644 --- a/os/gfile/gfile_size.go +++ b/os/gfile/gfile_size.go @@ -9,6 +9,8 @@ package gfile import ( "fmt" "os" + "strconv" + "strings" ) // Size returns the size of file specified by in byte. @@ -25,50 +27,100 @@ func ReadableSize(path string) string { return FormatSize(Size(path)) } +// StrToSize converts formatted size string to its size in bytes from Bytes to BrontoByte. +func StrToSize(sizeStr string) int64 { + i := 0 + for ; i < len(sizeStr); i++ { + if sizeStr[i] == '.' || (sizeStr[i] >= '0' && sizeStr[i] <= '9') { + continue + } else { + break + } + } + var ( + unit = sizeStr[i:] + number, _ = strconv.ParseFloat(sizeStr[:i], 64) + ) + if unit == "" { + return int64(number) + } + switch strings.ToLower(unit) { + case "b", "bytes": + return int64(number) + case "k", "kb", "kib", "kilobyte": + return int64(number * 1024) + case "m", "mb", "mib", "mebibyte": + return int64(number * 1024 * 1024) + case "g", "gb", "gib", "gigabyte": + return int64(number * 1024 * 1024 * 1024) + case "t", "tb", "tib", "terabyte": + return int64(number * 1024 * 1024 * 1024 * 1024) + case "p", "pb", "pib", "petabyte": + return int64(number * 1024 * 1024 * 1024 * 1024 * 1024) + case "e", "eb", "eib", "exabyte": + return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) + case "z", "zb", "zib", "zettabyte": + return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) + case "y", "yb", "yib", "yottabyte": + return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) + case "bb", "brontobyte": + return int64(number * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024) + } + return -1 +} + // FormatSize formats size for more human readable. func FormatSize(raw int64) string { var r float64 = float64(raw) var t float64 = 1024 var d float64 = 1 - if r < t { return fmt.Sprintf("%.2fB", r/d) } - d *= 1024 t *= 1024 - if r < t { return fmt.Sprintf("%.2fK", r/d) } - d *= 1024 t *= 1024 - if r < t { return fmt.Sprintf("%.2fM", r/d) } - d *= 1024 t *= 1024 - if r < t { return fmt.Sprintf("%.2fG", r/d) } - d *= 1024 t *= 1024 - if r < t { return fmt.Sprintf("%.2fT", r/d) } - d *= 1024 t *= 1024 - if r < t { return fmt.Sprintf("%.2fP", r/d) } - + d *= 1024 + t *= 1024 + if r < t { + return fmt.Sprintf("%.2fE", r/d) + } + d *= 1024 + t *= 1024 + if r < t { + return fmt.Sprintf("%.2fZ", r/d) + } + d *= 1024 + t *= 1024 + if r < t { + return fmt.Sprintf("%.2fY", r/d) + } + d *= 1024 + t *= 1024 + if r < t { + return fmt.Sprintf("%.2fBB", r/d) + } return "TooLarge" } diff --git a/os/gfile/gfile_z_size_test.go b/os/gfile/gfile_z_size_test.go index c0ae9289c..fe74cb392 100644 --- a/os/gfile/gfile_z_size_test.go +++ b/os/gfile/gfile_z_size_test.go @@ -7,6 +7,7 @@ package gfile_test import ( + "github.com/gogf/gf/util/gconv" "testing" "github.com/gogf/gf/os/gfile" @@ -32,6 +33,25 @@ func Test_Size(t *testing.T) { }) } +func Test_StrToSize(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gfile.StrToSize("0.00B"), 0) + t.Assert(gfile.StrToSize("16.00B"), 16) + t.Assert(gfile.StrToSize("1.00K"), 1024) + t.Assert(gfile.StrToSize("1.00KB"), 1024) + t.Assert(gfile.StrToSize("1.00KiloByte"), 1024) + t.Assert(gfile.StrToSize("15.26M"), gconv.Int64(15.26*1024*1024)) + t.Assert(gfile.StrToSize("15.26MB"), gconv.Int64(15.26*1024*1024)) + t.Assert(gfile.StrToSize("1.49G"), gconv.Int64(1.49*1024*1024*1024)) + t.Assert(gfile.StrToSize("1.49GB"), gconv.Int64(1.49*1024*1024*1024)) + t.Assert(gfile.StrToSize("8.73T"), gconv.Int64(8.73*1024*1024*1024*1024)) + t.Assert(gfile.StrToSize("8.73TB"), gconv.Int64(8.73*1024*1024*1024*1024)) + t.Assert(gfile.StrToSize("8.53P"), gconv.Int64(8.53*1024*1024*1024*1024*1024)) + t.Assert(gfile.StrToSize("8.53PB"), gconv.Int64(8.53*1024*1024*1024*1024*1024)) + t.Assert(gfile.StrToSize("8.01EB"), gconv.Int64(8.01*1024*1024*1024*1024*1024*1024)) + }) +} + func Test_FormatSize(t *testing.T) { gtest.C(t, func(t *gtest.T) { t.Assert(gfile.FormatSize(0), "0.00B") diff --git a/os/glog/glog_logger.go b/os/glog/glog_logger.go index 0575ed436..26212b56d 100644 --- a/os/glog/glog_logger.go +++ b/os/glog/glog_logger.go @@ -78,7 +78,7 @@ func (l *Logger) Clone() *Logger { // getFilePointer returns the file pinter for file logging. // It returns nil if file logging is disabled, or file opening fails. -func (l *Logger) getFilePointer() *gfpool.File { +func (l *Logger) getFilePointer(now time.Time) *gfpool.File { if path := l.config.Path; path != "" { // Create path if it does not exist. if !gfile.Exists(path) { @@ -88,7 +88,7 @@ func (l *Logger) getFilePointer() *gfpool.File { } } if fp, err := gfpool.Open( - l.getFilePath(), + l.getFilePath(now), gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil { @@ -101,17 +101,20 @@ func (l *Logger) getFilePointer() *gfpool.File { } // getFilePath returns the logging file path. -func (l *Logger) getFilePath() string { +func (l *Logger) getFilePath(now time.Time) string { // Content containing "{}" in the file name is formatted using gtime. file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string { - return gtime.Now().Format(strings.Trim(s, "{}")) + return gtime.New(now).Format(strings.Trim(s, "{}")) }) return gfile.Join(l.config.Path, file) } // print prints to defined writer, logging file or passed . func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { - buffer := bytes.NewBuffer(nil) + var ( + now = time.Now() + buffer = bytes.NewBuffer(nil) + ) if l.config.HeaderPrint { // Time. timeFormat := "" @@ -125,7 +128,7 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { timeFormat += "15:04:05.000 " } if len(timeFormat) > 0 { - buffer.WriteString(time.Now().Format(timeFormat)) + buffer.WriteString(now.Format(timeFormat)) } // Lead string. if len(lead) > 0 { @@ -179,20 +182,20 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { buffer.WriteString(valueStr + "\n") if l.config.Flags&F_ASYNC > 0 { err := asyncPool.Add(func() { - l.printToWriter(std, buffer) + l.printToWriter(now, std, buffer) }) if err != nil { intlog.Error(err) } } else { - l.printToWriter(std, buffer) + l.printToWriter(now, std, buffer) } } // printToWriter writes buffer to writer. -func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) { +func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffer) { if l.config.Writer == nil { - if f := l.getFilePointer(); f != nil { + if f := l.getFilePointer(now); f != nil { defer f.Close() // Rotation file size checks. if l.config.RotateSize > 0 { @@ -201,8 +204,8 @@ func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) { panic(err) } if state.Size() > l.config.RotateSize { - l.rotateFile() - l.printToWriter(std, buffer) + l.rotateFile(now) + l.printToWriter(now, std, buffer) return } } diff --git a/os/glog/glog_logger_config.go b/os/glog/glog_logger_config.go index e88f19b99..78fbf8f36 100644 --- a/os/glog/glog_logger_config.go +++ b/os/glog/glog_logger_config.go @@ -80,7 +80,7 @@ func (l *Logger) SetConfigWithMap(m map[string]interface{}) error { return errors.New("configuration cannot be empty") } // Change string configuration to int value for level. - levelKey, levelValue := gutil.MapPossibleItemByKey(m, "level") + levelKey, levelValue := gutil.MapPossibleItemByKey(m, "Level") if levelValue != nil { if level, ok := levelStringMap[strings.ToUpper(gconv.String(levelValue))]; ok { m[levelKey] = level @@ -88,6 +88,14 @@ func (l *Logger) SetConfigWithMap(m map[string]interface{}) error { return errors.New(fmt.Sprintf(`invalid level string: %v`, levelValue)) } } + // Change string configuration to int value for file rotation size. + rotateSizeKey, rotateSizeValue := gutil.MapPossibleItemByKey(m, "RotateSize") + if rotateSizeValue != nil { + m[rotateSizeKey] = gfile.StrToSize(gconv.String(rotateSizeValue)) + if m[rotateSizeKey] == -1 { + return errors.New(fmt.Sprintf(`invalid rotate size: %v`, rotateSizeValue)) + } + } config := DefaultConfig() err := gconv.Struct(m, &config) if err != nil { diff --git a/os/glog/glog_logger_rotate.go b/os/glog/glog_logger_rotate.go index 843e77f45..1dcd4474f 100644 --- a/os/glog/glog_logger_rotate.go +++ b/os/glog/glog_logger_rotate.go @@ -15,17 +15,18 @@ import ( "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/os/gtimer" "github.com/gogf/gf/text/gregex" + "time" ) // rotateFile rotates the current logging file. -func (l *Logger) rotateFile() { +func (l *Logger) rotateFile(now time.Time) { // Rotation feature is not enabled as rotation file size is zero. - if l.config.RotateSize == 0 { + if l.config.RotateSize <= 0 { return } l.mu.Lock() defer l.mu.Unlock() - filePath := l.getFilePath() + filePath := l.getFilePath(now) // No backups, it then just removes the current logging file. if l.config.RotateBackups == 0 { if err := gfile.Remove(filePath); err != nil { @@ -66,7 +67,7 @@ func (l *Logger) rotateChecks() { }() // Checks whether file rotation not enabled. - if l.config.RotateSize == 0 || l.config.RotateBackups == 0 { + if l.config.RotateSize <= 0 || l.config.RotateBackups == 0 { return } files, _ := gfile.ScanDirFile(l.config.Path, "*.*", true)