Merge pull request #19 from gogf/master

日常更新
This commit is contained in:
jroam 2019-06-10 16:12:51 +08:00 committed by GitHub
commit ebcc81c1ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1187 additions and 407 deletions

View File

@ -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

View File

@ -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)
## 新功能/改进

View File

@ -47,7 +47,7 @@
1. gtimer增加DelayAdd*方法返回Entry对象以便DelayAdd*的定时任务也能进行状态控制gcron同理需要改进
1. 改进gdb对pgsql/mssql/oracle的支持使用方法覆盖的方式改进操作而不是完全依靠正则替换的方式
1. gdb的Cache缓存功能增加可自定义缓存接口以便支持外部缓存功能缓存接口可以通过io.ReadWriter接口实现
1. grpool增加支持阻塞添加任务接口

View File

@ -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 <limit>.
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
}

View File

@ -37,8 +37,8 @@ const (
)
// New returns an empty queue object.
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited by default.
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib chan.
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited in default.
// When <limit> 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)

View File

@ -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初始化向量为非必需参数

View File

@ -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"

View File

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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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())
}

View File

@ -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() {

View File

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

View File

@ -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
}
}

View File

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

View File

@ -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
}

View File

@ -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
}

View File

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

View File

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

View File

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

View File

@ -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...)
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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 <name>,
// or it returns a new normal TCP server named <name> if it does not exist.
// The parameter <name> 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 <name> 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 <name> 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 <name> 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 {

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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()

View File

@ -426,7 +426,7 @@ func MainPkgPath() string {
}
// separator of <file> '/' 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)) {

View File

@ -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(' ')

View File

@ -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 <key> with write lock,
// it returns true if success, or if there's a write/read lock the <key>,
// it returns false. The parameter <expire> specifies the max duration it locks.
func TryLock(key string, expire...time.Duration) bool {
return locker.TryLock(key, expire...)
}
// 内存写锁锁成功返回true失败时阻塞当失败时表示有其他写锁存在;过期时间单位为秒默认为0表示不过期
// Lock locks the <key> with write lock.
// If there's a write/read lock the <key>,
// it will blocks until the lock is released.
// The parameter <expire> specifies the max duration it locks.
func Lock(key string, expire...time.Duration) {
locker.Lock(key, expire...)
}
// 解除基于内存锁的写锁
// Unlock unlocks the write lock of the <key>.
func Unlock(key string) {
locker.Unlock(key)
}
// 内存读锁如果锁成功返回true失败则返回false; 过期时间单位为秒默认为0表示不过期
// TryRLock tries locking the <key> with read lock.
// It returns true if success, or if there's a write lock on <key>, it returns false.
func TryRLock(key string) bool {
return locker.TryRLock(key)
}
// 内存写锁锁成功返回true失败时阻塞当失败时表示有写锁存在; 过期时间单位为秒默认为0表示不过期
// RLock locks the <key> with read lock.
// If there's a write lock on <key>,
// it will blocks until the write lock is released.
func RLock(key string) {
locker.RLock(key)
}
// 解除基于内存锁的读锁
// RUnlock unlocks the read lock of the <key>.
func RUnlock(key string) {
locker.RUnlock(key)
}
// TryLockFunc locks the <key> with write lock and callback function <f>.
// It returns true if success, or else if there's a write/read lock the <key>, it return false.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> 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 <key> with read lock and callback function <f>.
// It returns true if success, or else if there's a write lock the <key>, it returns false.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func TryRLockFunc(key string, f func()) bool {
return locker.TryRLockFunc(key, f)
}
// LockFunc locks the <key> with write lock and callback function <f>.
// If there's a write/read lock the <key>,
// it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func LockFunc(key string, f func(), expire...time.Duration) {
locker.LockFunc(key, f, expire...)
}
// RLockFunc locks the <key> with read lock and callback function <f>.
// If there's a write lock the <key>,
// it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> specifies the max duration it locks.
func RLockFunc(key string, f func()) {
locker.RLockFunc(key, f)
}

View File

@ -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 <key> with write lock,
// it returns true if success, or if there's a write/read lock the <key>,
// it returns false. The parameter <expire> 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 <key> with write lock.
// If there's a write/read lock the <key>,
// it will blocks until the lock is released.
// The parameter <expire> 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 <key>.
func (l *Locker) Unlock(key string) {
if v := l.m.Get(key); v != nil {
v.(*Mutex).Unlock()
}
}
// 内存读锁如果锁成功返回true失败则返回false; 过期时间单位为秒默认为0表示不过期
// TryRLock tries locking the <key> with read lock.
// It returns true if success, or if there's a write lock on <key>, it returns false.
func (l *Locker) TryRLock(key string) bool {
return l.doRLock(key, true)
}
// 内存写锁锁成功返回true失败时阻塞当失败时表示有写锁存在; 过期时间单位为秒默认为0表示不过期
// RLock locks the <key> with read lock.
// If there's a write lock on <key>,
// 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 <key>.
func (l *Locker) RUnlock(key string) {
if v := l.m.Get(key); v != nil {
v.(*Mutex).RUnlock()
}
}
// 获得过期时间没有设置时默认为0不过期
// TryLockFunc locks the <key> with write lock and callback function <f>.
// It returns true if success, or else if there's a write/read lock the <key>, it return false.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> 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 <key> with read lock and callback function <f>.
// It returns true if success, or else if there's a write lock the <key>, it returns false.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> 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 <key> with write lock and callback function <f>.
// If there's a write/read lock the <key>,
// it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> 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 <key> with read lock and callback function <f>.
// If there's a write lock the <key>,
// it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
//
// The parameter <expire> 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 <expire> 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失败则返回falsetry==false时成功时立即返回否则阻塞等待
// doLock locks writing on <key>.
// It returns true if success, or else returns false.
//
// The parameter <try> is true,
// it returns false immediately if it fails getting the write lock.
// If <true> is false, it blocks until it gets the write lock.
//
// The parameter <expire> 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失败则返回falsetry==false时成功时立即返回否则阻塞等待
// doRLock locks reading on <key>.
// It returns true if success, or else returns false.
//
// The parameter <try> is true,
// it returns false immediately if it fails getting the read lock.
// If <true> 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 <key> 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)

View File

@ -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 <f>.
// it returns true if success, or if there's a write/read lock on the mutex,
// it returns false.
//
// It releases the lock after <f> 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 <f>.
// It returns true if success, or if there's a write lock on the mutex, it returns false.
//
// It releases the lock after <f> 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 <f>.
// If there's a write/read lock the mutex, it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (m *Mutex) LockFunc(f func()) {
m.Lock()
defer m.Unlock()
f()
}
// RLockFunc locks the mutex for reading with given callback function <f>.
// If there's a write lock the mutex, it will blocks until the lock is released.
//
// It releases the lock after <f> is executed.
func (m *Mutex) RLockFunc(f func()) {
m.RLock()
defer m.RUnlock()
f()
}

View File

@ -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()

View File

@ -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 (

View File

@ -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.

View File

@ -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
}

View File

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

View File

@ -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)

View File

@ -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:

View File

@ -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() {

View File

@ -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 <catch> is given, it's used to handle the exception.
defer func() {
if e := recover(); e != nil {
catch[0](e)
}
}()
} else {
// If no <catch> function passed, it filters the exception.
defer func() {
recover()
}()
}
try()
}

View File

@ -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()

View File

@ -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()
}

View File

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

View File

@ -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()
}

View File

@ -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())
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

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

View File

@ -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())
}

View File

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

View File

@ -1,4 +1,4 @@
package gf
const VERSION = "v1.6.16"
const VERSION = "v1.7.0"
const AUTHORS = "john<john@goframe.org>"