diff --git a/DONATOR.MD b/DONATOR.MD index 64b845e96..892de6304 100644 --- a/DONATOR.MD +++ b/DONATOR.MD @@ -11,6 +11,7 @@ |[wxkj](https://gitee.com/wxkj)|wechat|¥10.00 |[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 +|[arden](https://github.com/arden)|alipay|¥10.00 |潘兄|wechat|¥100.00 |土豆相公|alipay|¥66.60 |上海金保证网络科技|bank|¥2000.00 diff --git a/RELEASE.MD b/RELEASE.MD index 93ce84596..31a031b1a 100644 --- a/RELEASE.MD +++ b/RELEASE.MD @@ -1,3 +1,63 @@ +# `v1.7.0` +## 新功能/改进 +1. 重构改进`glog`模块: + - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 + - 增加日志内容的异步输出特性:https://goframe.org/os/glog/async + - 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json + - 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags + - 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer + - 修改`SetStdPrint`方法名为`SetStdoutPrint` + - 修改链式方法`StdPrint`方法名为`Stdout` + - 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出 + - 新增更多的链式方法支持:https://goframe.org/os/glog/chain +1. 重构改进`gmap`模块: + - 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap` + - 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap` + - 改进`Map/Keys/Values`方法以提高性能 + - 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes` + - 新增更多功能方法支持:https://goframe.org/container/gmap/index +1. 改进`gtime`时间模块: + - 增加并完善更多的类`PHP`时间格式支持 + - 新增更多功能方法,如`FormatTo`/`LayoutTo`等等 + - 详见开发文档:https://goframe.org/os/gtime/index +1. 改进`gdb`数据库模块: + - 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior + - 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句 + - 其他的细节处理改进 +1. 改进`gtcp`通信模块: + - 完善处理细节,提高通信性能; + - 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls + - 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg + - TCP服务端增加`Close`方法 + - 更多细节查看开发文档:https://goframe.org/net/gtcp/index +1. 改进`gconv`类型转换模块 + - 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration` + - 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换 + - 详见开发文档:https://goframe.org/util/gconv/struct +1. 改进`ghttp`模块: + - 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs + - 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性 + - 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址 + - `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client +1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index +1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index +1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index +1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index +1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes` +1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index +1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境; +1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index +1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理 +1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法 +1. 去除`gvar.VarRead`接口类型支持 + +## Bug Fix +1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突; +1. 修复`gcron.AddOnce`方法的细节逻辑问题; +1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误; +1. 修复`gview`模板引擎的并发安全问题; +1. 修复`ghttp.Server`的SESSION初始化过期时间问题; + # `v1.6.0` (2019-04-09) ## 新功能/改进 diff --git a/TODO.MD b/TODO.MD index f68a8f50e..072b2c7d4 100644 --- a/TODO.MD +++ b/TODO.MD @@ -47,7 +47,7 @@ 1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进; 1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式; 1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现; - +1. grpool增加支持阻塞添加任务接口; diff --git a/g/container/gchan/gchan.go b/g/container/gchan/gchan.go index 6b6be6da3..9e762162f 100644 --- a/g/container/gchan/gchan.go +++ b/g/container/gchan/gchan.go @@ -4,7 +4,7 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gchan provides graceful channel for safe operations. +// Package gchan provides graceful channel for no panic operations. // // It's safe to call Chan.Push/Close functions repeatedly. package gchan @@ -14,12 +14,13 @@ import ( "github.com/gogf/gf/g/container/gtype" ) +// Graceful channel. type Chan struct { channel chan interface{} closed *gtype.Bool } -// New creates a graceful channel with given limit. +// New creates a graceful channel with given . func New(limit int) *Chan { return &Chan { channel : make(chan interface{}, limit), @@ -31,7 +32,7 @@ func New(limit int) *Chan { // It is safe to be called repeatedly. func (c *Chan) Push(value interface{}) error { if c.closed.Val() { - return errors.New("closed") + return errors.New("channel is closed") } c.channel <- value return nil @@ -39,6 +40,7 @@ func (c *Chan) Push(value interface{}) error { // Pop pops value from channel. // If there's no value in channel, it would block to wait. +// If the channel is closed, it will return a nil value immediately. func (c *Chan) Pop() interface{} { return <- c.channel } diff --git a/g/container/gqueue/gqueue.go b/g/container/gqueue/gqueue.go index ea99c0c6f..9ec7b12c7 100644 --- a/g/container/gqueue/gqueue.go +++ b/g/container/gqueue/gqueue.go @@ -37,8 +37,8 @@ const ( ) // New returns an empty queue object. -// Optional parameter is used to limit the size of the queue, which is unlimited by default. -// When is given, the queue will be static and high performance which is comparable with stdlib chan. +// Optional parameter is used to limit the size of the queue, which is unlimited in default. +// When is given, the queue will be static and high performance which is comparable with stdlib channel. func New(limit...int) *Queue { q := &Queue { closed : make(chan struct{}, 0), @@ -103,7 +103,7 @@ func (q *Queue) Pop() interface{} { // Close closes the queue. // Notice: It would notify all goroutines return immediately, -// which are being blocked reading by Pop method. +// which are being blocked reading using Pop method. func (q *Queue) Close() { close(q.C) close(q.events) diff --git a/g/crypto/gaes/gaes.go b/g/crypto/gaes/gaes.go index 746deba74..14f34f44b 100644 --- a/g/crypto/gaes/gaes.go +++ b/g/crypto/gaes/gaes.go @@ -25,18 +25,18 @@ func Encrypt(plainText []byte, key []byte, iv...[]byte) ([]byte, error) { return nil, err } blockSize := block.BlockSize() - plainText = PKCS5Padding(plainText, blockSize) - ivValue := ([]byte)(nil) + plainText = PKCS5Padding(plainText, blockSize) + ivValue := ([]byte)(nil) if len(iv) > 0 { ivValue = iv[0] } else { ivValue = []byte(ivDefValue) } blockMode := cipher.NewCBCEncrypter(block, ivValue) - ciphertext := make([]byte, len(plainText)) - blockMode.CryptBlocks(ciphertext, plainText) + cipherText := make([]byte, len(plainText)) + blockMode.CryptBlocks(cipherText, plainText) - return ciphertext, nil + return cipherText, nil } // AES解密, 使用CBC模式,注意key必须为16/24/32位长度,iv初始化向量为非必需参数 diff --git a/g/encoding/gjson/gjson_api_new_load.go b/g/encoding/gjson/gjson_api_new_load.go index 04a4b3166..712a4dd51 100644 --- a/g/encoding/gjson/gjson_api_new_load.go +++ b/g/encoding/gjson/gjson_api_new_load.go @@ -136,7 +136,10 @@ func LoadContent(data interface{}, unsafe...bool) (*Json, error) { var err error var result interface{} b := gconv.Bytes(data) - t := "json" + t := "" + if len(b) == 0 { + return New(nil, unsafe...), nil + } // auto check data type if json.Valid(b) { t = "json" diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 0794b6367..e055ac72d 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -219,13 +219,13 @@ func Redis(name...string) *gredis.Redis { Pass : array[4], }) } else { - glog.Errorfln(`invalid redis node configuration: "%s"`, line) + glog.Errorf(`invalid redis node configuration: "%s"`, line) } } else { - glog.Errorfln(`configuration for redis not found for group "%s"`, group) + glog.Errorf(`configuration for redis not found for group "%s"`, group) } } else { - glog.Errorfln(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath()) + glog.Errorf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath()) } return nil }) diff --git a/g/internal/empty/empty.go b/g/internal/empty/empty.go index 60d4d3406..a0a31d3d2 100644 --- a/g/internal/empty/empty.go +++ b/g/internal/empty/empty.go @@ -7,7 +7,7 @@ package empty import ( - "reflect" + "reflect" ) // 判断给定的变量是否为空。 @@ -35,17 +35,22 @@ func IsEmpty(value interface{}) bool { case string: return value == "" case []byte: return len(value) == 0 default: - // 最后通过反射来判断 + // Finally using reflect. rv := reflect.ValueOf(value) - if rv.IsNil() { - return true - } - kind := rv.Kind() - switch kind { - case reflect.Map: fallthrough - case reflect.Slice: fallthrough - case reflect.Array: + switch rv.Kind() { + case reflect.Chan, + reflect.Map, + reflect.Slice, + reflect.Array: return rv.Len() == 0 + + case reflect.Func, + reflect.Ptr, + reflect.Interface, + reflect.UnsafePointer: + if rv.IsNil() { + return true + } } } return false diff --git a/g/net/ghttp/ghttp_response.go b/g/net/ghttp/ghttp_response.go index b756df942..2956b5769 100644 --- a/g/net/ghttp/ghttp_response.go +++ b/g/net/ghttp/ghttp_response.go @@ -134,7 +134,9 @@ func (r *Response) WriteStatus(status int, content...string) { // 状态码注册回调函数处理 if status != http.StatusOK { if f := r.request.Server.getStatusHandler(status, r.request); f != nil { - f(r.request) + r.Server.niceCallFunc(func() { + f(r.request) + }) // 防止多次设置(http: multiple response.WriteHeader calls) if r.Status == 0 { r.WriteHeader(status) diff --git a/g/net/ghttp/ghttp_response_view.go b/g/net/ghttp/ghttp_response_view.go index 62ec7dd17..93d7a35b4 100644 --- a/g/net/ghttp/ghttp_response_view.go +++ b/g/net/ghttp/ghttp_response_view.go @@ -52,7 +52,10 @@ func (r *Response) buildInVars(params...map[string]interface{}) map[string]inter } else { vars = make(map[string]interface{}) } - vars["Config"] = gins.Config().GetMap("") + // 当配置文件不存在时就不赋值该模板变量,不然会报错 + if c := gins.Config(); c.FilePath() != "" { + vars["Config"] = c.GetMap("") + } vars["Cookie"] = r.request.Cookie.Map() vars["Session"] = r.request.Session.Map() vars["Get"] = r.request.GetQueryMap() diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 13efff4da..aa32e5359 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -389,7 +389,7 @@ func (s *Server) Run() error { // 阻塞等待服务执行完成 <- s.closeChan - glog.Printfln("%d: all servers shutdown", gproc.Pid()) + glog.Printf("%d: all servers shutdown", gproc.Pid()) return nil } @@ -400,7 +400,7 @@ func Wait() { // 阻塞等待服务执行完成 <- allDoneChan - glog.Printfln("%d: all servers shutdown", gproc.Pid()) + glog.Printf("%d: all servers shutdown", gproc.Pid()) } diff --git a/g/net/ghttp/ghttp_server_admin_process.go b/g/net/ghttp/ghttp_server_admin_process.go index 70865c4d1..5b9c1057c 100644 --- a/g/net/ghttp/ghttp_server_admin_process.go +++ b/g/net/ghttp/ghttp_server_admin_process.go @@ -129,7 +129,7 @@ func forkReloadProcess(newExeFilePath...string) error { buffer, _ := gjson.Encode(sfm) p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer)) if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) + glog.Errorf("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer)) return err } return nil @@ -147,7 +147,7 @@ func forkRestartProcess(newExeFilePath...string) error { env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1") p := gproc.NewProcess(path, os.Args, env) if _, err := p.Start(); err != nil { - glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error()) + glog.Errorf("%d: fork process failed, error:%s", gproc.Pid(), err.Error()) return err } return nil @@ -197,14 +197,14 @@ func restartWebServers(signal string, newExeFilePath...string) error { } } else { if err := forkReloadProcess(newExeFilePath...); err != nil { - glog.Printfln("%d: server restarts failed", gproc.Pid()) + glog.Printf("%d: server restarts failed", gproc.Pid()) serverProcessStatus.Set(gADMIN_ACTION_NONE) return err } else { if len(signal) > 0 { - glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal) + glog.Printf("%d: server restarting by signal: %s", gproc.Pid(), signal) } else { - glog.Printfln("%d: server restarting by web admin", gproc.Pid()) + glog.Printf("%d: server restarting by web admin", gproc.Pid()) } } @@ -216,12 +216,12 @@ func restartWebServers(signal string, newExeFilePath...string) error { func shutdownWebServers(signal...string) { serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN) if len(signal) > 0 { - glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal[0]) + glog.Printf("%d: server shutting down by signal: %s", gproc.Pid(), signal[0]) // 在终端信号下,立即执行关闭操作 forceCloseWebServers() allDoneChan <- struct{}{} } else { - glog.Printfln("%d: server shutting down by api", gproc.Pid()) + glog.Printf("%d: server shutting down by api", gproc.Pid()) // 非终端信号下,异步1秒后再执行关闭, // 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了) gtimer.SetTimeout(time.Second, func() { diff --git a/g/net/ghttp/ghttp_server_graceful.go b/g/net/ghttp/ghttp_server_graceful.go index 7ea854cd3..dbe99d32a 100644 --- a/g/net/ghttp/ghttp_server_graceful.go +++ b/g/net/ghttp/ghttp_server_graceful.go @@ -130,7 +130,7 @@ func (s *gracefulServer) doServe() error { if s.fd != 0 { action = "reloaded" } - glog.Printfln("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr) + glog.Printf("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr) s.status = SERVER_STATUS_RUNNING err := s.httpServer.Serve(s.listener) s.status = SERVER_STATUS_STOPPED @@ -173,7 +173,7 @@ func (s *gracefulServer) shutdown() { return } if err := s.httpServer.Shutdown(context.Background()); err != nil { - glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) + glog.Errorf("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err) } } @@ -183,7 +183,7 @@ func (s *gracefulServer) close() { return } if err := s.httpServer.Close(); err != nil { - glog.Errorfln("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err) + glog.Errorf("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err) } } diff --git a/g/net/ghttp/ghttp_server_router.go b/g/net/ghttp/ghttp_server_router.go index c367e371f..c7838c793 100644 --- a/g/net/ghttp/ghttp_server_router.go +++ b/g/net/ghttp/ghttp_server_router.go @@ -84,7 +84,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin caller := s.getHandlerRegisterCallerLine(handler) if len(hook) == 0 { if item, ok := s.routesMap[regkey]; ok { - glog.Errorfln(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file) + glog.Errorf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file) return } } diff --git a/g/net/ghttp/ghttp_server_router_group.go b/g/net/ghttp/ghttp_server_router_group.go index 5e859d0f5..075645b4f 100644 --- a/g/net/ghttp/ghttp_server_router_group.go +++ b/g/net/ghttp/ghttp_server_router_group.go @@ -50,7 +50,7 @@ func (d *Domain) Group(prefix...string) *RouterGroup { func (g *RouterGroup) Bind(items []GroupItem) { for _, item := range items { if len(item) < 3 { - glog.Fatalfln("invalid router item: %s", item) + glog.Fatalf("invalid router item: %s", item) } if strings.EqualFold(gconv.String(item[0]), "REST") { g.bind("REST", gconv.String(item[0]) + ":" + gconv.String(item[1]), item[2]) @@ -124,7 +124,7 @@ func (g *RouterGroup) bind(bindType string, pattern string, object interface{}, if len(g.prefix) > 0 { domain, method, path, err := g.server.parsePattern(pattern) if err != nil { - glog.Fatalfln("invalid pattern: %s", pattern) + glog.Fatalf("invalid pattern: %s", pattern) } if bindType == "HANDLER" { pattern = g.server.serveHandlerKey(method, g.prefix + "/" + strings.TrimLeft(path, "/"), domain) @@ -196,7 +196,7 @@ func (g *RouterGroup) bind(bindType string, pattern string, object interface{}, g.domain.BindHookHandler(pattern, methods[0], h) } } else { - glog.Fatalfln("invalid hook handler for pattern:%s", pattern) + glog.Fatalf("invalid hook handler for pattern:%s", pattern) } } } diff --git a/g/net/ghttp/ghttp_server_service_controller.go b/g/net/ghttp/ghttp_server_service_controller.go index 994d4124f..b5058ab23 100644 --- a/g/net/ghttp/ghttp_server_service_controller.go +++ b/g/net/ghttp/ghttp_server_service_controller.go @@ -50,11 +50,11 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) { if _, ok := v.Method(i).Interface().(func()); !ok { if len(methodMap) > 0 { // 指定的方法名称注册,那么需要使用错误提示 - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, pkgPath, ctlName, mname, v.Method(i).Type().String()) } else { // 否则只是Debug提示 - glog.Debugfln(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`, + glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`, pkgPath, ctlName, mname, v.Method(i).Type().String()) } continue @@ -108,7 +108,7 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string ctlName = fmt.Sprintf(`(%s)`, ctlName) } if _, ok := fval.Interface().(func()); !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, pkgPath, ctlName, mname, fval.Type().String()) return } @@ -147,7 +147,7 @@ func (s *Server)BindControllerRest(pattern string, c Controller) { ctlName = fmt.Sprintf(`(%s)`, ctlName) } if _, ok := v.Method(i).Interface().(func()); !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`, pkgPath, ctlName, mname, v.Method(i).Type().String()) return } diff --git a/g/net/ghttp/ghttp_server_service_object.go b/g/net/ghttp/ghttp_server_service_object.go index 74dcd8a32..5f07203de 100644 --- a/g/net/ghttp/ghttp_server_service_object.go +++ b/g/net/ghttp/ghttp_server_service_object.go @@ -57,11 +57,11 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) { if !ok { if len(methodMap) > 0 { // 指定的方法名称注册,那么需要使用错误提示 - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, pkgPath, objName, mname, v.Method(i).Type().String()) } else { // 否则只是Debug提示 - glog.Debugfln(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`, + glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`, pkgPath, objName, mname, v.Method(i).Type().String()) } continue @@ -127,7 +127,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string) } faddr, ok := fval.Interface().(func(*Request)) if !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, pkgPath, objName, mname, fval.Type().String()) return } @@ -174,7 +174,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) { } faddr, ok := v.Method(i).Interface().(func(*Request)) if !ok { - glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, + glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`, pkgPath, objName, mname, v.Method(i).Type().String()) continue } diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index 58e588be6..be2a6959f 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -20,10 +20,10 @@ import ( // SESSION对象 type Session struct { - id string // SessionId + id string // SessionId data *gmap.StrAnyMap // Session数据 - server *Server // 所属Server - request *Request // 关联的请求 + server *Server // 所属Server + request *Request // 关联的请求 } // 生成一个唯一的SessionId字符串,长度18位。 @@ -58,7 +58,7 @@ func (s *Session) init() { // 否则执行初始化创建 s.id = s.request.Cookie.MakeSessionId() s.data = gmap.NewStrAnyMap() - s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()) + s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000) } } diff --git a/g/net/ghttp/ghttp_unit_param_json_test.go b/g/net/ghttp/ghttp_unit_param_json_test.go index a6dfd8522..9e7188771 100644 --- a/g/net/ghttp/ghttp_unit_param_json_test.go +++ b/g/net/ghttp/ghttp_unit_param_json_test.go @@ -46,6 +46,48 @@ func Test_Params_Json(t *testing.T) { Pass2 : "456", }) }) + s.BindHandler("/json3", func(r *ghttp.Request){ + type Message struct { + Code int `json:"code"` + Body string `json:"body,omitempty"` + Error string `json:"error,omitempty"` + } + type ResponseJson struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + ExtData interface{} `json:"ext_data,omitempty"` + Paginate interface{} `json:"paginate,omitempty"` + Message Message `json:"message,omitempty"` + } + responseJson := &ResponseJson{ + Success: true, + Data: nil, + ExtData: nil, + Message: Message{3, "测试", "error"}, + } + r.Response.WriteJson(responseJson) + }) + s.BindHandler("/json4", func(r *ghttp.Request){ + type Message struct { + Code int `json:"code"` + Body string `json:"body,omitempty"` + Error string `json:"error,omitempty"` + } + type ResponseJson struct { + Success bool `json:"success"` + Data interface{} `json:"data,omitempty"` + ExtData interface{} `json:"ext_data,omitempty"` + Paginate interface{} `json:"paginate,omitempty"` + Message *Message `json:"message,omitempty"` + } + responseJson := ResponseJson{ + Success: true, + Data: nil, + ExtData: nil, + Message: &Message{3, "测试", "error"}, + } + r.Response.WriteJson(responseJson) + }) s.SetPort(p) s.SetDumpRouteMap(false) s.Start() @@ -67,7 +109,7 @@ func Test_Params_Json(t *testing.T) { gtest.Assert(map1["password2"], "456") map2 := make(map[string]interface{}) - err2 := json.Unmarshal([]byte(client.GetContent("/json1")), &map2) + err2 := json.Unmarshal([]byte(client.GetContent("/json2")), &map2) gtest.Assert(err2, nil) gtest.Assert(len(map2), 4) gtest.Assert(map2["Name"], "john") @@ -75,5 +117,18 @@ func Test_Params_Json(t *testing.T) { gtest.Assert(map2["password1"], "123") gtest.Assert(map2["password2"], "456") + map3 := make(map[string]interface{}) + err3 := json.Unmarshal([]byte(client.GetContent("/json3")), &map3) + gtest.Assert(err3, nil) + gtest.Assert(len(map3), 2) + gtest.Assert(map3["success"], "true") + gtest.Assert(map3["message"], g.Map{"body":"测试", "code":3, "error":"error"}) + + map4 := make(map[string]interface{}) + err4 := json.Unmarshal([]byte(client.GetContent("/json4")), &map4) + gtest.Assert(err4, nil) + gtest.Assert(len(map4), 2) + gtest.Assert(map4["success"], "true") + gtest.Assert(map4["message"], g.Map{"body":"测试", "code":3, "error":"error"}) }) } diff --git a/g/net/ghttp/ghttp_unit_router_hook_test.go b/g/net/ghttp/ghttp_unit_router_hook_test.go index 3fb7bf3c2..581c10c8d 100644 --- a/g/net/ghttp/ghttp_unit_router_hook_test.go +++ b/g/net/ghttp/ghttp_unit_router_hook_test.go @@ -85,3 +85,36 @@ func Test_Router_Hook_Priority(t *testing.T) { }) } +func Test_Router_Hook_Multi(t *testing.T) { + p := ports.PopRand() + s := g.Server(p) + s.BindHandler("/multi-hook", func(r *ghttp.Request) { + r.Response.Write("show") + }) + + s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc { + "BeforeServe" : func(r *ghttp.Request) { + r.Response.Write("1") + }, + }) + s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc { + "BeforeServe" : func(r *ghttp.Request) { + r.Response.Write("2") + }, + }) + s.SetPort(p) + s.SetDumpRouteMap(false) + s.Start() + defer s.Shutdown() + + // 等待启动完成 + time.Sleep(time.Second) + gtest.Case(t, func() { + client := ghttp.NewClient() + client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p)) + + gtest.Assert(client.GetContent("/"), "Not Found") + gtest.Assert(client.GetContent("/multi-hook"), "12show") + }) +} + diff --git a/g/net/gtcp/gtcp_conn.go b/g/net/gtcp/gtcp_conn.go index a608c848c..2b9952def 100644 --- a/g/net/gtcp/gtcp_conn.go +++ b/g/net/gtcp/gtcp_conn.go @@ -7,11 +7,12 @@ package gtcp import ( - "net" - "time" - "io" - "bufio" - "bytes" + "bufio" + "bytes" + "crypto/tls" + "io" + "net" + "time" ) // 封装的链接对象 @@ -21,7 +22,7 @@ type Conn struct { buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理) recvDeadline time.Time // 读取超时时间 sendDeadline time.Time // 写入超时时间 - recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔 + recvBufferWait time.Duration // 读取全部缓冲区数据时,读取缓冲区完毕后的等待间隔 } const ( @@ -38,6 +39,24 @@ func NewConn(addr string, timeout...int) (*Conn, error) { } } +// 创建支持TLS加密通信的TCP链接 +func NewConnTLS(addr string, tlsConfig *tls.Config) (*Conn, error) { + if conn, err := NewNetConnTLS(addr, tlsConfig); err == nil { + return NewConnByNetConn(conn), nil + } else { + return nil, err + } +} + +// 根据证书和密钥文件创建支持TLS加密通信的TCP链接 +func NewConnKeyCrt(addr, crtFile, keyFile string) (*Conn, error) { + if conn, err := NewNetConnKeyCrt(addr, crtFile, keyFile); err == nil { + return NewConnByNetConn(conn), nil + } else { + return nil, err + } +} + // 将net.Conn接口对象转换为*gtcp.Conn对象 func NewConnByNetConn(conn net.Conn) *Conn { return &Conn { @@ -50,16 +69,14 @@ func NewConnByNetConn(conn net.Conn) *Conn { } // 关闭连接 -func (c *Conn) Close() { - c.conn.Close() +func (c *Conn) Close() error { + return c.conn.Close() } // 发送数据 func (c *Conn) Send(data []byte, retry...Retry) error { - length := 0 for { - n, err := c.conn.Write(data) - if err != nil { + if _, err := c.conn.Write(data); err != nil { // 链接已关闭 if err == io.EOF { return err @@ -76,18 +93,17 @@ func (c *Conn) Send(data []byte, retry...Retry) error { time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond) } } else { - length += n - if length == len(data) { - return nil - } + return nil } } } -// 获取数据,指定读取的数据长度(length < 1表示获取所有可读数据),以及重试策略(retry) +// 阻塞等待获取指定读取的数据长度,并给定重试策略。 +// // 需要注意: // 1、往往在socket通信中需要指定固定的数据结构,并在设定对应的长度字段,并在读取数据时便于区分包大小; -// 2、当length < 1时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现非完整的包情况),因此需要解析端注意解析策略; +// 2、当length < 0时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现粘包/断包情况),因此需要解析端注意解析策略; +// 3、当length = 0时表示获取一次缓冲区的数据后立即返回; func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { var err error // 读取错误 var size int // 读取长度 @@ -106,9 +122,11 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { // 如果已经读取到数据(这点很关键,表明缓冲区已经有数据,剩下的操作就是将所有数据读取完毕), // 那么可以设置读取全部缓冲数据的超时时间;如果没有接收到任何数据,那么将会进入读取阻塞(或者自定义的超时阻塞); // 仅对读取全部缓冲区数据操作有效 - if length <= 0 && index > 0 { + if length < 0 && index > 0 { bufferWait = true - c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) + if err = c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil { + return nil, err + } } size, err = c.reader.Read(buffer[index:]) if size > 0 { @@ -137,7 +155,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } // 判断数据是否全部读取完毕(由于超时机制的存在,获取的数据完整性不可靠) if bufferWait && isTimeout(err) { - c.conn.SetReadDeadline(c.recvDeadline) + if err = c.conn.SetReadDeadline(c.recvDeadline); err != nil { + return nil, err + } err = nil break } @@ -155,6 +175,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } break } + // 只获取一次数据 + if length == 0 { + break + } } return buffer[:index], err } @@ -184,14 +208,18 @@ func (c *Conn) RecvLine(retry...Retry) ([]byte, error) { // 带超时时间的数据获取 func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.Recv(length, retry...) } // 带超时时间的数据发送 func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.Send(data, retry...) } diff --git a/g/net/gtcp/gtcp_conn_pkg.go b/g/net/gtcp/gtcp_conn_pkg.go index cd0a8ee53..e5a07cc17 100644 --- a/g/net/gtcp/gtcp_conn_pkg.go +++ b/g/net/gtcp/gtcp_conn_pkg.go @@ -14,88 +14,118 @@ import ( ) const ( - // 允许最大的简单协议包大小(byte), 15MB - PKG_MAX_SIZE = 0xFFFFFF - // 消息包头大小: "总长度"3字节+"校验码"4字节 - PKG_HEADER_SIZE = 7 + // 默认允许最大的简单协议包大小(byte), 65535 byte + gPKG_MAX_DATA_SIZE = 65535 + // 简单协议包头大小 + gPKG_HEADER_SIZE = 3 ) -// 根据简单协议发送数据包。 -// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。 -// 注意: -// 1. "总长度"包含自身3字节及"校验码"4字节。 -// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。 -func (c *Conn) SendPkg(data []byte, retry...Retry) error { - length := uint32(len(data)) - if length > PKG_MAX_SIZE - PKG_HEADER_SIZE { - return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE)) +// 数据读取选项 +type PkgOption struct { + MaxSize int // (byte)数据读取的最大包大小,最大不能超过3字节(0xFFFFFF,15MB),默认为65535byte + Retry Retry // 失败重试 +} + +// getPkgOption wraps and returns the PkgOption. +// If no option given, it returns a new option with default value. +func getPkgOption(option...PkgOption) (*PkgOption, error) { + pkgOption := PkgOption{} + if len(option) > 0 { + pkgOption = option[0] + } + if pkgOption.MaxSize == 0 { + pkgOption.MaxSize = gPKG_MAX_DATA_SIZE + } else if pkgOption.MaxSize > 0xFFFFFF { + return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxSize, 0xFFFFFF) + } + return &pkgOption, nil +} + +// 根据简单协议发送数据包。 +// +// 简单协议数据格式:数据长度(24bit)|数据字段(变长)。 +// +// 注意: +// 1. "数据长度"仅为"数据字段"的长度,不包含头信息的长度字段3字节。 +// 2. 由于"数据长度"为3字节,并且使用的BigEndian字节序,因此这里最后返回的buffer使用了buffer[1:]。 +func (c *Conn) SendPkg(data []byte, option...PkgOption) error { + pkgOption, err := getPkgOption(option...) + if err != nil { + return err + } + length := len(data) + if length > pkgOption.MaxSize { + return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_DATA_SIZE)) + } + buffer := make([]byte, gPKG_HEADER_SIZE + 1 + len(data)) + binary.BigEndian.PutUint32(buffer[0 : ], uint32(length)) + copy(buffer[gPKG_HEADER_SIZE + 1 : ], data) + if pkgOption.Retry.Count > 0 { + return c.Send(buffer[1:], pkgOption.Retry) } - buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data)) - copy(buffer[PKG_HEADER_SIZE + 1 : ], data) - binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length) - binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data)) //fmt.Println("SendPkg:", buffer[1:]) - return c.Send(buffer[1:], retry...) + return c.Send(buffer[1:]) } // 简单协议: 带超时时间的数据发送 -func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) +func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error { + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) - return c.SendPkg(data, retry...) + return c.SendPkg(data, option...) } // 简单协议: 发送数据并等待接收返回数据 -func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkg(retry...) +func (c *Conn) SendRecvPkg(data []byte, option...PkgOption) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkg(option...) } else { return nil, err } } // 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间) -func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkgWithTimeout(timeout, retry...) +func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkgWithTimeout(timeout, option...) } else { return nil, err } } // 简单协议: 获取一个数据包。 -func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { +func (c *Conn) RecvPkg(option...PkgOption) (result []byte, err error) { var temp []byte - var length uint32 + var length int + pkgOption, err := getPkgOption(option...) + if err != nil { + return nil, err + } for { // 先根据对象的缓冲区数据进行计算 for { - if len(c.buffer) >= PKG_HEADER_SIZE { - // 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 - length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}) - // 解析的大小是否符合规范 - if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE { - c.buffer = c.buffer[1:] - continue + if len(c.buffer) >= gPKG_HEADER_SIZE { + // 注意"数据长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0 + length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})) + // 解析的大小是否符合规范,清空从该连接接收到的所有数据包 + if length < 0 || length > pkgOption.MaxSize { + c.buffer = c.buffer[:0] + return nil, fmt.Errorf(`invalid package size %d`, length) } // 不满足包大小,需要继续读取 - if uint32(len(c.buffer)) < length { + if len(c.buffer) < length + gPKG_HEADER_SIZE { break } - // 数据校验 - if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) { - c.buffer = c.buffer[1:] - continue - } - result = c.buffer[PKG_HEADER_SIZE : length] - c.buffer = c.buffer[length: ] + result = c.buffer[gPKG_HEADER_SIZE : gPKG_HEADER_SIZE + length] + c.buffer = c.buffer[gPKG_HEADER_SIZE + length: ] return } else { break } } // 读取系统socket缓冲区的完整数据 - temp, err = c.Recv(-1, retry...) + temp, err = c.Recv(0, pkgOption.Retry) if err != nil { break } @@ -108,8 +138,10 @@ func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) { } // 简单协议: 带超时时间的消息包获取 -func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) +func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) { + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) - return c.RecvPkg(retry...) + return c.RecvPkg(option...) } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_func.go b/g/net/gtcp/gtcp_func.go index e4aacd6ef..7f9f39f38 100644 --- a/g/net/gtcp/gtcp_func.go +++ b/g/net/gtcp/gtcp_func.go @@ -7,13 +7,16 @@ package gtcp import ( - "net" - "time" + "crypto/rand" + "crypto/tls" + "github.com/gogf/gf/g/os/gfile" + "net" + "time" ) const ( - gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔 - gDEFAULT_READ_BUFFER_SIZE = 1024 // 默认数据读取缓冲区大小 + gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔 + gDEFAULT_READ_BUFFER_SIZE = 128 // (byte)默认数据读取缓冲区大小 ) type Retry struct { @@ -21,6 +24,7 @@ type Retry struct { Interval int // 重试间隔(毫秒) } +// Deprecated. // 常见的二进制数据校验方式,生成校验结果 func Checksum(buffer []byte) uint32 { var checksum uint32 @@ -39,6 +43,20 @@ func NewNetConn(addr string, timeout...int) (net.Conn, error) { } } +// 创建支持TLS的原生TCP链接, addr地址格式形如:127.0.0.1:80 +func NewNetConnTLS(addr string, tlsConfig *tls.Config) (net.Conn, error) { + return tls.Dial("tcp", addr, tlsConfig) +} + +// 根据给定的证书和密钥文件创建支持TLS的原生TCP链接, addr地址格式形如:127.0.0.1:80 +func NewNetConnKeyCrt(addr, crtFile, keyFile string) (net.Conn, error) { + tlsConfig, err := LoadKeyCrt(crtFile, keyFile) + if err != nil { + return nil, err + } + return NewNetConnTLS(addr, tlsConfig) +} + // (面向短链接)发送数据 func Send(addr string, data []byte, retry...Retry) error { conn, err := NewConn(addr) @@ -88,4 +106,25 @@ func isTimeout(err error) bool { return true } return false +} + +// 根据证书和密钥生成TLS对象 +func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) { + crtPath, err := gfile.Search(crtFile) + if err != nil { + return nil, err + } + keyPath, err := gfile.Search(keyFile) + if err != nil { + return nil, err + } + crt, err := tls.LoadX509KeyPair(crtPath, keyPath) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{} + tlsConfig.Certificates = []tls.Certificate{crt} + tlsConfig.Time = time.Now + tlsConfig.Rand = rand.Reader + return tlsConfig, nil } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_func_pkg.go b/g/net/gtcp/gtcp_func_pkg.go index 164d513cb..a5d50f234 100644 --- a/g/net/gtcp/gtcp_func_pkg.go +++ b/g/net/gtcp/gtcp_func_pkg.go @@ -9,41 +9,41 @@ package gtcp import "time" // 简单协议: (面向短链接)发送消息包 -func SendPkg(addr string, data []byte, retry...Retry) error { +func SendPkg(addr string, data []byte, option...PkgOption) error { conn, err := NewConn(addr) if err != nil { return err } defer conn.Close() - return conn.SendPkg(data, retry...) + return conn.SendPkg(data, option...) } // 简单协议: (面向短链接)发送数据并等待接收返回数据 -func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) { +func SendRecvPkg(addr string, data []byte, option...PkgOption) ([]byte, error) { conn, err := NewConn(addr) if err != nil { return nil, err } defer conn.Close() - return conn.SendRecvPkg(data, retry...) + return conn.SendRecvPkg(data, option...) } // 简单协议: (面向短链接)带超时时间的数据发送 -func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error { +func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, option...PkgOption) error { conn, err := NewConn(addr) if err != nil { return err } defer conn.Close() - return conn.SendPkgWithTimeout(data, timeout, retry...) + return conn.SendPkgWithTimeout(data, timeout, option...) } // 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间) -func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { +func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) { conn, err := NewConn(addr) if err != nil { return nil, err } defer conn.Close() - return conn.SendRecvPkgWithTimeout(data, timeout, retry...) + return conn.SendRecvPkgWithTimeout(data, timeout, option...) } \ No newline at end of file diff --git a/g/net/gtcp/gtcp_pool.go b/g/net/gtcp/gtcp_pool.go index fb739e2a8..0c45ecc01 100644 --- a/g/net/gtcp/gtcp_pool.go +++ b/g/net/gtcp/gtcp_pool.go @@ -67,7 +67,7 @@ func (c *PoolConn) Close() error { c.status = gCONN_STATUS_UNKNOWN c.pool.Put(c) } else { - c.Conn.Close() + return c.Conn.Close() } return nil } @@ -114,15 +114,19 @@ func (c *PoolConn) RecvLine(retry...Retry) ([]byte, error) { } // (方法覆盖)带超时时间的数据获取 -func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) +func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) (data []byte, err error) { + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.Recv(length, retry...) } // (方法覆盖)带超时时间的数据发送 func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.Send(data, retry...) } diff --git a/g/net/gtcp/gtcp_pool_pkg.go b/g/net/gtcp/gtcp_pool_pkg.go index f22b60da6..e30b909e0 100644 --- a/g/net/gtcp/gtcp_pool_pkg.go +++ b/g/net/gtcp/gtcp_pool_pkg.go @@ -11,11 +11,11 @@ import ( ) // 简单协议: (方法覆盖)发送数据 -func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) { - if err = c.Conn.SendPkg(data, retry...); err != nil && c.status == gCONN_STATUS_UNKNOWN { +func (c *PoolConn) SendPkg(data []byte, option...PkgOption) (err error) { + if err = c.Conn.SendPkg(data, option...); err != nil && c.status == gCONN_STATUS_UNKNOWN { if v, e := c.pool.NewFunc(); e == nil { c.Conn = v.(*PoolConn).Conn - err = c.Conn.SendPkg(data, retry...) + err = c.Conn.SendPkg(data, option...) } else { err = e } @@ -29,8 +29,8 @@ func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) { } // 简单协议: (方法覆盖)接收数据 -func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) { - data, err := c.Conn.RecvPkg(retry...) +func (c *PoolConn) RecvPkg(option...PkgOption) ([]byte, error) { + data, err := c.Conn.RecvPkg(option...) if err != nil { c.status = gCONN_STATUS_ERROR } else { @@ -40,32 +40,36 @@ func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) { } // 简单协议: (方法覆盖)带超时时间的数据获取 -func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) +func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) { + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) - return c.RecvPkg(retry...) + return c.RecvPkg(option...) } // 简单协议: (方法覆盖)带超时时间的数据发送 -func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) +func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error { + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) - return c.SendPkg(data, retry...) + return c.SendPkg(data, option...) } // 简单协议: (方法覆盖)发送数据并等待接收返回数据 -func (c *PoolConn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkg(retry...) +func (c *PoolConn) SendRecvPkg(data []byte, option...PkgOption) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkg(option...) } else { return nil, err } } // 简单协议: (方法覆盖)发送数据并等待接收返回数据(带返回超时等待时间) -func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) { - if err := c.SendPkg(data, retry...); err == nil { - return c.RecvPkgWithTimeout(timeout, retry...) +func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) { + if err := c.SendPkg(data, option...); err == nil { + return c.RecvPkgWithTimeout(timeout, option...) } else { return nil, err } diff --git a/g/net/gtcp/gtcp_server.go b/g/net/gtcp/gtcp_server.go index 7962d9240..f254d87ad 100644 --- a/g/net/gtcp/gtcp_server.go +++ b/g/net/gtcp/gtcp_server.go @@ -8,79 +8,132 @@ package gtcp import ( - "errors" - "github.com/gogf/gf/g/os/glog" - "net" - "github.com/gogf/gf/g/container/gmap" - "github.com/gogf/gf/g/util/gconv" + "crypto/tls" + "errors" + "github.com/gogf/gf/g/container/gmap" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "net" ) const ( gDEFAULT_SERVER = "default" ) -// tcp server结构体 +// TCP Server. type Server struct { + listen net.Listener address string handler func (*Conn) + tlsConfig *tls.Config } -// Server表,用以存储和检索名称与Server对象之间的关联关系 +// Map for name to server, for singleton purpose. var serverMapping = gmap.NewStrAnyMap() -// 获取/创建一个空配置的TCP Server -// 单例模式,请保证name的唯一性 +// GetServer returns the TCP server with specified , +// or it returns a new normal TCP server named if it does not exist. +// The parameter is used to specify the TCP server func GetServer(name...interface{}) *Server { serverName := gDEFAULT_SERVER if len(name) > 0 { serverName = gconv.String(name[0]) } - if s := serverMapping.Get(serverName); s != nil { - return s.(*Server) - } - s := NewServer("", nil) - serverMapping.Set(serverName, s) - return s + return serverMapping.GetOrSetFuncLock(serverName, func() interface{} { + return NewServer("", nil) + }).(*Server) } -// 创建一个tcp server对象,并且可以选择指定一个单例名字 -func NewServer(address string, handler func (*Conn), names...string) *Server { - s := &Server{address, handler} - if len(names) > 0 { - serverMapping.Set(names[0], s) +// NewServer creates and returns a new normal TCP server. +// The param is optional, which is used to specify the instance name of the server. +func NewServer(address string, handler func (*Conn), name...string) *Server { + s := &Server{ + address : address, + handler : handler, + } + if len(name) > 0 { + serverMapping.Set(name[0], s) } return s } -// 设置参数 - address -func (s *Server) SetAddress (address string) { +// NewServerTLS creates and returns a new TCP server with TLS support. +// The param is optional, which is used to specify the instance name of the server. +func NewServerTLS(address string, tlsConfig *tls.Config, handler func (*Conn), name...string) *Server { + s := NewServer(address, handler, name...) + s.SetTLSConfig(tlsConfig) + return s +} + +// NewServerKeyCrt creates and returns a new TCP server with TLS support. +// The param is optional, which is used to specify the instance name of the server. +func NewServerKeyCrt(address, crtFile, keyFile string, handler func (*Conn), name...string) *Server { + s := NewServer(address, handler, name...) + if err := s.SetTLSKeyCrt(crtFile, keyFile); err != nil { + glog.Error(err) + } + return s +} + +// SetAddress sets the listening address for server. +func (s *Server) SetAddress(address string) { s.address = address } -// 设置参数 - handler -func (s *Server) SetHandler (handler func (*Conn)) { +// SetHandler sets the connection handler for server. +func (s *Server) SetHandler(handler func (*Conn)) { s.handler = handler } -// 执行监听 -func (s *Server) Run() error { +// SetTlsKeyCrt sets the certificate and key file for TLS configuration of server. +func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error { + tlsConfig, err := LoadKeyCrt(crtFile, keyFile) + if err != nil { + return err + } + s.tlsConfig = tlsConfig + return nil +} + +// SetTlsConfig sets the TLS configuration of server. +func (s *Server) SetTLSConfig(tlsConfig *tls.Config) { + s.tlsConfig = tlsConfig +} + +// Close closes the listener and shutdowns the server. +func (s *Server) Close() error { + return s.listen.Close() +} + +// Run starts running the TCP Server. +func (s *Server) Run() (err error) { if s.handler == nil { - err := errors.New("start running failed: socket handler not defined") + err = errors.New("start running failed: socket handler not defined") glog.Error(err) - return err + return } - addr, err := net.ResolveTCPAddr("tcp", s.address) - if err != nil { - glog.Error(err) - return err + if s.tlsConfig != nil { + // TLS Server + s.listen, err = tls.Listen("tcp", s.address, s.tlsConfig) + if err != nil { + glog.Error(err) + return + } + } else { + // Normal Server + addr, err := net.ResolveTCPAddr("tcp", s.address) + if err != nil { + glog.Error(err) + return err + } + s.listen, err = net.ListenTCP("tcp", addr) + if err != nil { + glog.Error(err) + return err + } } - listen, err := net.ListenTCP("tcp", addr) - if err != nil { - glog.Error(err) - return err - } - for { - if conn, err := listen.Accept(); err != nil { + for { + if conn, err := s.listen.Accept(); err != nil { glog.Error(err) return err } else if conn != nil { diff --git a/g/net/gudp/gudp_conn.go b/g/net/gudp/gudp_conn.go index 0d924734f..8c69d28dc 100644 --- a/g/net/gudp/gudp_conn.go +++ b/g/net/gudp/gudp_conn.go @@ -12,7 +12,7 @@ import ( "io" ) -// 封装的链接对象 +// 封装的UDP链接对象 type Conn struct { conn *net.UDPConn // 底层链接对象 raddr *net.UDPAddr // 远程地址 @@ -53,15 +53,12 @@ func NewConnByNetConn(udp *net.UDPConn) *Conn { } // 发送数据 -func (c *Conn) Send(data []byte, retry...Retry) error { - var err error - var size int - var length int +func (c *Conn) Send(data []byte, retry...Retry) (err error) { for { if c.raddr != nil { - size, err = c.conn.WriteToUDP(data, c.raddr) + _, err = c.conn.WriteToUDP(data, c.raddr) } else { - size, err = c.conn.Write(data) + _, err = c.conn.Write(data) } if err != nil { // 链接已关闭 @@ -80,16 +77,16 @@ func (c *Conn) Send(data []byte, retry...Retry) error { time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond) } } else { - length += size - if length == len(data) { - return nil - } + return nil } } } -// 接收数据. -// 注意:UDP协议存在消息边界,因此使用 length<=0 可以获取缓冲区所有消息包数据,即一个完整包。 +// 接收UDP协议数据. +// +// 注意事项: +// 1、UDP协议存在消息边界,因此使用 length < 0 可以获取缓冲区所有消息包数据,即一个完整包; +// 2、当length = 0时,表示获取当前的缓冲区数据,获取一次后立即返回; func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { var err error // 读取错误 var size int // 读取长度 @@ -105,9 +102,11 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } for { - if length <= 0 && index > 0 { + if length < 0 && index > 0 { bufferWait = true - c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)) + if err = c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil { + return nil, err + } } size, raddr, err = c.conn.ReadFromUDP(buffer[index:]) if err == nil { @@ -139,7 +138,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } // 判断数据是否全部读取完毕(由于超时机制的存在,获取的数据完整性不可靠) if bufferWait && isTimeout(err) { - c.conn.SetReadDeadline(c.recvDeadline) + if err = c.conn.SetReadDeadline(c.recvDeadline); err != nil { + return nil, err + } err = nil break } @@ -157,6 +158,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) { } break } + // 只获取一次数据 + if length == 0 { + break + } } return buffer[:index], err } @@ -172,14 +177,18 @@ func (c *Conn) SendRecv(data []byte, receive int, retry...Retry) ([]byte, error) // 带超时时间的数据获取 func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) { - c.SetRecvDeadline(time.Now().Add(timeout)) + if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil { + return nil, err + } defer c.SetRecvDeadline(time.Time{}) return c.Recv(length, retry...) } // 带超时时间的数据发送 func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error { - c.SetSendDeadline(time.Now().Add(timeout)) + if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil { + return err + } defer c.SetSendDeadline(time.Time{}) return c.Send(data, retry...) } diff --git a/g/net/gudp/gudp_func.go b/g/net/gudp/gudp_func.go index 859832637..f77621ca5 100644 --- a/g/net/gudp/gudp_func.go +++ b/g/net/gudp/gudp_func.go @@ -10,6 +10,7 @@ import ( "net" ) +// Deprecated. // 常见的二进制数据校验方式,生成校验结果 func Checksum(buffer []byte) uint32 { var checksum uint32 @@ -22,18 +23,18 @@ func Checksum(buffer []byte) uint32 { // 创建标准库UDP链接操作对象 func NewNetConn(raddr string, laddr...string) (*net.UDPConn, error) { var err error - var rudpaddr, ludpaddr *net.UDPAddr - rudpaddr, err = net.ResolveUDPAddr("udp", raddr) + var remoteAddr, localAddr *net.UDPAddr + remoteAddr, err = net.ResolveUDPAddr("udp", raddr) if err != nil { return nil, err } if len(laddr) > 0 { - ludpaddr, err = net.ResolveUDPAddr("udp", laddr[0]) + localAddr, err = net.ResolveUDPAddr("udp", laddr[0]) if err != nil { return nil, err } } - conn, err := net.DialUDP("udp", ludpaddr, rudpaddr) + conn, err := net.DialUDP("udp", localAddr, remoteAddr) if err != nil { return nil, err } diff --git a/g/net/gudp/gudp_server.go b/g/net/gudp/gudp_server.go index a14a8d932..82abe518d 100644 --- a/g/net/gudp/gudp_server.go +++ b/g/net/gudp/gudp_server.go @@ -21,8 +21,9 @@ const ( // tcp server结构体 type Server struct { - address string - handler func (*Conn) + conn *Conn // UDP server connection object. + address string // Listening address. + handler func (*Conn) } // Server表,用以存储和检索名称与Server对象之间的关联关系 @@ -30,7 +31,7 @@ var serverMapping = gmap.NewStrAnyMap() // 获取/创建一个空配置的UDP Server // 单例模式,请保证name的唯一性 -func GetServer(name...interface{}) (*Server) { +func GetServer(name...interface{}) *Server { serverName := gDEFAULT_SERVER if len(name) > 0 { serverName = gconv.String(name[0]) @@ -44,8 +45,11 @@ func GetServer(name...interface{}) (*Server) { } // 创建一个tcp server对象,并且可以选择指定一个单例名字 -func NewServer (address string, handler func (*Conn), names...string) *Server { - s := &Server{address, handler} +func NewServer(address string, handler func (*Conn), names...string) *Server { + s := &Server{ + address : address, + handler : handler, + } if len(names) > 0 { serverMapping.Set(names[0], s) } @@ -58,10 +62,16 @@ func (s *Server) SetAddress (address string) { } // 设置参数 - handler -func (s *Server) SetHandler (handler func (*Conn)) { +func (s *Server) SetHandler(handler func (*Conn)) { s.handler = handler } +// Close closes the connection. +// It will make server shutdowns immediately. +func (s *Server) Close() error { + return s.conn.Close() +} + // 执行监听 func (s *Server) Run() error { if s.handler == nil { @@ -79,7 +89,7 @@ func (s *Server) Run() error { glog.Error(err) return err } - for { - s.handler(NewConnByNetConn(conn)) - } + s.conn = NewConnByNetConn(conn) + s.handler(s.conn) + return nil } diff --git a/g/os/gcfg/gcfg.go b/g/os/gcfg/gcfg.go index 70fcd6833..057216f16 100644 --- a/g/os/gcfg/gcfg.go +++ b/g/os/gcfg/gcfg.go @@ -55,20 +55,20 @@ func New(file...string) *Config { // Customized dir path from env/cmd. if envPath := cmdenv.Get("gf.gcfg.path").String(); envPath != "" { if gfile.Exists(envPath) { - c.SetPath(envPath) + _ = c.SetPath(envPath) } else { - glog.Errorfln("Configuration directory path does not exist: %s", envPath) + glog.Errorf("Configuration directory path does not exist: %s", envPath) } } else { // Dir path of working dir. - c.SetPath(gfile.Pwd()) + _ = c.SetPath(gfile.Pwd()) // Dir path of binary. if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) { - c.AddPath(selfPath) + _ = c.AddPath(selfPath) } // Dir path of main package. if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) { - c.AddPath(mainPath) + _ = c.AddPath(mainPath) } } return c @@ -276,9 +276,9 @@ func (c *Config) getJson(file...string) *gjson.Json { return j } else { if filePath != "" { - glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) + glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error()) } else { - glog.Criticalfln(`[gcfg] Load configuration failed: %s`, err.Error()) + glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error()) } } return nil diff --git a/g/os/gcron/gcron_entry.go b/g/os/gcron/gcron_entry.go index ecc880089..b4c136e5b 100644 --- a/g/os/gcron/gcron_entry.go +++ b/g/os/gcron/gcron_entry.go @@ -115,7 +115,7 @@ func (entry *Entry) check() { return case STATUS_CLOSED: - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName) + glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName) entry.Close() case STATUS_READY: fallthrough @@ -130,12 +130,12 @@ func (entry *Entry) check() { if times < 2000000000 && times > 1000000000 { entry.times.Set(gDEFAULT_TIMES) } - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName) + glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName) defer func() { if err := recover(); err != nil { - glog.Path(path).Level(level).Errorfln("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err) + glog.Path(path).Level(level).Errorf("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err) } else { - glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) + glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName) } if entry.entry.Status() == STATUS_CLOSED { entry.Close() diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index a5111de0c..e55e156f4 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -426,7 +426,7 @@ func MainPkgPath() string { } // separator of '/' will be converted to Separator. path = Dir(file) - for path != "/" && gstr.Contains(path, Separator) { + for path[len(path) - 1] != os.PathSeparator { files, _ := ScanDir(path, "*.go") for _, v := range files { if gregex.IsMatchString(`package\s+main`, GetContents(v)) { diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 7b9329c5f..5d6562d98 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -243,6 +243,13 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) { if len(timeFormat) > 0 { buffer.WriteString(time.Now().Format(timeFormat)) } + // Lead string. + if len(lead) > 0 { + buffer.WriteString(lead) + if len(value) > 0 { + buffer.WriteByte(' ') + } + } // Caller path. callerPath := "" if l.flags & F_FILE_LONG > 0 { @@ -259,12 +266,6 @@ func (l *Logger) print(std io.Writer, lead string, value...interface{}) { buffer.WriteString(l.prefix + " ") } } - if len(lead) > 0 { - buffer.WriteString(lead) - if len(value) > 0 { - buffer.WriteByte(' ') - } - } for k, v := range value { if k > 0 { buffer.WriteByte(' ') diff --git a/g/os/gmlock/gmlock.go b/g/os/gmlock/gmlock.go index ee785b27a..18bc88fa0 100644 --- a/g/os/gmlock/gmlock.go +++ b/g/os/gmlock/gmlock.go @@ -4,43 +4,92 @@ // If a copy of the MIT was not distributed with this file, // You can obtain one at https://github.com/gogf/gf. -// Package gmlock implements a thread-safe memory locker. -// -// 内存锁. +// Package gmlock implements a concurrent-safe memory-based locker. package gmlock import "time" var ( + // Default locker. locker = New() ) -// 内存写锁,如果锁成功返回true,失败则返回false;过期时间单位为秒,默认为0表示不过期 +// TryLock tries locking the with write lock, +// it returns true if success, or if there's a write/read lock the , +// it returns false. The parameter specifies the max duration it locks. func TryLock(key string, expire...time.Duration) bool { return locker.TryLock(key, expire...) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有其他写锁存在;过期时间单位为秒,默认为0表示不过期 +// Lock locks the with write lock. +// If there's a write/read lock the , +// it will blocks until the lock is released. +// The parameter specifies the max duration it locks. func Lock(key string, expire...time.Duration) { locker.Lock(key, expire...) } -// 解除基于内存锁的写锁 +// Unlock unlocks the write lock of the . func Unlock(key string) { locker.Unlock(key) } -// 内存读锁,如果锁成功返回true,失败则返回false; 过期时间单位为秒,默认为0表示不过期 +// TryRLock tries locking the with read lock. +// It returns true if success, or if there's a write lock on , it returns false. func TryRLock(key string) bool { return locker.TryRLock(key) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有写锁存在; 过期时间单位为秒,默认为0表示不过期 +// RLock locks the with read lock. +// If there's a write lock on , +// it will blocks until the write lock is released. func RLock(key string) { locker.RLock(key) } -// 解除基于内存锁的读锁 +// RUnlock unlocks the read lock of the . func RUnlock(key string) { locker.RUnlock(key) +} + +// TryLockFunc locks the with write lock and callback function . +// It returns true if success, or else if there's a write/read lock the , it return false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func TryLockFunc(key string, f func(), expire...time.Duration) bool { + return locker.TryLockFunc(key, f, expire...) +} + +// TryRLockFunc locks the with read lock and callback function . +// It returns true if success, or else if there's a write lock the , it returns false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func TryRLockFunc(key string, f func()) bool { + return locker.TryRLockFunc(key, f) +} + +// LockFunc locks the with write lock and callback function . +// If there's a write/read lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func LockFunc(key string, f func(), expire...time.Duration) { + locker.LockFunc(key, f, expire...) +} + +// RLockFunc locks the with read lock and callback function . +// If there's a write lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func RLockFunc(key string, f func()) { + locker.RLockFunc(key, f) } \ No newline at end of file diff --git a/g/os/gmlock/gmlock_locker.go b/g/os/gmlock/gmlock_locker.go index a43fb55e8..6eeafbff7 100644 --- a/g/os/gmlock/gmlock_locker.go +++ b/g/os/gmlock/gmlock_locker.go @@ -12,53 +12,119 @@ import ( "time" ) -// 内存锁管理对象 +// Memory locker. type Locker struct { m *gmap.StrAnyMap } -// 创建一把内存锁, 底层使用的是Mutex +// New creates and returns a new memory locker. +// A memory locker can lock/unlock with dynamic string key. func New() *Locker { return &Locker{ m : gmap.NewStrAnyMap(), } } -// 内存写锁,如果锁成功返回true,失败则返回false; 过期时间默认为0表示不过期 +// TryLock tries locking the with write lock, +// it returns true if success, or if there's a write/read lock the , +// it returns false. The parameter specifies the max duration it locks. func (l *Locker) TryLock(key string, expire...time.Duration) bool { return l.doLock(key, l.getExpire(expire...), true) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有其他写锁存在;过期时间默认为0表示不过期 +// Lock locks the with write lock. +// If there's a write/read lock the , +// it will blocks until the lock is released. +// The parameter specifies the max duration it locks. func (l *Locker) Lock(key string, expire...time.Duration) { l.doLock(key, l.getExpire(expire...), false) } -// 解除基于内存锁的写锁 +// Unlock unlocks the write lock of the . func (l *Locker) Unlock(key string) { if v := l.m.Get(key); v != nil { v.(*Mutex).Unlock() } } -// 内存读锁,如果锁成功返回true,失败则返回false; 过期时间单位为秒,默认为0表示不过期 +// TryRLock tries locking the with read lock. +// It returns true if success, or if there's a write lock on , it returns false. func (l *Locker) TryRLock(key string) bool { return l.doRLock(key, true) } -// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有写锁存在; 过期时间单位为秒,默认为0表示不过期 +// RLock locks the with read lock. +// If there's a write lock on , +// it will blocks until the write lock is released. func (l *Locker) RLock(key string) { l.doRLock(key, false) } -// 解除基于内存锁的读锁 +// RUnlock unlocks the read lock of the . func (l *Locker) RUnlock(key string) { if v := l.m.Get(key); v != nil { v.(*Mutex).RUnlock() } } -// 获得过期时间,没有设置时默认为0不过期 +// TryLockFunc locks the with write lock and callback function . +// It returns true if success, or else if there's a write/read lock the , it return false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func (l *Locker) TryLockFunc(key string, f func(), expire...time.Duration) bool { + if l.TryLock(key, expire...) { + defer l.Unlock(key) + f() + return true + } + return false +} + +// TryRLockFunc locks the with read lock and callback function . +// It returns true if success, or else if there's a write lock the , it returns false. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func (l *Locker) TryRLockFunc(key string, f func()) bool { + if l.TryRLock(key) { + defer l.RUnlock(key) + f() + return true + } + return false +} + +// LockFunc locks the with write lock and callback function . +// If there's a write/read lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func (l *Locker) LockFunc(key string, f func(), expire...time.Duration) { + l.Lock(key, expire...) + defer l.Unlock(key) + f() +} + +// RLockFunc locks the with read lock and callback function . +// If there's a write lock the , +// it will blocks until the lock is released. +// +// It releases the lock after is executed. +// +// The parameter specifies the max duration it locks. +func (l *Locker) RLockFunc(key string, f func()) { + l.RLock(key) + defer l.RUnlock(key) + f() +} + +// getExpire returns the duration object passed. +// If is not passed, it returns a default duration object. func (l *Locker) getExpire(expire...time.Duration) time.Duration { e := time.Duration(0) if len(expire) > 0 { @@ -67,7 +133,14 @@ func (l *Locker) getExpire(expire...time.Duration) time.Duration { return e } -// 内存写锁,当try==true时,如果锁成功返回true,失败则返回false;try==false时,成功时立即返回,否则阻塞等待 +// doLock locks writing on . +// It returns true if success, or else returns false. +// +// The parameter is true, +// it returns false immediately if it fails getting the write lock. +// If is false, it blocks until it gets the write lock. +// +// The parameter specifies the max duration it locks. func (l *Locker) doLock(key string, expire time.Duration, try bool) bool { mu := l.getOrNewMutex(key) ok := true @@ -77,7 +150,6 @@ func (l *Locker) doLock(key string, expire time.Duration, try bool) bool { mu.Lock() } if ok && expire > 0 { - // 异步goroutine计时处理 wid := mu.wid.Val() gtimer.AddOnce(expire, func() { if wid == mu.wid.Val() { @@ -88,7 +160,12 @@ func (l *Locker) doLock(key string, expire time.Duration, try bool) bool { return ok } -// 内存读锁,当try==true时,如果锁成功返回true,失败则返回false;try==false时,成功时立即返回,否则阻塞等待 +// doRLock locks reading on . +// It returns true if success, or else returns false. +// +// The parameter is true, +// it returns false immediately if it fails getting the read lock. +// If is false, it blocks until it gets the read lock. func (l *Locker) doRLock(key string, try bool) bool { mu := l.getOrNewMutex(key) ok := true @@ -100,8 +177,9 @@ func (l *Locker) doRLock(key string, try bool) bool { return ok } -// 根据指定key查询或者创建新的Mutex -func (l *Locker) getOrNewMutex(key string) (*Mutex) { +// getOrNewMutex returns the mutex of given if it exists, +// or else creates and returns a new one. +func (l *Locker) getOrNewMutex(key string) *Mutex { return l.m.GetOrSetFuncLock(key, func() interface{} { return NewMutex() }).(*Mutex) diff --git a/g/os/gmlock/gmlock_mutex.go b/g/os/gmlock/gmlock_mutex.go index 8bc7aaadb..4faf7b82d 100644 --- a/g/os/gmlock/gmlock_mutex.go +++ b/g/os/gmlock/gmlock_mutex.go @@ -7,19 +7,20 @@ package gmlock import ( - "github.com/gogf/gf/g/container/gtype" - "sync" + "github.com/gogf/gf/g/container/gtype" + "sync" ) -// 互斥锁对象 +// The high level Mutex. +// It wraps the sync.RWMutex to implements more rich features. type Mutex struct { mu sync.RWMutex - wid *gtype.Int64 // 当前Lock产生的唯一id(主要用于计时Unlock的校验) - rcount *gtype.Int // RLock次数 - wcount *gtype.Int // Lock次数 + wid *gtype.Int64 // Unique id, used for multiple safely Unlock. + rcount *gtype.Int // Reading locks count. + wcount *gtype.Int // Writing locks count. } -// 创建一把内存锁使用的底层RWMutex +// NewMutex creates and returns a new mutex. func NewMutex() *Mutex { return &Mutex{ wid : gtype.NewInt64(), @@ -28,65 +29,123 @@ func NewMutex() *Mutex { } } -func (l *Mutex) Lock() { - l.wcount.Add(1) - l.mu.Lock() - l.wid.Add(1) +// Lock locks mutex for writing. +// If the lock is already locked for reading or writing, +// Lock blocks until the lock is available. +func (m *Mutex) Lock() { + m.wcount.Add(1) + m.mu.Lock() + m.wid.Add(1) } -// 安全的Unlock -func (l *Mutex) Unlock() { - if l.wcount.Val() > 0 { - if l.wcount.Add(-1) >= 0 { - l.mu.Unlock() +// Unlock unlocks the write lock. +// It is safe to be called multiple times. +func (m *Mutex) Unlock() { + if m.wcount.Val() > 0 { + if m.wcount.Add(-1) >= 0 { + m.mu.Unlock() } else { - // 标准库这里会panic - l.wcount.Add(1) + m.wcount.Add(1) } } } -func (l *Mutex) RLock() { - l.rcount.Add(1) - l.mu.RLock() +// RLock locks mutex for reading. +// If the mutex is already locked for writing, +// It blocks until the lock is available. +func (m *Mutex) RLock() { + m.rcount.Add(1) + m.mu.RLock() } -// 安全的RUnlock -func (l *Mutex) RUnlock() { - if l.rcount.Val() > 0 { - if l.rcount.Add(-1) >= 0 { - l.mu.RUnlock() +// RUnlock undoes a single RLock call; +// it does not affect other simultaneous readers. +// It is a run-time error if mutex is not locked for reading +// on entry to RUnlock. +// It is safe to be called multiple times. +func (m *Mutex) RUnlock() { + if m.rcount.Val() > 0 { + if m.rcount.Add(-1) >= 0 { + m.mu.RUnlock() } else { - // 标准库这里会panic - l.rcount.Add(1) + m.rcount.Add(1) } } } -// 不阻塞Lock -func (l *Mutex) TryLock() bool { - // 初步读写次数检查, 但无法保证原子性 - if l.wcount.Val() == 0 && l.rcount.Val() == 0 { - // 第二次检查, 保证原子操作 - if l.wcount.Add(1) == 1 { - l.mu.Lock() - l.wid.Add(1) +// TryLock tries locking the mutex for writing. +// It returns true if success, or if there's a write/read lock on the mutex, +// it returns false. +func (m *Mutex) TryLock() bool { + // The first check, but it cannot ensure the atomicity. + if m.wcount.Val() == 0 && m.rcount.Val() == 0 { + // The second check, it ensures the atomicity with atomic Add. + if m.wcount.Add(1) == 1 { + m.mu.Lock() + m.wid.Add(1) return true } else { - l.wcount.Add(-1) + m.wcount.Add(-1) } } return false } -// 不阻塞RLock -func (l *Mutex) TryRLock() bool { - // 只要不存在写锁 - if l.wcount.Val() == 0 { - l.rcount.Add(1) - l.mu.RLock() +// TryRLock tries locking the mutex for reading. +// It returns true if success, or if there's a write lock on the mutex, it returns false. +func (m *Mutex) TryRLock() bool { + // There must be no write lock on mutex. + if m.wcount.Val() == 0 { + m.rcount.Add(1) + m.mu.RLock() return true } return false } +// TryLockFunc tries locking the mutex for writing with given callback function . +// it returns true if success, or if there's a write/read lock on the mutex, +// it returns false. +// +// It releases the lock after is executed. +func (m *Mutex) TryLockFunc(f func()) bool { + if m.TryLock() { + defer m.Unlock() + f() + return true + } + return false +} + +// TryRLockFunc tries locking the mutex for reading with given callback function . +// It returns true if success, or if there's a write lock on the mutex, it returns false. +// +// It releases the lock after is executed. +func (m *Mutex) TryRLockFunc(f func()) bool { + if m.TryRLock() { + defer m.RUnlock() + f() + return true + } + return false +} + +// LockFunc locks the mutex for writing with given callback function . +// If there's a write/read lock the mutex, it will blocks until the lock is released. +// +// It releases the lock after is executed. +func (m *Mutex) LockFunc(f func()) { + m.Lock() + defer m.Unlock() + f() +} + +// RLockFunc locks the mutex for reading with given callback function . +// If there's a write lock the mutex, it will blocks until the lock is released. +// +// It releases the lock after is executed. +func (m *Mutex) RLockFunc(f func()) { + m.RLock() + defer m.RUnlock() + f() +} diff --git a/g/os/grpool/grpool.go b/g/os/grpool/grpool.go index 8d2bf116c..9add6fe8b 100644 --- a/g/os/grpool/grpool.go +++ b/g/os/grpool/grpool.go @@ -14,10 +14,10 @@ import ( // Goroutine Pool type Pool struct { - limit int // Max goroutine count limit. - count *gtype.Int // Current running goroutine count. - list *glist.List // Job list. - closed *gtype.Bool // Is pool closed or not. + limit int // Max goroutine count limit. + count *gtype.Int // Current running goroutine count. + list *glist.List // Job list for asynchronous job adding purpose. + closed *gtype.Bool // Is pool closed or not. } // Default goroutine pool. @@ -33,7 +33,7 @@ func New(limit...int) *Pool { list : glist.New(), closed : gtype.NewBool(), } - if len(limit) > 0 { + if len(limit) > 0 && limit[0] > 0 { p.limit = limit[0] } return p @@ -72,6 +72,7 @@ func (p *Pool) Add(f func()) { p.fork() } + // Size returns current goroutine count of the pool. func (p *Pool) Size() int { return p.count.Val() diff --git a/g/os/gtime/gtime.go b/g/os/gtime/gtime.go index 5e45231a9..abd557567 100644 --- a/g/os/gtime/gtime.go +++ b/g/os/gtime/gtime.go @@ -5,8 +5,6 @@ // You can obtain one at https://github.com/gogf/gf. // Package gtime provides functionality for measuring and displaying time. -// -// 时间管理. package gtime import ( diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 5d97f39aa..98957cef0 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -69,7 +69,7 @@ func New(path...string) *View { if gfile.Exists(envPath) { view.SetPath(envPath) } else { - glog.Errorfln("Template directory path does not exist: %s", envPath) + glog.Errorf("Template directory path does not exist: %s", envPath) } } else { // Dir path of working dir. diff --git a/g/os/gview/gview_doparse.go b/g/os/gview/gview_doparse.go index b122d977c..1ae3d750e 100644 --- a/g/os/gview/gview_doparse.go +++ b/g/os/gview/gview_doparse.go @@ -15,11 +15,17 @@ import ( "github.com/gogf/gf/g/os/gfile" "github.com/gogf/gf/g/os/gfsnotify" "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/os/gmlock" "github.com/gogf/gf/g/os/gspath" "github.com/gogf/gf/g/text/gstr" "text/template" ) +const ( + // Template name for content parsing. + gCONTENT_TEMPLATE_NAME = "template content" +) + var ( // Templates cache map for template folder. templates = gmap.NewStrAnyMap() @@ -40,7 +46,7 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa if tpl, err = tpl.ParseFiles(files...); err != nil { return nil } - gfsnotify.Add(path, func(event *gfsnotify.Event) { + _, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) { templates.Remove(path) gfsnotify.Exit() }) @@ -102,7 +108,10 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error) if err != nil { return "", err } - tpl, err = tpl.Parse(gfcache.GetContents(path)) + // Using memory lock to ensure concurrent safety for template parsing. + gmlock.LockFunc("gview-parsing:" + folder, func() { + tpl, err = tpl.Parse(gfcache.GetContents(path)) + }) if err != nil { return "", err } @@ -146,8 +155,14 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error) func (view *View) ParseContent(content string, params...Params) (string, error) { view.mu.RLock() defer view.mu.RUnlock() - tpl := template.New("template content").Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) - tpl, err := tpl.Parse(content) + err := (error)(nil) + tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface {} { + return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) + }).(*template.Template) + // Using memory lock to ensure concurrent safety for content parsing. + gmlock.LockFunc("gview-parsing:content", func() { + tpl, err = tpl.Parse(content) + }) if err != nil { return "", err } diff --git a/g/text/gregex/gregex_z_bench_test.go b/g/text/gregex/gregex_z_bench_test.go index db72f9d28..cd83dc1db 100644 --- a/g/text/gregex/gregex_z_bench_test.go +++ b/g/text/gregex/gregex_z_bench_test.go @@ -10,51 +10,29 @@ package gregex_test import ( "github.com/gogf/gf/g/text/gregex" - "testing" + "regexp" + "testing" ) -var pattern = `(.+):(\d+)` -var src = "johng.cn:80" -var replace = "johng.cn" +var pattern = `(\w+).+\-\-\s*(.+)` +var src = `GF is best! -- John` -func BenchmarkValidate(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.Validate(pattern) - } +func Benchmark_GF(b *testing.B) { + for i := 0; i < b.N; i++ { + gregex.IsMatchString(pattern, src) + } } -func BenchmarkIsMatch(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.IsMatch(pattern, []byte(src)) - } +func Benchmark_Compile(b *testing.B) { + var wcdRegexp = regexp.MustCompile(pattern) + for i := 0; i < b.N; i++ { + wcdRegexp.MatchString(src) + } } -func BenchmarkIsMatchString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.IsMatchString(pattern, src) - } -} - -func BenchmarkMatchString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.MatchString(pattern, src) - } -} - -func BenchmarkMatchAllString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.MatchAllString(pattern, src) - } -} - -func BenchmarkReplace(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.Replace(pattern, []byte(replace), []byte(src)) - } -} - -func BenchmarkReplaceString(b *testing.B) { - for i := 0; i < b.N; i++ { - gregex.ReplaceString(pattern, replace, src) - } +func Benchmark_Compile_Actual(b *testing.B) { + for i := 0; i < b.N; i++ { + wcdRegexp := regexp.MustCompile(pattern) + wcdRegexp.MatchString(src) + } } diff --git a/g/util/gconv/gconv.go b/g/util/gconv/gconv.go index 6b517ca37..635645a6d 100644 --- a/g/util/gconv/gconv.go +++ b/g/util/gconv/gconv.go @@ -20,6 +20,11 @@ type apiString interface { String() string } +// Type assert api for Error(). +type apiError interface { + Error() string +} + var ( // Empty strings. emptyStringMap = map[string]struct{}{ @@ -143,6 +148,10 @@ func String(i interface{}) string { // If the variable implements the String() interface, // then use that interface to perform the conversion return f.String() + } else if f, ok := value.(apiError); ok { + // If the variable implements the Error() interface, + // then use that interface to perform the conversion + return f.Error() } else { // Finally we use json.Marshal to convert. jsonContent, _ := json.Marshal(value) diff --git a/g/util/gconv/gconv_struct.go b/g/util/gconv/gconv_struct.go index e84455496..d5cc119c4 100644 --- a/g/util/gconv/gconv_struct.go +++ b/g/util/gconv/gconv_struct.go @@ -46,6 +46,7 @@ func Struct(params interface{}, pointer interface{}, mapping...map[string]string if kind := rv.Kind(); kind != reflect.Ptr { return fmt.Errorf("object pointer should be type of: %v", kind) } + // Using IsNil on reflect.Ptr variable is OK. if !rv.IsValid() || rv.IsNil() { return errors.New("object pointer cannot be nil") } @@ -156,7 +157,9 @@ func StructDeep(params interface{}, pointer interface{}, mapping...map[string]st trv := rv.Field(i) switch trv.Kind() { case reflect.Struct: - StructDeep(params, trv, mapping...) + if err := StructDeep(params, trv, mapping...); err != nil { + return err + } } } } @@ -238,7 +241,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er switch structFieldValue.Kind() { // 属性为结构体 case reflect.Struct: - Struct(value, structFieldValue) + if err := Struct(value, structFieldValue); err != nil { + structFieldValue.Set(reflect.ValueOf(value)) + } // 属性为数组类型 case reflect.Slice: fallthrough @@ -252,11 +257,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er for i := 0; i < v.Len(); i++ { if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - Struct(v.Index(i).Interface(), e) + if err := Struct(v.Index(i).Interface(), e); err != nil { + e.Set(reflect.ValueOf(v.Index(i).Interface())) + } a.Index(i).Set(e.Addr()) } else { e := reflect.New(t).Elem() - Struct(v.Index(i).Interface(), e) + if err := Struct(v.Index(i).Interface(), e); err != nil { + e.Set(reflect.ValueOf(v.Index(i).Interface())) + } a.Index(i).Set(e) } } @@ -266,11 +275,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er t := a.Index(0).Type() if t.Kind() == reflect.Ptr { e := reflect.New(t.Elem()).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } a.Index(0).Set(e.Addr()) } else { e := reflect.New(t).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } a.Index(0).Set(e) } } @@ -279,7 +292,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er // 属性为指针类型 case reflect.Ptr: e := reflect.New(structFieldValue.Type().Elem()).Elem() - Struct(value, e) + if err := Struct(value, e); err != nil { + e.Set(reflect.ValueOf(value)) + } structFieldValue.Set(e.Addr()) default: diff --git a/g/util/gconv/gconv_z_unit_struct_test.go b/g/util/gconv/gconv_z_unit_struct_test.go index 35d4274f8..0efe7987f 100644 --- a/g/util/gconv/gconv_z_unit_struct_test.go +++ b/g/util/gconv/gconv_z_unit_struct_test.go @@ -7,12 +7,12 @@ package gconv_test import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/os/gtime" - "github.com/gogf/gf/g/test/gtest" - "github.com/gogf/gf/g/util/gconv" - "testing" - "time" + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/os/gtime" + "github.com/gogf/gf/g/test/gtest" + "github.com/gogf/gf/g/util/gconv" + "testing" + "time" ) func Test_Struct_Basic1(t *testing.T) { @@ -104,6 +104,26 @@ func Test_Struct_Basic2(t *testing.T) { }) } +// 带有指针的基础类型属性 +func Test_Struct_Basic3(t *testing.T) { + gtest.Case(t, func() { + type User struct { + Uid int + Name *string + } + user := new(User) + params := g.Map { + "uid" : 1, + "Name" : "john", + } + if err := gconv.Struct(params, user); err != nil { + gtest.Error(err) + } + gtest.Assert(user.Uid, 1) + gtest.Assert(*user.Name, "john") + }) +} + // slice类型属性的赋值 func Test_Struct_Attr_Slice(t *testing.T) { gtest.Case(t, func() { diff --git a/g/util/gutil/gutil.go b/g/util/gutil/gutil.go index b664df319..625214bac 100644 --- a/g/util/gutil/gutil.go +++ b/g/util/gutil/gutil.go @@ -69,11 +69,17 @@ func Throw(exception interface{}) { // TryCatch implements try...catch... logistics. func TryCatch(try func(), catch ... func(exception interface{})) { if len(catch) > 0 { + // If is given, it's used to handle the exception. defer func() { if e := recover(); e != nil { catch[0](e) } }() + } else { + // If no function passed, it filters the exception. + defer func() { + recover() + }() } try() } diff --git a/geg/net/ghttp/server/hello.go b/geg/net/ghttp/server/hello.go index f3ba0a357..c23e92b92 100644 --- a/geg/net/ghttp/server/hello.go +++ b/geg/net/ghttp/server/hello.go @@ -3,14 +3,13 @@ package main import ( "github.com/gogf/gf/g" "github.com/gogf/gf/g/net/ghttp" - "github.com/gogf/gf/g/os/gproc" ) func main() { s := g.Server() s.SetIndexFolder(true) s.BindHandler("/", func(r *ghttp.Request) { - r.Response.Write("pid:", gproc.Pid()) + r.Response.Write("hello world") }) s.SetPort(8199) s.Run() diff --git a/geg/net/ghttp/server/websocket/echo/main-group.go b/geg/net/ghttp/server/websocket/echo/main-group.go new file mode 100644 index 000000000..8b6851cb6 --- /dev/null +++ b/geg/net/ghttp/server/websocket/echo/main-group.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gogf/gf/g" + "github.com/gogf/gf/g/net/ghttp" + "github.com/gogf/gf/g/os/gfile" + "github.com/gogf/gf/g/os/glog" +) + + +func ws(r *ghttp.Request) { + ws, err := r.WebSocket() + if err != nil { + glog.Error(err) + r.Exit() + } + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + return + } + if err = ws.WriteMessage(msgType, msg); err != nil { + return + } + } +} + +func main() { + s := g.Server() + s.Group().Bind([]ghttp.GroupItem{ + {"ALL", "/ws", ws}, + }) + + s.SetServerRoot(gfile.MainPkgPath()) + s.SetPort(8199) + s.Run() +} diff --git a/geg/net/gtcp/gtcp_func.go b/geg/net/gtcp/gtcp_func.go index b28d9154c..96e7563a7 100644 --- a/geg/net/gtcp/gtcp_func.go +++ b/geg/net/gtcp/gtcp_func.go @@ -7,7 +7,7 @@ import ( ) func main() { - data, err := gtcp.SendRecv("www.baidu.com:80", []byte("GET / HTTP/1.1\n\n"), -1) + data, err := gtcp.SendRecv("www.baidu.com:80", []byte("HEAD / HTTP/1.1\n\n"), -1) if len(data) > 0 { fmt.Println(string(data)) } diff --git a/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go b/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go index e4d9ed49c..1669d8784 100644 --- a/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go +++ b/geg/net/gtcp/pkg_operations/common/gtcp_common_client.go @@ -40,21 +40,21 @@ func main() { case "doexit": onServerDoExit(conn, msg) case "heartbeat": onServerHeartBeat(conn, msg) default: - glog.Errorfln("invalid message: %v", msg) + glog.Errorf("invalid message: %v", msg) break } } } func onServerHello(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + glog.Printf("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) } func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("heartbeat from [%s]", conn.RemoteAddr().String()) + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) } func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("exit command from [%s]", conn.RemoteAddr().String()) + glog.Printf("exit command from [%s]", conn.RemoteAddr().String()) conn.Close() } \ No newline at end of file diff --git a/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go b/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go index 51bffe544..375c66adb 100644 --- a/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go +++ b/geg/net/gtcp/pkg_operations/common/gtcp_common_server.go @@ -36,10 +36,10 @@ func main() { } func onClientHello(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + glog.Printf("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) funcs.SendPkg(conn, msg.Act, "Nice to meet you!") } func onClientHeartBeat(conn *gtcp.Conn, msg *types.Msg) { - glog.Printfln("heartbeat from [%s]", conn.RemoteAddr().String()) + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) } diff --git a/geg/net/gtcp/pkg_operations/gtcp_server_client1.go b/geg/net/gtcp/pkg_operations/gtcp_basic.go similarity index 96% rename from geg/net/gtcp/pkg_operations/gtcp_server_client1.go rename to geg/net/gtcp/pkg_operations/gtcp_basic.go index 867a7e404..f390e1224 100644 --- a/geg/net/gtcp/pkg_operations/gtcp_server_client1.go +++ b/geg/net/gtcp/pkg_operations/gtcp_basic.go @@ -32,7 +32,7 @@ func main() { defer conn.Close() for i := 0; i < 10000; i++ { if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { - glog.Error(err) + glog.Error(err.Error()) } time.Sleep(1*time.Second) } diff --git a/geg/net/gtcp/pkg_operations/gtcp_server_client2.go b/geg/net/gtcp/pkg_operations/gtcp_empty_data.go similarity index 100% rename from geg/net/gtcp/pkg_operations/gtcp_server_client2.go rename to geg/net/gtcp/pkg_operations/gtcp_empty_data.go diff --git a/geg/net/gtcp/pkg_operations/gtcp_pkg_option.go b/geg/net/gtcp/pkg_operations/gtcp_pkg_option.go new file mode 100644 index 000000000..31f603ae5 --- /dev/null +++ b/geg/net/gtcp/pkg_operations/gtcp_pkg_option.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "time" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg(gtcp.PkgOption{MaxSize : 1}) + if err != nil { + fmt.Println(err) + break + } + fmt.Println("RecvPkg:", string(data)) + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10000; i++ { + if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(1*time.Second) + } +} diff --git a/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go index e91f1b34b..d8b2e2979 100644 --- a/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go +++ b/geg/net/gtcp/pkg_operations/monitor/gtcp_monitor_server.go @@ -21,7 +21,7 @@ func main() { } info := &types.NodeInfo{} if err := json.Unmarshal(data, info); err != nil { - glog.Errorfln("invalid package structure: %s", err.Error()) + glog.Errorf("invalid package structure: %s", err.Error()) } else { glog.Println(info) conn.SendPkg([]byte("ok")) diff --git a/geg/net/gtcp/tls/gtcp_server_client.go b/geg/net/gtcp/tls/gtcp_server_client.go new file mode 100644 index 000000000..cfac328ca --- /dev/null +++ b/geg/net/gtcp/tls/gtcp_server_client.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "github.com/gogf/gf/g/net/gtcp" + "github.com/gogf/gf/g/os/glog" + "github.com/gogf/gf/g/util/gconv" + "time" +) + +func main() { + address := "127.0.0.1:8999" + crtFile := "server.crt" + keyFile := "server.key" + // TLS Server + go gtcp.NewServerKeyCrt(address, crtFile, keyFile, func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + // if client closes, err will be: EOF + glog.Error(err) + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + tlsConfig, err := gtcp.LoadKeyCrt(crtFile, keyFile) + if err != nil { + panic(err) + } + tlsConfig.InsecureSkipVerify = true + + conn, err := gtcp.NewConnTLS(address, tlsConfig) + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(time.Second) + if i == 5 { + conn.Close() + break + } + } + + // exit after 5 seconds + time.Sleep(5 * time.Second) +} diff --git a/geg/net/gtcp/tls/server.crt b/geg/net/gtcp/tls/server.crt new file mode 100644 index 000000000..4d254ea21 --- /dev/null +++ b/geg/net/gtcp/tls/server.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIJAJYpWLkC2lEXMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAkNIMRAwDgYDVQQIDAdTaUNodWFuMRAwDgYDVQQHDAdDaGVuZ2R1MRAwDgYD +VQQKDAdKb2huLmNuMQwwCgYDVQQLDANEZXYxDTALBgNVBAMMBEpvaG4xHDAaBgkq +hkiG9w0BCQEWDWpvaG5Aam9obmcuY24wHhcNMTgwNDIzMTMyNjA4WhcNMTkwNDIz +MTMyNjA4WjB+MQswCQYDVQQGEwJDSDEQMA4GA1UECAwHU2lDaHVhbjEQMA4GA1UE +BwwHQ2hlbmdkdTEQMA4GA1UECgwHSm9obi5jbjEMMAoGA1UECwwDRGV2MQ0wCwYD +VQQDDARKb2huMRwwGgYJKoZIhvcNAQkBFg1qb2huQGpvaG5nLmNuMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6cngPUrDgBhiNfn+7MMHPzOoO+oVavlS +F/tCPyKINhsePGqHkR4ILkHu9IuoBiPYR1JgrMz5goQ6mkrvq/LMfo4dCuA29ZRg ++Vps/RimBpiz+RU3FDGyqc7d+fk74dElGk6NhJJ6XO3qHqgIg1yc6d5DiZfEnlMz +CRKoZ2dQ+98o5LwES+XJBVWfZiC1pEfyppIh+ci7fXajxkRPJ+5qYWaS5cIHmJIN +DIp5Ypszg1cPs0gIr5EgPeGwZzOeqMMzsbLLE8kjSw59Pt1/+Jkdm1e0GhO18qIO +NcqaHeGaTUVjzX9XwRj8cw+q3kRoqD5aWMjUzAg9+IDrMqvo6VZQ5QIDAQABo1Aw +TjAdBgNVHQ4EFgQU1/tUQpOK0xEwLLlYDiNrckqPlDowHwYDVR0jBBgwFoAU1/tU +QpOK0xEwLLlYDiNrckqPlDowDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AQEA5MbG2xU3s/GDU1MV4f0wKhWCNhXfrLaYSwNYGT/eb8ZG2iHSTO0dvl0+pjO2 +EK63PDMvMhUtL1Zlyvl+OqssYcDhVfDzdFoYX6TZNbYxFwSzcx78mO6boAADk9ro +GEQWN+VHsl984SzBRZRJbtNbiw5iVuPruofeKHrrk4dLMiCsStyUaz9lUZxjo2Fi +vVJOY+mRNOBqz1HgU2+RilFTl04zWadCWPJMugQSgJcUPgxRXQ96PkC8uYevEnmR +2DUReSRULIOYEjHw0DZ6yGlqUkJcUGge3XAQEx3LlCpJasOC8Xpsh5i6WBnDPbMh +kPBjRRTooSrJOQJC5v3QW+0Kgw== +-----END CERTIFICATE----- diff --git a/geg/net/gtcp/tls/server.key b/geg/net/gtcp/tls/server.key new file mode 100644 index 000000000..e0f909629 --- /dev/null +++ b/geg/net/gtcp/tls/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6cngPUrDgBhiNfn+7MMHPzOoO+oVavlSF/tCPyKINhsePGqH +kR4ILkHu9IuoBiPYR1JgrMz5goQ6mkrvq/LMfo4dCuA29ZRg+Vps/RimBpiz+RU3 +FDGyqc7d+fk74dElGk6NhJJ6XO3qHqgIg1yc6d5DiZfEnlMzCRKoZ2dQ+98o5LwE +S+XJBVWfZiC1pEfyppIh+ci7fXajxkRPJ+5qYWaS5cIHmJINDIp5Ypszg1cPs0gI +r5EgPeGwZzOeqMMzsbLLE8kjSw59Pt1/+Jkdm1e0GhO18qIONcqaHeGaTUVjzX9X +wRj8cw+q3kRoqD5aWMjUzAg9+IDrMqvo6VZQ5QIDAQABAoIBAHF7cMHPvL49F88j +nr7GnIntRUhwBB19EIBbknibBotc9nxVKaEjds0dbCSAdfslAyL7tbmrdaIJFXk3 +zsckgGceDLLuyz7B26CuaCEjCdRB43qQ9b9zsEoFBHMGrC6dGul+H+uuPn9FbVOc +NSWumuxa22W6qdJAiJFq4RvwZrsbVnYs5V29Y4Y20IlVUj3siJpAny//UUHequW9 +A/U7RvVssDsEEbbKvCpfcS7STNJKU7GlgV5l5hMKN2xLs1bVG5OKiZN82Zh9r7e1 +m2irxu/ehu6rENxZN0gsfPE4vqoQpbRMNAJlCfq9a3k0PH0TOy5oOVJXPGTIDQab +E3PeAwECgYEA9wh4+bPgMuO04hsAqsoO0DJ9Cwa+BzoDPYOvENobDzmcMErSDLKb +ekl1ej+fBTHRHVaBkuOf/9neLjhjMLad1B+I5gLksqwoMh87odDRCCpkO/B20ln8 +IN6RFiMiNjOaZqjPCCUobgzjbaIz3I69lCQQnMNPwjllSgZs9Lh/PjUCgYEA8kZU +hhUN6ctHIo8ocnmqa4AUPbt2l4qOoBGHCMmhjthyft6g8y6cQlACVJzbco37MhjY +uCOhhOClyUS1tyfds3NXdzAxXPl8SwQJGvl3zqkDQG7/GhCh6AzvHhZR8u7UaweC +kVnAG87Ck6Qqo5ZNbjhMIUm0ujm2cdVd3vyV3fECgYEAmJSMHDck8GnCzLE+/T5m +XeQBZfEZKF+FptYSKId+lS3RMebUzHD5JVQAEqz/LHczoTpQOAkORzorSEMdyPXS +kDWWGfOJjG5XOXYfH/hZVADS/k6tJYnc9/RgitrSg8XlxSjZDz/cM/UT+CBqhf1I +TRrlg94DAoTu8gT8AT9/oE0CgYB5CSPO/JO/2jtGi6iUUC4QmKMEGDRuDt2kID2K +6ViaCY5hzY0xEHcmNdyEMvz7JO16oKkcjUhzHtwUSgxSXUtIDHaE6AGxRj6PJ4v4 ++uqcxxkFxq4Rcn/Acz2+lT4JlMFwWwci4Gi2O7w/kENxCHTUfLGj67OrWYvJIORN +s3iXsQKBgD1I+v+simBvKZKmozzv99EgGfxkRxmrUQsclg1V8a1VTNfE5X9oNaE5 +kjp+dTnwbtmFl3SHVdFUzX/L6FvQIQ9FIwWI2bsszPm4rw8FBeOvH+8lXwVhCwPs +y9him/PhdjBPX0zydDI+h+fmrxH/XbmryZcq1rNmEtFRHBsUs5jg +-----END RSA PRIVATE KEY----- diff --git a/geg/net/gtcp/tls/server.key.public b/geg/net/gtcp/tls/server.key.public new file mode 100644 index 000000000..e0f909629 --- /dev/null +++ b/geg/net/gtcp/tls/server.key.public @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6cngPUrDgBhiNfn+7MMHPzOoO+oVavlSF/tCPyKINhsePGqH +kR4ILkHu9IuoBiPYR1JgrMz5goQ6mkrvq/LMfo4dCuA29ZRg+Vps/RimBpiz+RU3 +FDGyqc7d+fk74dElGk6NhJJ6XO3qHqgIg1yc6d5DiZfEnlMzCRKoZ2dQ+98o5LwE +S+XJBVWfZiC1pEfyppIh+ci7fXajxkRPJ+5qYWaS5cIHmJINDIp5Ypszg1cPs0gI +r5EgPeGwZzOeqMMzsbLLE8kjSw59Pt1/+Jkdm1e0GhO18qIONcqaHeGaTUVjzX9X +wRj8cw+q3kRoqD5aWMjUzAg9+IDrMqvo6VZQ5QIDAQABAoIBAHF7cMHPvL49F88j +nr7GnIntRUhwBB19EIBbknibBotc9nxVKaEjds0dbCSAdfslAyL7tbmrdaIJFXk3 +zsckgGceDLLuyz7B26CuaCEjCdRB43qQ9b9zsEoFBHMGrC6dGul+H+uuPn9FbVOc +NSWumuxa22W6qdJAiJFq4RvwZrsbVnYs5V29Y4Y20IlVUj3siJpAny//UUHequW9 +A/U7RvVssDsEEbbKvCpfcS7STNJKU7GlgV5l5hMKN2xLs1bVG5OKiZN82Zh9r7e1 +m2irxu/ehu6rENxZN0gsfPE4vqoQpbRMNAJlCfq9a3k0PH0TOy5oOVJXPGTIDQab +E3PeAwECgYEA9wh4+bPgMuO04hsAqsoO0DJ9Cwa+BzoDPYOvENobDzmcMErSDLKb +ekl1ej+fBTHRHVaBkuOf/9neLjhjMLad1B+I5gLksqwoMh87odDRCCpkO/B20ln8 +IN6RFiMiNjOaZqjPCCUobgzjbaIz3I69lCQQnMNPwjllSgZs9Lh/PjUCgYEA8kZU +hhUN6ctHIo8ocnmqa4AUPbt2l4qOoBGHCMmhjthyft6g8y6cQlACVJzbco37MhjY +uCOhhOClyUS1tyfds3NXdzAxXPl8SwQJGvl3zqkDQG7/GhCh6AzvHhZR8u7UaweC +kVnAG87Ck6Qqo5ZNbjhMIUm0ujm2cdVd3vyV3fECgYEAmJSMHDck8GnCzLE+/T5m +XeQBZfEZKF+FptYSKId+lS3RMebUzHD5JVQAEqz/LHczoTpQOAkORzorSEMdyPXS +kDWWGfOJjG5XOXYfH/hZVADS/k6tJYnc9/RgitrSg8XlxSjZDz/cM/UT+CBqhf1I +TRrlg94DAoTu8gT8AT9/oE0CgYB5CSPO/JO/2jtGi6iUUC4QmKMEGDRuDt2kID2K +6ViaCY5hzY0xEHcmNdyEMvz7JO16oKkcjUhzHtwUSgxSXUtIDHaE6AGxRj6PJ4v4 ++uqcxxkFxq4Rcn/Acz2+lT4JlMFwWwci4Gi2O7w/kENxCHTUfLGj67OrWYvJIORN +s3iXsQKBgD1I+v+simBvKZKmozzv99EgGfxkRxmrUQsclg1V8a1VTNfE5X9oNaE5 +kjp+dTnwbtmFl3SHVdFUzX/L6FvQIQ9FIwWI2bsszPm4rw8FBeOvH+8lXwVhCwPs +y9him/PhdjBPX0zydDI+h+fmrxH/XbmryZcq1rNmEtFRHBsUs5jg +-----END RSA PRIVATE KEY----- diff --git a/geg/os/glog/glog_line.go b/geg/os/glog/glog_line.go index 41d2e0779..ab28cb622 100644 --- a/geg/os/glog/glog_line.go +++ b/geg/os/glog/glog_line.go @@ -5,6 +5,6 @@ import ( ) func main() { - glog.Line().Println("this is the short file name with its line number") - glog.Line(true).Println("lone file name with line number") + glog.Line().Debug("this is the short file name with its line number") + glog.Line(true).Debug("lone file name with line number") } diff --git a/geg/other/test.go b/geg/other/test.go index 93993139e..9f3000841 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,20 +1,11 @@ package main import ( - "encoding/json" "fmt" - "github.com/gogf/gf/g/encoding/gparser" + "github.com/gogf/gf/g/os/gtime" ) -type User struct { - Id int `json:"id" gconv:"i_d"` -} - func main() { - user := User{100} - jsonBytes, _ := json.Marshal(user) - fmt.Println(string(jsonBytes)) - - b, _ := gparser.VarToJson(user) - fmt.Println(string(b)) + fmt.Println(gtime.Now().Format("U")) + fmt.Println(gtime.Second()) } \ No newline at end of file diff --git a/geg/other/try-catch.go b/geg/other/try-catch.go index 085b77bb0..a0bb032c7 100644 --- a/geg/other/try-catch.go +++ b/geg/other/try-catch.go @@ -7,9 +7,15 @@ import ( func main() { g.TryCatch(func() { - glog.Printfln("hello") + glog.Println("hello") g.Throw("exception") - glog.Printfln("world") + glog.Println("world") + }) + + g.TryCatch(func() { + glog.Println("hello") + g.Throw("exception") + glog.Println("world") }, func(exception interface{}) { glog.Error(exception) }) diff --git a/version.go b/version.go index 904859e1e..ad997480a 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package gf -const VERSION = "v1.6.16" +const VERSION = "v1.7.0" const AUTHORS = "john"