├── .DS_Store ├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── app └── app.go ├── build_pb.sh ├── conf └── config.go ├── examples ├── README.md ├── client │ └── client.go ├── conf │ └── server.json ├── greeter │ └── greeter.proto ├── proto.sh ├── proto │ └── examples │ │ └── greeter │ │ ├── greeter.pb.go │ │ └── greeter_mqant.pb.go └── service │ └── server.go ├── gate ├── base │ ├── gate_handler.go │ ├── mqtt │ │ ├── mqtt.go │ │ ├── mqtt_client_server.go │ │ ├── mqtt_test.go │ │ └── pack_queue.go │ ├── mqtt_agent.go │ ├── mqtt_gate.go │ ├── session.go │ ├── session.pb.go │ ├── session.proto │ └── session_test.go ├── define.go ├── options.go └── uriroute │ ├── IM通信协议.md │ ├── README.md │ └── uri_route.go ├── go.mod ├── go.sum ├── httpgateway ├── api.go ├── api │ ├── util.go │ └── util_test.go ├── errors │ ├── errors.go │ └── errors_test.go ├── options.go └── proto │ ├── api.pb.go │ └── api.proto ├── log ├── beego │ ├── README.md │ ├── color.go │ ├── color_windows.go │ ├── color_windows_test.go │ ├── conn.go │ ├── conn_test.go │ ├── console.go │ ├── console_test.go │ ├── dingtalk.go │ ├── file.go │ ├── file_test.go │ ├── jianliao.go │ ├── log.go │ ├── logger.go │ ├── logger_test.go │ ├── multifile.go │ ├── multifile_test.go │ ├── slack.go │ ├── smtp.go │ ├── smtp_test.go │ └── trace.go ├── beego_logger.go ├── define.go ├── gen_options.go ├── gen_options_optiongen.go └── log.go ├── module ├── base │ ├── DefaultModule.go │ ├── ModuleManager.go │ ├── ServerSession.go │ └── base_module.go ├── module.go ├── modules │ ├── timer │ │ └── timingwheel.go │ └── timer_module.go └── options.go ├── mqant.go ├── network ├── agent.go ├── conn.go ├── tcp_conn.go ├── tcp_server.go ├── ws_conn_x.go ├── ws_server.go └── ws_server_x.go ├── registry ├── consul │ ├── consul.go │ └── options.go ├── consul_registry.go ├── consul_registry_test.go ├── consul_watcher.go ├── consul_watcher_test.go ├── encoding.go ├── encoding_test.go ├── etcdv3 │ ├── etcdv3.go │ ├── options.go │ └── watcher.go ├── mock │ ├── helper.go │ ├── helper_test.go │ ├── mock.go │ ├── mock_test.go │ └── mock_watcher.go ├── options.go ├── registry.go ├── service.go └── watcher.go ├── rpc ├── base │ ├── nats_client.go │ ├── nats_server.go │ ├── options.go │ ├── rpc_client.go │ └── rpc_server.go ├── pb │ ├── mqant_rpc.pb.go │ ├── mqant_rpc.proto │ ├── new.go │ └── rpc_test.go ├── reply.go ├── rpc.go └── util │ └── argsutil.go ├── selector ├── cache │ ├── cache.go │ ├── cache_test.go │ └── options.go ├── default.go ├── default_test.go ├── filter.go ├── filter_test.go ├── options.go ├── selector.go ├── strategy.go └── strategy_test.go ├── server ├── context.go ├── options.go ├── rpc_server.go └── server.go ├── service ├── options.go └── service.go ├── utils ├── aes │ └── aes_encrypt.go ├── base62.go ├── base62_test.go ├── fatih │ └── structs │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── field.go │ │ ├── field_test.go │ │ ├── structs.go │ │ ├── structs_example_test.go │ │ ├── structs_test.go │ │ ├── tags.go │ │ └── tags_test.go ├── id.go ├── id_test.go ├── ip │ ├── iptool.go │ └── iptool_test.go ├── json │ └── json.go ├── lib │ └── addr │ │ ├── addr.go │ │ └── addr_test.go ├── minifmt.go ├── params_bytes.go ├── params_bytes_test.go ├── queue.go ├── randInt.go ├── safemap.go ├── safemap_test.go └── uuid │ └── uuid.go └── version.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangdas/mqant/a530a28c5adf612b9b419dbfc38757b72d473f4c/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.13 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.13 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | if [ -f Gopkg.toml ]; then 23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 24 | dep ensure 25 | fi 26 | 27 | - name: Build 28 | run: go build -v . 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | .idea 3 | *.iml 4 | bin 5 | pkg 6 | *.o 7 | *.a 8 | *.so 9 | 10 | # Folders 11 | _obj 12 | _test 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.exe 27 | *.test 28 | *.prof 29 | 30 | server.conf.bat 31 | 32 | src/hitball 33 | .history 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqant 2 | mqant是一款基于Golang语言的简洁,高效,高性能的分布式微服务游戏服务器框架,研发的初衷是要实现一款能支持高并发,高性能,高实时性,的游戏服务器框架,也希望mqant未来能够做即时通讯和物联网方面的应用 3 | 4 | # 特性 5 | 1. 高性能分布式 6 | 2. 支持分布式服务注册发现,是一款功能完整的微服务框架 7 | 3. 基于golang协程,开发过程全程做到无callback回调,代码可读性更高 8 | 4. 远程RPC使用nats作为通道 9 | 5. 网关采用MQTT协议,无需再开发客户端底层库,直接套用已有的MQTT客户端代码库,可以支持IOS,Android,websocket,PC等多平台通信 10 | 6. 默认支持mqtt协议,同时网关也支持开发者自定义的粘包协议 11 | 12 | # 文档 13 | 14 | [在线文档](https://liangdas.github.io/mqant/) 15 | [在线文档-访问不了用这个](http://docs.mqant.com/) 16 | 17 | # 模块 18 | 19 | > 将不断加入更多的模块 20 | 21 | [mqant组件库](https://github.com/liangdas/mqant-modules) 22 | 23 | 短信验证码 24 | 房间模块 25 | 26 | [压力测试工具:armyant](https://github.com/liangdas/armyant) 27 | 28 | # 社区贡献的库 29 | [mqant-docker](https://github.com/bjfumac/mqant-docker) 30 | [MQTT-Laya](https://github.com/bjfumac/MQTT-Laya) 31 | 32 | # 应用案例 33 | 34 | [恰玩-实时互动游戏社交app](https://tiyfr.com/) 35 | 36 | # 演示示例 37 | mqant 项目只包含mqant的代码文件 38 | mqantserver 项目包括了完整的测试demo代码和mqant所依赖的库 39 | 如果你是新手可以优先下载mqantserver项目进行试验 40 | 41 | 42 | [在线Demo演示](http://www.mqant.com/mqant/chat/) 【[源码下载](https://github.com/liangdas/mqantserver)】 43 | 44 | [多人对战吃小球游戏(绿色小球是在线玩家,点击屏幕任意位置移动小球,可以同时开两个浏览器测试,支持移动端)](http://www.mqant.com/mqant/hitball/)【[源码下载](https://github.com/liangdas/mqantserver)】 45 | 46 | 47 | # 贡献者 48 | 49 | 欢迎提供dev分支的pull request 50 | 51 | bug请直接通过issue提交 52 | 53 | 凡提交代码和建议, bug的童鞋, 均会在下列贡献者名单者出现 54 | 55 | 1. [xlionet](https://github.com/xlionet) 56 | 2. [lulucas](https://github.com/lulucas/mqant-UnityExample) 57 | 3. [c2matrix](https://github.com/c2matrix) 58 | 4. [bjfumac【mqant-docker】[MQTT-Laya]](https://github.com/bjfumac) 59 | 5. [jarekzha 【jarekzha-master】](https://github.com/jarekzha) 60 | 61 | 62 | ## 打赏作者 63 | 64 | ![](http://docs.mqant.com/images/donation.png) 65 | 66 | -------------------------------------------------------------------------------- /build_pb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #go build -o improving-server improving-server.go 3 | 4 | export GOPATH=$GOPATH:$PWD 5 | 6 | echo $GOPATH 7 | 8 | 9 | # echo "CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build ludo-server" 10 | # CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o ludo-server main.go 11 | 12 | echo "protoc --proto_path=rpc/pb --go_out=rpc/pb --go_opt=paths=source_relative mqant_rpc.proto" 13 | 14 | protoc --proto_path=rpc/pb --go_out=rpc/pb --go_opt=paths=source_relative mqant_rpc.proto 15 | 16 | echo "protoc --proto_path=gate/base --go_out=gate/base --go_opt=paths=source_relative session.proto" 17 | 18 | protoc --proto_path=gate/base --go_out=gate/base --go_opt=paths=source_relative session.proto 19 | 20 | echo "protoc --proto_path=httpgateway/proto --go_out=httpgateway/proto --go_opt=paths=source_relative api.proto" 21 | 22 | protoc --proto_path=httpgateway/proto --go_out=httpgateway/proto --go_opt=paths=source_relative api.proto -------------------------------------------------------------------------------- /conf/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package conf 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "encoding/json" 21 | "io/ioutil" 22 | "os" 23 | "strings" 24 | ) 25 | 26 | var ( 27 | // LenStackBuf 异常堆栈信息 28 | LenStackBuf = 1024 29 | // Conf 配置结构体 30 | Conf = Config{} 31 | ) 32 | 33 | // LoadConfig 加载配置 34 | func LoadConfig(Path string) { 35 | // Read config. 36 | if err := readFileInto(Path); err != nil { 37 | panic(err) 38 | } 39 | if Conf.RPC.RPCExpired == 0 { 40 | Conf.RPC.RPCExpired = 3 41 | } 42 | if Conf.RPC.MaxCoroutine == 0 { 43 | Conf.RPC.MaxCoroutine = 100 44 | } 45 | } 46 | 47 | // Config 配置结构体 48 | type Config struct { 49 | Log map[string]interface{} 50 | BI map[string]interface{} 51 | OP map[string]interface{} 52 | RPC RPC `json:"rpc"` 53 | Module map[string][]*ModuleSettings 54 | Mqtt Mqtt 55 | Settings map[string]interface{} 56 | } 57 | 58 | // rpc 进程间通信配置 59 | type RPC struct { 60 | MaxCoroutine int //模块同时可以创建的最大协程数量默认是100 61 | RPCExpired int `json:"RpcExpired"` //远程访问最后期限值 单位秒[默认5秒] 这个值指定了在客户端可以等待服务端多长时间来应答 62 | Log bool //是否打印RPC的日志 63 | } 64 | 65 | // ModuleSettings 模块配置 66 | type ModuleSettings struct { 67 | ID string `json:"ID"` 68 | Host string 69 | ProcessID string 70 | Settings map[string]interface{} 71 | } 72 | 73 | // Mqtt mqtt协议配置 74 | type Mqtt struct { 75 | WirteLoopChanNum int // Should > 1 // 最大写入包队列缓存 76 | ReadPackLoop int // 最大读取包队列缓存 77 | ReadTimeout int // 读取超时 78 | WriteTimeout int // 写入超时 79 | } 80 | 81 | func readFileInto(path string) error { 82 | var data []byte 83 | buf := new(bytes.Buffer) 84 | f, err := os.Open(path) 85 | if err != nil { 86 | return err 87 | } 88 | defer f.Close() 89 | r := bufio.NewReader(f) 90 | for { 91 | line, err := r.ReadSlice('\n') 92 | if err != nil { 93 | if len(line) > 0 { 94 | buf.Write(line) 95 | } 96 | break 97 | } 98 | if !strings.HasPrefix(strings.TrimLeft(string(line), "\t "), "//") { 99 | buf.Write(line) 100 | } 101 | } 102 | data = buf.Bytes() 103 | //fmt.Print(string(data)) 104 | return json.Unmarshal(data, &Conf) 105 | } 106 | 107 | // If read the file has an error,it will throws a panic. 108 | func fileToStruct(path string, ptr *[]byte) { 109 | data, err := ioutil.ReadFile(path) 110 | if err != nil { 111 | panic(err) 112 | } 113 | *ptr = data 114 | } 115 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # mqant 使用demo 2 | 3 | # 自动生成代码介绍 4 | 5 | > protoc-gen-mqant 是一款可以自动生成rpc服务注册和调用的工具,其基于mqant微服务框架作为构建模板,通过读取Protobuffer的message和serveice构建服务注册和调用 6 | 7 | ## 构建生成代码工具 8 | 9 | 1. 下载代码到本地 10 | ```git 11 | git clone https://github.com/GodWY/protoc-gen-hip.git 12 | ``` 13 | 2. 执行 make {电脑型号},构建protoc-gen-mqant 可执行文件 14 | 15 | 总结: 下载代码目的是将二进制文件下载到gopath的bin文件下。 16 | ## 构建一个服务 17 | 18 | 1. 定义请求greeter.proto文件 19 | ```go 20 | syntax = "proto3"; 21 | package examples; 22 | option go_package = "proto/examples/greeter"; 23 | 24 | 25 | message Request { 26 | } 27 | 28 | message Response { 29 | 30 | } 31 | ``` 32 | 2. 定义一个rpc请求 33 | 34 | ```go 35 | syntax = "proto3"; 36 | package examples1; 37 | option go_package = "proto/greeter1"; 38 | import "proto/examples/greeter.proto"; 39 | 40 | 41 | message Response { 42 | 43 | } 44 | 45 | service Greeter { 46 | rpc Hello(examples.Request) returns (Response) {} 47 | rpc Stream(examples.Request) returns ( Response) { 48 | } 49 | } 50 | 51 | ``` 52 | 3. 执行构建代码 53 | 54 | ```shell 55 | protoc --plugin=protoc-gen-go=/Users/GodWY/go/bin/protoc-gen-go --go_out=./ ./proto/examples/*.proto --experimental_allow_proto3_optional 56 | protoc --plugin=protoc-gen-mqant=protoc-gen-mqant --proto_path=. --mqant_out=./ ./proto/examples/*.proto ./proto/examples1/*.proto --experimental_allow_proto3_optional 57 | ``` 58 | 59 | 3. 生成的代码 60 | 61 | ```go 62 | // Code generated by protoc-gen-go-hi. DO NOT EDIT. 63 | // versions: 64 | 65 | package greeter1 66 | 67 | import ( 68 | greeter "proto/examples/greeter" 69 | ) 70 | 71 | // This is a compile-time assertion to ensure that this generated file 72 | // is compatible with the kratos package it is being compiled against. 73 | import ( 74 | "errors" 75 | basemodule "github.com/liangdas/mqant/module/base" 76 | client "github.com/liangdas/mqant/module" 77 | mqrpc "github.com/liangdas/mqant/rpc" 78 | "golang.org/x/net/context" 79 | ) 80 | 81 | // generated mqant method 82 | type Greeter interface { 83 | // @GET@gin.Logger() 84 | Hello(in *greeter.Request) (out *Response, err error) 85 | // @POST 86 | Stream(in *greeter.Request) (out *Response, err error) 87 | } 88 | 89 | func RegisterGreeterTcpHandler(m *basemodule.BaseModule, ser Greeter) { 90 | m.GetServer().RegisterGO("hello", ser.Hello) 91 | m.GetServer().RegisterGO("stream", ser.Stream) 92 | } 93 | 94 | // generated proxxy handle 95 | type ClientProxyService struct { 96 | cli client.App 97 | name string 98 | } 99 | 100 | func NewLoginClient(cli client.App, name string) *ClientProxyService { 101 | return &ClientProxyService{ 102 | cli: cli, 103 | name: name, 104 | } 105 | } 106 | 107 | var ClientProxyIsNil = errors.New("proxy is nil") 108 | 109 | func (proxy *ClientProxyService) Hello(req *greeter.Request) (rsp *Response, err error) { 110 | if proxy == nil { 111 | return nil, ClientProxyIsNil 112 | } 113 | rsp = &Response{} 114 | err = mqrpc.Proto(rsp, func() (reply interface{}, err interface{}) { 115 | return proxy.cli.Call(context.TODO(), proxy.name, "hello", mqrpc.Param(req)) 116 | }) 117 | return rsp, err 118 | } 119 | func (proxy *ClientProxyService) Stream(req *greeter.Request) (rsp *Response, err error) { 120 | if proxy == nil { 121 | return nil, ClientProxyIsNil 122 | } 123 | rsp = &Response{} 124 | err = mqrpc.Proto(rsp, func() (reply interface{}, err interface{}) { 125 | return proxy.cli.Call(context.TODO(), proxy.name, "stream", mqrpc.Param(req)) 126 | }) 127 | return rsp, err 128 | } 129 | 130 | ``` 131 | 132 | 在mqant 初始化函数里调用RegisterGreeterTcpHandler方法可以注册协议,同时如果想调用rpc协议。创建NewLoginClient代理类调用相应的rpc即可 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /examples/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/liangdas/mqant" 7 | "github.com/liangdas/mqant/conf" 8 | "github.com/liangdas/mqant/examples/proto/examples/greeter" 9 | "github.com/liangdas/mqant/log" 10 | "github.com/liangdas/mqant/module" 11 | basemodule "github.com/liangdas/mqant/module/base" 12 | ) 13 | 14 | func main() { 15 | // 服务实例 16 | app := mqant.CreateApp( 17 | module.Debug(false), 18 | module.WithLogFile(func(logdir, prefix, processID, suffix string) string { 19 | return fmt.Sprintf("%s/%v%s%s%s", logdir, prefix, processID, "xxx", suffix) 20 | }), 21 | ) 22 | // 配置加载 23 | app.OnConfigurationLoaded(func(app module.App) { 24 | }) 25 | // 调用hello方法 26 | rsp, err := greeter.NewGreeterClient(app, "greeter").Hello(&greeter.Request{}) 27 | if err != nil { 28 | log.Info("xxxx %s", err) 29 | } 30 | log.Info("xxxx %s", rsp.Msg) 31 | s := &Server{} 32 | app.Run(s) 33 | } 34 | 35 | type Server struct { 36 | basemodule.BaseModule 37 | version string 38 | // 模块名字 39 | Name string 40 | } 41 | 42 | // GetApp module.App 43 | func (m *Server) GetApp() module.App { 44 | return m.App 45 | } 46 | 47 | // OnInit() 初始化配置 48 | func (s *Server) OnInit(app module.App, settings *conf.ModuleSettings) { 49 | s.BaseModule.OnInit(s, app, settings) 50 | } 51 | 52 | // Run() 运行服务 53 | func (s *Server) Run(closeSig chan bool) { 54 | //创建MongoDB连接实例 55 | } 56 | 57 | // 销毁服务 58 | func (s *Server) OnDestroy() { 59 | //一定别忘了继承 60 | s.BaseModule.OnDestroy() 61 | s.GetServer().OnDestroy() 62 | } 63 | 64 | // Version() 获取当前服务的代码版本 65 | func (s *Server) Version() string { 66 | //可以在监控时了解代码版本 67 | return s.version 68 | } 69 | 70 | func (s *Server) GetType() string { 71 | return "client" 72 | } 73 | -------------------------------------------------------------------------------- /examples/conf/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "Settings":{ 3 | }, 4 | "Module":{ 5 | "greeter":[ 6 | { 7 | //Id在整个Module中必须唯一,不能重复 8 | "Id":"greeter", 9 | //这个模块所属进程,非常重要,进程会根据该参数来判断是否需要运行该模块 [development]为默认值代表开发环境 10 | "ProcessID":"development", 11 | "Settings":{ 12 | "Port": 7370 13 | } 14 | } 15 | ], 16 | "client":[ 17 | { 18 | //Id在整个Module中必须唯一,不能重复 19 | "Id":"client", 20 | //这个模块所属进程,非常重要,进程会根据该参数来判断是否需要运行该模块 [development]为默认值代表开发环境 21 | "ProcessID":"development", 22 | "Settings":{ 23 | } 24 | } 25 | ] 26 | }, 27 | "Log": { 28 | "contenttype":"application/json", 29 | "multifile": { 30 | "maxLines": 0, 31 | "maxsize": 0, 32 | "daily": true, 33 | "rotate": true, 34 | "perm": "0600", 35 | "prefix":"a", 36 | "separate": [ 37 | "error" 38 | ] 39 | 40 | }, 41 | "file": { 42 | "maxLines": 0, 43 | "maxsize": 0, 44 | "daily": true, 45 | "prefix":"n", 46 | "rotate": true, 47 | "perm": "0600" 48 | } 49 | 50 | }, 51 | "Mqtt":{ 52 | // 最大写入包队列缓存 53 | "WirteLoopChanNum": 10, 54 | // 最大读取包队列缓存 55 | "ReadPackLoop": 1, 56 | // 读取超时 57 | "ReadTimeout": 10, 58 | // 写入超时 59 | "WriteTimeout": 10 60 | }, 61 | "Rpc":{ 62 | "MaxCoroutine":100, 63 | // 远程访问最后期限值 单位秒 这个值指定了在客户端可以等待服务端多长时间来应答 64 | "RpcExpired": 3, 65 | //默认是 false 不打印 66 | "LogSuccess":true 67 | } 68 | } -------------------------------------------------------------------------------- /examples/greeter/greeter.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package examples; 3 | option go_package = "proto/examples/greeter"; 4 | 5 | 6 | message Request { 7 | } 8 | 9 | message Response { 10 | string Msg = 1; 11 | } 12 | 13 | service Greeter { 14 | rpc Hello(Request) returns (Response) {} 15 | rpc Stream(Request) returns ( Response) { 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /examples/proto.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # protoc --plugin=protoc-gen-mqant=protoc-gen-mqant --plugin=protoc-gen-go=/Users/wangxinyu/go/bin/protoc-gen-go --proto_path=proto --mqant_out=./ --go_out=./ proto/examples1/*greeter_1.proto --experimental_allow_proto3_optional --go_out=paths=source_relative:. 3 | protoc --plugin=protoc-gen-go=/Users/wangxinyu/go/bin/protoc-gen-go --go_out=./ ./greeter/*.proto --experimental_allow_proto3_optional 4 | protoc --plugin=protoc-gen-mqant=/Users/wangxinyu/go/bin/protoc-gen-mqant --proto_path=. --mqant_out=./ ./greeter/*.proto ./greeter/*.proto --experimental_allow_proto3_optional -------------------------------------------------------------------------------- /examples/proto/examples/greeter/greeter_mqant.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-hi. DO NOT EDIT. 2 | // versions: 3 | 4 | package greeter 5 | 6 | // This is a compile-time assertion to ensure that this generated file 7 | // is compatible with the kratos package it is being compiled against. 8 | import ( 9 | "errors" 10 | basemodule "github.com/liangdas/mqant/module/base" 11 | client "github.com/liangdas/mqant/module" 12 | mqrpc "github.com/liangdas/mqant/rpc" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | // generated mqant method 17 | type Greeter interface { 18 | Hello(in *Request) (out *Response, err error) 19 | Stream(in *Request) (out *Response, err error) 20 | } 21 | 22 | func RegisterGreeterTcpHandler(m *basemodule.BaseModule, ser Greeter) { 23 | m.GetServer().RegisterGO("hello", ser.Hello) 24 | m.GetServer().RegisterGO("stream", ser.Stream) 25 | } 26 | 27 | // generated proxxy handle 28 | type ClientProxyService struct { 29 | cli client.App 30 | name string 31 | } 32 | 33 | var ClientProxyIsNil = errors.New("proxy is nil") 34 | 35 | func NewGreeterClient(cli client.App, name string) *ClientProxyService { 36 | return &ClientProxyService{ 37 | cli: cli, 38 | name: name, 39 | } 40 | } 41 | func (proxy *ClientProxyService) Hello(req *Request) (rsp *Response, err error) { 42 | if proxy == nil { 43 | return nil, ClientProxyIsNil 44 | } 45 | rsp = &Response{} 46 | err = mqrpc.Proto(rsp, func() (reply interface{}, err interface{}) { 47 | return proxy.cli.Call(context.TODO(), proxy.name, "hello", mqrpc.Param(req)) 48 | }) 49 | return rsp, err 50 | } 51 | func (proxy *ClientProxyService) Stream(req *Request) (rsp *Response, err error) { 52 | if proxy == nil { 53 | return nil, ClientProxyIsNil 54 | } 55 | rsp = &Response{} 56 | err = mqrpc.Proto(rsp, func() (reply interface{}, err interface{}) { 57 | return proxy.cli.Call(context.TODO(), proxy.name, "stream", mqrpc.Param(req)) 58 | }) 59 | return rsp, err 60 | } 61 | -------------------------------------------------------------------------------- /examples/service/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/liangdas/mqant" 5 | "github.com/liangdas/mqant/conf" 6 | "github.com/liangdas/mqant/examples/proto/examples/greeter" 7 | "github.com/liangdas/mqant/module" 8 | basemodule "github.com/liangdas/mqant/module/base" 9 | ) 10 | 11 | type Greeter struct { 12 | } 13 | 14 | func (g *Greeter) Hello(in *greeter.Request) (out *greeter.Response, err error) { 15 | out = &greeter.Response{ 16 | Msg: "success", 17 | } 18 | return 19 | } 20 | func (g *Greeter) Stream(in *greeter.Request) (out *greeter.Response, err error) { 21 | return 22 | } 23 | func main() { 24 | 25 | // 服务实例 26 | app := mqant.CreateApp( 27 | module.Debug(false), 28 | ) 29 | // 配置加载 30 | app.OnConfigurationLoaded(func(app module.App) { 31 | 32 | }) 33 | s := &Server{} 34 | app.Run(s) 35 | } 36 | 37 | type Server struct { 38 | basemodule.BaseModule 39 | version string 40 | // 模块名字 41 | Name string 42 | } 43 | 44 | // GetApp module.App 45 | func (m *Server) GetApp() module.App { 46 | return m.App 47 | } 48 | 49 | // OnInit() 初始化配置 50 | func (s *Server) OnInit(app module.App, settings *conf.ModuleSettings) { 51 | s.BaseModule.OnInit(s, app, settings) 52 | srv := &Greeter{} 53 | greeter.RegisterGreeterTcpHandler(&s.BaseModule, srv) 54 | } 55 | 56 | // Run() 运行服务 57 | func (s *Server) Run(closeSig chan bool) { 58 | //创建MongoDB连接实例 59 | } 60 | 61 | // 销毁服务 62 | func (s *Server) OnDestroy() { 63 | //一定别忘了继承 64 | s.BaseModule.OnDestroy() 65 | s.GetServer().OnDestroy() 66 | } 67 | 68 | // Version() 获取当前服务的代码版本 69 | func (s *Server) Version() string { 70 | //可以在监控时了解代码版本 71 | return s.version 72 | } 73 | 74 | func (s *Server) GetType() string { 75 | return "greeter" 76 | } 77 | -------------------------------------------------------------------------------- /gate/base/mqtt/mqtt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package mqtt 15 | 16 | import ( 17 | "bufio" 18 | "fmt" 19 | "net" 20 | "testing" 21 | ) 22 | 23 | func TestConnet(t *testing.T) { 24 | // Set the listener 25 | l, err := net.Listen("tcp", ":9001") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | conn, err := l.Accept() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | r := bufio.NewReader(conn) 34 | w := bufio.NewWriter(conn) 35 | pack, err := ReadPack(r, 2048) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | // Check the pack 40 | c := pack.variable.(*Connect) 41 | fmt.Println(*c.uname) 42 | fmt.Println(*c.upassword) 43 | fmt.Println(*c.id) 44 | 45 | // Return the connection ack 46 | pack = new(Pack) 47 | pack.msg_type = CONNACK 48 | 49 | ack := new(Connack) 50 | ack.return_code = 0 51 | pack.variable = ack 52 | 53 | if err := WritePack(pack, w); err != nil { 54 | t.Error(err) 55 | return 56 | } 57 | 58 | pack = new(Pack) 59 | pack.qos_level = 1 60 | pack.dup_flag = 0 61 | pack.msg_type = PUBLISH 62 | pub := new(Publish) 63 | pub.mid = 1 64 | s := "chat" 65 | pub.topic_name = &s 66 | pub.msg = []byte("Hello push server") 67 | pack.variable = pub 68 | if err := WritePack(pack, w); err != nil { 69 | t.Error(err) 70 | return 71 | } 72 | 73 | if _, err := ReadPack(r, 2048); err != nil { 74 | t.Error(err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gate/base/session.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package basegate; 3 | option go_package = "github.com/liangdas/mqant/gate/base/basegate"; 4 | message sessionImp { 5 | string IP = 1; 6 | string Network = 2; 7 | string UserId = 3; 8 | string SessionId = 4; 9 | string ServerId = 5; 10 | string TraceId = 6; 11 | string SpanId = 7; 12 | map Settings = 8; 13 | map Carrier=9; 14 | string Topic = 10; 15 | } -------------------------------------------------------------------------------- /gate/base/session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package basegate 15 | 16 | import ( 17 | "fmt" 18 | "google.golang.org/protobuf/proto" 19 | "sync" 20 | "testing" 21 | ) 22 | 23 | func TestSession(t *testing.T) { 24 | session := &SessionImp{ // 使用辅助函数设置域的值 25 | IP: *proto.String("127.0.0.1"), 26 | Network: *proto.String("tcp"), 27 | } // 进行编码 28 | session.Settings = map[string]string{"isLogin": "true"} 29 | data, err := proto.Marshal(session) 30 | if err != nil { 31 | t.Fatalf("marshaling error: %v", err) 32 | } // 进行解码 33 | newSession := &SessionImp{} 34 | err = proto.Unmarshal(data, newSession) 35 | if err != nil { 36 | t.Fatalf("unmarshaling error: %v", err) 37 | } // 测试结果 38 | if newSession.GetSettings() == nil { 39 | t.Fatalf("data mismatch Settings == nil") 40 | } else { 41 | if newSession.GetSettings()["isLogin"] != "true" { 42 | t.Fatalf("data mismatch %q != %q", session.GetSettings()["isLogin"], newSession.GetSettings()["isLogin"]) 43 | } 44 | } 45 | 46 | } 47 | 48 | func TestSessionagent_Serializable(t *testing.T) { 49 | session, err := NewSessionByMap(nil, map[string]interface{}{ 50 | "IP": "IP", 51 | }) 52 | if err != nil { 53 | t.Fatalf("NewSessionByMap error: %v", err) 54 | } 55 | settings := map[string]string{"a": "a"} 56 | var wg sync.WaitGroup 57 | wg.Add(1) 58 | go func() { //開一個協程寫map 59 | for j := 0; j < 1000000; j++ { 60 | _session := session.Clone() 61 | session.Serializable() 62 | session.SetLocalKV("ff", "sss") 63 | session.ImportSettings(settings) 64 | _session.Set("TestTopic", fmt.Sprintf("set %v", j)) 65 | _session.SetTopic("ttt") 66 | _session.Serializable() 67 | a, ok := session.Load("a") 68 | if a != "a" || ok != true { 69 | t.Fatalf("Load error: %v", err) 70 | } 71 | cs := session.CloneSettings() 72 | for k, v := range settings { 73 | if _, ok := cs[k]; ok { 74 | //不用替换 75 | } else { 76 | cs[k] = v 77 | } 78 | } 79 | } 80 | wg.Done() 81 | }() 82 | wg.Add(1) 83 | go func() { //開一個協程讀map 84 | for j := 0; j < 1000000; j++ { 85 | session.Clone() 86 | session.Serializable() 87 | session.SetLocalKV("ff", "sss") 88 | session.ImportSettings(settings) 89 | session.SetTopic("ttt") 90 | //fmt.Println("Serializable", b) 91 | } 92 | wg.Done() 93 | }() 94 | wg.Wait() 95 | } 96 | -------------------------------------------------------------------------------- /gate/uriroute/IM通信协议.md: -------------------------------------------------------------------------------- 1 | # 通信协议 2 | 3 | ### http协议 4 | 5 | 1. 历史消息拉取 6 | 2. 群组列表拉取 7 | 3. server-to-server接口调用 8 | 9 | ### mqtt协议 10 | 11 | 基于tcp,websocket长连接 12 | 13 | 1. 消息下发 14 | 15 | 16 | ### mqtt协议的使用 17 | 18 | mqant只用了mqtt 3.1版本的协议,目前主流开发语言都有相应的客户端开发库。 19 | 20 | mqant只用到了mqtt的【发布消息】协议,订阅协议未支持 21 | 22 | ### mqtt协议简介 23 | 24 | mqtt协议包可以简单的理解为就以下的这两个字段,不管是server-to-client 还是client-to-server 25 | 26 | -- topic string 可以理解为路由 27 | -- body []byte 消息体,是任意二进制流 28 | 29 | ### im模块中mqtt协议的用法 30 | 31 | im模块中topic被限定为标准的URI格式 32 | 33 | ://:@:/;?# 34 | 35 | ### uri资源划分 36 | 37 | ##### scheme 38 | 39 | 1. http/https 40 | 41 | 用于客户端http流量代理(httpproxy) 42 | 43 | client-mqtt->im-server-http->http-server 44 | 45 | eg. 46 | 47 | topic: 48 | 49 | http://www.wanwan.com/pay/one.json 50 | 51 | body: 52 | 53 | type HttpRequest struct { 54 | Header map[string]string 55 | Body string 56 | Method string 57 | } 58 | 59 | return: 60 | 61 | type HttpResponse struct { 62 | Header http.Header 63 | Body interface{} 64 | Err error 65 | StatusCode int 66 | } 67 | 68 | 2. local 69 | 70 | im模块预留私有协议 71 | 72 | 3. system 73 | 74 | im模块预留私有协议 75 | 76 | 4. 业务模块 77 | 78 | 1. scheme 用于指定mqant后端微服务的模块ID 79 | 80 | 2. hostname 用于指定业务模块节点路由规则 81 | 82 | >业务模块可能有多个节点 83 | 84 | 1. modulus 85 | 86 | 取模 87 | 2. cache 88 | 89 | 缓存 90 | 3. random 91 | 92 | 随机 93 | 4. 其他规则 94 | 95 | 例如节点ID 96 | 97 | eg. 98 | 99 | topic1: 100 | 101 | im://modulus/remove_feeds_member?msg_id=1002 102 | 103 | 代表访问后端的im模块,按取模的规则选择im模块的一个节点 104 | 105 | 访问方法 /remove_feeds_member 106 | 107 | msg_id=1002 用来唯一标识一次请求 108 | 109 | body: 110 | { 111 | "member":"要移除成员userId", 112 | "feeds":"feedsid" 113 | } 114 | 115 | topic2: 116 | 117 | im://d8feff3dc8daf472/agree_join_feeds?msg_id=1003 118 | 119 | 代表访问后端的im模块的d8feff3dc8daf472节点 120 | 121 | 访问方法 /remove_feeds_member 122 | 123 | msg_id=1002 用来唯一标识一次请求 124 | 125 | body: 126 | 127 | { 128 | "applicant":"申请人userId", 129 | "feeds":"feedsid" 130 | } 131 | 132 | 133 | ### 客户端如何接受服务端返回消息 134 | 135 | ## 请求-响应模式 136 | 137 | 客户端先发起一次请求,服务器在处理完后返回对应结果 138 | 139 | 140 | ### 客户端请求 141 | 142 | topic: 143 | 144 | im://modulus/remove_feeds_member?msg_id=1002 145 | 146 | body: 147 | 148 | { 149 | "member":"要移除成员userId", 150 | "feeds":"feedsid" 151 | } 152 | 153 | 154 | ### 服务器响应 155 | > 请看,服务器响应消息时topic跟客户端请求时完全相同, 156 | > 因此客户端只要保证topic每一次请求都是唯一的,并且记住它, 157 | > 那么在服务器响应时就能找到正确的处理代码了 158 | topic: 159 | 160 | im://modulus/remove_feeds_member?msg_id=1002 161 | 162 | body: 163 | 164 | { 165 | "Trace":"94218104768aa033", 166 | "Error":"", 167 | "Result":"success" 168 | } 169 | 170 | ## 服务器PUSH模式 171 | > 当用户A给用户B发消息是,im工具需要主动的通知用户B消息 172 | 173 | 这种情况一般我们会提前跟服务器约定好这类消息的topic 174 | 175 | eg. 176 | 177 | 通知feeds有新消息 178 | 179 | topic: 180 | 181 | impush://modulus/news 182 | 183 | body: 184 | 185 | { 186 | "feeds":"feeds id" 187 | "last_seq_id":int //最新消息的序列号 188 | "message":{ 189 | "MsgId":string //消息唯一ID 190 | "Feeds":string //消息所属消息组 191 | "SeqId":int //消息顺序ID 192 | "MsgType":string //消息类型 text image 语音:voice 视频:video 小视频:shortvideo 地理位置:location 连接消息:link 193 | *"From":string //谁发出的 194 | "Payload":Object //谁发出的 195 | "TimeCreate":int //消息创建时间(发送) 196 | } 197 | } -------------------------------------------------------------------------------- /gate/uriroute/README.md: -------------------------------------------------------------------------------- 1 | # topic uri路由器 2 | mqant gate网关的默认路由规则为 3 | 4 | [moduleType@moduleID]/[handler]/[msgid] 5 | 6 | 但这个规则不太灵活,因此设计了一套基于URI规则的topic路由规则。 7 | 8 | # 基于uri协议的路由规则 9 | 10 | ://:@:/;?# 11 | 12 | 1. 可以充分利用uri公共库 13 | 2. 资源划分更加清晰明确 14 | 15 | 16 | ## 示例 17 | 18 | 见 19 | 20 | # 如何启用模块 21 | 22 | ## 创建一个UriRoute结构体 23 | 24 | route:=uriRoute.NewUriRoute(this, 25 | uriRoute.Selector(func(topic string, u *url.URL) (s module.ServerSession, err error) { 26 | moduleType:=u.Scheme 27 | nodeId:=u.Hostname() 28 | //使用自己的 29 | if nodeId=="modulus"{ 30 | //取模 31 | }else if nodeId=="cache"{ 32 | //缓存 33 | }else if nodeId=="random"{ 34 | //随机 35 | }else{ 36 | // 37 | //指定节点规则就是 module://[user:pass@]nodeId/path 38 | //方式1 39 | // moduleType=fmt.Sprintf("%v@%v",moduleType,u.Hostname()) 40 | //方式2 41 | return this.GetRouteServer(moduleType,selector.WithFilter(selector.FilterEndpoint(nodeId))) 42 | } 43 | return this.GetRouteServer(moduleType) 44 | }), 45 | uriRoute.DataParsing(func(topic string, u *url.URL, msg []byte) (bean interface{}, err error) { 46 | //根据topic解析msg为指定的结构体 47 | //结构体必须满足mqant的参数传递标准 48 | //例如mqrpc.Marshaler 49 | //type Marshaler interface { 50 | // Marshal() ([]byte, error) 51 | // Unmarshal([]byte) error 52 | // String() string 53 | //} 54 | return 55 | }), 56 | uriRoute.CallTimeOut(3*time.Second), 57 | ) 58 | 59 | ## 替换默认的gate路由规则 60 | 61 | this.Gate.OnInit(this, app, settings, 62 | gate.SetRouteHandler(route), 63 | ... 64 | ) 65 | 66 | ## 完结 67 | 68 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liangdas/mqant 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/armon/go-metrics v0.3.10 // indirect 7 | github.com/coreos/bbolt v1.3.3 // indirect 8 | github.com/coreos/etcd v3.3.17+incompatible // indirect 9 | github.com/coreos/go-semver v0.3.0 // indirect 10 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect 11 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect 12 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 13 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 14 | github.com/google/btree v1.0.0 // indirect 15 | github.com/gorilla/websocket v1.4.1 // indirect 16 | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect 17 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 18 | github.com/grpc-ecosystem/grpc-gateway v1.11.2 // indirect 19 | github.com/hashicorp/consul/api v1.20.0 20 | github.com/hashicorp/go-hclog v0.14.1 // indirect 21 | github.com/hashicorp/go-immutable-radix v1.3.0 // indirect 22 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 23 | github.com/hashicorp/go-multierror v1.1.1 // indirect 24 | github.com/jonboulle/clockwork v0.1.0 // indirect 25 | github.com/json-iterator/go v1.1.9 26 | github.com/mitchellh/hashstructure v1.0.0 27 | github.com/nats-io/nats-server/v2 v2.9.15 // indirect 28 | github.com/nats-io/nats.go v1.25.0 29 | github.com/pborman/uuid v1.2.0 30 | github.com/pkg/errors v0.9.1 31 | github.com/soheilhy/cmux v0.1.4 // indirect 32 | github.com/stretchr/testify v1.7.1 33 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect 34 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect 35 | go.etcd.io/bbolt v1.3.3 // indirect 36 | go.etcd.io/etcd v3.3.15+incompatible 37 | golang.org/x/net v0.8.0 38 | google.golang.org/protobuf v1.26.0 39 | sigs.k8s.io/yaml v1.1.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /httpgateway/api.go: -------------------------------------------------------------------------------- 1 | // Package httpgateway provides an http-rpc handler which provides the entire http request over rpc 2 | package httpgateway 3 | 4 | import ( 5 | "context" 6 | "github.com/liangdas/mqant/httpgateway/api" 7 | "github.com/liangdas/mqant/httpgateway/errors" 8 | "github.com/liangdas/mqant/httpgateway/proto" 9 | "github.com/liangdas/mqant/module" 10 | "github.com/liangdas/mqant/rpc" 11 | "net/http" 12 | ) 13 | 14 | //APIHandler 网关handler 15 | type APIHandler struct { 16 | Opts Options 17 | App module.App 18 | } 19 | 20 | // API handler is the default handler which takes api.Request and returns api.Response 21 | func (a *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 22 | request, err := httpgatewayapi.RequestToProto(r) 23 | if err != nil { 24 | er := errors.InternalServerError("httpgateway", err.Error()) 25 | w.Header().Set("Content-Type", "application/json") 26 | w.WriteHeader(500) 27 | w.Write([]byte(er.Error())) 28 | return 29 | } 30 | server, err := a.Opts.Route(a.App, r) 31 | if err != nil { 32 | er := errors.InternalServerError("httpgateway", err.Error()) 33 | w.Header().Set("Content-Type", "application/json") 34 | w.WriteHeader(500) 35 | w.Write([]byte(er.Error())) 36 | return 37 | } 38 | rsp := &go_api.Response{} 39 | ctx, _ := context.WithTimeout(context.TODO(), a.Opts.TimeOut) 40 | if err = mqrpc.Proto(rsp, func() (reply interface{}, errstr interface{}) { 41 | return server.SrvSession.Call(ctx, server.Hander, request) 42 | }); err != nil { 43 | w.Header().Set("Content-Type", "application/json") 44 | ce := errors.Parse(err.Error()) 45 | switch ce.Code { 46 | case 0: 47 | w.WriteHeader(500) 48 | default: 49 | w.WriteHeader(int(ce.Code)) 50 | } 51 | _, err = w.Write([]byte(ce.Error())) 52 | return 53 | } else if rsp.StatusCode == 0 { 54 | rsp.StatusCode = http.StatusOK 55 | } 56 | 57 | for _, header := range rsp.GetHeader() { 58 | for _, val := range header.Values { 59 | w.Header().Add(header.Key, val) 60 | } 61 | } 62 | 63 | if len(w.Header().Get("Content-Type")) == 0 { 64 | w.Header().Set("Content-Type", "application/json") 65 | } 66 | 67 | w.WriteHeader(int(rsp.StatusCode)) 68 | w.Write([]byte(rsp.Body)) 69 | } 70 | 71 | // NewHandler 创建网关 72 | func NewHandler(app module.App, opts ...Option) http.Handler { 73 | options := NewOptions(app, opts...) 74 | return &APIHandler{ 75 | Opts: options, 76 | App: app, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /httpgateway/api/util.go: -------------------------------------------------------------------------------- 1 | package httpgatewayapi 2 | 3 | import ( 4 | "fmt" 5 | api "github.com/liangdas/mqant/httpgateway/proto" 6 | "io/ioutil" 7 | "mime" 8 | "net" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | func RequestToProto(r *http.Request) (*api.Request, error) { 14 | if err := r.ParseForm(); err != nil { 15 | return nil, fmt.Errorf("Error parsing form: %v", err) 16 | } 17 | 18 | req := &api.Request{ 19 | Path: r.URL.Path, 20 | Method: r.Method, 21 | Header: make(map[string]*api.Pair), 22 | Get: make(map[string]*api.Pair), 23 | Post: make(map[string]*api.Pair), 24 | Url: r.URL.String(), 25 | } 26 | 27 | ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) 28 | if err != nil { 29 | ct = "text/plain; charset=UTF-8" //default CT is text/plain 30 | r.Header.Set("Content-Type", ct) 31 | } 32 | 33 | //set the body: 34 | if r.Body != nil { 35 | switch ct { 36 | case "application/x-www-form-urlencoded": 37 | // expect form vals in Post data 38 | default: 39 | 40 | data, _ := ioutil.ReadAll(r.Body) 41 | req.Body = string(data) 42 | } 43 | } 44 | 45 | // Set X-Forwarded-For if it does not exist 46 | if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { 47 | if prior, ok := r.Header["X-Forwarded-For"]; ok { 48 | ip = strings.Join(prior, ", ") + ", " + ip 49 | } 50 | 51 | // Set the header 52 | req.Header["X-Forwarded-For"] = &api.Pair{ 53 | Key: "X-Forwarded-For", 54 | Values: []string{ip}, 55 | } 56 | } 57 | 58 | // Host is stripped from net/http Headers so let's add it 59 | req.Header["Host"] = &api.Pair{ 60 | Key: "Host", 61 | Values: []string{r.Host}, 62 | } 63 | 64 | // Get data 65 | for key, vals := range r.URL.Query() { 66 | header, ok := req.Get[key] 67 | if !ok { 68 | header = &api.Pair{ 69 | Key: key, 70 | } 71 | req.Get[key] = header 72 | } 73 | header.Values = vals 74 | } 75 | 76 | // Post data 77 | for key, vals := range r.PostForm { 78 | header, ok := req.Post[key] 79 | if !ok { 80 | header = &api.Pair{ 81 | Key: key, 82 | } 83 | req.Post[key] = header 84 | } 85 | header.Values = vals 86 | } 87 | 88 | for key, vals := range r.Header { 89 | header, ok := req.Header[key] 90 | if !ok { 91 | header = &api.Pair{ 92 | Key: key, 93 | } 94 | req.Header[key] = header 95 | } 96 | header.Values = vals 97 | } 98 | 99 | return req, nil 100 | } 101 | -------------------------------------------------------------------------------- /httpgateway/api/util_test.go: -------------------------------------------------------------------------------- 1 | package httpgatewayapi 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "testing" 7 | ) 8 | 9 | func TestRequestToProto(t *testing.T) { 10 | testData := []*http.Request{ 11 | { 12 | Method: "GET", 13 | Header: http.Header{ 14 | "Header": []string{"test"}, 15 | }, 16 | URL: &url.URL{ 17 | Scheme: "http", 18 | Host: "localhost", 19 | Path: "/foo/bar", 20 | RawQuery: "param1=value1", 21 | }, 22 | }, 23 | } 24 | 25 | for _, d := range testData { 26 | p, err := RequestToProto(d) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | if p.Path != d.URL.Path { 31 | t.Fatalf("Expected path %s got %s", d.URL.Path, p.Path) 32 | } 33 | if p.Method != d.Method { 34 | t.Fatalf("Expected method %s got %s", d.Method, p.Method) 35 | } 36 | for k, v := range d.Header { 37 | if val, ok := p.Header[k]; !ok { 38 | t.Fatalf("Expected header %s", k) 39 | } else { 40 | if val.Values[0] != v[0] { 41 | t.Fatalf("Expected val %s, got %s", val.Values[0], v[0]) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /httpgateway/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides a way to return detailed information 2 | // for an rpc request error. The error is normally JSON encoded. 3 | package errors 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "net/http" 9 | ) 10 | 11 | // Error implements the error interface. 12 | type Error struct { 13 | Id string `json:"id"` 14 | Code int32 `json:"code"` 15 | Detail string `json:"detail"` 16 | Status string `json:"status"` 17 | } 18 | 19 | func (e *Error) Error() string { 20 | b, _ := json.Marshal(e) 21 | return string(b) 22 | } 23 | 24 | // New generates a custom error. 25 | func New(id, detail string, code int32) error { 26 | return &Error{ 27 | Id: id, 28 | Code: code, 29 | Detail: detail, 30 | Status: http.StatusText(int(code)), 31 | } 32 | } 33 | 34 | // Parse tries to parse a JSON string into an error. If that 35 | // fails, it will set the given string as the error detail. 36 | func Parse(err string) *Error { 37 | e := new(Error) 38 | errr := json.Unmarshal([]byte(err), e) 39 | if errr != nil { 40 | e.Detail = err 41 | } 42 | return e 43 | } 44 | 45 | // BadRequest generates a 400 error. 46 | func BadRequest(id, format string, a ...interface{}) error { 47 | return &Error{ 48 | Id: id, 49 | Code: 400, 50 | Detail: fmt.Sprintf(format, a...), 51 | Status: http.StatusText(400), 52 | } 53 | } 54 | 55 | // Unauthorized generates a 401 error. 56 | func Unauthorized(id, format string, a ...interface{}) error { 57 | return &Error{ 58 | Id: id, 59 | Code: 401, 60 | Detail: fmt.Sprintf(format, a...), 61 | Status: http.StatusText(401), 62 | } 63 | } 64 | 65 | // Forbidden generates a 403 error. 66 | func Forbidden(id, format string, a ...interface{}) error { 67 | return &Error{ 68 | Id: id, 69 | Code: 403, 70 | Detail: fmt.Sprintf(format, a...), 71 | Status: http.StatusText(403), 72 | } 73 | } 74 | 75 | // NotFound generates a 404 error. 76 | func NotFound(id, format string, a ...interface{}) error { 77 | return &Error{ 78 | Id: id, 79 | Code: 404, 80 | Detail: fmt.Sprintf(format, a...), 81 | Status: http.StatusText(404), 82 | } 83 | } 84 | 85 | // MethodNotAllowed generates a 405 error. 86 | func MethodNotAllowed(id, format string, a ...interface{}) error { 87 | return &Error{ 88 | Id: id, 89 | Code: 405, 90 | Detail: fmt.Sprintf(format, a...), 91 | Status: http.StatusText(405), 92 | } 93 | } 94 | 95 | // Timeout generates a 408 error. 96 | func Timeout(id, format string, a ...interface{}) error { 97 | return &Error{ 98 | Id: id, 99 | Code: 408, 100 | Detail: fmt.Sprintf(format, a...), 101 | Status: http.StatusText(408), 102 | } 103 | } 104 | 105 | // Conflict generates a 409 error. 106 | func Conflict(id, format string, a ...interface{}) error { 107 | return &Error{ 108 | Id: id, 109 | Code: 409, 110 | Detail: fmt.Sprintf(format, a...), 111 | Status: http.StatusText(409), 112 | } 113 | } 114 | 115 | // InternalServerError generates a 500 error. 116 | func InternalServerError(id, format string, a ...interface{}) error { 117 | return &Error{ 118 | Id: id, 119 | Code: 500, 120 | Detail: fmt.Sprintf(format, a...), 121 | Status: http.StatusText(500), 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /httpgateway/errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestErrors(t *testing.T) { 9 | testData := []*Error{ 10 | { 11 | Id: "test", 12 | Code: 500, 13 | Detail: "Internal server error", 14 | Status: http.StatusText(500), 15 | }, 16 | } 17 | 18 | for _, e := range testData { 19 | ne := New(e.Id, e.Detail, e.Code) 20 | 21 | if e.Error() != ne.Error() { 22 | t.Fatalf("Expected %s got %s", e.Error(), ne.Error()) 23 | } 24 | 25 | pe := Parse(ne.Error()) 26 | 27 | if pe == nil { 28 | t.Fatalf("Expected error got nil %v", pe) 29 | } 30 | 31 | if pe.Id != e.Id { 32 | t.Fatalf("Expected %s got %s", e.Id, pe.Id) 33 | } 34 | 35 | if pe.Detail != e.Detail { 36 | t.Fatalf("Expected %s got %s", e.Detail, pe.Detail) 37 | } 38 | 39 | if pe.Code != e.Code { 40 | t.Fatalf("Expected %d got %d", e.Code, pe.Code) 41 | } 42 | 43 | if pe.Status != e.Status { 44 | t.Fatalf("Expected %s got %s", e.Status, pe.Status) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /httpgateway/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //Package httpgateway 网关配置 16 | package httpgateway 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "github.com/liangdas/mqant/module" 22 | "github.com/liangdas/mqant/registry" 23 | "github.com/liangdas/mqant/selector" 24 | "math/rand" 25 | "net/http" 26 | "strings" 27 | "sync" 28 | "time" 29 | ) 30 | 31 | // Service represents an API service 32 | type Service struct { 33 | // hander 34 | Hander string 35 | // node 36 | SrvSession module.ServerSession 37 | } 38 | 39 | // DefaultRoute 默认路由规则 40 | var DefaultRoute = func(app module.App, r *http.Request) (*Service, error) { 41 | if r.URL.Path == "" { 42 | return nil, errors.New("path is nil") 43 | } 44 | handers := strings.Split(r.URL.Path, "/") 45 | if len(handers) < 2 { 46 | return nil, errors.New("path is not /[server]/path") 47 | } 48 | server := handers[1] 49 | if server == "" { 50 | return nil, errors.New("server is nil") 51 | } 52 | session, err := app.GetRouteServer(server, 53 | selector.WithStrategy(func(services []*registry.Service) selector.Next { 54 | var nodes []*registry.Node 55 | 56 | // Filter the nodes for datacenter 57 | for _, service := range services { 58 | for _, node := range service.Nodes { 59 | nodes = append(nodes, node) 60 | } 61 | } 62 | 63 | var mtx sync.Mutex 64 | //log.Info("services[0] $v",services[0].Nodes[0]) 65 | return func() (*registry.Node, error) { 66 | mtx.Lock() 67 | defer mtx.Unlock() 68 | if len(nodes) == 0 { 69 | return nil, fmt.Errorf("no node") 70 | } 71 | index := rand.Intn(int(len(nodes))) 72 | return nodes[index], nil 73 | } 74 | }), 75 | ) 76 | if err != nil { 77 | return nil, errors.New(err.Error()) 78 | } 79 | return &Service{SrvSession: session, Hander: r.URL.Path}, err 80 | } 81 | 82 | // Route 路由器定义 83 | type Route func(app module.App, r *http.Request) (*Service, error) 84 | 85 | // Option 配置 86 | type Option func(*Options) 87 | 88 | // Options 网关配置项 89 | type Options struct { 90 | TimeOut time.Duration 91 | Route Route 92 | } 93 | 94 | // NewOptions 创建配置 95 | func NewOptions(app module.App, opts ...Option) Options { 96 | opt := Options{ 97 | Route: DefaultRoute, 98 | TimeOut: app.Options().RPCExpired, 99 | } 100 | 101 | for _, o := range opts { 102 | o(&opt) 103 | } 104 | 105 | return opt 106 | } 107 | 108 | // SetRoute 设置路由器 109 | func SetRoute(s Route) Option { 110 | return func(o *Options) { 111 | o.Route = s 112 | } 113 | } 114 | 115 | // TimeOut 设置网关超时时间 116 | func TimeOut(s time.Duration) Option { 117 | return func(o *Options) { 118 | o.TimeOut = s 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /httpgateway/proto/api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.api; 4 | option go_package = "github.com/liangdas/mqant/httpgateway/proto/go_api"; 5 | message Pair { 6 | string key = 1; 7 | repeated string values = 2; 8 | } 9 | 10 | // A HTTP request as RPC 11 | // Forward by the api handler 12 | message Request { 13 | string method = 1; 14 | string path = 2; 15 | map header = 3; 16 | map get = 4; 17 | map post = 5; 18 | string body = 6; // raw request body; if not application/x-www-form-urlencoded 19 | string url = 7; 20 | } 21 | 22 | // A HTTP response as RPC 23 | // Expected response for the api handler 24 | message Response { 25 | int32 statusCode = 1; 26 | map header = 2; 27 | string body = 3; 28 | } 29 | 30 | // A HTTP event as RPC 31 | // Forwarded by the event handler 32 | message Event { 33 | // e.g login 34 | string name = 1; 35 | // uuid 36 | string id = 2; 37 | // unix timestamp of event 38 | int64 timestamp = 3; 39 | // event headers 40 | map header = 4; 41 | // the event data 42 | string data = 5; 43 | } 44 | -------------------------------------------------------------------------------- /log/beego/README.md: -------------------------------------------------------------------------------- 1 | ## logs 2 | logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` . 3 | 4 | 5 | ## How to install? 6 | 7 | go get github.com/astaxie/beego/logs 8 | 9 | 10 | ## What adapters are supported? 11 | 12 | As of now this logs support console, file,smtp and conn. 13 | 14 | 15 | ## How to use it? 16 | 17 | First you must import it 18 | 19 | import ( 20 | "github.com/astaxie/beego/logs" 21 | ) 22 | 23 | Then init a Log (example with console adapter) 24 | 25 | log := NewLogger(10000) 26 | log.SetLogger("console", "") 27 | 28 | > the first params stand for how many channel 29 | 30 | Use it like this: 31 | 32 | log.Trace("trace") 33 | log.Info("info") 34 | log.Warn("warning") 35 | log.Debug("debug") 36 | log.Critical("critical") 37 | 38 | 39 | ## File adapter 40 | 41 | Configure file adapter like this: 42 | 43 | log := NewLogger(10000) 44 | log.SetLogger("file", `{"filename":"test.log"}`) 45 | 46 | 47 | ## Conn adapter 48 | 49 | Configure like this: 50 | 51 | log := NewLogger(1000) 52 | log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) 53 | log.Info("info") 54 | 55 | 56 | ## Smtp adapter 57 | 58 | Configure like this: 59 | 60 | log := NewLogger(10000) 61 | log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) 62 | log.Critical("sendmail critical") 63 | time.Sleep(time.Second * 30) 64 | -------------------------------------------------------------------------------- /log/beego/color.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build !windows 16 | 17 | package logs 18 | 19 | import "io" 20 | 21 | type ansiColorWriter struct { 22 | w io.Writer 23 | mode outputMode 24 | } 25 | 26 | func (cw *ansiColorWriter) Write(p []byte) (int, error) { 27 | return cw.w.Write(p) 28 | } 29 | -------------------------------------------------------------------------------- /log/beego/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "encoding/json" 19 | "io" 20 | "net" 21 | "time" 22 | ) 23 | 24 | // connWriter implements LoggerInterface. 25 | // it writes messages in keep-live tcp connection. 26 | type connWriter struct { 27 | lg *logWriter 28 | innerWriter io.WriteCloser 29 | ReconnectOnMsg bool `json:"reconnectOnMsg"` 30 | Reconnect bool `json:"reconnect"` 31 | Net string `json:"net"` 32 | Addr string `json:"addr"` 33 | Level int `json:"level"` 34 | } 35 | 36 | // NewConn create new ConnWrite returning as LoggerInterface. 37 | func NewConn() Logger { 38 | conn := new(connWriter) 39 | conn.Level = LevelTrace 40 | return conn 41 | } 42 | 43 | // Init init connection writer with json config. 44 | // json config only need key "level". 45 | func (c *connWriter) Init(jsonConfig string) error { 46 | return json.Unmarshal([]byte(jsonConfig), c) 47 | } 48 | 49 | // WriteMsg write message in connection. 50 | // if connection is down, try to re-connect. 51 | func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { 52 | if level > c.Level { 53 | return nil 54 | } 55 | if c.needToConnectOnMsg() { 56 | err := c.connect() 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | 62 | if c.ReconnectOnMsg { 63 | defer c.innerWriter.Close() 64 | } 65 | 66 | c.lg.println(when, msg) 67 | return nil 68 | } 69 | 70 | func (c *connWriter) WriteOriginalMsg(when time.Time, msg string, level int) error { 71 | return c.WriteMsg(when, msg, level) 72 | } 73 | 74 | // Flush implementing method. empty. 75 | func (c *connWriter) Flush() { 76 | 77 | } 78 | 79 | // Destroy destroy connection writer and close tcp listener. 80 | func (c *connWriter) Destroy() { 81 | if c.innerWriter != nil { 82 | c.innerWriter.Close() 83 | } 84 | } 85 | 86 | func (c *connWriter) connect() error { 87 | if c.innerWriter != nil { 88 | c.innerWriter.Close() 89 | c.innerWriter = nil 90 | } 91 | 92 | conn, err := net.Dial(c.Net, c.Addr) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | if tcpConn, ok := conn.(*net.TCPConn); ok { 98 | tcpConn.SetKeepAlive(true) 99 | } 100 | 101 | c.innerWriter = conn 102 | c.lg = newLogWriter(conn) 103 | return nil 104 | } 105 | 106 | func (c *connWriter) needToConnectOnMsg() bool { 107 | if c.Reconnect { 108 | c.Reconnect = false 109 | return true 110 | } 111 | 112 | if c.innerWriter == nil { 113 | return true 114 | } 115 | 116 | return c.ReconnectOnMsg 117 | } 118 | 119 | func init() { 120 | Register(AdapterConn, NewConn) 121 | } 122 | -------------------------------------------------------------------------------- /log/beego/conn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestConn(t *testing.T) { 22 | log := NewLogger(1000) 23 | log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) 24 | log.Informational("informational") 25 | } 26 | -------------------------------------------------------------------------------- /log/beego/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "encoding/json" 19 | "os" 20 | "runtime" 21 | "time" 22 | ) 23 | 24 | // brush is a color join function 25 | type brush func(string) string 26 | 27 | // newBrush return a fix color Brush 28 | func newBrush(color string) brush { 29 | pre := "\033[" 30 | reset := "\033[0m" 31 | return func(text string) string { 32 | return pre + color + "m" + text + reset 33 | } 34 | } 35 | 36 | var colors = []brush{ 37 | newBrush("1;37"), // Emergency white 38 | newBrush("1;36"), // Alert cyan 39 | newBrush("1;35"), // Critical magenta 40 | newBrush("1;31"), // Error red 41 | newBrush("1;33"), // Warning yellow 42 | newBrush("1;32"), // Notice green 43 | newBrush("1;34"), // Informational blue 44 | newBrush("1;44"), // Debug Background blue 45 | } 46 | 47 | // consoleWriter implements LoggerInterface and writes messages to terminal. 48 | type consoleWriter struct { 49 | lg *logWriter 50 | Level int `json:"level"` 51 | Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color 52 | } 53 | 54 | // NewConsole create ConsoleWriter returning as LoggerInterface. 55 | func NewConsole() Logger { 56 | cw := &consoleWriter{ 57 | lg: newLogWriter(os.Stdout), 58 | Level: LevelDebug, 59 | Colorful: runtime.GOOS != "windows", 60 | } 61 | return cw 62 | } 63 | 64 | // Init init console logger. 65 | // jsonConfig like '{"level":LevelTrace}'. 66 | func (c *consoleWriter) Init(jsonConfig string) error { 67 | if len(jsonConfig) == 0 { 68 | return nil 69 | } 70 | err := json.Unmarshal([]byte(jsonConfig), c) 71 | if runtime.GOOS == "windows" { 72 | c.Colorful = false 73 | } 74 | return err 75 | } 76 | 77 | // WriteMsg write message in console. 78 | func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error { 79 | if level > c.Level { 80 | return nil 81 | } 82 | if c.Colorful { 83 | msg = colors[level](msg) 84 | } 85 | c.lg.println(when, msg) 86 | return nil 87 | } 88 | 89 | func (c *consoleWriter) WriteOriginalMsg(when time.Time, msg string, level int) error { 90 | return c.WriteMsg(when, msg, level) 91 | } 92 | 93 | // Destroy implementing method. empty. 94 | func (c *consoleWriter) Destroy() { 95 | 96 | } 97 | 98 | // Flush implementing method. empty. 99 | func (c *consoleWriter) Flush() { 100 | 101 | } 102 | 103 | func init() { 104 | Register(AdapterConsole, NewConsole) 105 | } 106 | -------------------------------------------------------------------------------- /log/beego/console_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | // Try each log level in decreasing order of priority. 22 | func testConsoleCalls(bl *BeeLogger) { 23 | bl.Emergency("emergency") 24 | bl.Alert("alert") 25 | bl.Critical("critical") 26 | bl.Error("error") 27 | bl.Warning("warning") 28 | bl.Notice("notice") 29 | bl.Informational("informational") 30 | bl.Debug("debug") 31 | } 32 | 33 | // Test console logging by visually comparing the lines being output with and 34 | // without a log level specification. 35 | func TestConsole(t *testing.T) { 36 | log1 := NewLogger(10000) 37 | log1.EnableFuncCallDepth(true) 38 | log1.SetLogger("console", "") 39 | testConsoleCalls(log1) 40 | 41 | log2 := NewLogger(100) 42 | log2.SetLogger("console", `{"level":3}`) 43 | testConsoleCalls(log2) 44 | } 45 | 46 | // Test console without color 47 | func TestConsoleNoColor(t *testing.T) { 48 | log := NewLogger(100) 49 | log.SetLogger("console", `{"color":false}`) 50 | testConsoleCalls(log) 51 | } 52 | -------------------------------------------------------------------------------- /log/beego/dingtalk.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/json-iterator/go" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | // SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook 13 | type DingtalkWriter struct { 14 | WebhookURL string `json:"webhookurl"` 15 | Level int `json:"level"` 16 | } 17 | 18 | // newSLACKWriter create jiaoliao writer. 19 | func newDingtalkWriter() Logger { 20 | return &DingtalkWriter{Level: LevelTrace} 21 | } 22 | 23 | // Init SLACKWriter with json config string 24 | func (s *DingtalkWriter) Init(jsonconfig string) error { 25 | return json.Unmarshal([]byte(jsonconfig), s) 26 | } 27 | 28 | // WriteMsg write message in smtp writer. 29 | // it will send an email with subject and only this message. 30 | func (s *DingtalkWriter) WriteMsg(when time.Time, msg string, level int) error { 31 | if level > s.Level { 32 | return nil 33 | } 34 | dingtalk := map[string]interface{}{ 35 | "msgtype": "text", 36 | "text": map[string]string{ 37 | "content": fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg), 38 | }, 39 | } 40 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 41 | text, err := json.Marshal(dingtalk) 42 | if err != nil { 43 | return err 44 | } 45 | reader := bytes.NewReader(text) 46 | request, err := http.NewRequest("POST", s.WebhookURL, reader) 47 | if err != nil { 48 | fmt.Println(err.Error()) 49 | return err 50 | } 51 | request.Header.Set("Content-Type", "application/json;charset=UTF-8") 52 | client := http.Client{} 53 | resp, err := client.Do(request) 54 | if err != nil { 55 | fmt.Println(err.Error()) 56 | return err 57 | } 58 | defer resp.Body.Close() 59 | if resp.StatusCode != http.StatusOK { 60 | return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) 61 | } 62 | return nil 63 | } 64 | 65 | func (s *DingtalkWriter) WriteOriginalMsg(when time.Time, msg string, level int) error { 66 | return s.WriteMsg(when, msg, level) 67 | } 68 | 69 | // Flush implementing method. empty. 70 | func (s *DingtalkWriter) Flush() { 71 | } 72 | 73 | // Destroy implementing method. empty. 74 | func (s *DingtalkWriter) Destroy() { 75 | } 76 | 77 | func init() { 78 | Register(AdapterDingtalk, newDingtalkWriter) 79 | } 80 | -------------------------------------------------------------------------------- /log/beego/jianliao.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "fmt" 5 | "github.com/json-iterator/go" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | // JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook 12 | type JLWriter struct { 13 | AuthorName string `json:"authorname"` 14 | Title string `json:"title"` 15 | WebhookURL string `json:"webhookurl"` 16 | RedirectURL string `json:"redirecturl,omitempty"` 17 | ImageURL string `json:"imageurl,omitempty"` 18 | Level int `json:"level"` 19 | } 20 | 21 | // newJLWriter create jiaoliao writer. 22 | func newJLWriter() Logger { 23 | return &JLWriter{Level: LevelTrace} 24 | } 25 | 26 | // Init JLWriter with json config string 27 | func (s *JLWriter) Init(jsonconfig string) error { 28 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 29 | return json.Unmarshal([]byte(jsonconfig), s) 30 | } 31 | 32 | // WriteMsg write message in smtp writer. 33 | // it will send an email with subject and only this message. 34 | func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error { 35 | if level > s.Level { 36 | return nil 37 | } 38 | 39 | text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg) 40 | 41 | form := url.Values{} 42 | form.Add("authorName", s.AuthorName) 43 | form.Add("title", s.Title) 44 | form.Add("text", text) 45 | if s.RedirectURL != "" { 46 | form.Add("redirectUrl", s.RedirectURL) 47 | } 48 | if s.ImageURL != "" { 49 | form.Add("imageUrl", s.ImageURL) 50 | } 51 | 52 | resp, err := http.PostForm(s.WebhookURL, form) 53 | if err != nil { 54 | return err 55 | } 56 | defer resp.Body.Close() 57 | if resp.StatusCode != http.StatusOK { 58 | return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) 59 | } 60 | return nil 61 | } 62 | 63 | func (s *JLWriter) WriteOriginalMsg(when time.Time, msg string, level int) error { 64 | return s.WriteMsg(when, msg, level) 65 | } 66 | 67 | // Flush implementing method. empty. 68 | func (s *JLWriter) Flush() { 69 | } 70 | 71 | // Destroy implementing method. empty. 72 | func (s *JLWriter) Destroy() { 73 | } 74 | 75 | func init() { 76 | Register(AdapterJianLiao, newJLWriter) 77 | } 78 | -------------------------------------------------------------------------------- /log/beego/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "bytes" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | func TestFormatHeader_0(t *testing.T) { 24 | tm := time.Now() 25 | if tm.Year() >= 2100 { 26 | t.FailNow() 27 | } 28 | dur := time.Second 29 | for { 30 | if tm.Year() >= 2100 { 31 | break 32 | } 33 | h, _ := formatTimeHeader(tm) 34 | if tm.Format("2006/01/02 15:04:05 ") != string(h) { 35 | t.Log(tm) 36 | t.FailNow() 37 | } 38 | tm = tm.Add(dur) 39 | dur *= 2 40 | } 41 | } 42 | 43 | func TestFormatHeader_1(t *testing.T) { 44 | tm := time.Now() 45 | year := tm.Year() 46 | dur := time.Second 47 | for { 48 | if tm.Year() >= year+1 { 49 | break 50 | } 51 | h, _ := formatTimeHeader(tm) 52 | if tm.Format("2006/01/02 15:04:05 ") != string(h) { 53 | t.Log(tm) 54 | t.FailNow() 55 | } 56 | tm = tm.Add(dur) 57 | } 58 | } 59 | 60 | func TestNewAnsiColor1(t *testing.T) { 61 | inner := bytes.NewBufferString("") 62 | w := NewAnsiColorWriter(inner) 63 | if w == inner { 64 | t.Errorf("Get %#v, want %#v", w, inner) 65 | } 66 | } 67 | 68 | func TestNewAnsiColor2(t *testing.T) { 69 | inner := bytes.NewBufferString("") 70 | w1 := NewAnsiColorWriter(inner) 71 | w2 := NewAnsiColorWriter(w1) 72 | if w1 != w2 { 73 | t.Errorf("Get %#v, want %#v", w1, w2) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /log/beego/multifile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "encoding/json" 19 | "time" 20 | ) 21 | 22 | // A filesLogWriter manages several fileLogWriter 23 | // filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file 24 | // means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log 25 | // and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log 26 | // the rotate attribute also acts like fileLogWriter 27 | type multiFileLogWriter struct { 28 | writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter 29 | fullLogWriter *fileLogWriter 30 | Separate []string `json:"separate"` 31 | } 32 | 33 | var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"} 34 | 35 | // Init file logger with json config. 36 | // jsonConfig like: 37 | // { 38 | // "filename":"logs/beego.log", 39 | // "maxLines":0, 40 | // "maxsize":0, 41 | // "daily":true, 42 | // "maxDays":15, 43 | // "rotate":true, 44 | // "perm":0600, 45 | // "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"], 46 | // } 47 | 48 | func (f *multiFileLogWriter) Init(config string) error { 49 | writer := newFileWriter().(*fileLogWriter) 50 | err := writer.Init(config) 51 | if err != nil { 52 | return err 53 | } 54 | f.fullLogWriter = writer 55 | f.writers[LevelDebug+1] = writer 56 | 57 | //unmarshal "separate" field to f.Separate 58 | json.Unmarshal([]byte(config), f) 59 | 60 | jsonMap := map[string]interface{}{} 61 | json.Unmarshal([]byte(config), &jsonMap) 62 | 63 | for i := LevelEmergency; i < LevelDebug+1; i++ { 64 | for _, v := range f.Separate { 65 | if v == levelNames[i] { 66 | jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix 67 | jsonMap["level"] = i 68 | bs, _ := json.Marshal(jsonMap) 69 | writer = newFileWriter().(*fileLogWriter) 70 | writer.Init(string(bs)) 71 | f.writers[i] = writer 72 | } 73 | } 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func (f *multiFileLogWriter) Destroy() { 80 | for i := 0; i < len(f.writers); i++ { 81 | if f.writers[i] != nil { 82 | f.writers[i].Destroy() 83 | } 84 | } 85 | } 86 | 87 | func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error { 88 | if f.fullLogWriter != nil { 89 | f.fullLogWriter.WriteMsg(when, msg, level) 90 | } 91 | for i := 0; i < len(f.writers)-1; i++ { 92 | if f.writers[i] != nil { 93 | if level == f.writers[i].Level { 94 | f.writers[i].WriteMsg(when, msg, level) 95 | } 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | func (f *multiFileLogWriter) WriteOriginalMsg(when time.Time, msg string, level int) error { 102 | if f.fullLogWriter != nil { 103 | f.fullLogWriter.WriteOriginalMsg(when, msg, level) 104 | } 105 | for i := 0; i < len(f.writers)-1; i++ { 106 | if f.writers[i] != nil { 107 | if level == f.writers[i].Level { 108 | f.writers[i].WriteOriginalMsg(when, msg, level) 109 | } 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | func (f *multiFileLogWriter) Flush() { 116 | for i := 0; i < len(f.writers); i++ { 117 | if f.writers[i] != nil { 118 | f.writers[i].Flush() 119 | } 120 | } 121 | } 122 | 123 | // newFilesWriter create a FileLogWriter returning as LoggerInterface. 124 | func newFilesWriter() Logger { 125 | return &multiFileLogWriter{} 126 | } 127 | 128 | func init() { 129 | Register(AdapterMultiFile, newFilesWriter) 130 | } 131 | -------------------------------------------------------------------------------- /log/beego/multifile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "bufio" 19 | "os" 20 | "strconv" 21 | "strings" 22 | "testing" 23 | ) 24 | 25 | func TestFiles_1(t *testing.T) { 26 | log := NewLogger(10000) 27 | log.SetLogger("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`) 28 | log.Debug("debug") 29 | log.Informational("info") 30 | log.Notice("notice") 31 | log.Warning("warning") 32 | log.Error("error") 33 | log.Alert("alert") 34 | log.Critical("critical") 35 | log.Emergency("emergency") 36 | fns := []string{""} 37 | fns = append(fns, levelNames[0:]...) 38 | name := "test" 39 | suffix := ".log" 40 | for _, fn := range fns { 41 | 42 | file := name + suffix 43 | if fn != "" { 44 | file = name + "." + fn + suffix 45 | } 46 | f, err := os.Open(file) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | b := bufio.NewReader(f) 51 | lineNum := 0 52 | lastLine := "" 53 | for { 54 | line, _, err := b.ReadLine() 55 | if err != nil { 56 | break 57 | } 58 | if len(line) > 0 { 59 | lastLine = string(line) 60 | lineNum++ 61 | } 62 | } 63 | var expected = 1 64 | if fn == "" { 65 | expected = LevelDebug + 1 66 | } 67 | if lineNum != expected { 68 | t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines") 69 | } 70 | if lineNum == 1 { 71 | if !strings.Contains(lastLine, fn) { 72 | t.Fatal(file + " " + lastLine + " not contains the log msg " + fn) 73 | } 74 | } 75 | os.Remove(file) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /log/beego/slack.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | // SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook 12 | type SLACKWriter struct { 13 | WebhookURL string `json:"webhookurl"` 14 | Level int `json:"level"` 15 | } 16 | 17 | // newSLACKWriter create jiaoliao writer. 18 | func newSLACKWriter() Logger { 19 | return &SLACKWriter{Level: LevelTrace} 20 | } 21 | 22 | // Init SLACKWriter with json config string 23 | func (s *SLACKWriter) Init(jsonconfig string) error { 24 | return json.Unmarshal([]byte(jsonconfig), s) 25 | } 26 | 27 | // WriteMsg write message in smtp writer. 28 | // it will send an email with subject and only this message. 29 | func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error { 30 | if level > s.Level { 31 | return nil 32 | } 33 | 34 | text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg) 35 | 36 | form := url.Values{} 37 | form.Add("payload", text) 38 | 39 | resp, err := http.PostForm(s.WebhookURL, form) 40 | if err != nil { 41 | return err 42 | } 43 | defer resp.Body.Close() 44 | if resp.StatusCode != http.StatusOK { 45 | return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) 46 | } 47 | return nil 48 | } 49 | 50 | func (s *SLACKWriter) WriteOriginalMsg(when time.Time, msg string, level int) error { 51 | return s.WriteMsg(when, msg, level) 52 | } 53 | 54 | // Flush implementing method. empty. 55 | func (s *SLACKWriter) Flush() { 56 | } 57 | 58 | // Destroy implementing method. empty. 59 | func (s *SLACKWriter) Destroy() { 60 | } 61 | 62 | func init() { 63 | Register(AdapterSlack, newSLACKWriter) 64 | } 65 | -------------------------------------------------------------------------------- /log/beego/smtp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestSmtp(t *testing.T) { 23 | log := NewLogger(10000) 24 | log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) 25 | log.Critical("sendmail critical") 26 | time.Sleep(time.Second * 30) 27 | } 28 | -------------------------------------------------------------------------------- /log/beego/trace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package logs 15 | 16 | type BeegoTraceSpan struct { 17 | Trace string 18 | Span string 19 | } 20 | -------------------------------------------------------------------------------- /log/beego_logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package log beego日志 16 | package log 17 | 18 | import ( 19 | "encoding/json" 20 | "fmt" 21 | 22 | logs "github.com/liangdas/mqant/log/beego" 23 | ) 24 | 25 | //NewBeegoLogger beego 26 | func NewBeegoLogger(debug bool, ProcessID string, Logdir string, settings map[string]interface{}, logFilePath func(logdir, prefix, processID, suffix string) string) *logs.BeeLogger { 27 | log := logs.NewLogger() 28 | log.ProcessID = ProcessID 29 | log.EnableFuncCallDepth(true) 30 | //log.Async(1024) //同步打印,可能影响性能 31 | log.SetLogFuncCallDepth(4) 32 | if debug { 33 | //控制台 34 | log.SetLogger(logs.AdapterConsole) 35 | } 36 | if contenttype, ok := settings["contenttype"]; ok { 37 | log.SetContentType(contenttype.(string)) 38 | } 39 | if f, ok := settings["file"]; ok { 40 | ff := f.(map[string]interface{}) 41 | Prefix := "" 42 | if prefix, ok := ff["prefix"]; ok { 43 | Prefix = prefix.(string) 44 | } 45 | Suffix := ".log" 46 | if suffix, ok := ff["suffix"]; ok { 47 | Suffix = suffix.(string) 48 | } 49 | ff["filename"] = fmt.Sprintf("%s/%v%s%s", Logdir, Prefix, ProcessID, Suffix) 50 | if logFilePath != nil { 51 | ff["filename"] = logFilePath(Logdir, Prefix, ProcessID, Suffix) 52 | } 53 | config, err := json.Marshal(ff) 54 | if err != nil { 55 | logs.Error(err) 56 | } 57 | log.SetLogger(logs.AdapterFile, string(config)) 58 | } 59 | if f, ok := settings["multifile"]; ok { 60 | multifile := f.(map[string]interface{}) 61 | Prefix := "" 62 | if prefix, ok := multifile["prefix"]; ok { 63 | Prefix = prefix.(string) 64 | } 65 | Suffix := ".log" 66 | if suffix, ok := multifile["suffix"]; ok { 67 | Suffix = suffix.(string) 68 | } 69 | multifile["filename"] = fmt.Sprintf("%s/%v%s%s", Logdir, Prefix, ProcessID, Suffix) 70 | if logFilePath != nil { 71 | multifile["filename"] = logFilePath(Logdir, Prefix, ProcessID, Suffix) 72 | } 73 | config, err := json.Marshal(multifile) 74 | if err != nil { 75 | logs.Error(err) 76 | } 77 | log.SetLogger(logs.AdapterMultiFile, string(config)) 78 | } 79 | if dingtalk, ok := settings["dingtalk"]; ok { 80 | config, err := json.Marshal(dingtalk) 81 | if err != nil { 82 | logs.Error(err) 83 | } 84 | log.SetLogger(logs.AdapterDingtalk, string(config)) 85 | } 86 | if slack, ok := settings["slack"]; ok { 87 | config, err := json.Marshal(slack) 88 | if err != nil { 89 | logs.Error(err) 90 | } 91 | log.SetLogger(logs.AdapterSlack, string(config)) 92 | } 93 | if jianliao, ok := settings["jianliao"]; ok { 94 | config, err := json.Marshal(jianliao) 95 | if err != nil { 96 | logs.Error(err) 97 | } 98 | log.SetLogger(logs.AdapterJianLiao, string(config)) 99 | } 100 | if conn, ok := settings["conn"]; ok { 101 | config, err := json.Marshal(conn) 102 | if err != nil { 103 | logs.Error(err) 104 | } 105 | log.SetLogger(logs.AdapterConn, string(config)) 106 | } 107 | if smtp, ok := settings["smtp"]; ok { 108 | config, err := json.Marshal(smtp) 109 | if err != nil { 110 | logs.Error(err) 111 | } 112 | log.SetLogger(logs.AdapterMail, string(config)) 113 | } 114 | return log 115 | } 116 | -------------------------------------------------------------------------------- /log/define.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package log 日志结构定义 16 | package log 17 | 18 | import "github.com/liangdas/mqant/utils" 19 | 20 | // TraceSpan A SpanID refers to a single span. 21 | type TraceSpan interface { 22 | 23 | // Trace is the root ID of the tree that contains all of the spans 24 | // related to this one. 25 | TraceId() string 26 | 27 | // Span is an ID that probabilistically uniquely identifies this 28 | // span. 29 | SpanId() string 30 | 31 | ExtractSpan() TraceSpan 32 | } 33 | 34 | // TraceSpanImp TraceSpanImp 35 | type TraceSpanImp struct { 36 | Trace string `json:"Trace"` 37 | Span string `json:"Span"` 38 | } 39 | 40 | // TraceId TraceId 41 | // Deprecated: 因为命名规范问题函数将废弃,请用TraceID代替 42 | func (t *TraceSpanImp) TraceId() string { 43 | return t.Trace 44 | } 45 | 46 | // TraceID TraceID 47 | func (t *TraceSpanImp) TraceID() string { 48 | return t.Trace 49 | } 50 | 51 | // SpanId SpanId 52 | // Deprecated: 因为命名规范问题函数将废弃,请用SpanID代替 53 | func (t *TraceSpanImp) SpanId() string { 54 | return t.Span 55 | } 56 | 57 | // SpanID SpanID 58 | func (t *TraceSpanImp) SpanID() string { 59 | return t.Span 60 | } 61 | 62 | // ExtractSpan ExtractSpan 63 | func (t *TraceSpanImp) ExtractSpan() TraceSpan { 64 | return &TraceSpanImp{ 65 | Trace: t.Trace, 66 | Span: mqanttools.GenerateID().String(), 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /log/gen_options.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | //go:generate optiongen --option_with_struct_name=false 4 | func OptionsOptionDeclareWithDefault() interface{} { 5 | return map[string]interface{}{ 6 | "Debug": false, 7 | "ProcessID": "", 8 | "LogDir": "", 9 | "LogFileName": func(logdir, prefix, processID, suffix string) string { return "" }, 10 | "BiDir": "", 11 | "BIFileName": func(logdir, prefix, processID, suffix string) string { return "" }, 12 | "BiSetting": map[string]interface{}{}, 13 | "LogSetting": map[string]interface{}{}, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /log/gen_options_optiongen.go: -------------------------------------------------------------------------------- 1 | // Code generated by optiongen. DO NOT EDIT. 2 | // optiongen: github.com/timestee/optiongen 3 | 4 | package log 5 | 6 | type Options struct { 7 | Debug bool 8 | ProcessID string 9 | LogDir string 10 | LogFileName func(logdir, prefix, processID, suffix string) string 11 | BiDir string 12 | BIFileName func(logdir, prefix, processID, suffix string) string 13 | BiSetting map[string]interface{} 14 | LogSetting map[string]interface{} 15 | } 16 | 17 | func (cc *Options) SetOption(opt Option) { 18 | _ = opt(cc) 19 | } 20 | 21 | func (cc *Options) ApplyOption(opts ...Option) { 22 | for _, opt := range opts { 23 | _ = opt(cc) 24 | } 25 | } 26 | 27 | func (cc *Options) GetSetOption(opt Option) Option { 28 | return opt(cc) 29 | } 30 | 31 | type Option func(cc *Options) Option 32 | 33 | func WithDebug(v bool) Option { 34 | return func(cc *Options) Option { 35 | previous := cc.Debug 36 | cc.Debug = v 37 | return WithDebug(previous) 38 | } 39 | } 40 | 41 | func WithProcessID(v string) Option { 42 | return func(cc *Options) Option { 43 | previous := cc.ProcessID 44 | cc.ProcessID = v 45 | return WithProcessID(previous) 46 | } 47 | } 48 | 49 | func WithLogDir(v string) Option { 50 | return func(cc *Options) Option { 51 | previous := cc.LogDir 52 | cc.LogDir = v 53 | return WithLogDir(previous) 54 | } 55 | } 56 | 57 | func WithLogFileName(v func(logdir, prefix, processID, suffix string) string) Option { 58 | return func(cc *Options) Option { 59 | previous := cc.LogFileName 60 | cc.LogFileName = v 61 | return WithLogFileName(previous) 62 | } 63 | } 64 | 65 | func WithBiDir(v string) Option { 66 | return func(cc *Options) Option { 67 | previous := cc.BiDir 68 | cc.BiDir = v 69 | return WithBiDir(previous) 70 | } 71 | } 72 | 73 | func WithBIFileName(v func(logdir, prefix, processID, suffix string) string) Option { 74 | return func(cc *Options) Option { 75 | previous := cc.BIFileName 76 | cc.BIFileName = v 77 | return WithBIFileName(previous) 78 | } 79 | } 80 | 81 | func WithBiSetting(v map[string]interface{}) Option { 82 | return func(cc *Options) Option { 83 | previous := cc.BiSetting 84 | cc.BiSetting = v 85 | return WithBiSetting(previous) 86 | } 87 | } 88 | 89 | func WithLogSetting(v map[string]interface{}) Option { 90 | return func(cc *Options) Option { 91 | previous := cc.LogSetting 92 | cc.LogSetting = v 93 | return WithLogSetting(previous) 94 | } 95 | } 96 | 97 | func NewOptions(opts ...Option) *Options { 98 | cc := newDefaultOptions() 99 | 100 | for _, opt := range opts { 101 | _ = opt(cc) 102 | } 103 | if watchDogOptions != nil { 104 | watchDogOptions(cc) 105 | } 106 | return cc 107 | } 108 | 109 | func InstallOptionsWatchDog(dog func(cc *Options)) { 110 | watchDogOptions = dog 111 | } 112 | 113 | var watchDogOptions func(cc *Options) 114 | 115 | func newDefaultOptions() *Options { 116 | 117 | cc := &Options{} 118 | 119 | for _, opt := range [...]Option{ 120 | WithDebug(false), 121 | WithProcessID(""), 122 | WithLogDir(""), 123 | WithLogFileName(func(logdir, prefix, processID, suffix string) string { 124 | return "" 125 | }), 126 | WithBiDir(""), 127 | WithBIFileName(func(logdir, prefix, processID, suffix string) string { 128 | return "" 129 | }), 130 | WithBiSetting(make(map[string]interface{}, 0)), 131 | WithLogSetting(make(map[string]interface{}, 0)), 132 | } { 133 | _ = opt(cc) 134 | } 135 | 136 | return cc 137 | } 138 | -------------------------------------------------------------------------------- /module/base/DefaultModule.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package basemodule 模块管理 16 | package basemodule 17 | 18 | import ( 19 | "github.com/liangdas/mqant/conf" 20 | "github.com/liangdas/mqant/log" 21 | "github.com/liangdas/mqant/module" 22 | "runtime" 23 | "sync" 24 | ) 25 | 26 | // DefaultModule 模块结构 27 | type DefaultModule struct { 28 | mi module.Module 29 | settings *conf.ModuleSettings 30 | closeSig chan bool 31 | wg sync.WaitGroup 32 | } 33 | 34 | func run(m *DefaultModule) { 35 | defer func() { 36 | if r := recover(); r != nil { 37 | if conf.LenStackBuf > 0 { 38 | buf := make([]byte, conf.LenStackBuf) 39 | l := runtime.Stack(buf, false) 40 | log.Error("%v: %s", r, buf[:l]) 41 | } else { 42 | log.Error("%v", r) 43 | } 44 | } 45 | }() 46 | m.mi.Run(m.closeSig) 47 | m.wg.Done() 48 | } 49 | 50 | func destroy(m *DefaultModule) { 51 | defer func() { 52 | if r := recover(); r != nil { 53 | if conf.LenStackBuf > 0 { 54 | buf := make([]byte, conf.LenStackBuf) 55 | l := runtime.Stack(buf, false) 56 | log.Error("%v: %s", r, buf[:l]) 57 | } else { 58 | log.Error("%v", r) 59 | } 60 | } 61 | }() 62 | m.mi.OnDestroy() 63 | } 64 | -------------------------------------------------------------------------------- /module/base/ModuleManager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package basemodule 模块管理器 16 | package basemodule 17 | 18 | import ( 19 | "fmt" 20 | "github.com/liangdas/mqant/conf" 21 | "github.com/liangdas/mqant/log" 22 | "github.com/liangdas/mqant/module" 23 | ) 24 | 25 | // NewModuleManager 新建模块管理器 26 | func NewModuleManager() (m *ModuleManager) { 27 | m = new(ModuleManager) 28 | return 29 | } 30 | 31 | // ModuleManager 模块管理器 32 | type ModuleManager struct { 33 | app module.App 34 | mods []*DefaultModule 35 | runMods []*DefaultModule 36 | } 37 | 38 | // Register 注册模块 39 | func (mer *ModuleManager) Register(mi module.Module) { 40 | md := new(DefaultModule) 41 | md.mi = mi 42 | md.closeSig = make(chan bool, 1) 43 | 44 | mer.mods = append(mer.mods, md) 45 | } 46 | 47 | // RegisterRunMod 注册需要运行的模块 48 | func (mer *ModuleManager) RegisterRunMod(mi module.Module) { 49 | md := new(DefaultModule) 50 | md.mi = mi 51 | md.closeSig = make(chan bool, 1) 52 | 53 | mer.runMods = append(mer.runMods, md) 54 | } 55 | 56 | // Init 初始化 57 | func (mer *ModuleManager) Init(app module.App, ProcessID string) { 58 | log.Info("This service ModuleGroup(ProcessID) is [%s]", ProcessID) 59 | mer.app = app 60 | mer.CheckModuleSettings() //配置文件规则检查 61 | for i := 0; i < len(mer.mods); i++ { 62 | for Type, modSettings := range app.GetSettings().Module { 63 | if mer.mods[i].mi.GetType() == Type { 64 | //匹配 65 | for _, setting := range modSettings { 66 | //这里可能有BUG 公网IP和局域网IP处理方式可能不一样,先不管 67 | if ProcessID == setting.ProcessID { 68 | mer.runMods = append(mer.runMods, mer.mods[i]) //这里加入能够运行的组件 69 | mer.mods[i].settings = setting 70 | } 71 | } 72 | break //跳出内部循环 73 | } 74 | } 75 | } 76 | 77 | for i := 0; i < len(mer.runMods); i++ { 78 | m := mer.runMods[i] 79 | m.mi.OnInit(app, m.settings) 80 | 81 | if app.GetModuleInited() != nil { 82 | app.GetModuleInited()(app, m.mi) 83 | } 84 | 85 | m.wg.Add(1) 86 | go run(m) 87 | } 88 | //timer.SetTimer(3, mer.ReportStatistics, nil) //统计汇报定时任务 89 | } 90 | 91 | // CheckModuleSettings module配置文件规则检查 92 | // ID全局必须唯一 93 | // 每一个类型的Module列表中ProcessID不能重复 94 | func (mer *ModuleManager) CheckModuleSettings() { 95 | gid := map[string]string{} //用来保存全局ID-ModuleType 96 | for Type, modSettings := range conf.Conf.Module { 97 | pid := map[string]string{} //用来保存模块中的 ProcessID-ID 98 | for _, setting := range modSettings { 99 | if Stype, ok := gid[setting.ID]; ok { 100 | //如果Id已经存在,说明有两个相同Id的模块,这种情况不能被允许,这里就直接抛异常 强制崩溃以免以后调试找不到问题 101 | panic(fmt.Sprintf("ID (%s) been used in modules of type [%s] and cannot be reused", setting.ID, Stype)) 102 | } else { 103 | gid[setting.ID] = Type 104 | } 105 | 106 | if id, ok := pid[setting.ProcessID]; ok { 107 | //如果Id已经存在,说明有两个相同Id的模块,这种情况不能被允许,这里就直接抛异常 强制崩溃以免以后调试找不到问题 108 | panic(fmt.Sprintf("In the list of modules of type [%s], ProcessID (%s) has been used for ID module for (%s)", Type, setting.ProcessID, id)) 109 | } else { 110 | pid[setting.ProcessID] = setting.ID 111 | } 112 | } 113 | } 114 | } 115 | 116 | // Destroy 停止模块 117 | func (mer *ModuleManager) Destroy() { 118 | for i := len(mer.runMods) - 1; i >= 0; i-- { 119 | m := mer.runMods[i] 120 | m.closeSig <- true 121 | m.wg.Wait() 122 | destroy(m) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /module/base/ServerSession.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package basemodule 服务节点实例定义 16 | package basemodule 17 | 18 | import ( 19 | "context" 20 | "github.com/liangdas/mqant/module" 21 | "github.com/liangdas/mqant/registry" 22 | "github.com/liangdas/mqant/rpc" 23 | "github.com/liangdas/mqant/rpc/base" 24 | ) 25 | 26 | // NewServerSession 创建一个节点实例 27 | func NewServerSession(app module.App, name string, node *registry.Node) (module.ServerSession, error) { 28 | session := &serverSession{ 29 | name: name, 30 | node: node, 31 | app: app, 32 | } 33 | rpc, err := defaultrpc.NewRPCClient(app, session) 34 | if err != nil { 35 | return nil, err 36 | } 37 | session.rpc = rpc 38 | return session, err 39 | } 40 | 41 | type serverSession struct { 42 | node *registry.Node 43 | name string 44 | rpc mqrpc.RPCClient 45 | app module.App 46 | } 47 | 48 | func (c *serverSession) GetID() string { 49 | return c.node.Id 50 | } 51 | 52 | // Deprecated: 因为命名规范问题函数将废弃,请用GetID代替 53 | func (c *serverSession) GetId() string { 54 | return c.node.Id 55 | } 56 | func (c *serverSession) GetName() string { 57 | return c.name 58 | } 59 | func (c *serverSession) GetRPC() mqrpc.RPCClient { 60 | return c.rpc 61 | } 62 | 63 | // Deprecated: 因为命名规范问题函数将废弃,请用GetRPC代替 64 | func (c *serverSession) GetRpc() mqrpc.RPCClient { 65 | return c.rpc 66 | } 67 | 68 | func (c *serverSession) GetApp() module.App { 69 | return c.app 70 | } 71 | func (c *serverSession) GetNode() *registry.Node { 72 | return c.node 73 | } 74 | 75 | func (c *serverSession) SetNode(node *registry.Node) (err error) { 76 | c.node = node 77 | return 78 | } 79 | 80 | /** 81 | 消息请求 需要回复 82 | */ 83 | func (c *serverSession) Call(ctx context.Context, _func string, params ...interface{}) (interface{}, string) { 84 | return c.rpc.Call(ctx, _func, params...) 85 | } 86 | 87 | /** 88 | 消息请求 不需要回复 89 | */ 90 | func (c *serverSession) CallNR(_func string, params ...interface{}) (err error) { 91 | return c.rpc.CallNR(_func, params...) 92 | } 93 | 94 | /** 95 | 消息请求 需要回复 96 | */ 97 | func (c *serverSession) CallArgs(ctx context.Context, _func string, ArgsType []string, args [][]byte) (interface{}, string) { 98 | return c.rpc.CallArgs(ctx, _func, ArgsType, args) 99 | } 100 | 101 | /** 102 | 消息请求 不需要回复 103 | */ 104 | func (c *serverSession) CallNRArgs(_func string, ArgsType []string, args [][]byte) (err error) { 105 | return c.rpc.CallNRArgs(_func, ArgsType, args) 106 | } 107 | -------------------------------------------------------------------------------- /module/modules/timer_module.go: -------------------------------------------------------------------------------- 1 | /** 2 | 一定要记得在confin.json配置这个模块的参数,否则无法使用 3 | */ 4 | package modules 5 | 6 | import ( 7 | "github.com/liangdas/mqant/conf" 8 | "github.com/liangdas/mqant/module" 9 | "github.com/liangdas/mqant/module/modules/timer" 10 | "time" 11 | ) 12 | 13 | var TimerModule = func() module.Module { 14 | Timer := new(Timer) 15 | return Timer 16 | } 17 | 18 | type Timer struct { 19 | module.Module 20 | } 21 | 22 | func (m *Timer) GetType() string { 23 | //很关键,需要与配置文件中的Module配置对应 24 | return "Timer" 25 | } 26 | 27 | func (m *Timer) OnInit(app module.App, settings *conf.ModuleSettings) { 28 | timewheel.SetTimeWheel(timewheel.New(10*time.Millisecond, 36)) 29 | // 时间轮使用方式 30 | //import "github.com/liangdas/mqant/module/modules/timer" 31 | //执行过的定时器会自动被删除 32 | //timewheel.GetTimeWheel().AddTimer(66 * time.Millisecond , nil,self.Update) 33 | // 34 | //timewheel.GetTimeWheel().AddTimerCustom(66 * time.Millisecond ,"baba", nil,self.Update) 35 | //删除一个为执行的定时器, 参数为添加定时器传递的唯一标识 36 | //timewheel.GetTimeWheel().RemoveTimer("baba") 37 | } 38 | 39 | func (m *Timer) Run(closeSig chan bool) { 40 | timewheel.GetTimeWheel().Start(closeSig) 41 | } 42 | 43 | func (m *Timer) OnDestroy() { 44 | } 45 | -------------------------------------------------------------------------------- /mqant.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //Package mqant mqant 16 | package mqant 17 | 18 | import ( 19 | "github.com/liangdas/mqant/app" 20 | "github.com/liangdas/mqant/module" 21 | ) 22 | 23 | //CreateApp 创建mqant的app实例 24 | func CreateApp(opts ...module.Option) module.App { 25 | opts = append(opts, module.Version(version)) 26 | return app.NewApp(opts...) 27 | } 28 | -------------------------------------------------------------------------------- /network/agent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package network 网络代理 16 | package network 17 | 18 | // Agent 代理 19 | type Agent interface { 20 | Run() error 21 | OnClose() error 22 | } 23 | -------------------------------------------------------------------------------- /network/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package network 网络代理器 16 | package network 17 | 18 | import ( 19 | "net" 20 | ) 21 | 22 | // Conn 网络代理接口 23 | type Conn interface { 24 | net.Conn 25 | Destroy() 26 | doDestroy() 27 | } 28 | -------------------------------------------------------------------------------- /network/tcp_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package network tcp网络控制器 16 | package network 17 | 18 | import ( 19 | "bytes" 20 | "io" 21 | "net" 22 | "sync" 23 | "time" 24 | ) 25 | 26 | // ConnSet tcp连接管理器 27 | type ConnSet map[net.Conn]struct{} 28 | 29 | // TCPConn tcp连接 30 | type TCPConn struct { 31 | io.Reader //Read(p []byte) (n int, err error) 32 | io.Writer //Write(p []byte) (n int, err error) 33 | sync.Mutex 34 | bufLocks chan bool //当有写入一次数据设置一次 35 | buffer bytes.Buffer 36 | conn net.Conn 37 | closeFlag bool 38 | } 39 | 40 | func newTCPConn(conn net.Conn) *TCPConn { 41 | tcpConn := new(TCPConn) 42 | tcpConn.conn = conn 43 | 44 | return tcpConn 45 | } 46 | 47 | func (tcpConn *TCPConn) doDestroy() { 48 | tcpConn.conn.(*net.TCPConn).SetLinger(0) 49 | tcpConn.conn.Close() 50 | 51 | if !tcpConn.closeFlag { 52 | tcpConn.closeFlag = true 53 | } 54 | } 55 | 56 | // Destroy 断连 57 | func (tcpConn *TCPConn) Destroy() { 58 | tcpConn.Lock() 59 | defer tcpConn.Unlock() 60 | 61 | tcpConn.doDestroy() 62 | } 63 | 64 | // Close 关闭tcp连接 65 | func (tcpConn *TCPConn) Close() error { 66 | tcpConn.Lock() 67 | defer tcpConn.Unlock() 68 | if tcpConn.closeFlag { 69 | return nil 70 | } 71 | 72 | tcpConn.closeFlag = true 73 | return tcpConn.conn.Close() 74 | } 75 | 76 | // Write b must not be modified by the others goroutines 77 | func (tcpConn *TCPConn) Write(b []byte) (n int, err error) { 78 | tcpConn.Lock() 79 | defer tcpConn.Unlock() 80 | if tcpConn.closeFlag || b == nil { 81 | return 82 | } 83 | 84 | return tcpConn.conn.Write(b) 85 | } 86 | 87 | // Read read data 88 | func (tcpConn *TCPConn) Read(b []byte) (int, error) { 89 | return tcpConn.conn.Read(b) 90 | } 91 | 92 | // LocalAddr 本地socket端口地址 93 | func (tcpConn *TCPConn) LocalAddr() net.Addr { 94 | return tcpConn.conn.LocalAddr() 95 | } 96 | 97 | // RemoteAddr 远程socket端口地址 98 | func (tcpConn *TCPConn) RemoteAddr() net.Addr { 99 | return tcpConn.conn.RemoteAddr() 100 | } 101 | 102 | // SetDeadline A zero value for t means I/O operations will not time out. 103 | func (tcpConn *TCPConn) SetDeadline(t time.Time) error { 104 | return tcpConn.conn.SetDeadline(t) 105 | } 106 | 107 | // SetReadDeadline sets the deadline for future Read calls. 108 | // A zero value for t means Read will not time out. 109 | func (tcpConn *TCPConn) SetReadDeadline(t time.Time) error { 110 | return tcpConn.conn.SetReadDeadline(t) 111 | } 112 | 113 | // SetWriteDeadline sets the deadline for future Write calls. 114 | // Even if write times out, it may return n > 0, indicating that 115 | // some of the data was successfully written. 116 | // A zero value for t means Write will not time out. 117 | func (tcpConn *TCPConn) SetWriteDeadline(t time.Time) error { 118 | return tcpConn.conn.SetWriteDeadline(t) 119 | } 120 | -------------------------------------------------------------------------------- /network/tcp_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package network tcp服务器 16 | package network 17 | 18 | import ( 19 | "crypto/tls" 20 | "github.com/liangdas/mqant/log" 21 | "net" 22 | "sync" 23 | "time" 24 | ) 25 | 26 | // TCPServer tcp服务器 27 | type TCPServer struct { 28 | Addr string 29 | TLS bool //是否支持tls 30 | CertFile string 31 | KeyFile string 32 | MaxConnNum int 33 | NewAgent func(*TCPConn) Agent 34 | ln net.Listener 35 | mutexConns sync.Mutex 36 | wgLn sync.WaitGroup 37 | wgConns sync.WaitGroup 38 | } 39 | 40 | // Start 开始tcp监听 41 | func (server *TCPServer) Start() { 42 | server.init() 43 | log.Info("TCP Listen :%s", server.Addr) 44 | go server.run() 45 | } 46 | 47 | func (server *TCPServer) init() { 48 | ln, err := net.Listen("tcp", server.Addr) 49 | if err != nil { 50 | log.Warning("%v", err) 51 | } 52 | 53 | if server.NewAgent == nil { 54 | log.Warning("NewAgent must not be nil") 55 | } 56 | if server.TLS { 57 | tlsConf := new(tls.Config) 58 | tlsConf.Certificates = make([]tls.Certificate, 1) 59 | tlsConf.Certificates[0], err = tls.LoadX509KeyPair(server.CertFile, server.KeyFile) 60 | if err == nil { 61 | ln = tls.NewListener(ln, tlsConf) 62 | log.Info("TCP Listen TLS load success") 63 | } else { 64 | log.Warning("tcp_server tls :%v", err) 65 | } 66 | } 67 | 68 | server.ln = ln 69 | } 70 | func (server *TCPServer) run() { 71 | server.wgLn.Add(1) 72 | defer server.wgLn.Done() 73 | 74 | var tempDelay time.Duration 75 | for { 76 | conn, err := server.ln.Accept() 77 | if err != nil { 78 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 79 | if tempDelay == 0 { 80 | tempDelay = 5 * time.Millisecond 81 | } else { 82 | tempDelay *= 2 83 | } 84 | if max := 1 * time.Second; tempDelay > max { 85 | tempDelay = max 86 | } 87 | log.Info("accept error: %v; retrying in %v", err, tempDelay) 88 | time.Sleep(tempDelay) 89 | continue 90 | } 91 | return 92 | } 93 | tempDelay = 0 94 | tcpConn := newTCPConn(conn) 95 | agent := server.NewAgent(tcpConn) 96 | go func() { 97 | server.wgConns.Add(1) 98 | agent.Run() 99 | 100 | // cleanup 101 | tcpConn.Close() 102 | agent.OnClose() 103 | 104 | server.wgConns.Done() 105 | }() 106 | } 107 | } 108 | 109 | // Close 关闭TCP监听 110 | func (server *TCPServer) Close() { 111 | server.ln.Close() 112 | server.wgLn.Wait() 113 | server.wgConns.Wait() 114 | } 115 | -------------------------------------------------------------------------------- /network/ws_conn_x.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package network websocket连接器 16 | package network 17 | 18 | import ( 19 | "github.com/liangdas/mqant/utils/ip" 20 | "golang.org/x/net/websocket" 21 | "io" 22 | "net" 23 | "sync" 24 | "time" 25 | ) 26 | 27 | // Addr is an implementation of net.Addr for WebSocket. 28 | type Addr struct { 29 | ip string 30 | } 31 | 32 | // Network returns the network type for a WebSocket, "websocket". 33 | func (addr *Addr) Network() string { return "websocket" } 34 | func (addr *Addr) String() string { return addr.ip } 35 | 36 | // WSConn websocket连接 37 | type WSConn struct { 38 | io.Reader //Read(p []byte) (n int, err error) 39 | io.Writer //Write(p []byte) (n int, err error) 40 | sync.Mutex 41 | conn *websocket.Conn 42 | closeFlag bool 43 | } 44 | 45 | func newWSConn(conn *websocket.Conn) *WSConn { 46 | wsConn := new(WSConn) 47 | wsConn.conn = conn 48 | return wsConn 49 | } 50 | 51 | func (wsConn *WSConn) Conn() *websocket.Conn { 52 | return wsConn.conn 53 | } 54 | 55 | func (wsConn *WSConn) doDestroy() { 56 | wsConn.conn.Close() 57 | if !wsConn.closeFlag { 58 | wsConn.closeFlag = true 59 | } 60 | } 61 | 62 | // Destroy 注销连接 63 | func (wsConn *WSConn) Destroy() { 64 | //wsConn.Lock() 65 | //defer wsConn.Unlock() 66 | 67 | wsConn.doDestroy() 68 | } 69 | 70 | // Close 关闭连接 71 | func (wsConn *WSConn) Close() error { 72 | //wsConn.Lock() 73 | //defer wsConn.Unlock() 74 | if wsConn.closeFlag { 75 | return nil 76 | } 77 | wsConn.closeFlag = true 78 | return wsConn.conn.Close() 79 | } 80 | 81 | // Write Write 82 | func (wsConn *WSConn) Write(p []byte) (int, error) { 83 | return wsConn.conn.Write(p) 84 | } 85 | 86 | // Read goroutine not safe 87 | func (wsConn *WSConn) Read(p []byte) (n int, err error) { 88 | return wsConn.conn.Read(p) 89 | } 90 | 91 | // LocalAddr 获取本地socket地址 92 | func (wsConn *WSConn) LocalAddr() net.Addr { 93 | return wsConn.conn.LocalAddr() 94 | } 95 | 96 | // RemoteAddr 获取远程socket地址 97 | func (wsConn *WSConn) RemoteAddr() net.Addr { 98 | return &Addr{ip: iptool.RealIP(wsConn.conn.Request())} 99 | } 100 | 101 | // SetDeadline A zero value for t means I/O operations will not time out. 102 | func (wsConn *WSConn) SetDeadline(t time.Time) error { 103 | err := wsConn.conn.SetReadDeadline(t) 104 | if err != nil { 105 | return err 106 | } 107 | return wsConn.conn.SetWriteDeadline(t) 108 | } 109 | 110 | // SetReadDeadline sets the deadline for future Read calls. 111 | // A zero value for t means Read will not time out. 112 | func (wsConn *WSConn) SetReadDeadline(t time.Time) error { 113 | return wsConn.conn.SetReadDeadline(t) 114 | } 115 | 116 | // SetWriteDeadline sets the deadline for future Write calls. 117 | // Even if write times out, it may return n > 0, indicating that 118 | // some of the data was successfully written. 119 | // A zero value for t means Write will not time out. 120 | func (wsConn *WSConn) SetWriteDeadline(t time.Time) error { 121 | return wsConn.conn.SetWriteDeadline(t) 122 | } 123 | -------------------------------------------------------------------------------- /network/ws_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package network websocket服务器 16 | package network 17 | 18 | // 19 | //import ( 20 | // "crypto/tls" 21 | // "github.com/liangdas/mqant/log" 22 | // "net" 23 | // "net/http" 24 | // "sync" 25 | // "time" 26 | //) 27 | // 28 | //type WSServer struct { 29 | // Addr string 30 | // TLS bool //是否支持tls 31 | // CertFile string 32 | // KeyFile string 33 | // MaxConnNum int 34 | // MaxMsgLen uint32 35 | // HTTPTimeout time.Duration 36 | // NewAgent func(*WSConn) Agent 37 | // ln net.Listener 38 | // handler *WSHandler 39 | //} 40 | // 41 | //type WSHandler struct { 42 | // maxConnNum int 43 | // maxMsgLen uint32 44 | // newAgent func(*WSConn) Agent 45 | // upgrader websocket.Upgrader 46 | // mutexConns sync.Mutex 47 | // wg sync.WaitGroup 48 | //} 49 | // 50 | //func (handler *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 51 | // if r.Method != "GET" { 52 | // http.Error(w, "Method not allowed", 405) 53 | // return 54 | // } 55 | // conn, err := handler.upgrader.Upgrade(w, r, nil) 56 | // if err != nil { 57 | // log.Warning("upgrade error: %v", err) 58 | // return 59 | // } 60 | // conn.SetReadLimit(int64(handler.maxMsgLen)) 61 | // 62 | // handler.wg.Add(1) 63 | // defer handler.wg.Done() 64 | // 65 | // wsConn := newWSConn(conn) 66 | // agent := handler.newAgent(wsConn) 67 | // agent.Run() 68 | // 69 | // // cleanup 70 | // wsConn.Close() 71 | // handler.mutexConns.Lock() 72 | // handler.mutexConns.Unlock() 73 | // agent.OnClose() 74 | //} 75 | // 76 | //func (server *WSServer) Start() { 77 | // ln, err := net.Listen("tcp", server.Addr) 78 | // if err != nil { 79 | // log.Warning("%v", err) 80 | // } 81 | // 82 | // if server.HTTPTimeout <= 0 { 83 | // server.HTTPTimeout = 10 * time.Second 84 | // log.Warning("invalid HTTPTimeout, reset to %v", server.HTTPTimeout) 85 | // } 86 | // if server.NewAgent == nil { 87 | // log.Warning("NewAgent must not be nil") 88 | // } 89 | // if server.TLS { 90 | // tlsConf := new(tls.Config) 91 | // tlsConf.Certificates = make([]tls.Certificate, 1) 92 | // tlsConf.Certificates[0], err = tls.LoadX509KeyPair(server.CertFile, server.KeyFile) 93 | // if err == nil { 94 | // ln = tls.NewListener(ln, tlsConf) 95 | // log.Info("WS Listen TLS load success") 96 | // } else { 97 | // log.Warning("ws_server tls :%v", err) 98 | // } 99 | // } 100 | // server.ln = ln 101 | // server.handler = &WSHandler{ 102 | // maxConnNum: server.MaxConnNum, 103 | // maxMsgLen: server.MaxMsgLen, 104 | // newAgent: server.NewAgent, 105 | // upgrader: websocket.Upgrader{ 106 | // HandshakeTimeout: server.HTTPTimeout, 107 | // Subprotocols: []string{"mqttv3.1"}, 108 | // CheckOrigin: func(_ *http.Request) bool { return true }, 109 | // }, 110 | // } 111 | // 112 | // httpServer := &http.Server{ 113 | // Addr: server.Addr, 114 | // Handler: server.handler, 115 | // ReadTimeout: server.HTTPTimeout, 116 | // WriteTimeout: server.HTTPTimeout, 117 | // MaxHeaderBytes: 1024, 118 | // } 119 | // log.Info("WS Listen :%s", server.Addr) 120 | // go httpServer.Serve(ln) 121 | //} 122 | // 123 | //func (server *WSServer) Close() { 124 | // server.ln.Close() 125 | // 126 | // server.handler.wg.Wait() 127 | //} 128 | -------------------------------------------------------------------------------- /network/ws_server_x.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/liangdas/mqant/log" 6 | "github.com/liangdas/mqant/utils/ip" 7 | "golang.org/x/net/websocket" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // WSServer websocket服务器 17 | type WSServer struct { 18 | Addr string 19 | TLS bool //是否支持tls 20 | CertFile string 21 | KeyFile string 22 | MaxConnNum int 23 | MaxMsgLen uint32 24 | HTTPTimeout time.Duration 25 | NewAgent func(*WSConn) Agent 26 | ln net.Listener 27 | handler *WSHandler 28 | } 29 | 30 | // WSHandler websocket 处理器 31 | type WSHandler struct { 32 | maxConnNum int 33 | maxMsgLen uint32 34 | newAgent func(*WSConn) Agent 35 | mutexConns sync.Mutex 36 | wg sync.WaitGroup 37 | } 38 | 39 | func (handler *WSHandler) echo(conn *websocket.Conn) { 40 | handler.wg.Add(1) 41 | defer handler.wg.Done() 42 | conn.PayloadType = websocket.BinaryFrame 43 | wsConn := newWSConn(conn) 44 | agent := handler.newAgent(wsConn) 45 | agent.Run() 46 | 47 | // cleanup 48 | wsConn.Close() 49 | agent.OnClose() 50 | } 51 | 52 | //func (handler *WSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 53 | // if r.Method != "GET" { 54 | // http.Error(w, "Method not allowed", 405) 55 | // return 56 | // } 57 | // ws := websocket.Server{ 58 | // Handler: websocket.Handler(handler.echo), 59 | // Handshake: func(config *websocket.Config, request *http.Request) error { 60 | // var scheme string 61 | // if request.TLS != nil { 62 | // scheme = "wss" 63 | // } else { 64 | // scheme = "ws" 65 | // } 66 | // config.Origin, _ = url.ParseRequestURI(scheme + "://" + request.RemoteAddr + request.URL.RequestURI()) 67 | // offeredProtocol := r.Header.Get("Sec-WebSocket-Protocol") 68 | // config.Protocol = []string{offeredProtocol} 69 | // return nil 70 | // }, 71 | // } 72 | // ws.ServeHTTP(w, r) 73 | //} 74 | 75 | // Start 开启监听websocket端口 76 | func (server *WSServer) Start() { 77 | ln, err := net.Listen("tcp", server.Addr) 78 | if err != nil { 79 | log.Warning("%v", err) 80 | } 81 | 82 | if server.HTTPTimeout <= 0 { 83 | server.HTTPTimeout = 10 * time.Second 84 | log.Warning("invalid HTTPTimeout, reset to %v", server.HTTPTimeout) 85 | } 86 | if server.NewAgent == nil { 87 | log.Warning("NewAgent must not be nil") 88 | } 89 | if server.TLS { 90 | tlsConf := new(tls.Config) 91 | tlsConf.Certificates = make([]tls.Certificate, 1) 92 | tlsConf.Certificates[0], err = tls.LoadX509KeyPair(server.CertFile, server.KeyFile) 93 | if err == nil { 94 | ln = tls.NewListener(ln, tlsConf) 95 | log.Info("WS Listen TLS load success") 96 | } else { 97 | log.Warning("ws_server tls :%v", err) 98 | } 99 | } 100 | server.ln = ln 101 | server.handler = &WSHandler{ 102 | maxConnNum: server.MaxConnNum, 103 | maxMsgLen: server.MaxMsgLen, 104 | newAgent: server.NewAgent, 105 | } 106 | ws := websocket.Server{ 107 | Handler: websocket.Handler(server.handler.echo), 108 | Handshake: func(config *websocket.Config, r *http.Request) error { 109 | var scheme string 110 | if r.TLS != nil { 111 | scheme = "wss" 112 | } else { 113 | scheme = "ws" 114 | } 115 | real_ip := iptool.RealIP(r) 116 | config.Origin, _ = url.ParseRequestURI(scheme + "://" + real_ip + r.URL.RequestURI()) 117 | offeredProtocol := r.Header.Get("Sec-WebSocket-Protocol") 118 | ptls := strings.Split(offeredProtocol, ",") 119 | if len(ptls) > 0 { 120 | config.Protocol = []string{ptls[0]} 121 | } else { 122 | config.Protocol = []string{"mqtt"} 123 | } 124 | return nil 125 | }, 126 | } 127 | httpServer := &http.Server{ 128 | Addr: server.Addr, 129 | Handler: ws, 130 | ReadTimeout: server.HTTPTimeout, 131 | WriteTimeout: server.HTTPTimeout, 132 | MaxHeaderBytes: 1024, 133 | } 134 | log.Info("WS Listen :%s", server.Addr) 135 | go httpServer.Serve(ln) 136 | } 137 | 138 | // Close 停止监听websocket端口 139 | func (server *WSServer) Close() { 140 | server.ln.Close() 141 | 142 | server.handler.wg.Wait() 143 | } 144 | -------------------------------------------------------------------------------- /registry/consul/consul.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import () 4 | import "github.com/liangdas/mqant/registry" 5 | 6 | func NewRegistry(opts ...registry.Option) registry.Registry { 7 | return registry.NewRegistry(opts...) 8 | } 9 | -------------------------------------------------------------------------------- /registry/consul/options.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | consul "github.com/hashicorp/consul/api" 8 | 9 | "github.com/liangdas/mqant/registry" 10 | ) 11 | 12 | // Connect specifies services should be registered as Consul Connect services 13 | func Connect() registry.Option { 14 | return func(o *registry.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | o.Context = context.WithValue(o.Context, "consul_connect", true) 19 | } 20 | } 21 | 22 | func Config(c *consul.Config) registry.Option { 23 | return func(o *registry.Options) { 24 | if o.Context == nil { 25 | o.Context = context.Background() 26 | } 27 | o.Context = context.WithValue(o.Context, "consul_config", c) 28 | } 29 | } 30 | 31 | // 32 | // TCPCheck will tell the service provider to check the service address 33 | // and port every `t` interval. It will enabled only if `t` is greater than 0. 34 | // See `TCP + Interval` for more information [1]. 35 | // 36 | // [1] https://www.consul.io/docs/agent/checks.html 37 | // 38 | func TCPCheck(t time.Duration) registry.Option { 39 | return func(o *registry.Options) { 40 | if t <= time.Duration(0) { 41 | return 42 | } 43 | if o.Context == nil { 44 | o.Context = context.Background() 45 | } 46 | o.Context = context.WithValue(o.Context, "consul_tcp_check", t) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /registry/consul_watcher_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | func TestHealthyServiceHandler(t *testing.T) { 10 | watcher := newWatcher() 11 | serviceEntry := newServiceEntry( 12 | "node-name", "node-address", "service-name", "v1.0.0", 13 | []*api.HealthCheck{ 14 | newHealthCheck("node-name", "service-name", "passing"), 15 | }, 16 | ) 17 | 18 | watcher.serviceHandler(1234, []*api.ServiceEntry{serviceEntry}) 19 | 20 | if len(watcher.services["service-name"][0].Nodes) != 1 { 21 | t.Errorf("Expected length of the service nodes to be 1") 22 | } 23 | } 24 | 25 | func TestUnhealthyServiceHandler(t *testing.T) { 26 | watcher := newWatcher() 27 | serviceEntry := newServiceEntry( 28 | "node-name", "node-address", "service-name", "v1.0.0", 29 | []*api.HealthCheck{ 30 | newHealthCheck("node-name", "service-name", "critical"), 31 | }, 32 | ) 33 | 34 | watcher.serviceHandler(1234, []*api.ServiceEntry{serviceEntry}) 35 | 36 | if len(watcher.services["service-name"][0].Nodes) != 0 { 37 | t.Errorf("Expected length of the service nodes to be 0") 38 | } 39 | } 40 | 41 | func TestUnhealthyNodeServiceHandler(t *testing.T) { 42 | watcher := newWatcher() 43 | serviceEntry := newServiceEntry( 44 | "node-name", "node-address", "service-name", "v1.0.0", 45 | []*api.HealthCheck{ 46 | newHealthCheck("node-name", "service-name", "passing"), 47 | newHealthCheck("node-name", "serfHealth", "critical"), 48 | }, 49 | ) 50 | 51 | watcher.serviceHandler(1234, []*api.ServiceEntry{serviceEntry}) 52 | 53 | if len(watcher.services["service-name"][0].Nodes) != 0 { 54 | t.Errorf("Expected length of the service nodes to be 0") 55 | } 56 | } 57 | 58 | func newWatcher() *consulWatcher { 59 | return &consulWatcher{ 60 | exit: make(chan bool), 61 | next: make(chan *Result, 10), 62 | services: make(map[string][]*Service), 63 | } 64 | } 65 | 66 | func newHealthCheck(node, name, status string) *api.HealthCheck { 67 | return &api.HealthCheck{ 68 | Node: node, 69 | Name: name, 70 | Status: status, 71 | ServiceName: name, 72 | } 73 | } 74 | 75 | func newServiceEntry(node, address, name, version string, checks []*api.HealthCheck) *api.ServiceEntry { 76 | return &api.ServiceEntry{ 77 | Node: &api.Node{Node: node, Address: name}, 78 | Service: &api.AgentService{ 79 | Service: name, 80 | Address: address, 81 | Tags: encodeVersion(version), 82 | }, 83 | Checks: checks, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /registry/encoding.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "encoding/hex" 7 | "encoding/json" 8 | "io/ioutil" 9 | ) 10 | 11 | func encode(buf []byte) string { 12 | var b bytes.Buffer 13 | defer b.Reset() 14 | 15 | w := zlib.NewWriter(&b) 16 | if _, err := w.Write(buf); err != nil { 17 | return "" 18 | } 19 | w.Close() 20 | 21 | return hex.EncodeToString(b.Bytes()) 22 | } 23 | 24 | func decode(d string) []byte { 25 | hr, err := hex.DecodeString(d) 26 | if err != nil { 27 | return nil 28 | } 29 | 30 | br := bytes.NewReader(hr) 31 | zr, err := zlib.NewReader(br) 32 | if err != nil { 33 | return nil 34 | } 35 | 36 | rbuf, err := ioutil.ReadAll(zr) 37 | if err != nil { 38 | return nil 39 | } 40 | 41 | return rbuf 42 | } 43 | 44 | func encodeEndpoints(en []*Endpoint) []string { 45 | var tags []string 46 | for _, e := range en { 47 | if b, err := json.Marshal(e); err == nil { 48 | tags = append(tags, "e-"+encode(b)) 49 | } 50 | } 51 | return tags 52 | } 53 | 54 | func decodeEndpoints(tags []string) []*Endpoint { 55 | var en []*Endpoint 56 | 57 | // use the first format you find 58 | var ver byte 59 | 60 | for _, tag := range tags { 61 | if len(tag) == 0 || tag[0] != 'e' { 62 | continue 63 | } 64 | 65 | // check version 66 | if ver > 0 && tag[1] != ver { 67 | continue 68 | } 69 | 70 | var e *Endpoint 71 | var buf []byte 72 | 73 | // Old encoding was plain 74 | if tag[1] == '=' { 75 | buf = []byte(tag[2:]) 76 | } 77 | 78 | // New encoding is hex 79 | if tag[1] == '-' { 80 | buf = decode(tag[2:]) 81 | } 82 | 83 | if err := json.Unmarshal(buf, &e); err == nil { 84 | en = append(en, e) 85 | } 86 | 87 | // set version 88 | ver = tag[1] 89 | } 90 | return en 91 | } 92 | 93 | func encodeMetadata(md map[string]string) []string { 94 | var tags []string 95 | for k, v := range md { 96 | if b, err := json.Marshal(map[string]string{ 97 | k: v, 98 | }); err == nil { 99 | // new encoding 100 | tags = append(tags, "t-"+encode(b)) 101 | } 102 | } 103 | return tags 104 | } 105 | 106 | func decodeMetadata(tags []string) map[string]string { 107 | md := make(map[string]string) 108 | 109 | var ver byte 110 | 111 | for _, tag := range tags { 112 | if len(tag) == 0 || tag[0] != 't' { 113 | continue 114 | } 115 | 116 | // check version 117 | if ver > 0 && tag[1] != ver { 118 | continue 119 | } 120 | 121 | var kv map[string]string 122 | var buf []byte 123 | 124 | // Old encoding was plain 125 | if tag[1] == '=' { 126 | buf = []byte(tag[2:]) 127 | } 128 | 129 | // New encoding is hex 130 | if tag[1] == '-' { 131 | buf = decode(tag[2:]) 132 | } 133 | 134 | // Now unmarshal 135 | if err := json.Unmarshal(buf, &kv); err == nil { 136 | for k, v := range kv { 137 | md[k] = v 138 | } 139 | } 140 | 141 | // set version 142 | ver = tag[1] 143 | } 144 | return md 145 | } 146 | 147 | func encodeVersion(v string) []string { 148 | return []string{"v-" + encode([]byte(v))} 149 | } 150 | 151 | func decodeVersion(tags []string) (string, bool) { 152 | for _, tag := range tags { 153 | if len(tag) < 2 || tag[0] != 'v' { 154 | continue 155 | } 156 | 157 | // Old encoding was plain 158 | if tag[1] == '=' { 159 | return tag[2:], true 160 | } 161 | 162 | // New encoding is hex 163 | if tag[1] == '-' { 164 | return string(decode(tag[2:])), true 165 | } 166 | } 167 | return "", false 168 | } 169 | -------------------------------------------------------------------------------- /registry/encoding_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "testing" 7 | ) 8 | 9 | func TestEncodingEndpoints(t *testing.T) { 10 | eps := []*Endpoint{ 11 | &Endpoint{ 12 | Name: "endpoint1", 13 | Request: &Value{ 14 | Name: "request", 15 | Type: "request", 16 | }, 17 | Response: &Value{ 18 | Name: "response", 19 | Type: "response", 20 | }, 21 | Metadata: map[string]string{ 22 | "foo1": "bar1", 23 | }, 24 | }, 25 | &Endpoint{ 26 | Name: "endpoint2", 27 | Request: &Value{ 28 | Name: "request", 29 | Type: "request", 30 | }, 31 | Response: &Value{ 32 | Name: "response", 33 | Type: "response", 34 | }, 35 | Metadata: map[string]string{ 36 | "foo2": "bar2", 37 | }, 38 | }, 39 | &Endpoint{ 40 | Name: "endpoint3", 41 | Request: &Value{ 42 | Name: "request", 43 | Type: "request", 44 | }, 45 | Response: &Value{ 46 | Name: "response", 47 | Type: "response", 48 | }, 49 | Metadata: map[string]string{ 50 | "foo3": "bar3", 51 | }, 52 | }, 53 | } 54 | 55 | testEp := func(ep *Endpoint, enc string) { 56 | // encode endpoint 57 | e := encodeEndpoints([]*Endpoint{ep}) 58 | 59 | // check there are two tags; old and new 60 | if len(e) != 1 { 61 | t.Fatalf("Expected 1 encoded tags, got %v", e) 62 | } 63 | 64 | // check old encoding 65 | var seen bool 66 | 67 | for _, en := range e { 68 | if en == enc { 69 | seen = true 70 | break 71 | } 72 | } 73 | 74 | if !seen { 75 | t.Fatalf("Expected %s but not found", enc) 76 | } 77 | 78 | // decode 79 | d := decodeEndpoints([]string{enc}) 80 | if len(d) == 0 { 81 | t.Fatalf("Expected %v got %v", ep, d) 82 | } 83 | 84 | // check name 85 | if d[0].Name != ep.Name { 86 | t.Fatalf("Expected ep %s got %s", ep.Name, d[0].Name) 87 | } 88 | 89 | // check all the metadata exists 90 | for k, v := range ep.Metadata { 91 | if gv := d[0].Metadata[k]; gv != v { 92 | t.Fatalf("Expected key %s val %s got val %s", k, v, gv) 93 | } 94 | } 95 | } 96 | 97 | for _, ep := range eps { 98 | // JSON encoded 99 | jencoded, err := json.Marshal(ep) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | // HEX encoded 105 | hencoded := encode(jencoded) 106 | // endpoint tag 107 | hepTag := "e-" + hencoded 108 | testEp(ep, hepTag) 109 | } 110 | } 111 | 112 | func TestEncodingVersion(t *testing.T) { 113 | testData := []struct { 114 | decoded string 115 | encoded string 116 | }{ 117 | {"1.0.0", "v-789c32d433d03300040000ffff02ce00ee"}, 118 | {"latest", "v-789cca492c492d2e01040000ffff08cc028e"}, 119 | } 120 | 121 | for _, data := range testData { 122 | e := encodeVersion(data.decoded) 123 | 124 | if e[0] != data.encoded { 125 | t.Fatalf("Expected %s got %s", data.encoded, e) 126 | } 127 | 128 | d, ok := decodeVersion(e) 129 | if !ok { 130 | t.Fatalf("Unexpected %t for %s", ok, data.encoded) 131 | } 132 | 133 | if d != data.decoded { 134 | t.Fatalf("Expected %s got %s", data.decoded, d) 135 | } 136 | 137 | d, ok = decodeVersion([]string{data.encoded}) 138 | if !ok { 139 | t.Fatalf("Unexpected %t for %s", ok, data.encoded) 140 | } 141 | 142 | if d != data.decoded { 143 | t.Fatalf("Expected %s got %s", data.decoded, d) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /registry/etcdv3/options.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "context" 5 | "github.com/liangdas/mqant/registry" 6 | ) 7 | 8 | type authKey struct{} 9 | 10 | type authCreds struct { 11 | Username string 12 | Password string 13 | } 14 | 15 | // Auth allows you to specify username/password 16 | func Auth(username, password string) registry.Option { 17 | return func(o *registry.Options) { 18 | if o.Context == nil { 19 | o.Context = context.Background() 20 | } 21 | o.Context = context.WithValue(o.Context, authKey{}, &authCreds{Username: username, Password: password}) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /registry/etcdv3/watcher.go: -------------------------------------------------------------------------------- 1 | package etcdv3 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | 8 | "github.com/liangdas/mqant/registry" 9 | "go.etcd.io/etcd/clientv3" 10 | ) 11 | 12 | type etcdv3Watcher struct { 13 | stop chan bool 14 | w clientv3.WatchChan 15 | client *clientv3.Client 16 | timeout time.Duration 17 | } 18 | 19 | func newEtcdv3Watcher(r *etcdv3Registry, timeout time.Duration, opts ...registry.WatchOption) (registry.Watcher, error) { 20 | var wo registry.WatchOptions 21 | for _, o := range opts { 22 | o(&wo) 23 | } 24 | 25 | ctx, cancel := context.WithCancel(context.Background()) 26 | stop := make(chan bool, 1) 27 | 28 | go func() { 29 | <-stop 30 | cancel() 31 | }() 32 | 33 | watchPath := prefix 34 | if len(wo.Service) > 0 { 35 | watchPath = servicePath(wo.Service) + "/" 36 | } 37 | 38 | return &etcdv3Watcher{ 39 | stop: stop, 40 | w: r.client.Watch(ctx, watchPath, clientv3.WithPrefix(), clientv3.WithPrevKV()), 41 | client: r.client, 42 | timeout: timeout, 43 | }, nil 44 | } 45 | 46 | func (ew *etcdv3Watcher) Next() (*registry.Result, error) { 47 | for wresp := range ew.w { 48 | if wresp.Err() != nil { 49 | return nil, wresp.Err() 50 | } 51 | for _, ev := range wresp.Events { 52 | service := decode(ev.Kv.Value) 53 | var action string 54 | 55 | switch ev.Type { 56 | case clientv3.EventTypePut: 57 | if ev.IsCreate() { 58 | action = "create" 59 | } else if ev.IsModify() { 60 | action = "update" 61 | } 62 | case clientv3.EventTypeDelete: 63 | action = "delete" 64 | 65 | // get service from prevKv 66 | service = decode(ev.PrevKv.Value) 67 | } 68 | 69 | if service == nil { 70 | continue 71 | } 72 | return ®istry.Result{ 73 | Action: action, 74 | Service: service, 75 | }, nil 76 | } 77 | } 78 | return nil, errors.New("could not get next") 79 | } 80 | 81 | func (ew *etcdv3Watcher) Stop() { 82 | select { 83 | case <-ew.stop: 84 | return 85 | default: 86 | close(ew.stop) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /registry/mock/helper.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import () 4 | import "github.com/liangdas/mqant/registry" 5 | 6 | func addNodes(old, neu []*registry.Node) []*registry.Node { 7 | for _, n := range neu { 8 | var seen bool 9 | for i, o := range old { 10 | if o.Id == n.Id { 11 | seen = true 12 | old[i] = n 13 | break 14 | } 15 | } 16 | if !seen { 17 | old = append(old, n) 18 | } 19 | } 20 | return old 21 | } 22 | 23 | func addServices(old, neu []*registry.Service) []*registry.Service { 24 | for _, s := range neu { 25 | var seen bool 26 | for i, o := range old { 27 | if o.Version == s.Version { 28 | s.Nodes = addNodes(o.Nodes, s.Nodes) 29 | seen = true 30 | old[i] = s 31 | break 32 | } 33 | } 34 | if !seen { 35 | old = append(old, s) 36 | } 37 | } 38 | return old 39 | } 40 | 41 | func delNodes(old, del []*registry.Node) []*registry.Node { 42 | var nodes []*registry.Node 43 | for _, o := range old { 44 | var rem bool 45 | for _, n := range del { 46 | if o.Id == n.Id { 47 | rem = true 48 | break 49 | } 50 | } 51 | if !rem { 52 | nodes = append(nodes, o) 53 | } 54 | } 55 | return nodes 56 | } 57 | 58 | func delServices(old, del []*registry.Service) []*registry.Service { 59 | var services []*registry.Service 60 | for i, o := range old { 61 | var rem bool 62 | for _, s := range del { 63 | if o.Version == s.Version { 64 | old[i].Nodes = delNodes(o.Nodes, s.Nodes) 65 | if len(old[i].Nodes) == 0 { 66 | rem = true 67 | } 68 | } 69 | } 70 | if !rem { 71 | services = append(services, o) 72 | } 73 | } 74 | return services 75 | } 76 | -------------------------------------------------------------------------------- /registry/mock/helper_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangdas/mqant/registry" 7 | ) 8 | 9 | func TestDelServices(t *testing.T) { 10 | services := []*registry.Service{ 11 | { 12 | Name: "foo", 13 | Version: "1.0.0", 14 | Nodes: []*registry.Node{ 15 | { 16 | Id: "foo-123", 17 | Address: "localhost", 18 | Port: 9999, 19 | }, 20 | }, 21 | }, 22 | { 23 | Name: "foo", 24 | Version: "1.0.0", 25 | Nodes: []*registry.Node{ 26 | { 27 | Id: "foo-123", 28 | Address: "localhost", 29 | Port: 6666, 30 | }, 31 | }, 32 | }, 33 | } 34 | 35 | servs := delServices([]*registry.Service{services[0]}, []*registry.Service{services[1]}) 36 | if i := len(servs); i > 0 { 37 | t.Errorf("Expected 0 nodes, got %d: %+v", i, servs) 38 | } 39 | t.Logf("Services %+v", servs) 40 | } 41 | 42 | func TestDelNodes(t *testing.T) { 43 | services := []*registry.Service{ 44 | { 45 | Name: "foo", 46 | Version: "1.0.0", 47 | Nodes: []*registry.Node{ 48 | { 49 | Id: "foo-123", 50 | Address: "localhost", 51 | Port: 9999, 52 | }, 53 | { 54 | Id: "foo-321", 55 | Address: "localhost", 56 | Port: 6666, 57 | }, 58 | }, 59 | }, 60 | { 61 | Name: "foo", 62 | Version: "1.0.0", 63 | Nodes: []*registry.Node{ 64 | { 65 | Id: "foo-123", 66 | Address: "localhost", 67 | Port: 6666, 68 | }, 69 | }, 70 | }, 71 | } 72 | 73 | nodes := delNodes(services[0].Nodes, services[1].Nodes) 74 | if i := len(nodes); i != 1 { 75 | t.Errorf("Expected only 1 node, got %d: %+v", i, nodes) 76 | } 77 | t.Logf("Nodes %+v", nodes) 78 | } 79 | -------------------------------------------------------------------------------- /registry/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import () 4 | import "github.com/liangdas/mqant/registry" 5 | 6 | type mockRegistry struct { 7 | Services map[string][]*registry.Service 8 | } 9 | 10 | var ( 11 | mockData = map[string][]*registry.Service{ 12 | "foo": []*registry.Service{ 13 | { 14 | Name: "foo", 15 | Version: "1.0.0", 16 | Nodes: []*registry.Node{ 17 | { 18 | Id: "foo-1.0.0-123", 19 | Address: "localhost", 20 | Port: 9999, 21 | }, 22 | { 23 | Id: "foo-1.0.0-321", 24 | Address: "localhost", 25 | Port: 9999, 26 | }, 27 | }, 28 | }, 29 | { 30 | Name: "foo", 31 | Version: "1.0.1", 32 | Nodes: []*registry.Node{ 33 | { 34 | Id: "foo-1.0.1-321", 35 | Address: "localhost", 36 | Port: 6666, 37 | }, 38 | }, 39 | }, 40 | { 41 | Name: "foo", 42 | Version: "1.0.3", 43 | Nodes: []*registry.Node{ 44 | { 45 | Id: "foo-1.0.3-345", 46 | Address: "localhost", 47 | Port: 8888, 48 | }, 49 | }, 50 | }, 51 | }, 52 | } 53 | ) 54 | 55 | func (m *mockRegistry) init() { 56 | // add some mock data 57 | m.Services = mockData 58 | } 59 | 60 | func (m *mockRegistry) GetService(service string) ([]*registry.Service, error) { 61 | s, ok := m.Services[service] 62 | if !ok || len(s) == 0 { 63 | return nil, registry.ErrNotFound 64 | } 65 | return s, nil 66 | 67 | } 68 | 69 | func (m *mockRegistry) ListServices() ([]*registry.Service, error) { 70 | var services []*registry.Service 71 | for _, service := range m.Services { 72 | services = append(services, service...) 73 | } 74 | return services, nil 75 | } 76 | 77 | func (m *mockRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error { 78 | services := addServices(m.Services[s.Name], []*registry.Service{s}) 79 | m.Services[s.Name] = services 80 | return nil 81 | } 82 | 83 | func (m *mockRegistry) Deregister(s *registry.Service) error { 84 | services := delServices(m.Services[s.Name], []*registry.Service{s}) 85 | m.Services[s.Name] = services 86 | return nil 87 | } 88 | 89 | func (m *mockRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) { 90 | var wopts registry.WatchOptions 91 | for _, o := range opts { 92 | o(&wopts) 93 | } 94 | return &mockWatcher{exit: make(chan bool), opts: wopts}, nil 95 | } 96 | 97 | func (m *mockRegistry) String() string { 98 | return "mock" 99 | } 100 | 101 | func (m *mockRegistry) Init(opts ...registry.Option) error { 102 | return nil 103 | } 104 | 105 | func (m *mockRegistry) Options() registry.Options { 106 | return registry.Options{} 107 | } 108 | 109 | func NewRegistry(opts ...registry.Options) registry.Registry { 110 | m := &mockRegistry{Services: make(map[string][]*registry.Service)} 111 | m.init() 112 | return m 113 | } 114 | -------------------------------------------------------------------------------- /registry/mock/mock_test.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangdas/mqant/registry" 7 | ) 8 | 9 | var ( 10 | testData = map[string][]*registry.Service{ 11 | "foo": []*registry.Service{ 12 | { 13 | Name: "foo", 14 | Version: "1.0.0", 15 | Nodes: []*registry.Node{ 16 | { 17 | Id: "foo-1.0.0-123", 18 | Address: "localhost", 19 | Port: 9999, 20 | }, 21 | { 22 | Id: "foo-1.0.0-321", 23 | Address: "localhost", 24 | Port: 9999, 25 | }, 26 | }, 27 | }, 28 | { 29 | Name: "foo", 30 | Version: "1.0.1", 31 | Nodes: []*registry.Node{ 32 | { 33 | Id: "foo-1.0.1-321", 34 | Address: "localhost", 35 | Port: 6666, 36 | }, 37 | }, 38 | }, 39 | { 40 | Name: "foo", 41 | Version: "1.0.3", 42 | Nodes: []*registry.Node{ 43 | { 44 | Id: "foo-1.0.3-345", 45 | Address: "localhost", 46 | Port: 8888, 47 | }, 48 | }, 49 | }, 50 | }, 51 | "bar": []*registry.Service{ 52 | { 53 | Name: "bar", 54 | Version: "default", 55 | Nodes: []*registry.Node{ 56 | { 57 | Id: "bar-1.0.0-123", 58 | Address: "localhost", 59 | Port: 9999, 60 | }, 61 | { 62 | Id: "bar-1.0.0-321", 63 | Address: "localhost", 64 | Port: 9999, 65 | }, 66 | }, 67 | }, 68 | { 69 | Name: "bar", 70 | Version: "latest", 71 | Nodes: []*registry.Node{ 72 | { 73 | Id: "bar-1.0.1-321", 74 | Address: "localhost", 75 | Port: 6666, 76 | }, 77 | }, 78 | }, 79 | }, 80 | } 81 | ) 82 | 83 | func TestMockRegistry(t *testing.T) { 84 | m := NewRegistry() 85 | 86 | fn := func(k string, v []*registry.Service) { 87 | services, err := m.GetService(k) 88 | if err != nil { 89 | t.Errorf("Unexpected error getting service %s: %v", k, err) 90 | } 91 | 92 | if len(services) != len(v) { 93 | t.Errorf("Expected %d services for %s, got %d", len(v), k, len(services)) 94 | } 95 | 96 | for _, service := range v { 97 | var seen bool 98 | for _, s := range services { 99 | if s.Version == service.Version { 100 | seen = true 101 | break 102 | } 103 | } 104 | if !seen { 105 | t.Errorf("expected to find version %s", service.Version) 106 | } 107 | } 108 | } 109 | 110 | // test existing mock data 111 | for k, v := range mockData { 112 | fn(k, v) 113 | } 114 | 115 | // register data 116 | for _, v := range testData { 117 | for _, service := range v { 118 | if err := m.Register(service); err != nil { 119 | t.Errorf("Unexpected register error: %v", err) 120 | } 121 | } 122 | } 123 | 124 | // using test data 125 | for k, v := range testData { 126 | 127 | fn(k, v) 128 | } 129 | 130 | // deregister 131 | for _, v := range testData { 132 | for _, service := range v { 133 | if err := m.Deregister(service); err != nil { 134 | t.Errorf("Unexpected deregister error: %v", err) 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /registry/mock/mock_watcher.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/liangdas/mqant/registry" 7 | ) 8 | 9 | type mockWatcher struct { 10 | exit chan bool 11 | opts registry.WatchOptions 12 | } 13 | 14 | func (m *mockWatcher) Next() (*registry.Result, error) { 15 | // not implement so we just block until exit 16 | select { 17 | case <-m.exit: 18 | return nil, errors.New("watcher stopped") 19 | } 20 | } 21 | 22 | func (m *mockWatcher) Stop() { 23 | select { 24 | case <-m.exit: 25 | return 26 | default: 27 | close(m.exit) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /registry/options.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "time" 7 | ) 8 | 9 | // Options Options 10 | type Options struct { 11 | Addrs []string 12 | Timeout time.Duration 13 | Secure bool 14 | TLSConfig *tls.Config 15 | 16 | // Other options for implementations of the interface 17 | // can be stored in a context 18 | Context context.Context 19 | } 20 | 21 | // RegisterOptions RegisterOptions 22 | type RegisterOptions struct { 23 | TTL time.Duration 24 | // Other options for implementations of the interface 25 | // can be stored in a context 26 | Context context.Context 27 | } 28 | 29 | // WatchOptions WatchOptions 30 | type WatchOptions struct { 31 | // Specify a service to watch 32 | // If blank, the watch is for all services 33 | Service string 34 | // Other options for implementations of the interface 35 | // can be stored in a context 36 | Context context.Context 37 | } 38 | 39 | // Addrs is the registry addresses to use 40 | func Addrs(addrs ...string) Option { 41 | return func(o *Options) { 42 | o.Addrs = addrs 43 | } 44 | } 45 | 46 | // Timeout Timeout 47 | func Timeout(t time.Duration) Option { 48 | return func(o *Options) { 49 | o.Timeout = t 50 | } 51 | } 52 | 53 | // Secure communication with the registry 54 | func Secure(b bool) Option { 55 | return func(o *Options) { 56 | o.Secure = b 57 | } 58 | } 59 | 60 | // TLSConfig TLS Config 61 | func TLSConfig(t *tls.Config) Option { 62 | return func(o *Options) { 63 | o.TLSConfig = t 64 | } 65 | } 66 | 67 | // RegisterTTL ttl 68 | func RegisterTTL(t time.Duration) RegisterOption { 69 | return func(o *RegisterOptions) { 70 | o.TTL = t 71 | } 72 | } 73 | 74 | // WatchService a service 75 | func WatchService(name string) WatchOption { 76 | return func(o *WatchOptions) { 77 | o.Service = name 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | // Package registry is an interface for service discovery 2 | package registry 3 | 4 | import ( 5 | "errors" 6 | ) 7 | 8 | // Registry The registry provides an interface for service discovery 9 | // and an abstraction over varying implementations 10 | // {consul, etcd, zookeeper, ...} 11 | type Registry interface { 12 | Init(...Option) error 13 | Options() Options 14 | Register(*Service, ...RegisterOption) error 15 | Deregister(*Service) error 16 | GetService(string) ([]*Service, error) 17 | ListServices() ([]*Service, error) 18 | Watch(...WatchOption) (Watcher, error) 19 | String() string 20 | } 21 | 22 | // Option Option 23 | type Option func(*Options) 24 | 25 | // RegisterOption RegisterOption 26 | type RegisterOption func(*RegisterOptions) 27 | 28 | // WatchOption WatchOption 29 | type WatchOption func(*WatchOptions) 30 | 31 | var ( 32 | // DefaultRegistry 默认注册中心 33 | DefaultRegistry = newConsulRegistry() 34 | // ErrNotFound ErrNotFound 35 | ErrNotFound = errors.New("not found") 36 | ) 37 | 38 | // NewRegistry 新建注册中心 39 | func NewRegistry(opts ...Option) Registry { 40 | return newConsulRegistry(opts...) 41 | } 42 | 43 | // Register a service node. Additionally supply options such as TTL. 44 | func Register(s *Service, opts ...RegisterOption) error { 45 | return DefaultRegistry.Register(s, opts...) 46 | } 47 | 48 | // Deregister a service node 49 | func Deregister(s *Service) error { 50 | return DefaultRegistry.Deregister(s) 51 | } 52 | 53 | // GetService Retrieve a service. A slice is returned since we separate Name/Version. 54 | func GetService(name string) ([]*Service, error) { 55 | return DefaultRegistry.GetService(name) 56 | } 57 | 58 | // ListServices List the services. Only returns service names 59 | func ListServices() ([]*Service, error) { 60 | return DefaultRegistry.ListServices() 61 | } 62 | 63 | // Watch returns a watcher which allows you to track updates to the registry. 64 | func Watch(opts ...WatchOption) (Watcher, error) { 65 | return DefaultRegistry.Watch(opts...) 66 | } 67 | 68 | // String String 69 | func String() string { 70 | return DefaultRegistry.String() 71 | } 72 | -------------------------------------------------------------------------------- /registry/service.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // Service Service 4 | type Service struct { 5 | Name string `json:"name"` 6 | Version string `json:"version"` 7 | Metadata map[string]string `json:"metadata"` 8 | Endpoints []*Endpoint `json:"endpoints"` 9 | Nodes []*Node `json:"nodes"` 10 | } 11 | 12 | // Node 服务节点信息 13 | type Node struct { 14 | Id string `json:"id"` 15 | Address string `json:"address"` 16 | Port int `json:"port"` 17 | Metadata map[string]string `json:"metadata"` 18 | } 19 | 20 | // Endpoint 服务节点信息 21 | type Endpoint struct { 22 | Name string `json:"name"` 23 | Request *Value `json:"request"` 24 | Response *Value `json:"response"` 25 | Metadata map[string]string `json:"metadata"` 26 | } 27 | 28 | // Value Value 29 | type Value struct { 30 | Name string `json:"name"` 31 | Type string `json:"type"` 32 | Values []*Value `json:"values"` 33 | } 34 | -------------------------------------------------------------------------------- /registry/watcher.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | // Watcher is an interface that returns updates 4 | // about services within the registry. 5 | type Watcher interface { 6 | // Next is a blocking call 7 | Next() (*Result, error) 8 | Stop() 9 | } 10 | 11 | // Result is returned by a call to Next on 12 | // the watcher. Actions can be create, update, delete 13 | type Result struct { 14 | Action string 15 | Service *Service 16 | } 17 | -------------------------------------------------------------------------------- /rpc/base/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package defaultrpc 15 | 16 | import "github.com/liangdas/mqant/rpc/pb" 17 | 18 | type ClinetCallInfo struct { 19 | correlation_id string 20 | timeout int64 //超时 21 | call chan *rpcpb.ResultInfo 22 | } 23 | -------------------------------------------------------------------------------- /rpc/pb/mqant_rpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package rpcpb; 3 | option go_package = "github.com/liangdas/mqant/rpc/rpcpb"; 4 | message RPCInfo { 5 | string Cid = 1; 6 | string Fn = 2; 7 | string ReplyTo = 3; 8 | string track = 4; 9 | int64 Expired = 5; 10 | bool Reply = 6; 11 | repeated string ArgsType = 7; 12 | repeated bytes Args = 8; 13 | string caller = 9; 14 | string hostname =10; 15 | } 16 | 17 | message ResultInfo { 18 | string Cid = 1; 19 | string Error = 2; 20 | string ResultType = 4; 21 | bytes Result = 5; 22 | } -------------------------------------------------------------------------------- /rpc/pb/new.go: -------------------------------------------------------------------------------- 1 | package rpcpb 2 | 3 | import ( 4 | proto "google.golang.org/protobuf/proto" 5 | ) 6 | 7 | func NewResultInfo(Cid string, Error string, ArgsType string, result []byte) *ResultInfo { 8 | resultInfo := &ResultInfo{ 9 | Cid: *proto.String(Cid), 10 | Error: *proto.String(Error), 11 | ResultType: *proto.String(ArgsType), 12 | Result: result, 13 | } 14 | return resultInfo 15 | } 16 | -------------------------------------------------------------------------------- /rpc/pb/rpc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package rpcpb 15 | 16 | import ( 17 | "google.golang.org/protobuf/proto" 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestRPCInfo(t *testing.T) { 23 | rpc := &RPCInfo{ // 使用辅助函数设置域的值 24 | Cid: *proto.String("123457"), 25 | Fn: *proto.String("hello"), 26 | Expired: *proto.Int64(time.Now().UnixNano() / 1000000), 27 | Reply: *proto.Bool(true), 28 | ReplyTo: *proto.String("232244"), 29 | } // 进行编码 30 | rpc.ArgsType = []string{"s", "s"} 31 | rpc.Args = [][]byte{[]byte("hello"), []byte("world")} 32 | data, err := proto.Marshal(rpc) 33 | if err != nil { 34 | t.Fatalf("marshaling error: ", err) 35 | } // 进行解码 36 | newRPC := &RPCInfo{} 37 | err = proto.Unmarshal(data, newRPC) 38 | if err != nil { 39 | t.Fatalf("unmarshaling error: ", err) 40 | } // 测试结果 41 | if rpc.ReplyTo != newRPC.GetReplyTo() { 42 | t.Fatalf("data mismatch %q != %q", rpc.GetReplyTo(), newRPC.GetReplyTo()) 43 | } 44 | } 45 | 46 | func TestResultInfo(t *testing.T) { 47 | result := &ResultInfo{ // 使用辅助函数设置域的值 48 | Cid: *proto.String("123457"), 49 | Error: *proto.String("hello"), 50 | ResultType: *proto.String("s"), 51 | Result: []byte("232244"), 52 | } // 进行编码 53 | data, err := proto.Marshal(result) 54 | if err != nil { 55 | t.Fatalf("marshaling error: ", err) 56 | } // 进行解码 57 | newResult := &ResultInfo{} 58 | err = proto.Unmarshal(data, newResult) 59 | if err != nil { 60 | t.Fatalf("unmarshaling error: ", err) 61 | } // 测试结果 62 | if result.Cid != newResult.GetCid() { 63 | t.Fatalf("data mismatch %q != %q", result.GetCid(), newResult.GetCid()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rpc/rpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package mqrpc rpc接口定义 16 | package mqrpc 17 | 18 | import ( 19 | "context" 20 | "github.com/liangdas/mqant/rpc/pb" 21 | "reflect" 22 | ) 23 | 24 | // FunctionInfo handler接口信息 25 | type FunctionInfo struct { 26 | Function reflect.Value 27 | FuncType reflect.Type 28 | InType []reflect.Type 29 | Goroutine bool 30 | } 31 | 32 | //MQServer 代理者 33 | type MQServer interface { 34 | Callback(callinfo *CallInfo) error 35 | } 36 | 37 | //CallInfo RPC的请求信息 38 | type CallInfo struct { 39 | RPCInfo *rpcpb.RPCInfo 40 | Result *rpcpb.ResultInfo 41 | Props map[string]interface{} 42 | ExecTime int64 43 | Agent MQServer //代理者 AMQPServer / LocalServer 都继承 Callback(callinfo CallInfo)(error) 方法 44 | } 45 | 46 | // RPCListener 事件监听器 47 | type RPCListener interface { 48 | /** 49 | NoFoundFunction 当未找到请求的handler时会触发该方法 50 | *FunctionInfo 选择可执行的handler 51 | return error 52 | */ 53 | NoFoundFunction(fn string) (*FunctionInfo, error) 54 | /** 55 | BeforeHandle会对请求做一些前置处理,如:检查当前玩家是否已登录,打印统计日志等。 56 | @session 可能为nil 57 | return error 当error不为nil时将直接返回改错误信息而不会再执行后续调用 58 | */ 59 | BeforeHandle(fn string, callInfo *CallInfo) error 60 | OnTimeOut(fn string, Expired int64) 61 | OnError(fn string, callInfo *CallInfo, err error) 62 | /** 63 | fn 方法名 64 | params 参数 65 | result 执行结果 66 | exec_time 方法执行时间 单位为 Nano 纳秒 1000000纳秒等于1毫秒 67 | */ 68 | OnComplete(fn string, callInfo *CallInfo, result *rpcpb.ResultInfo, execTime int64) 69 | } 70 | 71 | // GoroutineControl 服务协程数量控制 72 | type GoroutineControl interface { 73 | Wait() error 74 | Finish() 75 | } 76 | 77 | // RPCServer 服务定义 78 | type RPCServer interface { 79 | Addr() string 80 | SetListener(listener RPCListener) 81 | SetGoroutineControl(control GoroutineControl) 82 | GetExecuting() int64 83 | Register(id string, f interface{}) 84 | RegisterGO(id string, f interface{}) 85 | Done() (err error) 86 | } 87 | 88 | // RPCClient 客户端定义 89 | type RPCClient interface { 90 | Done() (err error) 91 | CallArgs(ctx context.Context, _func string, ArgsType []string, args [][]byte) (interface{}, string) 92 | CallNRArgs(_func string, ArgsType []string, args [][]byte) (err error) 93 | Call(ctx context.Context, _func string, params ...interface{}) (interface{}, string) 94 | CallNR(_func string, params ...interface{}) (err error) 95 | } 96 | 97 | // Marshaler is a simple encoding interface used for the broker/transport 98 | // where headers are not supported by the underlying implementation. 99 | type Marshaler interface { 100 | Marshal() ([]byte, error) 101 | Unmarshal([]byte) error 102 | String() string 103 | } 104 | 105 | // ParamOption ParamOption 106 | type ParamOption func() []interface{} 107 | 108 | // Param 请求参数包装器 109 | func Param(params ...interface{}) ParamOption { 110 | return func() []interface{} { 111 | return params 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /selector/cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangdas/mqant/registry/mock" 7 | "github.com/liangdas/mqant/selector" 8 | ) 9 | 10 | func TestCacheSelector(t *testing.T) { 11 | counts := map[string]int{} 12 | 13 | cache := NewSelector(selector.Registry(mock.NewRegistry())) 14 | 15 | next, err := cache.Select("foo") 16 | if err != nil { 17 | t.Errorf("Unexpected error calling cache select: %v", err) 18 | } 19 | 20 | for i := 0; i < 100; i++ { 21 | node, err := next() 22 | if err != nil { 23 | t.Errorf("Expected node err, got err: %v", err) 24 | } 25 | counts[node.Id]++ 26 | } 27 | 28 | t.Logf("Cache Counts %v", counts) 29 | } 30 | -------------------------------------------------------------------------------- /selector/cache/options.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/liangdas/mqant/selector" 8 | ) 9 | 10 | type ttlKey struct{} 11 | 12 | // Set the cache ttl 13 | func TTL(t time.Duration) selector.Option { 14 | return func(o *selector.Options) { 15 | if o.Context == nil { 16 | o.Context = context.Background() 17 | } 18 | o.Context = context.WithValue(o.Context, ttlKey{}, t) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /selector/default.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import "github.com/liangdas/mqant/registry" 4 | 5 | type defaultSelector struct { 6 | so Options 7 | } 8 | 9 | func (r *defaultSelector) Init(opts ...Option) error { 10 | for _, o := range opts { 11 | o(&r.so) 12 | } 13 | return nil 14 | } 15 | 16 | func (r *defaultSelector) Options() Options { 17 | return r.so 18 | } 19 | 20 | func (r *defaultSelector) GetService(service string) ([]*registry.Service, error) { 21 | services, err := r.so.Registry.GetService(service) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return services, nil 26 | } 27 | 28 | func (r *defaultSelector) Select(service string, opts ...SelectOption) (Next, error) { 29 | sopts := SelectOptions{ 30 | Strategy: r.so.Strategy, 31 | } 32 | 33 | for _, opt := range opts { 34 | opt(&sopts) 35 | } 36 | 37 | // get the service 38 | services, err := r.so.Registry.GetService(service) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | // apply the filters 44 | for _, filter := range sopts.Filters { 45 | services = filter(services) 46 | } 47 | 48 | // if there's nothing left, return 49 | if len(services) == 0 { 50 | return nil, ErrNoneAvailable 51 | } 52 | 53 | return sopts.Strategy(services), nil 54 | } 55 | 56 | func (r *defaultSelector) Mark(service string, node *registry.Node, err error) { 57 | return 58 | } 59 | 60 | func (r *defaultSelector) Reset(service string) { 61 | return 62 | } 63 | 64 | func (r *defaultSelector) Close() error { 65 | return nil 66 | } 67 | 68 | func (r *defaultSelector) String() string { 69 | return "default" 70 | } 71 | 72 | func newDefaultSelector(opts ...Option) Selector { 73 | sopts := Options{ 74 | Strategy: Random, 75 | } 76 | 77 | for _, opt := range opts { 78 | opt(&sopts) 79 | } 80 | 81 | if sopts.Registry == nil { 82 | sopts.Registry = registry.DefaultRegistry 83 | } 84 | 85 | return &defaultSelector{ 86 | so: sopts, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /selector/default_test.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangdas/mqant/registry/mock" 7 | ) 8 | 9 | func TestDefaultSelector(t *testing.T) { 10 | counts := map[string]int{} 11 | 12 | rs := newDefaultSelector(Registry(mock.NewRegistry())) 13 | 14 | next, err := rs.Select("foo") 15 | if err != nil { 16 | t.Errorf("Unexpected error calling default select: %v", err) 17 | } 18 | 19 | for i := 0; i < 100; i++ { 20 | node, err := next() 21 | if err != nil { 22 | t.Errorf("Expected node err, got err: %v", err) 23 | } 24 | counts[node.Id]++ 25 | } 26 | 27 | t.Logf("Default Counts %v", counts) 28 | } 29 | -------------------------------------------------------------------------------- /selector/filter.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import () 4 | import "github.com/liangdas/mqant/registry" 5 | 6 | // FilterEndpoint is an endpoint based Select Filter which will 7 | // only return services with the endpoint specified. 8 | func FilterEndpoint(name string) Filter { 9 | return func(old []*registry.Service) []*registry.Service { 10 | var services []*registry.Service 11 | 12 | for _, service := range old { 13 | for _, ep := range service.Endpoints { 14 | if ep.Name == name { 15 | services = append(services, service) 16 | break 17 | } 18 | } 19 | } 20 | 21 | return services 22 | } 23 | } 24 | 25 | // FilterLabel is a label based Select Filter which will 26 | // only return services with the label specified. 27 | func FilterLabel(key, val string) Filter { 28 | return func(old []*registry.Service) []*registry.Service { 29 | var services []*registry.Service 30 | 31 | for _, service := range old { 32 | serv := new(registry.Service) 33 | var nodes []*registry.Node 34 | 35 | for _, node := range service.Nodes { 36 | if node.Metadata == nil { 37 | continue 38 | } 39 | 40 | if node.Metadata[key] == val { 41 | nodes = append(nodes, node) 42 | } 43 | } 44 | 45 | // only add service if there's some nodes 46 | if len(nodes) > 0 { 47 | // copy 48 | *serv = *service 49 | serv.Nodes = nodes 50 | services = append(services, serv) 51 | } 52 | } 53 | 54 | return services 55 | } 56 | } 57 | 58 | // FilterVersion is a version based Select Filter which will 59 | // only return services with the version specified. 60 | func FilterVersion(version string) Filter { 61 | return func(old []*registry.Service) []*registry.Service { 62 | var services []*registry.Service 63 | 64 | for _, service := range old { 65 | if service.Version == version { 66 | services = append(services, service) 67 | } 68 | } 69 | 70 | return services 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /selector/options.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/liangdas/mqant/registry" 7 | ) 8 | 9 | // Options Options 10 | type Options struct { 11 | Watcher Watcher 12 | Registry registry.Registry 13 | Strategy Strategy 14 | 15 | // Other options for implementations of the interface 16 | // can be stored in a context 17 | Context context.Context 18 | } 19 | 20 | // SelectOptions SelectOptions 21 | type SelectOptions struct { 22 | Filters []Filter 23 | Strategy Strategy 24 | 25 | Param []interface{} 26 | // Other options for implementations of the interface 27 | // can be stored in a context 28 | Context context.Context 29 | } 30 | 31 | // Watcher Watcher 32 | type Watcher func(node *registry.Node) 33 | 34 | // Option used to initialise the selector 35 | type Option func(*Options) 36 | 37 | // SelectOption used when making a select call 38 | type SelectOption func(*SelectOptions) 39 | 40 | // Registry sets the registry used by the selector 41 | func Registry(r registry.Registry) Option { 42 | return func(o *Options) { 43 | o.Registry = r 44 | } 45 | } 46 | 47 | // SetStrategy sets the default strategy for the selector 48 | func SetStrategy(fn Strategy) Option { 49 | return func(o *Options) { 50 | o.Strategy = fn 51 | } 52 | } 53 | 54 | // SetWatcher sets the default strategy for the selector 55 | func SetWatcher(fn Watcher) Option { 56 | return func(o *Options) { 57 | o.Watcher = fn 58 | } 59 | } 60 | 61 | // WithFilter adds a filter function to the list of filters 62 | // used during the Select call. 63 | func WithFilter(fn ...Filter) SelectOption { 64 | return func(o *SelectOptions) { 65 | o.Filters = append(o.Filters, fn...) 66 | } 67 | } 68 | 69 | // WithStrategy sets the selector strategy 70 | func WithStrategy(fn Strategy) SelectOption { 71 | return func(o *SelectOptions) { 72 | o.Strategy = fn 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /selector/selector.go: -------------------------------------------------------------------------------- 1 | // Package selector is a way to load balance service nodes 2 | package selector 3 | 4 | import ( 5 | "errors" 6 | 7 | "github.com/liangdas/mqant/registry" 8 | ) 9 | 10 | // Selector builds on the registry as a mechanism to pick nodes 11 | // and mark their status. This allows host pools and other things 12 | // to be built using various algorithms. 13 | type Selector interface { 14 | Init(opts ...Option) error 15 | Options() Options 16 | // Select returns a function which should return the next node 17 | Select(service string, opts ...SelectOption) (Next, error) 18 | 19 | GetService(name string) ([]*registry.Service, error) 20 | 21 | // Mark sets the success/error against a node 22 | Mark(service string, node *registry.Node, err error) 23 | // Reset returns state back to zero for a service 24 | Reset(service string) 25 | // Close renders the selector unusable 26 | Close() error 27 | // Name of the selector 28 | String() string 29 | } 30 | 31 | // Next is a function that returns the next node 32 | // based on the selector's strategy 33 | type Next func() (*registry.Node, error) 34 | 35 | // Filter is used to filter a service during the selection process 36 | type Filter func([]*registry.Service) []*registry.Service 37 | 38 | // Strategy is a selection strategy e.g random, round robin 39 | type Strategy func([]*registry.Service) Next 40 | 41 | var ( 42 | // DefaultSelector 默认选择器 43 | DefaultSelector = newDefaultSelector() 44 | // ErrNotFound ErrNotFound 45 | ErrNotFound = errors.New("not found") 46 | // ErrNoneAvailable ErrNoneAvailable 47 | ErrNoneAvailable = errors.New("none available") 48 | ) 49 | 50 | // NewSelector NewSelector 51 | func NewSelector(opts ...Option) Selector { 52 | return newDefaultSelector(opts...) 53 | } 54 | -------------------------------------------------------------------------------- /selector/strategy.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "time" 7 | 8 | "github.com/liangdas/mqant/registry" 9 | ) 10 | 11 | func init() { 12 | rand.Seed(time.Now().UnixNano()) 13 | } 14 | 15 | // Random is a random strategy algorithm for node selection 16 | func Random(services []*registry.Service) Next { 17 | var nodes []*registry.Node 18 | 19 | for _, service := range services { 20 | nodes = append(nodes, service.Nodes...) 21 | } 22 | 23 | return func() (*registry.Node, error) { 24 | if len(nodes) == 0 { 25 | return nil, ErrNoneAvailable 26 | } 27 | 28 | i := rand.Int() % len(nodes) 29 | return nodes[i], nil 30 | } 31 | } 32 | 33 | // RoundRobin is a roundrobin strategy algorithm for node selection 34 | func RoundRobin(services []*registry.Service) Next { 35 | var nodes []*registry.Node 36 | 37 | for _, service := range services { 38 | nodes = append(nodes, service.Nodes...) 39 | } 40 | 41 | var i = rand.Int() 42 | var mtx sync.Mutex 43 | 44 | return func() (*registry.Node, error) { 45 | if len(nodes) == 0 { 46 | return nil, ErrNoneAvailable 47 | } 48 | 49 | mtx.Lock() 50 | node := nodes[i%len(nodes)] 51 | i++ 52 | mtx.Unlock() 53 | 54 | return node, nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /selector/strategy_test.go: -------------------------------------------------------------------------------- 1 | package selector 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/liangdas/mqant/registry" 7 | ) 8 | 9 | func TestStrategies(t *testing.T) { 10 | testData := []*registry.Service{ 11 | ®istry.Service{ 12 | Name: "test1", 13 | Version: "latest", 14 | Nodes: []*registry.Node{ 15 | ®istry.Node{ 16 | Id: "test1-1", 17 | Address: "10.0.0.1", 18 | Port: 1001, 19 | }, 20 | ®istry.Node{ 21 | Id: "test1-2", 22 | Address: "10.0.0.2", 23 | Port: 1002, 24 | }, 25 | }, 26 | }, 27 | ®istry.Service{ 28 | Name: "test1", 29 | Version: "default", 30 | Nodes: []*registry.Node{ 31 | ®istry.Node{ 32 | Id: "test1-3", 33 | Address: "10.0.0.3", 34 | Port: 1003, 35 | }, 36 | ®istry.Node{ 37 | Id: "test1-4", 38 | Address: "10.0.0.4", 39 | Port: 1004, 40 | }, 41 | }, 42 | }, 43 | } 44 | 45 | for name, strategy := range map[string]Strategy{"random": Random, "roundrobin": RoundRobin} { 46 | next := strategy(testData) 47 | counts := make(map[string]int) 48 | 49 | for i := 0; i < 100; i++ { 50 | node, err := next() 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | counts[node.Id]++ 55 | } 56 | 57 | t.Logf("%s: %+v\n", name, counts) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /server/context.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type serverKey struct{} 8 | 9 | func wait(ctx context.Context) bool { 10 | if ctx == nil { 11 | return false 12 | } 13 | wait, ok := ctx.Value("wait").(bool) 14 | if !ok { 15 | return false 16 | } 17 | return wait 18 | } 19 | 20 | // FromContext FromContext 21 | func FromContext(ctx context.Context) (Server, bool) { 22 | c, ok := ctx.Value(serverKey{}).(Server) 23 | return c, ok 24 | } 25 | 26 | // NewContext NewContext 27 | func NewContext(ctx context.Context, s Server) context.Context { 28 | return context.WithValue(ctx, serverKey{}, s) 29 | } 30 | -------------------------------------------------------------------------------- /server/options.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "github.com/liangdas/mqant/registry" 6 | "time" 7 | ) 8 | 9 | // Options Options 10 | type Options struct { 11 | Registry registry.Registry 12 | Metadata map[string]string 13 | Name string 14 | Address string 15 | Advertise string 16 | ID string 17 | Version string 18 | 19 | RegisterInterval time.Duration 20 | RegisterTTL time.Duration 21 | 22 | // Other options for implementations of the interface 23 | // can be stored in a context 24 | Context context.Context 25 | } 26 | 27 | func newOptions(opt ...Option) Options { 28 | opts := Options{ 29 | Metadata: map[string]string{}, 30 | } 31 | 32 | for _, o := range opt { 33 | o(&opts) 34 | } 35 | 36 | if opts.Registry == nil { 37 | opts.Registry = registry.DefaultRegistry 38 | } 39 | 40 | if len(opts.Address) == 0 { 41 | opts.Address = DefaultAddress 42 | } 43 | 44 | if len(opts.Name) == 0 { 45 | opts.Name = DefaultName 46 | } 47 | 48 | if len(opts.ID) == 0 { 49 | opts.ID = DefaultID 50 | } 51 | 52 | if len(opts.Version) == 0 { 53 | opts.Version = DefaultVersion 54 | } 55 | 56 | return opts 57 | } 58 | 59 | // Name Server name 60 | func Name(n string) Option { 61 | return func(o *Options) { 62 | o.Name = n 63 | } 64 | } 65 | 66 | // Id Unique server id 67 | // Deprecated: 因为命名规范问题函数将废弃,请用ID代替 68 | func Id(id string) Option { 69 | return func(o *Options) { 70 | o.ID = id 71 | } 72 | } 73 | 74 | // ID Unique server id 75 | func ID(id string) Option { 76 | return func(o *Options) { 77 | o.ID = id 78 | } 79 | } 80 | 81 | // Version of the service 82 | func Version(v string) Option { 83 | return func(o *Options) { 84 | o.Version = v 85 | } 86 | } 87 | 88 | // Address to bind to - host:port 89 | func Address(a string) Option { 90 | return func(o *Options) { 91 | o.Address = a 92 | } 93 | } 94 | 95 | // Advertise The address to advertise for discovery - host:port 96 | func Advertise(a string) Option { 97 | return func(o *Options) { 98 | o.Advertise = a 99 | } 100 | } 101 | 102 | // Registry used for discovery 103 | func Registry(r registry.Registry) Option { 104 | return func(o *Options) { 105 | o.Registry = r 106 | } 107 | } 108 | 109 | // Metadata associated with the server 110 | func Metadata(md map[string]string) Option { 111 | return func(o *Options) { 112 | o.Metadata = md 113 | } 114 | } 115 | 116 | // RegisterTTL Register the service with a TTL 117 | func RegisterTTL(t time.Duration) Option { 118 | return func(o *Options) { 119 | o.RegisterTTL = t 120 | } 121 | } 122 | 123 | // RegisterInterval specifies the interval on which to re-register 124 | func RegisterInterval(t time.Duration) Option { 125 | return func(o *Options) { 126 | o.RegisterInterval = t 127 | } 128 | } 129 | 130 | // Wait tells the server to wait for requests to finish before exiting 131 | func Wait(b bool) Option { 132 | return func(o *Options) { 133 | if o.Context == nil { 134 | o.Context = context.Background() 135 | } 136 | o.Context = context.WithValue(o.Context, "wait", b) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // Package server is an interface for a micro server 2 | package server 3 | 4 | import ( 5 | "context" 6 | "github.com/liangdas/mqant/conf" 7 | "github.com/liangdas/mqant/module" 8 | "github.com/liangdas/mqant/rpc" 9 | "github.com/pborman/uuid" 10 | ) 11 | 12 | // Server Server 13 | type Server interface { 14 | Options() Options 15 | OnInit(module module.Module, app module.App, settings *conf.ModuleSettings) error 16 | Init(...Option) error 17 | SetListener(listener mqrpc.RPCListener) 18 | Register(id string, f interface{}) 19 | RegisterGO(id string, f interface{}) 20 | ServiceRegister() error 21 | ServiceDeregister() error 22 | Start() error 23 | Stop() error 24 | OnDestroy() error 25 | String() string 26 | ID() string 27 | // Deprecated: 因为命名规范问题函数将废弃,请用ID代替 28 | Id() string 29 | } 30 | 31 | // Message RPC消息头 32 | type Message interface { 33 | Topic() string 34 | Payload() interface{} 35 | ContentType() string 36 | } 37 | 38 | // Request Request 39 | type Request interface { 40 | Service() string 41 | Method() string 42 | ContentType() string 43 | Request() interface{} 44 | // indicates whether the request will be streamed 45 | Stream() bool 46 | } 47 | 48 | // Stream represents a stream established with a client. 49 | // A stream can be bidirectional which is indicated by the request. 50 | // The last error will be left in Error(). 51 | // EOF indicated end of the stream. 52 | type Stream interface { 53 | Context() context.Context 54 | Request() Request 55 | Send(interface{}) error 56 | Recv(interface{}) error 57 | Error() error 58 | Close() error 59 | } 60 | 61 | // Option Option 62 | type Option func(*Options) 63 | 64 | var ( 65 | // DefaultAddress DefaultAddress 66 | DefaultAddress = ":0" 67 | // DefaultName DefaultName 68 | DefaultName = "go-server" 69 | // DefaultVersion DefaultVersion 70 | DefaultVersion = "1.0.0" 71 | // DefaultID DefaultID 72 | DefaultID = uuid.NewUUID().String() 73 | ) 74 | 75 | // NewServer returns a new server with options passed in 76 | func NewServer(opt ...Option) Server { 77 | return newRPCServer(opt...) 78 | } 79 | -------------------------------------------------------------------------------- /service/options.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/liangdas/mqant/registry" 8 | "github.com/liangdas/mqant/server" 9 | ) 10 | 11 | // Service Service 12 | type Service interface { 13 | Init(...Option) 14 | Options() Options 15 | Server() server.Server 16 | Run() error 17 | String() string 18 | } 19 | 20 | // Option Option 21 | type Option func(*Options) 22 | 23 | // Options Options 24 | type Options struct { 25 | Server server.Server 26 | Registry registry.Registry 27 | 28 | // Register loop interval 29 | RegisterInterval time.Duration 30 | 31 | // Before and After funcs 32 | BeforeStart []func() error 33 | BeforeStop []func() error 34 | AfterStart []func() error 35 | AfterStop []func() error 36 | 37 | // Other options for implementations of the interface 38 | // can be stored in a context 39 | Context context.Context 40 | } 41 | 42 | func newOptions(opts ...Option) Options { 43 | opt := Options{ 44 | Registry: registry.DefaultRegistry, 45 | Context: context.Background(), 46 | } 47 | 48 | for _, o := range opts { 49 | o(&opt) 50 | } 51 | 52 | return opt 53 | } 54 | 55 | // Context specifies a context for the service. 56 | // Can be used to signal shutdown of the service. 57 | // Can be used for extra option values. 58 | func Context(ctx context.Context) Option { 59 | return func(o *Options) { 60 | o.Context = ctx 61 | } 62 | } 63 | 64 | // Server Server 65 | func Server(s server.Server) Option { 66 | return func(o *Options) { 67 | o.Server = s 68 | } 69 | } 70 | 71 | // Registry sets the registry for the service 72 | // and the underlying components 73 | func Registry(r registry.Registry) Option { 74 | return func(o *Options) { 75 | o.Registry = r 76 | // Update Client and Server 77 | //o.Client.Init(client.Registry(r)) 78 | o.Server.Init(server.Registry(r)) 79 | // Update Selector 80 | //o.Client.Options().Selector.Init(selector.Registry(r)) 81 | // Update Broker 82 | //o.Broker.Init(broker.Registry(r)) 83 | } 84 | } 85 | 86 | // Convenience options 87 | 88 | // Name of the service 89 | func Name(n string) Option { 90 | return func(o *Options) { 91 | o.Server.Init(server.Name(n)) 92 | } 93 | } 94 | 95 | // Version of the service 96 | func Version(v string) Option { 97 | return func(o *Options) { 98 | o.Server.Init(server.Version(v)) 99 | } 100 | } 101 | 102 | // Metadata associated with the service 103 | func Metadata(md map[string]string) Option { 104 | return func(o *Options) { 105 | o.Server.Init(server.Metadata(md)) 106 | } 107 | } 108 | 109 | // RegisterTTL specifies the TTL to use when registering the service 110 | func RegisterTTL(t time.Duration) Option { 111 | return func(o *Options) { 112 | o.Server.Init(server.RegisterTTL(t)) 113 | } 114 | } 115 | 116 | // RegisterInterval specifies the interval on which to re-register 117 | func RegisterInterval(t time.Duration) Option { 118 | return func(o *Options) { 119 | o.RegisterInterval = t 120 | } 121 | } 122 | 123 | // BeforeStart Before and Afters 124 | func BeforeStart(fn func() error) Option { 125 | return func(o *Options) { 126 | o.BeforeStart = append(o.BeforeStart, fn) 127 | } 128 | } 129 | 130 | // BeforeStop Before and Afters 131 | func BeforeStop(fn func() error) Option { 132 | return func(o *Options) { 133 | o.BeforeStop = append(o.BeforeStop, fn) 134 | } 135 | } 136 | 137 | // AfterStart Before and Afters 138 | func AfterStart(fn func() error) Option { 139 | return func(o *Options) { 140 | o.AfterStart = append(o.AfterStart, fn) 141 | } 142 | } 143 | 144 | // AfterStop Before and Afters 145 | func AfterStop(fn func() error) Option { 146 | return func(o *Options) { 147 | o.AfterStop = append(o.AfterStop, fn) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/liangdas/mqant/log" 8 | "github.com/liangdas/mqant/server" 9 | ) 10 | 11 | // NewService NewService 12 | func NewService(opts ...Option) Service { 13 | return newService(opts...) 14 | } 15 | 16 | type service struct { 17 | opts Options 18 | 19 | once sync.Once 20 | } 21 | 22 | func newService(opts ...Option) Service { 23 | options := newOptions(opts...) 24 | 25 | return &service{ 26 | opts: options, 27 | } 28 | } 29 | 30 | func (s *service) run(exit chan bool) { 31 | if s.opts.RegisterInterval <= time.Duration(0) { 32 | return 33 | } 34 | 35 | t := time.NewTicker(s.opts.RegisterInterval) 36 | 37 | for { 38 | select { 39 | case <-t.C: 40 | err := s.opts.Server.ServiceRegister() 41 | if err != nil { 42 | log.Warning("service run Server.Register error: ", err) 43 | } 44 | case <-exit: 45 | t.Stop() 46 | return 47 | } 48 | } 49 | } 50 | 51 | // Init initialises options. Additionally it calls cmd.Init 52 | // which parses command line flags. cmd.Init is only called 53 | // on first Init. 54 | func (s *service) Init(opts ...Option) { 55 | // process options 56 | for _, o := range opts { 57 | o(&s.opts) 58 | } 59 | 60 | s.once.Do(func() { 61 | // save user action 62 | 63 | }) 64 | } 65 | 66 | func (s *service) Options() Options { 67 | return s.opts 68 | } 69 | 70 | func (s *service) Server() server.Server { 71 | return s.opts.Server 72 | } 73 | 74 | func (s *service) String() string { 75 | return "mqant" 76 | } 77 | 78 | func (s *service) Start() error { 79 | for _, fn := range s.opts.BeforeStart { 80 | if err := fn(); err != nil { 81 | return err 82 | } 83 | } 84 | 85 | if err := s.opts.Server.Start(); err != nil { 86 | return err 87 | } 88 | 89 | if err := s.opts.Server.ServiceRegister(); err != nil { 90 | return err 91 | } 92 | 93 | for _, fn := range s.opts.AfterStart { 94 | if err := fn(); err != nil { 95 | return err 96 | } 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func (s *service) Stop() error { 103 | var gerr error 104 | 105 | for _, fn := range s.opts.BeforeStop { 106 | if err := fn(); err != nil { 107 | gerr = err 108 | } 109 | } 110 | 111 | if err := s.opts.Server.ServiceDeregister(); err != nil { 112 | return err 113 | } 114 | 115 | if err := s.opts.Server.Stop(); err != nil { 116 | return err 117 | } 118 | 119 | for _, fn := range s.opts.AfterStop { 120 | if err := fn(); err != nil { 121 | gerr = err 122 | } 123 | } 124 | 125 | return gerr 126 | } 127 | 128 | func (s *service) Run() error { 129 | if err := s.Start(); err != nil { 130 | return err 131 | } 132 | 133 | // start reg loop 134 | ex := make(chan bool) 135 | go s.run(ex) 136 | 137 | //ch := make(chan os.Signal, 1) 138 | //signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 139 | 140 | select { 141 | // wait on kill signal 142 | //case <-ch: 143 | // wait on context cancel 144 | case <-s.opts.Context.Done(): 145 | } 146 | 147 | // exit reg loop 148 | close(ex) 149 | return s.Stop() 150 | } 151 | -------------------------------------------------------------------------------- /utils/aes/aes_encrypt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqantserver Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package aes 15 | 16 | import ( 17 | "crypto/aes" 18 | "crypto/cipher" 19 | "fmt" 20 | ) 21 | 22 | func NewAesEncrypt(key string) (aes *AesEncrypt, err error) { 23 | keyLen := len(key) 24 | if keyLen < 16 { 25 | err = fmt.Errorf("The length of res key shall not be less than 16") 26 | return 27 | } 28 | aes = &AesEncrypt{ 29 | StrKey: key, 30 | } 31 | return aes, nil 32 | } 33 | 34 | type AesEncrypt struct { 35 | StrKey string 36 | } 37 | 38 | func (this *AesEncrypt) getKey() []byte { 39 | keyLen := len(this.StrKey) 40 | if keyLen < 16 { 41 | panic("The length of res key shall not be less than 16") 42 | } 43 | arrKey := []byte(this.StrKey) 44 | if keyLen >= 32 { 45 | //取前32个字节 46 | return arrKey[:32] 47 | } 48 | if keyLen >= 24 { 49 | //取前24个字节 50 | return arrKey[:24] 51 | } 52 | //取前16个字节 53 | return arrKey[:16] 54 | } 55 | 56 | //加密字符串 57 | func (this *AesEncrypt) Encrypt(strMesg string) ([]byte, error) { 58 | key := this.getKey() 59 | var iv = []byte(key)[:aes.BlockSize] 60 | encrypted := make([]byte, len(strMesg)) 61 | aesBlockEncrypter, err := aes.NewCipher(key) 62 | if err != nil { 63 | return nil, err 64 | } 65 | aesEncrypter := cipher.NewCFBEncrypter(aesBlockEncrypter, iv) 66 | aesEncrypter.XORKeyStream(encrypted, []byte(strMesg)) 67 | return encrypted, nil 68 | } 69 | 70 | //解密字符串 71 | func (this *AesEncrypt) Decrypt(src []byte) (strDesc string, err error) { 72 | defer func() { 73 | //错误处理 74 | if e := recover(); e != nil { 75 | err = e.(error) 76 | } 77 | }() 78 | key := this.getKey() 79 | var iv = []byte(key)[:aes.BlockSize] 80 | decrypted := make([]byte, len(src)) 81 | var aesBlockDecrypter cipher.Block 82 | aesBlockDecrypter, err = aes.NewCipher([]byte(key)) 83 | if err != nil { 84 | return "", err 85 | } 86 | aesDecrypter := cipher.NewCFBDecrypter(aesBlockDecrypter, iv) 87 | aesDecrypter.XORKeyStream(decrypted, src) 88 | return string(decrypted), nil 89 | } 90 | -------------------------------------------------------------------------------- /utils/base62.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package mqanttools 工具箱 16 | package mqanttools 17 | 18 | import ( 19 | "math" 20 | "strings" 21 | ) 22 | 23 | // CODE62 62进制码 24 | const CODE62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 25 | 26 | // CodeLenth 进制位 27 | const CodeLenth = 62 28 | 29 | // EDOC 62进制码 30 | var EDOC = map[string]int64{"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15, "g": 16, "h": 17, "i": 18, "j": 19, "k": 20, "l": 21, "m": 22, "n": 23, "o": 24, "p": 25, "q": 26, "r": 27, "s": 28, "t": 29, "u": 30, "v": 31, "w": 32, "x": 33, "y": 34, "z": 35, "A": 36, "B": 37, "C": 38, "D": 39, "E": 40, "F": 41, "G": 42, "H": 43, "I": 44, "J": 45, "K": 46, "L": 47, "M": 48, "N": 49, "O": 50, "P": 51, "Q": 52, "R": 53, "S": 54, "T": 55, "U": 56, "V": 57, "W": 58, "X": 59, "Y": 60, "Z": 61} 31 | 32 | // IntToBase62 编码 整数 为 base62 字符串 33 | func IntToBase62(number int64) string { 34 | if number == 0 { 35 | return "0" 36 | } 37 | result := make([]byte, 0) 38 | for number > 0 { 39 | round := number / CodeLenth 40 | remain := number % CodeLenth 41 | result = append(result, CODE62[remain]) 42 | number = round 43 | } 44 | return string(result) 45 | } 46 | 47 | // Base62ToInt 解码字符串为整数 48 | func Base62ToInt(str string) int64 { 49 | str = strings.TrimSpace(str) 50 | var result int64 = 0 51 | for index, char := range []byte(str) { 52 | result += EDOC[string(char)] * int64(math.Pow(CodeLenth, float64(index))) 53 | } 54 | return result 55 | } 56 | -------------------------------------------------------------------------------- /utils/base62_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package mqanttools 15 | 16 | import ( 17 | "testing" 18 | ) 19 | 20 | func TestBase62ToInt(t *testing.T) { 21 | i := Base62ToInt("LLqbOL1") 22 | assertEqual(t, int64(100600020001), i) 23 | 24 | i1 := Base62ToInt("eg") 25 | assertEqual(t, int64(1006), i1) 26 | 27 | i2 := Base62ToInt("2cq") 28 | assertEqual(t, int64(100690), i2) 29 | 30 | i3 := Base62ToInt("mim3") 31 | assertEqual(t, int64(800690), i3) 32 | } 33 | 34 | func TestIntToBase62(t *testing.T) { 35 | b := IntToBase62(100600020001) 36 | assertEqual(t, "LLqbOL1", b) 37 | 38 | b1 := IntToBase62(1006) 39 | assertEqual(t, "eg", b1) 40 | 41 | b2 := IntToBase62(100690) 42 | assertEqual(t, "2cq", b2) 43 | 44 | b3 := IntToBase62(800690) 45 | assertEqual(t, "mim3", b3) 46 | } 47 | -------------------------------------------------------------------------------- /utils/fatih/structs/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /utils/fatih/structs/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.7.x 4 | - tip 5 | sudo: false 6 | before_install: 7 | - go get github.com/axw/gocov/gocov 8 | - go get github.com/mattn/goveralls 9 | - if ! go get github.com/golang/tools/cmd/cover; then go get github.com/liangdas/mqant/mqant_tools/x/tools/cmd/cover; fi 10 | script: 11 | - $HOME/gopath/bin/goveralls -service=travis-ci 12 | -------------------------------------------------------------------------------- /utils/fatih/structs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Fatih Arslan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /utils/fatih/structs/tags.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import "strings" 4 | 5 | // tagOptions contains a slice of tag options 6 | type tagOptions []string 7 | 8 | // Has returns true if the given optiton is available in tagOptions 9 | func (t tagOptions) Has(opt string) bool { 10 | for _, tagOpt := range t { 11 | if tagOpt == opt { 12 | return true 13 | } 14 | } 15 | 16 | return false 17 | } 18 | 19 | // parseTag splits a struct field's tag into its name and a list of options 20 | // which comes after a name. A tag is in the form of: "name,option1,option2". 21 | // The name can be neglectected. 22 | func parseTag(tag string) (string, tagOptions) { 23 | // tag is one of followings: 24 | // "" 25 | // "name" 26 | // "name,opt" 27 | // "name,opt,opt2" 28 | // ",opt" 29 | 30 | res := strings.Split(tag, ",") 31 | return res[0], res[1:] 32 | } 33 | -------------------------------------------------------------------------------- /utils/fatih/structs/tags_test.go: -------------------------------------------------------------------------------- 1 | package structs 2 | 3 | import "testing" 4 | 5 | func TestParseTag_Name(t *testing.T) { 6 | tags := []struct { 7 | tag string 8 | has bool 9 | }{ 10 | {"", false}, 11 | {"name", true}, 12 | {"name,opt", true}, 13 | {"name , opt, opt2", false}, // has a single whitespace 14 | {", opt, opt2", false}, 15 | } 16 | 17 | for _, tag := range tags { 18 | name, _ := parseTag(tag.tag) 19 | 20 | if (name != "name") && tag.has { 21 | t.Errorf("Parse tag should return name: %#v", tag) 22 | } 23 | } 24 | } 25 | 26 | func TestParseTag_Opts(t *testing.T) { 27 | tags := []struct { 28 | opts string 29 | has bool 30 | }{ 31 | {"name", false}, 32 | {"name,opt", true}, 33 | {"name , opt, opt2", false}, // has a single whitespace 34 | {",opt, opt2", true}, 35 | {", opt3, opt4", false}, 36 | } 37 | 38 | // search for "opt" 39 | for _, tag := range tags { 40 | _, opts := parseTag(tag.opts) 41 | 42 | if opts.Has("opt") != tag.has { 43 | t.Errorf("Tag opts should have opt: %#v", tag) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /utils/id.go: -------------------------------------------------------------------------------- 1 | package mqanttools 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "strconv" 11 | "sync" 12 | "unsafe" 13 | ) 14 | 15 | // An ID is a unique, uniformly distributed 64-bit ID. 16 | type ID uint64 17 | 18 | // String returns the ID as a hex string. 19 | func (id ID) String() string { 20 | return fmt.Sprintf("%016x", uint64(id)) 21 | } 22 | 23 | // MarshalJSON encodes the ID as a hex string. 24 | func (id ID) MarshalJSON() ([]byte, error) { 25 | return json.Marshal(id.String()) 26 | } 27 | 28 | // UnmarshalJSON decodes the given data as either a hexadecimal string or JSON 29 | // integer. 30 | func (id *ID) UnmarshalJSON(data []byte) error { 31 | i, err := parseJSONString(data) 32 | if err == nil { 33 | *id = i 34 | return nil 35 | } 36 | i, err = parseJSONInt(data) 37 | if err == nil { 38 | *id = i 39 | return nil 40 | } 41 | return fmt.Errorf("%s is not a valid ID", data) 42 | } 43 | 44 | // ParseID parses the given string as a hexadecimal string. 45 | func ParseID(s string) (ID, error) { 46 | i, err := strconv.ParseUint(s, 16, 64) 47 | if err != nil { 48 | return 0, err 49 | } 50 | return ID(i), nil 51 | } 52 | 53 | // GenerateID returns a randomly-generated 64-bit ID. This function is 54 | // thread-safe. IDs are produced by consuming an AES-CTR-128 keystream in 55 | // 64-bit chunks. The AES key is randomly generated on initialization, as is the 56 | // counter's initial state. On machines with AES-NI support, ID generation takes 57 | // ~30ns and generates no garbage. 58 | func GenerateID() ID { 59 | m.Lock() 60 | if n == aes.BlockSize { 61 | c.Encrypt(b, ctr) 62 | for i := aes.BlockSize - 1; i >= 0; i-- { // increment ctr 63 | ctr[i]++ 64 | if ctr[i] != 0 { 65 | break 66 | } 67 | } 68 | n = 0 69 | } 70 | id := *(*ID)(unsafe.Pointer(&b[n])) // zero-copy b/c we're arch-neutral 71 | n += idSize 72 | m.Unlock() 73 | return id 74 | } 75 | 76 | const ( 77 | idSize = aes.BlockSize / 2 // 64 bits 78 | keySize = aes.BlockSize // 128 bits 79 | ) 80 | 81 | var ( 82 | ctr []byte 83 | n int 84 | b []byte 85 | c cipher.Block 86 | m sync.Mutex 87 | ) 88 | 89 | func init() { 90 | buf := make([]byte, keySize+aes.BlockSize) 91 | _, err := io.ReadFull(rand.Reader, buf) 92 | if err != nil { 93 | panic(err) // /dev/urandom had better work 94 | } 95 | c, err = aes.NewCipher(buf[:keySize]) 96 | if err != nil { 97 | panic(err) // AES had better work 98 | } 99 | n = aes.BlockSize 100 | ctr = buf[keySize:] 101 | b = make([]byte, aes.BlockSize) 102 | } 103 | 104 | func parseJSONString(data []byte) (ID, error) { 105 | var s string 106 | if err := json.Unmarshal(data, &s); err != nil { 107 | return 0, err 108 | } 109 | i, err := ParseID(s) 110 | if err != nil { 111 | return 0, err 112 | } 113 | return i, nil 114 | } 115 | 116 | func parseJSONInt(data []byte) (ID, error) { 117 | var i uint64 118 | if err := json.Unmarshal(data, &i); err != nil { 119 | return 0, err 120 | } 121 | return ID(i), nil 122 | } 123 | -------------------------------------------------------------------------------- /utils/id_test.go: -------------------------------------------------------------------------------- 1 | package mqanttools 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestIDMarshalJSON(t *testing.T) { 11 | id := ID(10018820) 12 | buf := bytes.NewBuffer(nil) 13 | json.NewEncoder(buf).Encode(id) 14 | got := `"000000000098e004"` 15 | want := strings.TrimSpace(buf.String()) 16 | if got != want { 17 | t.Errorf("got %v, want %v", got, want) 18 | } 19 | } 20 | 21 | func TestIDUnmarshalJSONHexString(t *testing.T) { 22 | j := []byte(`"000000000098e004"`) 23 | var got ID 24 | if err := json.Unmarshal(j, &got); err != nil { 25 | t.Fatal(err) 26 | } 27 | want := ID(10018820) 28 | if got != want { 29 | t.Errorf("got %v, want %v", got, want) 30 | } 31 | } 32 | 33 | func TestIDUnmarshalJSONInt(t *testing.T) { 34 | j := []byte(`10018820`) 35 | var got ID 36 | if err := json.Unmarshal(j, &got); err != nil { 37 | t.Fatal(err) 38 | } 39 | want := ID(10018820) 40 | if got != want { 41 | t.Errorf("got %v, want %v", got, want) 42 | } 43 | } 44 | 45 | func TestIDUnmarshalJSONNonInt(t *testing.T) { 46 | j := []byte(`[]`) 47 | var got ID 48 | err := json.Unmarshal(j, &got) 49 | if err == nil { 50 | t.Fatalf("unexpectedly unmarshalled %v", got) 51 | } 52 | } 53 | 54 | func TestIDUnmarshalJSONNonHexString(t *testing.T) { 55 | j := []byte(`"woo"`) 56 | var got ID 57 | err := json.Unmarshal(j, &got) 58 | if err == nil { 59 | t.Fatalf("unexpectedly unmarshalled %v", got) 60 | } 61 | } 62 | 63 | func TestIDGeneration(t *testing.T) { 64 | n := 10000 65 | ids := make(map[ID]bool, n) 66 | for i := 0; i < n; i++ { 67 | id := generateID() 68 | if ids[id] { 69 | t.Errorf("duplicate ID: %v", id) 70 | } 71 | ids[id] = true 72 | } 73 | } 74 | 75 | func TestParseID(t *testing.T) { 76 | want := ID(10018181901) 77 | got, err := ParseID(want.String()) 78 | if err != nil { 79 | t.Error(err) 80 | } 81 | if got != want { 82 | t.Errorf("got %v, want %v", got, want) 83 | } 84 | } 85 | 86 | func TestParseIDError(t *testing.T) { 87 | id, err := ParseID("woo") 88 | if err == nil { 89 | t.Errorf("unexpectedly parsed value: %v", id) 90 | } 91 | } 92 | 93 | func BenchmarkIDGeneration(b *testing.B) { 94 | for i := 0; i < b.N; i++ { 95 | generateID() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /utils/ip/iptool.go: -------------------------------------------------------------------------------- 1 | package iptool 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func RealIP(r *http.Request) string { 11 | remote := strings.Split(r.RemoteAddr, ":")[0] 12 | if !IsInnerIp(remote) { 13 | return remote 14 | } 15 | forwarded := r.Header.Get("X-Forwarded-For") 16 | if len(forwarded) > 0 { 17 | ip := GetGlobalIPFromXforwardedFor(forwarded) 18 | if ip != "" { 19 | return ip 20 | } 21 | } 22 | forwarded = r.Header.Get("x-forwarded-for") 23 | if len(forwarded) > 0 { 24 | ip := GetGlobalIPFromXforwardedFor(forwarded) 25 | if ip != "" { 26 | return ip 27 | } 28 | } 29 | return remote 30 | } 31 | 32 | func CheckIp(ipStr string) bool { 33 | address := net.ParseIP(ipStr) 34 | if address == nil { 35 | //fmt.Println("ip地址格式不正确") 36 | return false 37 | } else { 38 | //fmt.Println("正确的ip地址", address.String()) 39 | return true 40 | } 41 | } 42 | 43 | // ip to int64 44 | func InetAton(ipStr string) int64 { 45 | bits := strings.Split(ipStr, ".") 46 | 47 | b0, _ := strconv.Atoi(bits[0]) 48 | b1, _ := strconv.Atoi(bits[1]) 49 | b2, _ := strconv.Atoi(bits[2]) 50 | b3, _ := strconv.Atoi(bits[3]) 51 | 52 | var sum int64 53 | 54 | sum += int64(b0) << 24 55 | sum += int64(b1) << 16 56 | sum += int64(b2) << 8 57 | sum += int64(b3) 58 | 59 | return sum 60 | } 61 | 62 | //int64 to IP 63 | func InetNtoa(ipnr int64) net.IP { 64 | var bytes [4]byte 65 | bytes[0] = byte(ipnr & 0xFF) 66 | bytes[1] = byte((ipnr >> 8) & 0xFF) 67 | bytes[2] = byte((ipnr >> 16) & 0xFF) 68 | bytes[3] = byte((ipnr >> 24) & 0xFF) 69 | 70 | return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0]) 71 | } 72 | 73 | func IsInnerIp(ipStr string) bool { 74 | if !CheckIp(ipStr) { 75 | return false 76 | } 77 | inputIpNum := InetAton(ipStr) 78 | innerIpA := InetAton("10.255.255.255") 79 | innerIpB := InetAton("172.16.255.255") 80 | innerIpC := InetAton("192.168.255.255") 81 | innerIpD := InetAton("100.64.255.255") 82 | innerIpF := InetAton("127.255.255.255") 83 | 84 | return inputIpNum>>24 == innerIpA>>24 || inputIpNum>>20 == innerIpB>>20 || 85 | inputIpNum>>16 == innerIpC>>16 || inputIpNum>>22 == innerIpD>>22 || 86 | inputIpNum>>24 == innerIpF>>24 87 | } 88 | 89 | func GetGlobalIPFromXforwardedFor(xforwardedFor string) string { 90 | ips := strings.Split(xforwardedFor, ",") 91 | for _, ip := range ips { 92 | ip = strings.TrimSpace(ip) 93 | if !IsInnerIp(ip) { 94 | return ip 95 | } 96 | } 97 | return "" 98 | } 99 | -------------------------------------------------------------------------------- /utils/ip/iptool_test.go: -------------------------------------------------------------------------------- 1 | package iptool 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestIsInnerIp(t *testing.T) { 9 | assert.Equal(t, IsInnerIp("192.168.0.2"), true) 10 | assert.Equal(t, IsInnerIp("172.17.0.1"), true) 11 | assert.Equal(t, IsInnerIp("220.249.15.134"), false) 12 | assert.Equal(t, IsInnerIp("100.122.245.119"), true) 13 | 14 | gip := GetGlobalIPFromXforwardedFor("192.168.0.2, 100.64.235.3, 202.106.9.134, 101.32.47.171, 34.102.170.30, 127.0.0.1") 15 | assert.Equal(t, gip, "202.106.9.134") 16 | } 17 | -------------------------------------------------------------------------------- /utils/json/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package json 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | ) 21 | 22 | func Getter(call func() (string, error), v interface{}) error { 23 | str, err := call() 24 | if err != nil { 25 | return err 26 | } 27 | fmt.Println("Etcd-->", str) 28 | return json.Unmarshal([]byte(str), v) 29 | } 30 | -------------------------------------------------------------------------------- /utils/lib/addr/addr.go: -------------------------------------------------------------------------------- 1 | package addr 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | var ( 9 | privateBlocks []*net.IPNet 10 | ) 11 | 12 | func init() { 13 | for _, b := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10"} { 14 | if _, block, err := net.ParseCIDR(b); err == nil { 15 | privateBlocks = append(privateBlocks, block) 16 | } 17 | } 18 | } 19 | 20 | func isPrivateIP(ipAddr string) bool { 21 | ip := net.ParseIP(ipAddr) 22 | for _, priv := range privateBlocks { 23 | if priv.Contains(ip) { 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | // Extract returns a real ip 31 | func Extract(addr string) (string, error) { 32 | // if addr specified then its returned 33 | if len(addr) > 0 && (addr != "0.0.0.0" && addr != "[::]") { 34 | return addr, nil 35 | } 36 | 37 | addrs, err := net.InterfaceAddrs() 38 | if err != nil { 39 | return "", fmt.Errorf("Failed to get interface addresses! Err: %v", err) 40 | } 41 | 42 | var ipAddr []byte 43 | 44 | for _, rawAddr := range addrs { 45 | var ip net.IP 46 | switch addr := rawAddr.(type) { 47 | case *net.IPAddr: 48 | ip = addr.IP 49 | case *net.IPNet: 50 | ip = addr.IP 51 | default: 52 | continue 53 | } 54 | 55 | if ip.To4() == nil { 56 | continue 57 | } 58 | 59 | if !isPrivateIP(ip.String()) { 60 | continue 61 | } 62 | 63 | ipAddr = ip 64 | break 65 | } 66 | 67 | if ipAddr == nil { 68 | return "", fmt.Errorf("No private IP address found, and explicit IP not provided") 69 | } 70 | 71 | return net.IP(ipAddr).String(), nil 72 | } 73 | 74 | // IPs returns all known ips 75 | func IPs() []string { 76 | ifaces, err := net.Interfaces() 77 | if err != nil { 78 | return nil 79 | } 80 | 81 | var ipAddrs []string 82 | 83 | for _, i := range ifaces { 84 | addrs, err := i.Addrs() 85 | if err != nil { 86 | continue 87 | } 88 | 89 | for _, addr := range addrs { 90 | var ip net.IP 91 | switch v := addr.(type) { 92 | case *net.IPNet: 93 | ip = v.IP 94 | case *net.IPAddr: 95 | ip = v.IP 96 | } 97 | 98 | if ip == nil { 99 | continue 100 | } 101 | 102 | ip = ip.To4() 103 | if ip == nil { 104 | continue 105 | } 106 | 107 | ipAddrs = append(ipAddrs, ip.String()) 108 | } 109 | } 110 | 111 | return ipAddrs 112 | } 113 | -------------------------------------------------------------------------------- /utils/lib/addr/addr_test.go: -------------------------------------------------------------------------------- 1 | package addr 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func TestExtractor(t *testing.T) { 9 | testData := []struct { 10 | addr string 11 | expect string 12 | parse bool 13 | }{ 14 | {"127.0.0.1", "127.0.0.1", false}, 15 | {"10.0.0.1", "10.0.0.1", false}, 16 | {"", "", true}, 17 | {"0.0.0.0", "", true}, 18 | {"[::]", "", true}, 19 | } 20 | 21 | for _, d := range testData { 22 | addr, err := Extract(d.addr) 23 | if err != nil { 24 | t.Errorf("Unexpected error %v", err) 25 | } 26 | 27 | if d.parse { 28 | ip := net.ParseIP(addr) 29 | if ip == nil { 30 | t.Error("Unexpected nil IP") 31 | } 32 | 33 | } else if addr != d.expect { 34 | t.Errorf("Expected %s got %s", d.expect, addr) 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /utils/minifmt.go: -------------------------------------------------------------------------------- 1 | package mqanttools 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | regFields = regexp.MustCompile(`\{(\w+)\}`) 11 | regField = regexp.MustCompile(`[\{\}]`) 12 | ) 13 | 14 | // Sprintf 字符串格式,可以替换map中的内容 15 | // eg 你的名字是{name} extra=map[string]string{"name","mqant"} 16 | func Sprintf(format string, extra map[string]interface{}) string { 17 | fields := regFields.FindAllString(format, -1) 18 | ret := format 19 | for _, fieldName := range fields { 20 | field := regField.ReplaceAllString(fieldName, "") 21 | if v, ok := extra[field]; !ok { 22 | 23 | } else { 24 | ret = strings.Replace(ret, fieldName, fmt.Sprintf("%v", v), 1) 25 | } 26 | } 27 | return ret 28 | } 29 | -------------------------------------------------------------------------------- /utils/params_bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package mqanttools 字节转化 16 | package mqanttools 17 | 18 | import ( 19 | "encoding/binary" 20 | "encoding/json" 21 | "math" 22 | ) 23 | 24 | // BoolToBytes bool->bytes 25 | func BoolToBytes(v bool) []byte { 26 | var buf = make([]byte, 1) 27 | if v { 28 | buf[0] = 1 29 | } else { 30 | buf[0] = 0 31 | } 32 | return buf 33 | } 34 | 35 | // BytesToBool bytes->bool 36 | func BytesToBool(buf []byte) bool { 37 | var data bool = buf[0] != 0 38 | return data 39 | } 40 | 41 | // Int32ToBytes Int32ToBytes 42 | func Int32ToBytes(i int32) []byte { 43 | var buf = make([]byte, 4) 44 | binary.BigEndian.PutUint32(buf, uint32(i)) 45 | return buf 46 | } 47 | 48 | // BytesToInt32 BytesToInt32 49 | func BytesToInt32(buf []byte) int32 { 50 | return int32(binary.BigEndian.Uint32(buf)) 51 | } 52 | 53 | // Int64ToBytes Int64ToBytes 54 | func Int64ToBytes(i int64) []byte { 55 | var buf = make([]byte, 8) 56 | binary.BigEndian.PutUint64(buf, uint64(i)) 57 | return buf 58 | } 59 | 60 | // BytesToInt64 BytesToInt64 61 | func BytesToInt64(buf []byte) int64 { 62 | return int64(binary.BigEndian.Uint64(buf)) 63 | } 64 | 65 | // Float32ToBytes Float32ToBytes 66 | func Float32ToBytes(float float32) []byte { 67 | bits := math.Float32bits(float) 68 | bytes := make([]byte, 4) 69 | binary.LittleEndian.PutUint32(bytes, bits) 70 | 71 | return bytes 72 | } 73 | 74 | // BytesToFloat32 BytesToFloat32 75 | func BytesToFloat32(bytes []byte) float32 { 76 | bits := binary.LittleEndian.Uint32(bytes) 77 | 78 | return math.Float32frombits(bits) 79 | } 80 | 81 | // Float64ToBytes Float64ToBytes 82 | func Float64ToBytes(float float64) []byte { 83 | bits := math.Float64bits(float) 84 | bytes := make([]byte, 8) 85 | binary.LittleEndian.PutUint64(bytes, bits) 86 | 87 | return bytes 88 | } 89 | 90 | // BytesToFloat64 BytesToFloat64 91 | func BytesToFloat64(bytes []byte) float64 { 92 | bits := binary.LittleEndian.Uint64(bytes) 93 | 94 | return math.Float64frombits(bits) 95 | } 96 | 97 | // MapToBytes MapToBytes 98 | func MapToBytes(jmap map[string]interface{}) ([]byte, error) { 99 | bytes, err := json.Marshal(jmap) 100 | return bytes, err 101 | } 102 | 103 | // BytesToMap BytesToMap 104 | func BytesToMap(bytes []byte) (map[string]interface{}, error) { 105 | v := make(map[string]interface{}) 106 | err := json.Unmarshal(bytes, &v) 107 | 108 | return v, err 109 | } 110 | 111 | // MapToBytesString MapToBytesString 112 | func MapToBytesString(jmap map[string]string) ([]byte, error) { 113 | bytes, err := json.Marshal(jmap) 114 | return bytes, err 115 | } 116 | 117 | // BytesToMapString BytesToMapString 118 | func BytesToMapString(bytes []byte) (map[string]string, error) { 119 | v := make(map[string]string) 120 | err := json.Unmarshal(bytes, &v) 121 | 122 | return v, err 123 | } 124 | -------------------------------------------------------------------------------- /utils/params_bytes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 loolgame Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package mqanttools 15 | 16 | import ( 17 | "testing" 18 | ) 19 | 20 | func assertEqual(t *testing.T, val interface{}, exp interface{}) { 21 | if val != exp { 22 | t.Errorf("Expected %v, got %v.", exp, val) 23 | } 24 | } 25 | func TestBoolToBytes(t *testing.T) { 26 | buf := BoolToBytes(true) 27 | v := BytesToBool(buf) 28 | assertEqual(t, v, true) 29 | } 30 | 31 | func TestInt32ToBytes(t *testing.T) { 32 | n := int32(64) 33 | buf := Int32ToBytes(n) 34 | v := BytesToInt32(buf) 35 | assertEqual(t, v, n) 36 | } 37 | 38 | func TestInt64ToBytes(t *testing.T) { 39 | n := int64(64) 40 | buf := Int64ToBytes(n) 41 | v := BytesToInt64(buf) 42 | assertEqual(t, v, n) 43 | } 44 | 45 | func TestFloat32ToByte(t *testing.T) { 46 | n := float32(64.043) 47 | buf := Float32ToBytes(n) 48 | v := BytesToFloat32(buf) 49 | assertEqual(t, v, n) 50 | } 51 | 52 | func TestFloat64ToByte(t *testing.T) { 53 | n := float64(64.043) 54 | buf := Float64ToBytes(n) 55 | v := BytesToFloat64(buf) 56 | assertEqual(t, v, n) 57 | } 58 | -------------------------------------------------------------------------------- /utils/queue.go: -------------------------------------------------------------------------------- 1 | // Package queue provides a fast, ring-buffer queue based on the version suggested by Dariusz Górecki. 2 | // Using this instead of other, simpler, queue implementations (slice+append or linked list) provides 3 | // substantial memory and time benefits, and fewer GC pauses. 4 | // The queue implemented here is as fast as it is for an additional reason: it is *not* thread-safe. 5 | 6 | package mqanttools 7 | 8 | // minQueueLen is smallest capacity that queue may have. 9 | // Must be power of 2 for bitwise modulus: x % n == x & (n - 1). 10 | const minQueueLen = 16 11 | 12 | // Queue represents a single instance of the queue data structure. 13 | type Queue struct { 14 | buf []interface{} 15 | head, tail, count int 16 | } 17 | 18 | // NewQueue New constructs and returns a new Queue. 19 | func NewQueue() *Queue { 20 | return &Queue{ 21 | buf: make([]interface{}, minQueueLen), 22 | } 23 | } 24 | 25 | // Length returns the number of elements currently stored in the queue. 26 | func (q *Queue) Length() int { 27 | return q.count 28 | } 29 | 30 | // resizes the queue to fit exactly twice its current contents 31 | // this can result in shrinking if the queue is less than half-full 32 | func (q *Queue) resize() { 33 | newBuf := make([]interface{}, q.count<<1) 34 | 35 | if q.tail > q.head { 36 | copy(newBuf, q.buf[q.head:q.tail]) 37 | } else { 38 | n := copy(newBuf, q.buf[q.head:]) 39 | copy(newBuf[n:], q.buf[:q.tail]) 40 | } 41 | 42 | q.head = 0 43 | q.tail = q.count 44 | q.buf = newBuf 45 | } 46 | 47 | // Add puts an element on the end of the queue. 48 | func (q *Queue) Add(elem interface{}) { 49 | if q.count == len(q.buf) { 50 | q.resize() 51 | } 52 | 53 | q.buf[q.tail] = elem 54 | // bitwise modulus 55 | q.tail = (q.tail + 1) & (len(q.buf) - 1) 56 | q.count++ 57 | } 58 | 59 | // Peek returns the element at the head of the queue. This call panics 60 | // if the queue is empty. 61 | func (q *Queue) Peek() interface{} { 62 | if q.count <= 0 { 63 | panic("queue: Peek() called on empty queue") 64 | } 65 | return q.buf[q.head] 66 | } 67 | 68 | // Get returns the element at index i in the queue. If the index is 69 | // invalid, the call will panic. This method accepts both positive and 70 | // negative index values. Index 0 refers to the first element, and 71 | // index -1 refers to the last. 72 | func (q *Queue) Get(i int) interface{} { 73 | // If indexing backwards, convert to positive index. 74 | if i < 0 { 75 | i += q.count 76 | } 77 | if i < 0 || i >= q.count { 78 | panic("queue: Get() called with index out of range") 79 | } 80 | // bitwise modulus 81 | return q.buf[(q.head+i)&(len(q.buf)-1)] 82 | } 83 | 84 | // Remove removes and returns the element from the front of the queue. If the 85 | // queue is empty, the call will panic. 86 | func (q *Queue) Remove() interface{} { 87 | if q.count <= 0 { 88 | panic("queue: Remove() called on empty queue") 89 | } 90 | ret := q.buf[q.head] 91 | q.buf[q.head] = nil 92 | // bitwise modulus 93 | q.head = (q.head + 1) & (len(q.buf) - 1) 94 | q.count-- 95 | // Resize down if buffer 1/4 full. 96 | if len(q.buf) > minQueueLen && (q.count<<2) == len(q.buf) { 97 | q.resize() 98 | } 99 | return ret 100 | } 101 | -------------------------------------------------------------------------------- /utils/randInt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package mqanttools 随机数生成 16 | package mqanttools 17 | 18 | import "math/rand" 19 | 20 | // RandInt64 生成一个min->max的随机数 21 | func RandInt64(min, max int64) int64 { 22 | if min >= max { 23 | return max 24 | } 25 | return rand.Int63n(max-min) + min 26 | } 27 | -------------------------------------------------------------------------------- /utils/safemap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mqanttools 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | // BeeMap is a map with lock 22 | type BeeMap struct { 23 | lock *sync.RWMutex 24 | bm map[interface{}]interface{} 25 | } 26 | 27 | // NewBeeMap return new safemap 28 | func NewBeeMap() *BeeMap { 29 | return &BeeMap{ 30 | lock: new(sync.RWMutex), 31 | bm: make(map[interface{}]interface{}), 32 | } 33 | } 34 | 35 | // Get from maps return the k's value 36 | func (m *BeeMap) Get(k interface{}) interface{} { 37 | m.lock.RLock() 38 | if val, ok := m.bm[k]; ok { 39 | m.lock.RUnlock() 40 | return val 41 | } 42 | m.lock.RUnlock() 43 | return nil 44 | } 45 | 46 | // Set Maps the given key and value. Returns false 47 | // if the key is already in the map and changes nothing. 48 | func (m *BeeMap) Set(k interface{}, v interface{}) bool { 49 | m.lock.Lock() 50 | if val, ok := m.bm[k]; !ok { 51 | m.bm[k] = v 52 | m.lock.Unlock() 53 | } else if val != v { 54 | m.bm[k] = v 55 | m.lock.Unlock() 56 | } else { 57 | m.lock.Unlock() 58 | return false 59 | } 60 | return true 61 | } 62 | 63 | // Check Returns true if k is exist in the map. 64 | func (m *BeeMap) Check(k interface{}) bool { 65 | m.lock.RLock() 66 | if _, ok := m.bm[k]; !ok { 67 | m.lock.RUnlock() 68 | return false 69 | } 70 | m.lock.RUnlock() 71 | return true 72 | } 73 | 74 | // Delete the given key and value. 75 | func (m *BeeMap) Delete(k interface{}) { 76 | m.lock.Lock() 77 | delete(m.bm, k) 78 | m.lock.Unlock() 79 | } 80 | 81 | // DeleteAll DeleteAll 82 | func (m *BeeMap) DeleteAll() { 83 | m.lock.Lock() 84 | for k := range m.bm { 85 | delete(m.bm, k) 86 | } 87 | 88 | m.lock.Unlock() 89 | } 90 | 91 | // Items returns all items in safemap. 92 | func (m *BeeMap) Items() map[interface{}]interface{} { 93 | m.lock.RLock() 94 | r := make(map[interface{}]interface{}) 95 | for k, v := range m.bm { 96 | r[k] = v 97 | } 98 | m.lock.RUnlock() 99 | return r 100 | } 101 | -------------------------------------------------------------------------------- /utils/safemap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mqanttools 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func Test_beemap(t *testing.T) { 22 | bm := NewBeeMap() 23 | if !bm.Set("astaxie", 1) { 24 | t.Error("set Error") 25 | } 26 | if !bm.Check("astaxie") { 27 | t.Error("check err") 28 | } 29 | 30 | if v := bm.Get("astaxie"); v.(int) != 1 { 31 | t.Error("get err") 32 | } 33 | 34 | bm.Delete("astaxie") 35 | if bm.Check("astaxie") { 36 | t.Error("delete err") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /utils/uuid/uuid.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 mqant Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package uuid 15 | 16 | import ( 17 | crand "crypto/rand" 18 | "encoding/hex" 19 | "errors" 20 | "fmt" 21 | mrand "math/rand" 22 | "regexp" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | // seeded indicates if math/rand has been seeded 28 | var seeded bool = false 29 | 30 | // uuidRegex matches the UUID string 31 | var uuidRegex *regexp.Regexp = regexp.MustCompile(`^\{?([a-fA-F0-9]{8})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{4})-?([a-fA-F0-9]{12})\}?$`) 32 | 33 | // UUID type. 34 | type UUID [16]byte 35 | 36 | // Hex returns a hex string representation of the UUID in xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format. 37 | func (this UUID) Hex() string { 38 | x := [16]byte(this) 39 | return fmt.Sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 40 | x[0], x[1], x[2], x[3], x[4], 41 | x[5], x[6], 42 | x[7], x[8], 43 | x[9], x[10], x[11], x[12], x[13], x[14], x[15]) 44 | 45 | } 46 | 47 | // Rand generates a new version 4 UUID. 48 | func Rand() UUID { 49 | var x [16]byte 50 | randBytes(x[:]) 51 | x[6] = (x[6] & 0x0F) | 0x40 52 | x[8] = (x[8] & 0x3F) | 0x80 53 | return x 54 | } 55 | 56 | // FromStr returns a UUID based on a string. 57 | // The string could be in the following format: 58 | // 59 | // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 60 | // 61 | // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 62 | // 63 | // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} 64 | // 65 | // If the string is not in one of these formats, it'll return an error. 66 | func FromStr(s string) (id UUID, err error) { 67 | if s == "" { 68 | err = errors.New("Empty string") 69 | return 70 | } 71 | 72 | parts := uuidRegex.FindStringSubmatch(s) 73 | if parts == nil { 74 | err = errors.New("Invalid string format") 75 | return 76 | } 77 | 78 | var array [16]byte 79 | slice, _ := hex.DecodeString(strings.Join(parts[1:], "")) 80 | copy(array[:], slice) 81 | id = array 82 | return 83 | } 84 | 85 | // MustFromStr behaves similarly to FromStr except that it'll panic instead of 86 | // returning an error. 87 | func MustFromStr(s string) UUID { 88 | id, err := FromStr(s) 89 | if err != nil { 90 | panic(err) 91 | } 92 | return id 93 | } 94 | 95 | // randBytes uses crypto random to get random numbers. If fails then it uses math random. 96 | func randBytes(x []byte) { 97 | 98 | length := len(x) 99 | n, err := crand.Read(x) 100 | 101 | if n != length || err != nil { 102 | if !seeded { 103 | mrand.Seed(time.Now().UnixNano()) 104 | } 105 | 106 | for length > 0 { 107 | length-- 108 | x[length] = byte(mrand.Int31n(256)) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package mqant 2 | 3 | const version = "1.5.3" 4 | --------------------------------------------------------------------------------