├── protocol ├── protobuf │ └── pkg │ │ ├── build.sh │ │ ├── protos.proto │ │ └── package.go ├── pomelo │ ├── pkg │ │ ├── package_test.go │ │ └── package.go │ └── message │ │ └── message_test.go └── protocol.go ├── network ├── agent.go ├── conn.go ├── tcp_conn.go ├── ws_conn.go ├── tcp_server.go ├── tcp_msg.go └── tcp_client.go ├── config ├── global.go ├── config_test.go └── env │ ├── env_test.go │ └── env.go ├── rpcx ├── _documents │ ├── rpcx_dev_qq.png │ ├── rpcx_dev_qq2.png │ └── rpcx_tech_support.png ├── mailbox │ ├── queue.go │ ├── messages.go │ ├── unbounded.go │ ├── dispatcher.go │ └── queue │ │ ├── mpsc │ │ └── mpsc.go │ │ └── goring │ │ ├── queue_test.go │ │ └── queue.go ├── _testutils │ ├── GoUnusedProtection__.go │ ├── thrift_arith_service.thrift │ ├── arith_service.proto │ └── thrift_arith_service-consts.go ├── codec │ └── testdata │ │ ├── GoUnusedProtection__.go │ │ ├── thrift_colorgroup.thrift │ │ ├── protobuf.proto │ │ ├── gen.sh │ │ └── thrift_colorgroup-consts.go ├── protocol │ ├── testdata │ │ ├── gen.sh │ │ └── benchmark.proto │ ├── pool.go │ ├── session.go │ ├── message_test.go │ └── compressor.go ├── client │ ├── connection_nonkcp.go │ ├── connection_nonquic.go │ ├── connection_kcp.go │ ├── connection_quic.go │ ├── geo_utils.go │ ├── smooth-weighted-round-robin.go │ ├── mode.go │ ├── peer2peer_discovery.go │ ├── circuit_breaker_test.go │ ├── failmode_enumer.go │ ├── hash_utils.go │ ├── selectmode_enumer.go │ ├── opencensus.go │ ├── opentracing.go │ ├── ping_utils.go │ ├── circuit_breaker.go │ ├── multiple_servers_discovery.go │ └── oneclient_pool.go ├── server │ ├── options.go │ ├── quic.go │ ├── message_envelop.go │ ├── option_test.go │ ├── kcp.go │ ├── listener_unix.go │ ├── service_test.go │ ├── pool_test.go │ ├── option.go │ ├── converter_test.go │ ├── pool.go │ ├── listener.go │ └── plugin_test.go ├── serverplugin │ ├── registry_test.go │ ├── whitelist.go │ ├── blacklist.go │ ├── rate_limiting.go │ ├── etcd_test.go │ ├── consul_test.go │ ├── zookeeper_test.go │ ├── tee.go │ ├── req_rate_limiting.go │ ├── trace.go │ ├── opencensus.go │ ├── opentracing.go │ └── alias.go ├── util │ ├── converter.go │ ├── net_test.go │ ├── buffer_pool_test.go │ ├── compress.go │ ├── compress_test.go │ └── buffer_pool.go ├── LICENSE ├── TODO.md ├── reflection │ └── server_reflection_test.go ├── errors │ ├── error.go │ └── error_test.go ├── share │ ├── share_test.go │ ├── context_test.go │ └── context.go ├── log │ ├── dummy_logger.go │ ├── logger.go │ └── default_logger.go ├── Makefile └── tool │ └── xgen │ ├── README.md │ └── parser │ └── parser.go ├── filter └── filter.go ├── component ├── component.go ├── timers │ ├── options.go │ └── timers.go ├── connector │ ├── agent.go │ ├── channelRemote.go │ ├── sessionMap.go │ ├── timer.go │ ├── pomelo │ │ └── options.go │ ├── protobuf │ │ ├── options.go │ │ └── agentHandler.go │ └── sessionRemote.go ├── remote │ └── options.go ├── proxy │ └── options.go └── web │ ├── options.go │ └── webServer.go ├── doc.go ├── service ├── idService │ └── idService.go ├── codecService │ ├── json │ │ └── json.go │ ├── codec.go │ └── protobuf │ │ └── protobuf.go ├── sessionService │ └── sessionService.go ├── channelService │ ├── channelService.go │ └── channel.go ├── rpcClientService │ └── options.go └── msgService │ └── msgService.go ├── .gitignore ├── app ├── factory.go ├── app.go ├── serverDefault.go └── server.go ├── utils ├── array │ ├── array_test.go │ └── array.go ├── timer │ ├── example_test.go │ └── timer.go ├── file_test.go ├── safemap.go ├── safemap_test.go └── file.go ├── log ├── beego │ ├── alils │ │ ├── config.go │ │ ├── log_config.go │ │ ├── request.go │ │ └── machine_group.go │ ├── conn_test.go │ ├── smtp_test.go │ ├── logger_test.go │ ├── README.md │ ├── slack.go │ ├── console_test.go │ ├── es │ │ └── es.go │ ├── jianliao.go │ ├── multifile_test.go │ └── accesslog.go ├── log_test.go └── log.go ├── rpc ├── rpc.go └── session.go ├── LICENSE ├── Chinese.md └── README.md /protocol/protobuf/pkg/build.sh: -------------------------------------------------------------------------------- 1 | protoc --go_out=. protos.proto -------------------------------------------------------------------------------- /network/agent.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | type Agent interface { 4 | Run() 5 | OnClose() 6 | } 7 | -------------------------------------------------------------------------------- /config/global.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var ( 4 | NodeId int64 = 1 5 | LenStackBuf int = 1024 6 | ) 7 | -------------------------------------------------------------------------------- /rpcx/_documents/rpcx_dev_qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kudoochui/kudos/HEAD/rpcx/_documents/rpcx_dev_qq.png -------------------------------------------------------------------------------- /rpcx/_documents/rpcx_dev_qq2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kudoochui/kudos/HEAD/rpcx/_documents/rpcx_dev_qq2.png -------------------------------------------------------------------------------- /rpcx/mailbox/queue.go: -------------------------------------------------------------------------------- 1 | package mailbox 2 | 3 | type queue interface { 4 | Push(interface{}) 5 | Pop() interface{} 6 | } -------------------------------------------------------------------------------- /rpcx/_documents/rpcx_tech_support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kudoochui/kudos/HEAD/rpcx/_documents/rpcx_tech_support.png -------------------------------------------------------------------------------- /filter/filter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | type Filter interface { 4 | Before(route string, msgReq interface{}) 5 | After(route string, msgResp interface{}) 6 | } 7 | -------------------------------------------------------------------------------- /rpcx/_testutils/GoUnusedProtection__.go: -------------------------------------------------------------------------------- 1 | // Code generated by Thrift Compiler (0.14.0). DO NOT EDIT. 2 | 3 | package testutils 4 | 5 | var GoUnusedProtection__ int 6 | -------------------------------------------------------------------------------- /rpcx/_testutils/thrift_arith_service.thrift: -------------------------------------------------------------------------------- 1 | struct ThriftArgs 2 | { 3 | 1: i32 a, 4 | 2: i32 b, 5 | } 6 | 7 | 8 | struct ThriftReply 9 | { 10 | 1: i32 c, 11 | } -------------------------------------------------------------------------------- /rpcx/codec/testdata/GoUnusedProtection__.go: -------------------------------------------------------------------------------- 1 | // Code generated by Thrift Compiler (0.14.0). DO NOT EDIT. 2 | 3 | package testdata 4 | 5 | var GoUnusedProtection__ int; 6 | 7 | -------------------------------------------------------------------------------- /rpcx/codec/testdata/thrift_colorgroup.thrift: -------------------------------------------------------------------------------- 1 | namespace go testdata 2 | 3 | struct ThriftColorGroup { 4 | 1: i32 id = 0, 5 | 2: string name, 6 | 3: list colors, 7 | } 8 | -------------------------------------------------------------------------------- /rpcx/codec/testdata/protobuf.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package testdata; 4 | 5 | 6 | message ProtoColorGroup { 7 | int32 id = 1; 8 | string name = 2; 9 | repeated string colors = 3; 10 | } -------------------------------------------------------------------------------- /rpcx/protocol/testdata/gen.sh: -------------------------------------------------------------------------------- 1 | # curl -O https://raw.githubusercontent.com/rpcxio/rpcx-benchmark/master/proto/benchmark.proto 2 | 3 | # generate .go files from IDL 4 | protoc --go_out=./ ./benchmark.proto 5 | 6 | -------------------------------------------------------------------------------- /component/component.go: -------------------------------------------------------------------------------- 1 | package component 2 | 3 | type Component interface { 4 | OnInit(ServerImpl) 5 | OnDestroy() 6 | OnRun(closeSig chan bool) 7 | } 8 | 9 | type ServerImpl interface { 10 | GetServerId() string 11 | GetComponent(string) Component 12 | } -------------------------------------------------------------------------------- /rpcx/client/connection_nonkcp.go: -------------------------------------------------------------------------------- 1 | // +build !kcp 2 | 3 | package client 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func newDirectKCPConn(c *Client, network, address string) (net.Conn, error) { 11 | return nil, errors.New("kcp unsupported") 12 | } 13 | -------------------------------------------------------------------------------- /rpcx/client/connection_nonquic.go: -------------------------------------------------------------------------------- 1 | // +build !quic 2 | 3 | package client 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | ) 9 | 10 | func newDirectQuicConn(c *Client, network, address string) (net.Conn, error) { 11 | return nil, errors.New("quic unsupported") 12 | } 13 | -------------------------------------------------------------------------------- /rpcx/codec/testdata/gen.sh: -------------------------------------------------------------------------------- 1 | # generate .go files from IDL 2 | protoc --go_out=./ ./protobuf.proto 3 | 4 | thrift -r -out ../ --gen go ./thrift_colorgroup.thrift 5 | 6 | # # run benchmarks 7 | # go test -bench=. -run=none 8 | 9 | # # clean files 10 | # rm -rf ./testdata/*.go -------------------------------------------------------------------------------- /rpcx/server/options.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "time" 4 | 5 | // WithTCPKeepAlivePeriod sets tcp keepalive period. 6 | func WithTCPKeepAlivePeriod(period time.Duration) OptionFn { 7 | return func(s *Server) { 8 | s.options["TCPKeepAlivePeriod"] = period 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Kudos is a simple, high-performance, easy to expand and easy to deploy distributed game service framework 3 | based on microservice architecture, It is based on RPC of rpcx, supports pomelo communication protocol and 4 | can be easily applied to game development. 5 | */ 6 | package kudos 7 | -------------------------------------------------------------------------------- /protocol/pomelo/pkg/package_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | func TestPackage(t *testing.T){ 6 | _type := TYPE_DATA 7 | buffer := []byte{1,2,3,4,5,6,7,8,9} 8 | t.Log(Encode(_type, buffer)) 9 | 10 | buffer1 := []byte{4,0,0,9,1,2,3,4,5,6,7,8,9} 11 | t.Log(Decode(buffer1)) 12 | } -------------------------------------------------------------------------------- /network/conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | ) 7 | 8 | type Conn interface { 9 | Read(buffer []byte) (int, error) 10 | ReadMsg(buf *bytes.Buffer) error 11 | WriteMessage(buf []byte) error 12 | LocalAddr() net.Addr 13 | RemoteAddr() net.Addr 14 | Close() 15 | } 16 | -------------------------------------------------------------------------------- /service/idService/idService.go: -------------------------------------------------------------------------------- 1 | package idService 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/config" 5 | "sync" 6 | ) 7 | 8 | var node *Node 9 | var once sync.Once 10 | 11 | func GenerateID() ID { 12 | once.Do(func() { 13 | node, _ = NewNode(config.NodeId) 14 | }) 15 | return node.Generate() 16 | } -------------------------------------------------------------------------------- /rpcx/client/connection_kcp.go: -------------------------------------------------------------------------------- 1 | // +build kcp 2 | 3 | package client 4 | 5 | import ( 6 | "net" 7 | 8 | kcp "github.com/xtaci/kcp-go" 9 | ) 10 | 11 | func newDirectKCPConn(c *Client, network, address string) (net.Conn, error) { 12 | return kcp.DialWithOptions(address, c.option.Block.(kcp.BlockCrypt), 10, 3) 13 | } 14 | -------------------------------------------------------------------------------- /rpcx/_testutils/arith_service.proto: -------------------------------------------------------------------------------- 1 | // protoc --gogofaster_out=. arith_service.proto 2 | // mv arith_service.pb.go arith_service_test.go 3 | syntax = "proto3"; 4 | 5 | package client; 6 | 7 | message ProtoArgs { 8 | int32 A = 1; 9 | int32 B = 2; 10 | } 11 | 12 | message ProtoReply { 13 | int32 C = 1; 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /rpcx/serverplugin/registry_test.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import "context" 4 | 5 | type Args struct { 6 | A int 7 | B int 8 | } 9 | 10 | type Reply struct { 11 | C int 12 | } 13 | 14 | type Arith int 15 | 16 | func (t *Arith) Mul(ctx context.Context, args *Args, reply *Reply) error { 17 | reply.C = args.A * args.B 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /app/factory.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | type CreateServerFunc func(serverId string) Server 4 | 5 | var createMap = map[string]CreateServerFunc{} 6 | 7 | func RegisterCreateServerFunc(serverName string, createFunc CreateServerFunc) { 8 | createMap[serverName] = createFunc 9 | } 10 | 11 | func GetCreateServerFunc(serverName string) CreateServerFunc { 12 | return createMap[serverName] 13 | } 14 | -------------------------------------------------------------------------------- /utils/array/array_test.go: -------------------------------------------------------------------------------- 1 | package array 2 | 3 | import ( 4 | "gotest.tools/assert" 5 | "testing" 6 | ) 7 | 8 | func TestArray1(t *testing.T) { 9 | a := []int64{1,2,3,4,5} 10 | a = PullInt64(a, 5) 11 | t.Log(a) 12 | assert.DeepEqual(t, a, []int64{1,2,3,4}) 13 | } 14 | 15 | func TestArray2(t *testing.T) { 16 | a := []int64{1,2,3,4,5} 17 | a = append(a[:2],a[3:]...) 18 | t.Log(a) 19 | } -------------------------------------------------------------------------------- /rpcx/mailbox/messages.go: -------------------------------------------------------------------------------- 1 | package mailbox 2 | 3 | // ResumeMailbox is message sent by the actor system to resume mailbox processing. 4 | // 5 | // This will not be forwarded to the Receive method 6 | type ResumeMailbox struct{} 7 | 8 | // SuspendMailbox is message sent by the actor system to suspend mailbox processing. 9 | // 10 | // This will not be forwarded to the Receive method 11 | type SuspendMailbox struct{} 12 | 13 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/log" 5 | "os" 6 | "os/signal" 7 | ) 8 | 9 | func Run(servers ...Server) { 10 | 11 | for i := 0; i < len(servers); i++ { 12 | Register(servers[i]) 13 | } 14 | Init() 15 | 16 | // close 17 | c := make(chan os.Signal, 1) 18 | signal.Notify(c, os.Interrupt, os.Kill) 19 | sig := <-c 20 | log.Warning("Server closing down (signal: %v)", sig) 21 | Destroy() 22 | } -------------------------------------------------------------------------------- /log/beego/alils/config.go: -------------------------------------------------------------------------------- 1 | package alils 2 | 3 | const ( 4 | version = "0.5.0" // SDK version 5 | signatureMethod = "hmac-sha1" // Signature method 6 | 7 | // OffsetNewest stands for the log head offset, i.e. the offset that will be 8 | // assigned to the next message that will be produced to the shard. 9 | OffsetNewest = "end" 10 | // OffsetOldest stands for the oldest offset available on the logstore for a 11 | // shard. 12 | OffsetOldest = "begin" 13 | ) 14 | -------------------------------------------------------------------------------- /protocol/protobuf/pkg/protos.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | package pkg; 3 | 4 | enum EMsgType { 5 | INVALID_TYPE = 0; 6 | TYPE_HEARTBEAT = 70000; 7 | TYPE_COMMON_RESULT = 70001; 8 | TYPE_KICK_BY_SERVER = 70002; 9 | } 10 | 11 | enum EErrorCode { 12 | INVALID_ERROR_CODE = 0; 13 | SUCCESS = 1; 14 | ERROR_ROUTE_ID = 2; 15 | ERROR_KICK_BY_SERVER = 3; 16 | } 17 | 18 | message ReqHeartbeat { 19 | 20 | } 21 | 22 | message RespResult { 23 | int32 Code = 1; 24 | string Msg = 2; 25 | } -------------------------------------------------------------------------------- /component/timers/options.go: -------------------------------------------------------------------------------- 1 | package timers 2 | 3 | type Option func(*Options) 4 | 5 | type Options struct { 6 | TimerDispatcherLen int 7 | } 8 | 9 | func newOptions(opts ...Option) *Options { 10 | opt := &Options{ 11 | TimerDispatcherLen: 20, 12 | } 13 | 14 | for _,o := range opts { 15 | o(opt) 16 | } 17 | return opt 18 | } 19 | 20 | // Address of rpc service 21 | func TimerDispatcherLen(length int) Option { 22 | return func(options *Options) { 23 | options.TimerDispatcherLen = length 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rpcx/_testutils/thrift_arith_service-consts.go: -------------------------------------------------------------------------------- 1 | // Code generated by Thrift Compiler (0.14.0). DO NOT EDIT. 2 | 3 | package testutils 4 | 5 | import ( 6 | "bytes" 7 | "context" 8 | "fmt" 9 | "github.com/apache/thrift/lib/go/thrift" 10 | "time" 11 | ) 12 | 13 | // (needed to ensure safety because of naive import list construction.) 14 | var _ = thrift.ZERO 15 | var _ = fmt.Printf 16 | var _ = context.Background 17 | var _ = time.Now 18 | var _ = bytes.Equal 19 | 20 | func init() { 21 | } 22 | -------------------------------------------------------------------------------- /rpcx/util/converter.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | func SliceByteToString(b []byte) string { 8 | return *(*string)(unsafe.Pointer(&b)) 9 | } 10 | 11 | func StringToSliceByte(s string) []byte { 12 | x := (*[2]uintptr)(unsafe.Pointer(&s)) 13 | h := [3]uintptr{x[0], x[1], x[1]} 14 | return *(*[]byte)(unsafe.Pointer(&h)) 15 | } 16 | 17 | func CopyMeta(src, dst map[string]string) { 18 | if dst == nil { 19 | return 20 | } 21 | for k, v := range src { 22 | dst[k] = v 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rpcx/util/net_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestExternalIPV4(t *testing.T) { 6 | ip, err := ExternalIPV4() 7 | if err != nil { 8 | t.Fatal(err) 9 | } 10 | 11 | if ip == "" { 12 | t.Fatal("expect an IP but got empty") 13 | } 14 | t.Log(ip) 15 | } 16 | 17 | func TestExternalIPV6(t *testing.T) { 18 | ip, err := ExternalIPV6() 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | if ip == "" { 24 | t.Fatal("expect an IP but got empty") 25 | } 26 | t.Log(ip) 27 | } 28 | -------------------------------------------------------------------------------- /rpcx/codec/testdata/thrift_colorgroup-consts.go: -------------------------------------------------------------------------------- 1 | // Code generated by Thrift Compiler (0.14.0). DO NOT EDIT. 2 | 3 | package testdata 4 | 5 | import( 6 | "bytes" 7 | "context" 8 | "fmt" 9 | "time" 10 | "github.com/apache/thrift/lib/go/thrift" 11 | ) 12 | 13 | // (needed to ensure safety because of naive import list construction.) 14 | var _ = thrift.ZERO 15 | var _ = fmt.Printf 16 | var _ = context.Background 17 | var _ = time.Now 18 | var _ = bytes.Equal 19 | 20 | 21 | func init() { 22 | } 23 | 24 | -------------------------------------------------------------------------------- /component/connector/agent.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/rpc" 5 | "net" 6 | ) 7 | 8 | type Agent interface { 9 | Write(data *[]byte) 10 | LocalAddr() net.Addr 11 | RemoteAddr() net.Addr 12 | Close() 13 | UserData() interface{} 14 | SetUserData(data interface{}) 15 | GetSession() *rpc.Session 16 | PushMessage(uint32, []byte) 17 | KickMessage(string) 18 | } 19 | 20 | type Connector interface { 21 | GetSessionMap() *SessionMap 22 | } 23 | 24 | type Connection interface { 25 | OnDisconnect(session *rpc.Session) 26 | } -------------------------------------------------------------------------------- /utils/array/array.go: -------------------------------------------------------------------------------- 1 | package array 2 | 3 | // It returns the index of the first element 4 | func IndexOfInt64(items []int64, element int64) (int, bool) { 5 | var index = -1 6 | var ok bool 7 | 8 | for i, el := range items { 9 | if el == element { 10 | index = i 11 | ok = true 12 | break 13 | } 14 | } 15 | 16 | return index, ok 17 | } 18 | 19 | // Removes value from items. 20 | func PullInt64(items []int64, value int64) []int64 { 21 | if i,ok := IndexOfInt64(items, value); ok { 22 | return append(items[:i], items[i+1:]...) 23 | } 24 | return items 25 | } -------------------------------------------------------------------------------- /rpcx/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Chao yuepan 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. -------------------------------------------------------------------------------- /rpcx/server/quic.go: -------------------------------------------------------------------------------- 1 | // +build quic 2 | 3 | package server 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | 9 | "github.com/smallnest/quick" 10 | ) 11 | 12 | func init() { 13 | makeListeners["quic"] = quicMakeListener 14 | } 15 | 16 | func quicMakeListener(s *Server, address string) (ln net.Listener, err error) { 17 | if s.tlsConfig == nil { 18 | return nil, errors.New("TLSConfig must be configured in server.Options") 19 | } 20 | 21 | if len(s.tlsConfig.NextProtos) == 0 { 22 | s.tlsConfig.NextProtos = []string{"rpcx"} 23 | } 24 | 25 | return quick.Listen("udp", address, s.tlsConfig, nil) 26 | } 27 | -------------------------------------------------------------------------------- /rpcx/TODO.md: -------------------------------------------------------------------------------- 1 | # rpcx 2 | 3 | Milestone 6.0 4 | 5 | ### Todo 6 | 7 | - [ ] improve message.Encode to avoid copy header and payload [#399] 8 | - [ ] Users can customize the failure of calls [#396] 9 | - [ ] improve rpcx-gateway 6.0 feature [#390] 10 | - [ ] improve rust lib [#389] 11 | - [ ] add rpc request replay [#417] 12 | - [ ] support sub/pub [#415] 13 | - [ ] avoid too many connects after disconnected [#411] 14 | - [ ] re-register services if registery center has recovered [#420] 15 | 16 | ### In Progress 17 | 18 | 19 | ### Done ✓ 20 | 21 | - [x] can't get latest nodes for nacos [#422] 22 | - [x] add k8s deployment yaml [#355] -------------------------------------------------------------------------------- /rpcx/reflection/server_reflection_test.go: -------------------------------------------------------------------------------- 1 | package reflection 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/kr/pretty" 8 | 9 | testutils "github.com/kudoochui/kudos/rpcx/_testutils" 10 | ) 11 | 12 | type PBArith int 13 | 14 | func (t *PBArith) Mul(ctx context.Context, args *testutils.ProtoArgs, reply *testutils.ProtoReply) error { 15 | reply.C = args.A * args.B 16 | return nil 17 | } 18 | 19 | func TestReflection_Register(t *testing.T) { 20 | r := New() 21 | arith := PBArith(0) 22 | err := r.Register("Arith", &arith, "") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | pretty.Println(r.Services["Arith"].String()) 28 | } 29 | -------------------------------------------------------------------------------- /rpcx/client/connection_quic.go: -------------------------------------------------------------------------------- 1 | // +build quic 2 | 3 | package client 4 | 5 | import ( 6 | "crypto/tls" 7 | "net" 8 | 9 | "github.com/lucas-clemente/quic-go" 10 | 11 | "github.com/smallnest/quick" 12 | ) 13 | 14 | func newDirectQuicConn(c *Client, network, address string) (net.Conn, error) { 15 | tlsConf := c.option.TLSConfig 16 | if tlsConf == nil { 17 | tlsConf = &tls.Config{InsecureSkipVerify: true} 18 | } 19 | 20 | if len(tlsConf.NextProtos) == 0 { 21 | tlsConf.NextProtos = []string{"rpcx"} 22 | } 23 | 24 | quicConfig := &quic.Config{ 25 | KeepAlive: c.option.Heartbeat, 26 | } 27 | 28 | return quick.Dial(address, tlsConf, quicConfig) 29 | } 30 | -------------------------------------------------------------------------------- /rpcx/client/geo_utils.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | //https://gist.github.com/cdipaolo/d3f8db3848278b49db68 8 | func getDistanceFrom(lat1, lon1, lat2, lon2 float64) float64 { 9 | var la1, lo1, la2, lo2, r float64 10 | la1 = lat1 * math.Pi / 180 11 | lo1 = lon1 * math.Pi / 180 12 | la2 = lat2 * math.Pi / 180 13 | lo2 = lon2 * math.Pi / 180 14 | 15 | r = 6378100 // Earth radius in METERS 16 | 17 | // calculate 18 | h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1) 19 | 20 | return 2 * r * math.Asin(math.Sqrt(h)) 21 | } 22 | 23 | func hsin(theta float64) float64 { 24 | return math.Pow(math.Sin(theta/2), 2) 25 | } 26 | -------------------------------------------------------------------------------- /rpcx/errors/error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // MultiError holds multiple errors 9 | type MultiError struct { 10 | Errors []error 11 | mu sync.Mutex 12 | } 13 | 14 | // Error returns the message of the actual error 15 | func (e *MultiError) Error() string { 16 | return fmt.Sprintf("%v", e.Errors) 17 | } 18 | 19 | func (e *MultiError) Append(err error) { 20 | e.mu.Lock() 21 | defer e.mu.Unlock() 22 | e.Errors = append(e.Errors, err) 23 | } 24 | 25 | // NewMultiError creates and returns an Error with error splice 26 | func NewMultiError(errors []error) *MultiError { 27 | return &MultiError{Errors: errors} 28 | } 29 | -------------------------------------------------------------------------------- /rpcx/share/share_test.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kudoochui/kudos/rpcx/protocol" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type MockCodec struct {} 11 | 12 | func (codec MockCodec) Encode(i interface{}) ([]byte, error) { 13 | return nil, nil 14 | } 15 | 16 | func (codec MockCodec) Decode(data []byte, i interface{}) error { 17 | return nil 18 | } 19 | 20 | func TestShare(t *testing.T) { 21 | registeredCodecNum := len(Codecs) 22 | codec := MockCodec{} 23 | 24 | mockCodecType := 127 25 | RegisterCodec(protocol.SerializeType(mockCodecType), codec) 26 | assert.Equal(t, registeredCodecNum + 1, len(Codecs)) 27 | } 28 | -------------------------------------------------------------------------------- /rpcx/server/message_envelop.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/rpcx/protocol" 5 | "github.com/kudoochui/kudos/rpcx/share" 6 | ) 7 | 8 | type MessageEnvelope struct { 9 | Context *share.Context 10 | Request *protocol.Message 11 | } 12 | 13 | func newMessageEnvelope(ctx *share.Context, req *protocol.Message) *MessageEnvelope { 14 | return &MessageEnvelope{ 15 | Context: ctx, 16 | Request: req, 17 | } 18 | } 19 | 20 | type TimeEnvelope struct { 21 | Session *ServerSession 22 | Cb TimeTickCallback 23 | } 24 | 25 | func newTimeEnvelope(session *ServerSession, cb TimeTickCallback) *TimeEnvelope { 26 | return &TimeEnvelope{Session:session, Cb:cb} 27 | } -------------------------------------------------------------------------------- /log/log_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | logs "github.com/kudoochui/kudos/log/beego" 5 | "testing" 6 | ) 7 | 8 | func TestLog(t *testing.T) { 9 | LogBeego().SetLogger("console") 10 | LogBeego().SetLogger(logs.AdapterFile,`{"filename":"test.log"}`) 11 | LogBeego().EnableFuncCallDepth(true) 12 | LogBeego().SetLogFuncCallDepth(3) 13 | //LogBeego().Async() 14 | 15 | Debug("my book is bought in the year of ", 2016) 16 | Info("this %s cat is %v years old", "yellow", 3) 17 | Notice("ABCD") 18 | Warning("json is a type of kv like", map[string]int{"key": 2016}) 19 | Error(1024, "is a very", "good game") 20 | Critical("oh,crash") 21 | Alert(1E3) 22 | Emergency(232.32342233) 23 | } 24 | -------------------------------------------------------------------------------- /rpcx/protocol/pool.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import "sync" 4 | 5 | var msgPool = sync.Pool{ 6 | New: func() interface{} { 7 | header := Header([12]byte{}) 8 | header[0] = magicNumber 9 | 10 | return &Message{ 11 | Header: &header, 12 | } 13 | }, 14 | } 15 | 16 | // GetPooledMsg gets a pooled message. 17 | func GetPooledMsg() *Message { 18 | return msgPool.Get().(*Message) 19 | } 20 | 21 | // FreeMsg puts a msg into the pool. 22 | func FreeMsg(msg *Message) { 23 | if msg != nil { 24 | msg.Reset() 25 | msgPool.Put(msg) 26 | } 27 | } 28 | 29 | var poolUint32Data = sync.Pool{ 30 | New: func() interface{} { 31 | data := make([]byte, 4) 32 | return &data 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /rpcx/server/option_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestOption(t *testing.T) { 12 | server := NewServer() 13 | 14 | cert, _ := tls.LoadX509KeyPair("server.pem", "server.key") 15 | config := &tls.Config{Certificates: []tls.Certificate{cert}} 16 | 17 | o := WithTLSConfig(config) 18 | o(server) 19 | assert.Equal(t, config, server.tlsConfig) 20 | 21 | o = WithReadTimeout(time.Second) 22 | o(server) 23 | assert.Equal(t, time.Second, server.readTimeout) 24 | 25 | o = WithWriteTimeout(time.Second) 26 | o(server) 27 | assert.Equal(t, time.Second, server.writeTimeout) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /service/codecService/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "github.com/json-iterator/go" 5 | ) 6 | 7 | type JsonCodec struct { 8 | 9 | } 10 | 11 | func NewCodec() *JsonCodec { 12 | p := new(JsonCodec) 13 | return p 14 | } 15 | 16 | // goroutine safe 17 | func (p *JsonCodec) Unmarshal(obj interface{}, data []byte) error { 18 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 19 | err := json.Unmarshal(data, obj) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | return nil 25 | } 26 | 27 | // goroutine safe 28 | func (p *JsonCodec) Marshal(msg interface{}) ([]byte, error) { 29 | var json = jsoniter.ConfigCompatibleWithStandardLibrary 30 | data, err := json.Marshal(msg) 31 | return data, err 32 | } 33 | -------------------------------------------------------------------------------- /rpcx/server/kcp.go: -------------------------------------------------------------------------------- 1 | // +build kcp 2 | 3 | package server 4 | 5 | import ( 6 | "errors" 7 | "net" 8 | 9 | kcp "github.com/xtaci/kcp-go" 10 | ) 11 | 12 | func init() { 13 | makeListeners["kcp"] = kcpMakeListener 14 | } 15 | 16 | func kcpMakeListener(s *Server, address string) (ln net.Listener, err error) { 17 | if s.options == nil || s.options["BlockCrypt"] == nil { 18 | return nil, errors.New("KCP BlockCrypt must be configured in server.Options") 19 | } 20 | 21 | return kcp.ListenWithOptions(address, s.options["BlockCrypt"].(kcp.BlockCrypt), 10, 3) 22 | } 23 | 24 | // WithBlockCrypt sets kcp.BlockCrypt. 25 | func WithBlockCrypt(bc kcp.BlockCrypt) OptionFn { 26 | return func(s *Server) { 27 | s.options["BlockCrypt"] = bc 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rpcx/mailbox/unbounded.go: -------------------------------------------------------------------------------- 1 | package mailbox 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/rpcx/mailbox/queue/goring" 5 | "github.com/kudoochui/kudos/rpcx/mailbox/queue/mpsc" 6 | ) 7 | 8 | type unboundedMailboxQueue struct { 9 | userMailbox *goring.Queue 10 | } 11 | 12 | func (q *unboundedMailboxQueue) Push(m interface{}) { 13 | q.userMailbox.Push(m) 14 | } 15 | 16 | func (q *unboundedMailboxQueue) Pop() interface{} { 17 | m, o := q.userMailbox.Pop() 18 | if o { 19 | return m 20 | } 21 | return nil 22 | } 23 | 24 | // Unbounded creates an unbounded mailbox 25 | func Unbounded() Mailbox { 26 | q := &unboundedMailboxQueue{ 27 | userMailbox: goring.New(10), 28 | } 29 | return &defaultMailbox{ 30 | systemMailbox: mpsc.New(), 31 | userMailbox: q, 32 | } 33 | } -------------------------------------------------------------------------------- /service/sessionService/sessionService.go: -------------------------------------------------------------------------------- 1 | package sessionService 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/rpc" 5 | "github.com/kudoochui/kudos/service/rpcClientService" 6 | "sync" 7 | ) 8 | 9 | var service *SessionService 10 | var once sync.Once 11 | 12 | func GetSessionService() *SessionService { 13 | once.Do(func() { 14 | service = &SessionService{ 15 | } 16 | }) 17 | return service 18 | } 19 | 20 | type SessionService struct { 21 | 22 | } 23 | 24 | func (s *SessionService) KickBySid(nodeId string, sid int64, reason string) { 25 | args := &rpc.Args{ 26 | Session: rpc.Session{SessionId:sid}, 27 | MsgReq: reason, 28 | } 29 | reply := &rpc.Reply{} 30 | rpcClientService.GetRpcClientService().Call(nodeId, "SessionRemote","KickBySid", args, reply) 31 | } -------------------------------------------------------------------------------- /rpcx/mailbox/dispatcher.go: -------------------------------------------------------------------------------- 1 | package mailbox 2 | 3 | type Dispatcher interface { 4 | Schedule(fn func()) 5 | Throughput() int 6 | } 7 | 8 | type goroutineDispatcher int 9 | 10 | func (goroutineDispatcher) Schedule(fn func()) { 11 | go fn() 12 | } 13 | 14 | func (d goroutineDispatcher) Throughput() int { 15 | return int(d) 16 | } 17 | 18 | func NewDefaultDispatcher(throughput int) Dispatcher { 19 | return goroutineDispatcher(throughput) 20 | } 21 | 22 | type synchronizedDispatcher int 23 | 24 | func (synchronizedDispatcher) Schedule(fn func()) { 25 | fn() 26 | } 27 | 28 | func (d synchronizedDispatcher) Throughput() int { 29 | return int(d) 30 | } 31 | 32 | func NewSynchronizedDispatcher(throughput int) Dispatcher { 33 | return synchronizedDispatcher(throughput) 34 | } 35 | -------------------------------------------------------------------------------- /rpcx/server/listener_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package server 4 | 5 | import ( 6 | "net" 7 | 8 | reuseport "github.com/kavu/go_reuseport" 9 | ) 10 | 11 | func init() { 12 | makeListeners["reuseport"] = reuseportMakeListener 13 | makeListeners["unix"] = unixMakeListener 14 | } 15 | 16 | func reuseportMakeListener(s *Server, address string) (ln net.Listener, err error) { 17 | var network string 18 | if validIP4(address) { 19 | network = "tcp4" 20 | } else { 21 | network = "tcp6" 22 | } 23 | 24 | return reuseport.NewReusablePortListener(network, address) 25 | } 26 | 27 | func unixMakeListener(s *Server, address string) (ln net.Listener, err error) { 28 | laddr, err := net.ResolveUnixAddr("unix", address) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return net.ListenUnix("unix", laddr) 33 | } 34 | -------------------------------------------------------------------------------- /rpcx/serverplugin/whitelist.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import "net" 4 | 5 | // WhitelistPlugin is a plugin that control only ip addresses in whitelist can access services. 6 | type WhitelistPlugin struct { 7 | Whitelist map[string]bool 8 | WhitelistMask []*net.IPNet // net.ParseCIDR("172.17.0.0/16") to get *net.IPNet 9 | } 10 | 11 | // HandleConnAccept check ip. 12 | func (plugin *WhitelistPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) { 13 | ip, _, err := net.SplitHostPort(conn.RemoteAddr().String()) 14 | if err != nil { 15 | return conn, false 16 | } 17 | if plugin.Whitelist[ip] { 18 | return conn, true 19 | } 20 | 21 | remoteIP := net.ParseIP(ip) 22 | for _, mask := range plugin.WhitelistMask { 23 | if mask.Contains(remoteIP) { 24 | return conn, true 25 | } 26 | } 27 | 28 | return conn, false 29 | } 30 | -------------------------------------------------------------------------------- /rpcx/serverplugin/blacklist.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import "net" 4 | 5 | // BlacklistPlugin is a plugin that control only ip addresses in blacklist can **NOT** access services. 6 | type BlacklistPlugin struct { 7 | Blacklist map[string]bool 8 | BlacklistMask []*net.IPNet // net.ParseCIDR("172.17.0.0/16") to get *net.IPNet 9 | } 10 | 11 | // HandleConnAccept check ip. 12 | func (plugin *BlacklistPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) { 13 | ip, _, err := net.SplitHostPort(conn.RemoteAddr().String()) 14 | if err != nil { 15 | return conn, true 16 | } 17 | if plugin.Blacklist[ip] { 18 | return conn, false 19 | } 20 | 21 | remoteIP := net.ParseIP(ip) 22 | for _, mask := range plugin.BlacklistMask { 23 | if mask.Contains(remoteIP) { 24 | return conn, false 25 | } 26 | } 27 | 28 | return conn, true 29 | } 30 | -------------------------------------------------------------------------------- /service/channelService/channelService.go: -------------------------------------------------------------------------------- 1 | package channelService 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var _channelService *ChannelService 8 | var once sync.Once 9 | 10 | type ChannelService struct { 11 | channels sync.Map 12 | } 13 | 14 | func GetChannelService() *ChannelService { 15 | once.Do(func() { 16 | _channelService = &ChannelService{ 17 | 18 | } 19 | }) 20 | 21 | return _channelService 22 | } 23 | 24 | func (c *ChannelService) CreateChannel(name string) *Channel { 25 | channel := NewChannel(name) 26 | c.channels.Store(name, channel) 27 | return channel 28 | } 29 | 30 | func (c *ChannelService) DestroyChannel(name string) { 31 | c.channels.Delete(name) 32 | } 33 | 34 | func (c *ChannelService) GetChannel(name string) *Channel { 35 | channel, ok := c.channels.Load(name) 36 | if ok { 37 | return channel.(*Channel) 38 | } 39 | return nil 40 | } -------------------------------------------------------------------------------- /rpcx/server/service_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_isExported(t *testing.T) { 12 | 13 | assert.Equal(t, true, isExported("IsExported")) 14 | 15 | assert.Equal(t, false, isExported("isExported")) 16 | 17 | assert.Equal(t, false, isExported("_isExported")) 18 | 19 | assert.Equal(t, false, isExported("123_isExported")) 20 | 21 | assert.Equal(t, false, isExported("[]_isExported")) 22 | 23 | assert.Equal(t, false, isExported("&_isExported")) 24 | } 25 | 26 | 27 | func Mul(ctx context.Context, args *Args, reply *Reply) error { 28 | reply.C = args.A * args.B 29 | return nil 30 | } 31 | 32 | func Test_isExportedOrBuiltinType(t *testing.T) { 33 | typeOfMul := reflect.TypeOf(Mul) 34 | assert.Equal(t, true, isExportedOrBuiltinType(typeOfMul)) 35 | } -------------------------------------------------------------------------------- /rpcx/server/pool_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | 11 | func TestPool(t *testing.T) { 12 | // not pool anything yet 13 | UsePool = false 14 | // THE Magic Number 15 | magicNumber := 42 16 | 17 | intType := reflect.TypeOf(magicNumber) 18 | // init int pool 19 | argsReplyPools.Init(intType) 20 | // insert a integer 21 | argsReplyPools.Put(intType, magicNumber) 22 | // if UsePool == false, argsReplyPools.Get() will call reflect.New() which 23 | // returns a Value representing a pointer to a new zero value 24 | assert.Equal(t, 0, *argsReplyPools.Get(intType).(*int)) 25 | 26 | // start pooling 27 | UsePool = true 28 | 29 | argsReplyPools.Put(intType, magicNumber) 30 | // Get() will remove element from pool 31 | assert.Equal(t, magicNumber, argsReplyPools.Get(intType).(int)) 32 | } -------------------------------------------------------------------------------- /rpcx/serverplugin/rate_limiting.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/juju/ratelimit" 8 | ) 9 | 10 | // RateLimitingPlugin can limit connecting per unit time 11 | type RateLimitingPlugin struct { 12 | FillInterval time.Duration 13 | Capacity int64 14 | bucket *ratelimit.Bucket 15 | } 16 | 17 | // NewRateLimitingPlugin creates a new RateLimitingPlugin 18 | func NewRateLimitingPlugin(fillInterval time.Duration, capacity int64) *RateLimitingPlugin { 19 | tb := ratelimit.NewBucket(fillInterval, capacity) 20 | 21 | return &RateLimitingPlugin{ 22 | FillInterval: fillInterval, 23 | Capacity: capacity, 24 | bucket: tb} 25 | } 26 | 27 | // HandleConnAccept can limit connecting rate 28 | func (plugin *RateLimitingPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) { 29 | return conn, plugin.bucket.TakeAvailable(1) > 0 30 | } 31 | -------------------------------------------------------------------------------- /rpcx/serverplugin/etcd_test.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | metrics "github.com/rcrowley/go-metrics" 8 | "github.com/kudoochui/kudos/rpcx/server" 9 | ) 10 | 11 | func TestEtcdRegistry(t *testing.T) { 12 | s := server.NewServer() 13 | 14 | r := &EtcdRegisterPlugin{ 15 | ServiceAddress: "tcp@127.0.0.1:8972", 16 | EtcdServers: []string{"127.0.0.1:2379"}, 17 | BasePath: "/rpcx_test", 18 | Metrics: metrics.NewRegistry(), 19 | UpdateInterval: time.Minute, 20 | } 21 | err := r.Start() 22 | if err != nil { 23 | return 24 | } 25 | s.Plugins.Add(r) 26 | 27 | s.RegisterName("Arith", new(Arith), "") 28 | go s.Serve("tcp", "127.0.0.1:8972") 29 | defer s.Close() 30 | 31 | if len(r.Services) != 1 { 32 | t.Fatal("failed to register services in etcd") 33 | } 34 | 35 | if err := r.Stop(); err != nil { 36 | t.Fatal(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rpc/rpc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/service/codecService" 5 | "github.com/mitchellh/mapstructure" 6 | ) 7 | 8 | type Args struct { 9 | MsgId int 10 | MsgReq interface{} 11 | } 12 | 13 | func (a *Args) GetObject(t interface{}) error { 14 | switch a.MsgReq.(type) { 15 | case []byte: 16 | _codec := codecService.GetCodecService() 17 | return _codec.Unmarshal(t, a.MsgReq.([]byte)) 18 | default: 19 | return mapstructure.Decode(a.MsgReq, t) 20 | } 21 | } 22 | 23 | type Reply struct { 24 | Code int 25 | ErrMsg string 26 | MsgResp interface{} 27 | } 28 | 29 | // Group message request 30 | type ArgsGroup struct { 31 | Route string 32 | Payload []byte 33 | } 34 | 35 | // Group message response 36 | type ReplyGroup struct { 37 | 38 | } 39 | 40 | // Route msg to the specified node 41 | type CustomerRoute func(session *Session, servicePath, serviceName string) (string, error) -------------------------------------------------------------------------------- /rpcx/serverplugin/consul_test.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | metrics "github.com/rcrowley/go-metrics" 8 | "github.com/kudoochui/kudos/rpcx/server" 9 | ) 10 | 11 | func TestConsulRegistry(t *testing.T) { 12 | s := server.NewServer() 13 | 14 | r := &ConsulRegisterPlugin{ 15 | ServiceAddress: "tcp@127.0.0.1:8972", 16 | ConsulServers: []string{"127.0.0.1:8500"}, 17 | BasePath: "/rpcx_test", 18 | Metrics: metrics.NewRegistry(), 19 | UpdateInterval: time.Minute, 20 | } 21 | err := r.Start() 22 | if err != nil { 23 | return 24 | } 25 | s.Plugins.Add(r) 26 | 27 | s.RegisterName("Arith", new(Arith), "") 28 | go s.Serve("tcp", "127.0.0.1:8972") 29 | defer s.Close() 30 | 31 | if len(r.Services) != 1 { 32 | t.Fatal("failed to register services in consul") 33 | } 34 | 35 | if err := r.Stop(); err != nil { 36 | t.Fatal(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /component/connector/channelRemote.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "context" 5 | "github.com/kudoochui/kudos/rpc" 6 | msgService "github.com/kudoochui/kudos/service/msgService" 7 | ) 8 | 9 | type ChannelRemote struct { 10 | connector Connector 11 | } 12 | 13 | func NewChannelRemote(conn Connector) *ChannelRemote { 14 | return &ChannelRemote{connector:conn} 15 | } 16 | 17 | // Push message to client by uids. 18 | func (c *ChannelRemote) PushMessage(ctx context.Context, session *rpc.Session, args *rpc.ArgsGroup, reply *rpc.ReplyGroup) error { 19 | //log.Debug(">>>> %s push: %s, %+v, %s", c.connector.opts.WSAddr, args.Route, args.Sids, string(args.Payload)) 20 | sessioinId := session.GetSessionId() 21 | if a, err := c.connector.GetSessionMap().GetAgent(sessioinId); err == nil { 22 | routeId := msgService.GetMsgService().GetRouteId(args.Route) 23 | a.PushMessage(routeId, args.Payload) 24 | } 25 | return nil 26 | } -------------------------------------------------------------------------------- /rpcx/log/dummy_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type dummyLogger struct{} 4 | 5 | func (l *dummyLogger) Debug(v ...interface{}) { 6 | } 7 | 8 | func (l *dummyLogger) Debugf(format string, v ...interface{}) { 9 | } 10 | 11 | func (l *dummyLogger) Info(v ...interface{}) { 12 | } 13 | 14 | func (l *dummyLogger) Infof(format string, v ...interface{}) { 15 | } 16 | 17 | func (l *dummyLogger) Warn(v ...interface{}) { 18 | } 19 | 20 | func (l *dummyLogger) Warnf(format string, v ...interface{}) { 21 | } 22 | 23 | func (l *dummyLogger) Error(v ...interface{}) { 24 | } 25 | 26 | func (l *dummyLogger) Errorf(format string, v ...interface{}) { 27 | } 28 | 29 | func (l *dummyLogger) Fatal(v ...interface{}) { 30 | } 31 | 32 | func (l *dummyLogger) Fatalf(format string, v ...interface{}) { 33 | } 34 | 35 | func (l *dummyLogger) Panic(v ...interface{}) { 36 | } 37 | 38 | func (l *dummyLogger) Panicf(format string, v ...interface{}) { 39 | } 40 | -------------------------------------------------------------------------------- /rpcx/serverplugin/zookeeper_test.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | metrics "github.com/rcrowley/go-metrics" 8 | "github.com/kudoochui/kudos/rpcx/server" 9 | ) 10 | 11 | func TestZookeeperRegistry(t *testing.T) { 12 | s := server.NewServer() 13 | 14 | r := &ZooKeeperRegisterPlugin{ 15 | ServiceAddress: "tcp@127.0.0.1:8972", 16 | ZooKeeperServers: []string{"127.0.0.1:2181"}, 17 | BasePath: "/rpcx_test", 18 | Metrics: metrics.NewRegistry(), 19 | UpdateInterval: time.Minute, 20 | } 21 | err := r.Start() 22 | if err != nil { 23 | return 24 | } 25 | s.Plugins.Add(r) 26 | 27 | s.RegisterName("Arith", new(Arith), "") 28 | go s.Serve("tcp", "127.0.0.1:8972") 29 | defer s.Close() 30 | 31 | if len(r.Services) != 1 { 32 | t.Fatal("failed to register services in zookeeper") 33 | } 34 | 35 | if err := r.Stop(); err != nil { 36 | t.Fatal(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "github.com/kudoochui/kudos/rpcx/util" 6 | "sync" 7 | ) 8 | 9 | var ( 10 | bufferPool = util.NewLimitedPool(512, 4096) 11 | sPool = &sync.Pool{ 12 | New: func() interface{} { 13 | return &bytes.Buffer{} 14 | }, 15 | } 16 | ) 17 | 18 | func GetPoolMsg() *bytes.Buffer { 19 | return sPool.Get().(*bytes.Buffer) 20 | } 21 | 22 | func FreePoolMsg(buf *bytes.Buffer) { 23 | sPool.Put(buf) 24 | } 25 | 26 | func GetPoolBuffer(size int) *[]byte { 27 | return bufferPool.Get(size) 28 | } 29 | 30 | func FreePoolBuffer(buf *[]byte) { 31 | bufferPool.Put(buf) 32 | } 33 | 34 | var poolUint32Data = sync.Pool{ 35 | New: func() interface{} { 36 | data := make([]byte, 4) 37 | return &data 38 | }, 39 | } 40 | 41 | func GetUint32PoolData() *[]byte { 42 | return poolUint32Data.Get().(*[]byte) 43 | } 44 | 45 | func PutUint32PoolData(buffer *[]byte) { 46 | poolUint32Data.Put(buffer) 47 | } -------------------------------------------------------------------------------- /rpcx/server/option.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "time" 6 | ) 7 | 8 | // OptionFn configures options of server. 9 | type OptionFn func(*Server) 10 | 11 | // // WithOptions sets multiple options. 12 | // func WithOptions(ops map[string]interface{}) OptionFn { 13 | // return func(s *Server) { 14 | // for k, v := range ops { 15 | // s.options[k] = v 16 | // } 17 | // } 18 | // } 19 | 20 | // WithTLSConfig sets tls.Config. 21 | func WithTLSConfig(cfg *tls.Config) OptionFn { 22 | return func(s *Server) { 23 | s.tlsConfig = cfg 24 | } 25 | } 26 | 27 | // WithReadTimeout sets readTimeout. 28 | func WithReadTimeout(readTimeout time.Duration) OptionFn { 29 | return func(s *Server) { 30 | s.readTimeout = readTimeout 31 | } 32 | } 33 | 34 | // WithWriteTimeout sets writeTimeout. 35 | func WithWriteTimeout(writeTimeout time.Duration) OptionFn { 36 | return func(s *Server) { 37 | s.writeTimeout = writeTimeout 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /component/connector/sessionMap.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // goroutine safe 10 | type SessionMap struct { 11 | sessions sync.Map 12 | counter int32 13 | } 14 | 15 | func (s *SessionMap) AddSession(a Agent) { 16 | s.sessions.Store(a.GetSession().GetSessionId(), a) 17 | atomic.AddInt32(&s.counter, 1) 18 | } 19 | 20 | func (s *SessionMap) DelSession(a Agent) { 21 | s.sessions.Delete(a.GetSession().GetSessionId()) 22 | atomic.AddInt32(&s.counter, -1) 23 | } 24 | 25 | func (s *SessionMap) GetAgent(sessionId int64) (Agent, error) { 26 | a, ok := s.sessions.Load(sessionId) 27 | if !ok || a == nil { 28 | return nil, errors.New("No Sesssion found") 29 | } 30 | return a.(Agent), nil 31 | } 32 | 33 | func (s *SessionMap) Range(f func(k,v interface{})bool) { 34 | s.sessions.Range(f) 35 | } 36 | 37 | func (s *SessionMap) GetSessionCount() int32 { 38 | return atomic.LoadInt32(&s.counter) 39 | } 40 | 41 | -------------------------------------------------------------------------------- /service/codecService/codec.go: -------------------------------------------------------------------------------- 1 | package codecService 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/service/codecService/json" 5 | "github.com/kudoochui/kudos/service/codecService/protobuf" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | TYPE_CODEC_JSON = "json" 11 | TYPE_CODEC_PROTOBUF = "protobuf" 12 | ) 13 | 14 | var codecType = TYPE_CODEC_JSON 15 | var codec Codec 16 | var once sync.Once 17 | 18 | type Codec interface { 19 | // must goroutine safe 20 | Unmarshal(obj interface{}, data []byte) error 21 | // must goroutine safe 22 | Marshal(msg interface{}) ([]byte, error) 23 | } 24 | 25 | // Change codec type in the main 26 | func SetCodecType(t string) { 27 | codecType = t 28 | } 29 | 30 | func GetCodecType() string { 31 | return codecType 32 | } 33 | 34 | func GetCodecService() Codec { 35 | once.Do(func() { 36 | switch codecType { 37 | case TYPE_CODEC_JSON: 38 | codec = json.NewCodec() 39 | case TYPE_CODEC_PROTOBUF: 40 | codec = protobuf.NewCodec() 41 | } 42 | }) 43 | return codec 44 | } -------------------------------------------------------------------------------- /service/codecService/protobuf/protobuf.go: -------------------------------------------------------------------------------- 1 | package protobuf 2 | 3 | import ( 4 | "github.com/golang/protobuf/proto" 5 | ) 6 | 7 | // ------------------------- 8 | // | id | protobuf message | 9 | // ------------------------- 10 | type ProtobufCodec struct { 11 | littleEndian bool 12 | } 13 | 14 | func NewCodec() *ProtobufCodec { 15 | p := new(ProtobufCodec) 16 | p.littleEndian = false 17 | return p 18 | } 19 | 20 | // It's dangerous to call the method on routing or marshaling (unmarshaling) 21 | func (p *ProtobufCodec) SetByteOrder(littleEndian bool) { 22 | p.littleEndian = littleEndian 23 | } 24 | 25 | // goroutine safe 26 | func (p *ProtobufCodec) Unmarshal(obj interface{}, data []byte) error { 27 | err := proto.UnmarshalMerge(data, obj.(proto.Message)) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | 35 | // goroutine safe 36 | func (p *ProtobufCodec) Marshal(msg interface{}) ([]byte, error) { 37 | data, err := proto.Marshal(msg.(proto.Message)) 38 | return data, err 39 | } 40 | -------------------------------------------------------------------------------- /rpcx/protocol/session.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type ISession interface { 4 | GetNodeId() string 5 | GetSessionId() int64 6 | GetUserId() int64 7 | Get(key string) string 8 | Set(key, value string) 9 | GetCache(key string) string 10 | SetCache(key, value string) 11 | RemoveCache(key string) 12 | } 13 | 14 | type DummySession struct { 15 | 16 | } 17 | 18 | func NewDummySession() *DummySession { 19 | return &DummySession{} 20 | } 21 | 22 | func (d *DummySession) GetNodeId() string { 23 | return "" 24 | } 25 | 26 | func (d *DummySession) GetSessionId() int64 { 27 | return 0 28 | } 29 | 30 | func (d *DummySession) GetUserId() int64 { 31 | return 0 32 | } 33 | 34 | func (d *DummySession) Get(key string) string { 35 | return "" 36 | } 37 | 38 | func (d *DummySession) Set(key, value string) { 39 | 40 | } 41 | 42 | func (d *DummySession) GetCache(key string) string { 43 | return "" 44 | } 45 | 46 | func (d *DummySession) SetCache(key, value string) { 47 | 48 | } 49 | 50 | func (d *DummySession) RemoveCache(key string) { 51 | 52 | } -------------------------------------------------------------------------------- /component/remote/options.go: -------------------------------------------------------------------------------- 1 | package remote 2 | 3 | 4 | type Option func(*Options) 5 | 6 | type Options struct { 7 | Addr string 8 | RegistryType string 9 | RegistryAddr string 10 | BasePath string 11 | } 12 | 13 | func newOptions(opts ...Option) *Options { 14 | opt := &Options{ 15 | 16 | } 17 | 18 | for _,o := range opts { 19 | o(opt) 20 | } 21 | return opt 22 | } 23 | 24 | // Address of rpc service 25 | func Addr(s string) Option { 26 | return func(options *Options) { 27 | options.Addr = s 28 | } 29 | } 30 | 31 | // Register service type. option is consul, etcd, etcdv3, zookeeper. 32 | func RegistryType(s string) Option { 33 | return func(options *Options) { 34 | options.RegistryType = s 35 | } 36 | } 37 | 38 | // Address of register service 39 | func RegistryAddr(s string) Option { 40 | return func(options *Options) { 41 | options.RegistryAddr = s 42 | } 43 | } 44 | 45 | // Base path of rpc service 46 | func BasePath(s string) Option { 47 | return func(options *Options) { 48 | options.BasePath = s 49 | } 50 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/serverDefault.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/kudoochui/kudos/component" 4 | 5 | type ServerDefault struct { 6 | ServerId string 7 | //component 8 | Components map[string]component.Component 9 | } 10 | 11 | func NewServerDefault(serverId string) *ServerDefault { 12 | return &ServerDefault{ 13 | ServerId: serverId, 14 | Components: map[string]component.Component{}, 15 | } 16 | } 17 | 18 | func (s *ServerDefault) GetServerId() string { 19 | return s.ServerId 20 | } 21 | 22 | func (s *ServerDefault) GetComponent(name string) component.Component { 23 | return s.Components[name] 24 | } 25 | 26 | // Initialize components 27 | func (s *ServerDefault) OnInit() { 28 | for _,com := range s.Components { 29 | com.OnInit(s) 30 | } 31 | } 32 | 33 | // Destroy components 34 | func (s *ServerDefault) OnDestroy() { 35 | for _,com := range s.Components { 36 | com.OnDestroy() 37 | } 38 | } 39 | 40 | // Run components 41 | func (s *ServerDefault) OnRun(closeSig chan bool) { 42 | for _,com := range s.Components { 43 | com.OnRun(closeSig) 44 | } 45 | } -------------------------------------------------------------------------------- /rpcx/serverplugin/tee.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "io" 5 | "net" 6 | ) 7 | 8 | // TeeConnPlugin is a plugin that copy requests from clients and send to a io.Writer. 9 | type TeeConnPlugin struct { 10 | w io.Writer 11 | } 12 | 13 | func NewTeeConnPlugin(w io.Writer) *TeeConnPlugin { 14 | return &TeeConnPlugin{w: w} 15 | } 16 | 17 | // Update can start a stream copy by setting a non-nil w. 18 | // If you set a nil w, it doesn't copy stream. 19 | func (plugin *TeeConnPlugin) Update(w io.Writer) { 20 | plugin.w = w 21 | } 22 | 23 | // HandleConnAccept check ip. 24 | func (plugin *TeeConnPlugin) HandleConnAccept(conn net.Conn) (net.Conn, bool) { 25 | tc := &teeConn{conn, plugin.w} 26 | return tc, true 27 | } 28 | 29 | type teeConn struct { 30 | net.Conn 31 | w io.Writer 32 | } 33 | 34 | func (t *teeConn) Read(p []byte) (n int, err error) { 35 | n, err = t.Conn.Read(p) 36 | if n > 0 && t.w != nil { 37 | t.w.Write(p[:n]) 38 | // if _, err := t.w.Write(p[:n]); err != nil { 39 | // return n, err //discard error 40 | // } 41 | } 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /network/tcp_conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type ConnSet map[net.Conn]struct{} 11 | 12 | type TCPConn struct { 13 | sync.Mutex 14 | conn net.Conn 15 | } 16 | 17 | func newTCPConn(conn net.Conn) *TCPConn { 18 | tcpConn := new(TCPConn) 19 | tcpConn.conn = conn 20 | 21 | return tcpConn 22 | } 23 | 24 | func (tcpConn *TCPConn) Close() { 25 | tcpConn.Lock() 26 | defer tcpConn.Unlock() 27 | tcpConn.conn.(*net.TCPConn).SetLinger(0) 28 | tcpConn.conn.Close() 29 | } 30 | 31 | func (tcpConn *TCPConn) LocalAddr() net.Addr { 32 | return tcpConn.conn.LocalAddr() 33 | } 34 | 35 | func (tcpConn *TCPConn) RemoteAddr() net.Addr { 36 | return tcpConn.conn.RemoteAddr() 37 | } 38 | 39 | // Read buffer length data 40 | func (tcpConn *TCPConn) Read(buffer []byte) (int, error) { 41 | return io.ReadFull(tcpConn.conn, buffer) 42 | } 43 | 44 | func (tcpConn *TCPConn) ReadMsg(buf *bytes.Buffer) error { 45 | return nil 46 | } 47 | 48 | func (tcpConn *TCPConn) WriteMessage(buf []byte) error { 49 | _, err := tcpConn.conn.Write(buf) 50 | return err 51 | } -------------------------------------------------------------------------------- /rpcx/client/smooth-weighted-round-robin.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Weighted is a wrapped server with weight 4 | type Weighted struct { 5 | Server string 6 | Weight int 7 | CurrentWeight int 8 | EffectiveWeight int 9 | } 10 | 11 | // func (w *Weighted) fail() { 12 | // w.EffectiveWeight -= w.Weight 13 | // if w.EffectiveWeight < 0 { 14 | // w.EffectiveWeight = 0 15 | // } 16 | // } 17 | 18 | //https://github.com/phusion/nginx/commit/27e94984486058d73157038f7950a0a36ecc6e35 19 | func nextWeighted(servers []*Weighted) (best *Weighted) { 20 | total := 0 21 | 22 | for i := 0; i < len(servers); i++ { 23 | w := servers[i] 24 | 25 | if w == nil { 26 | continue 27 | } 28 | //if w is down, continue 29 | 30 | w.CurrentWeight += w.EffectiveWeight 31 | total += w.EffectiveWeight 32 | if w.EffectiveWeight < w.Weight { 33 | w.EffectiveWeight++ 34 | } 35 | 36 | if best == nil || w.CurrentWeight > best.CurrentWeight { 37 | best = w 38 | } 39 | 40 | } 41 | 42 | if best == nil { 43 | return nil 44 | } 45 | 46 | best.CurrentWeight -= total 47 | return best 48 | } 49 | -------------------------------------------------------------------------------- /rpcx/errors/error_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewMultiError(t *testing.T) { 12 | var errorSet []error 13 | errorSet = append(errorSet, errors.New("invalid")) 14 | errorSet = append(errorSet, errors.New("fatal")) 15 | 16 | multiError := NewMultiError(errorSet) 17 | assert.Equal(t, fmt.Sprintf("%v", errorSet), multiError.Error(), "Test NewMultiError()") 18 | } 19 | 20 | func TestMultiError_Append(t *testing.T) { 21 | multiErrors := MultiError{} 22 | multiErrors.Errors = append(multiErrors.Errors, errors.New("invalid")) 23 | multiErrors.Errors = append(multiErrors.Errors, errors.New("fatal")) 24 | 25 | assert.Equal(t, 2, len(multiErrors.Errors), "Test Append()") 26 | } 27 | 28 | func TestMultiError_Error(t *testing.T) { 29 | multiErrors := MultiError{} 30 | multiErrors.Errors = append(multiErrors.Errors, errors.New("invalid")) 31 | multiErrors.Errors = append(multiErrors.Errors, errors.New("fatal")) 32 | 33 | assert.Equal(t, "[invalid fatal]",multiErrors.Error(), "Test Error()") 34 | } -------------------------------------------------------------------------------- /rpcx/Makefile: -------------------------------------------------------------------------------- 1 | WORKDIR=`pwd` 2 | 3 | default: build 4 | 5 | vet: 6 | go vet ./... 7 | 8 | tools: 9 | go get github.com/golangci/golangci-lint/cmd/golangci-lint 10 | go get github.com/golang/lint/golint 11 | go get github.com/axw/gocov/gocov 12 | go get github.com/matm/gocov-html 13 | 14 | golangci-lint: 15 | golangci-lint run -D errcheck --build-tags 'quic kcp' 16 | 17 | lint: 18 | golint ./... 19 | 20 | doc: 21 | godoc -http=:6060 22 | 23 | deps: 24 | go list -f '{{ join .Deps "\n"}}' ./... |grep "/" | grep -v "github.com/smallnest/rpcx"| grep "\." | sort |uniq 25 | 26 | fmt: 27 | go fmt ./... 28 | 29 | build: 30 | go build ./... 31 | 32 | build-all: 33 | go build -tags "kcp quic" ./... 34 | 35 | test: 36 | go test -race -tags "kcp quic" ./... 37 | 38 | cover: 39 | gocov test -tags "kcp quic" ./... | gocov-html > cover.html 40 | open cover.html 41 | 42 | check-libs: 43 | GIT_TERMINAL_PROMPT=1 GO111MODULE=on go list -m -u all | column -t 44 | 45 | update-libs: 46 | GIT_TERMINAL_PROMPT=1 GO111MODULE=on go get -u -v ./... 47 | 48 | mod-tidy: 49 | GIT_TERMINAL_PROMPT=1 GO111MODULE=on go mod tidy 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 kuduo 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. 22 | -------------------------------------------------------------------------------- /rpcx/client/mode.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | //FailMode decides how clients action when clients fail to invoke services 4 | type FailMode int 5 | 6 | const ( 7 | //Failover selects another server automaticaly 8 | Failover FailMode = iota 9 | //Failfast returns error immediately 10 | Failfast 11 | //Failtry use current client again 12 | Failtry 13 | //Failbackup select another server if the first server doesn't respon in specified time and use the fast response. 14 | Failbackup 15 | ) 16 | 17 | // SelectMode defines the algorithm of selecting a services from candidates. 18 | type SelectMode int 19 | 20 | const ( 21 | //RandomSelect is selecting randomly 22 | RandomSelect SelectMode = iota 23 | //RoundRobin is selecting by round robin 24 | RoundRobin 25 | //WeightedRoundRobin is selecting by weighted round robin 26 | WeightedRoundRobin 27 | //WeightedICMP is selecting by weighted Ping time 28 | WeightedICMP 29 | //ConsistentHash is selecting by hashing 30 | ConsistentHash 31 | //Closest is selecting the closest server 32 | Closest 33 | 34 | // SelectByUser is selecting by implementation of users 35 | SelectByUser = 1000 36 | ) 37 | -------------------------------------------------------------------------------- /component/proxy/options.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | 4 | type Option func(*Options) 5 | 6 | type Options struct { 7 | RegistryType string 8 | RegistryAddr string 9 | BasePath string 10 | SelectMode string 11 | } 12 | 13 | func newOptions(opts ...Option) *Options { 14 | opt := &Options{ 15 | } 16 | 17 | for _,o := range opts { 18 | o(opt) 19 | } 20 | return opt 21 | } 22 | 23 | // Register service type. option is consul, etcd, etcdv3, zookeeper. 24 | func RegistryType(s string) Option { 25 | return func(options *Options) { 26 | options.RegistryType = s 27 | } 28 | } 29 | 30 | // Address of register service 31 | func RegistryAddr(s string) Option { 32 | return func(options *Options) { 33 | options.RegistryAddr = s 34 | } 35 | } 36 | 37 | // Base path of service 38 | func BasePath(s string) Option { 39 | return func(options *Options) { 40 | options.BasePath = s 41 | } 42 | } 43 | 44 | // Select mode of service. options is "RandomSelect","RoundRobin","WeightedRoundRobin", 45 | // "WeightedICMP","ConsistentHash","Closest". 46 | func SelectMode(s string) Option { 47 | return func(options *Options) { 48 | options.SelectMode = s 49 | } 50 | } -------------------------------------------------------------------------------- /service/rpcClientService/options.go: -------------------------------------------------------------------------------- 1 | package rpcClientService 2 | 3 | 4 | type Option func(*Options) 5 | 6 | type Options struct { 7 | RegistryType string 8 | RegistryAddr string 9 | BasePath string 10 | SelectMode string 11 | } 12 | 13 | func newOptions(opts ...Option) *Options { 14 | opt := &Options{ 15 | } 16 | 17 | for _,o := range opts { 18 | o(opt) 19 | } 20 | return opt 21 | } 22 | 23 | // Register service type. option is consul, etcd, etcdv3, zookeeper. 24 | func RegistryType(s string) Option { 25 | return func(options *Options) { 26 | options.RegistryType = s 27 | } 28 | } 29 | 30 | // Address of register service 31 | func RegistryAddr(s string) Option { 32 | return func(options *Options) { 33 | options.RegistryAddr = s 34 | } 35 | } 36 | 37 | // Base path of service 38 | func BasePath(s string) Option { 39 | return func(options *Options) { 40 | options.BasePath = s 41 | } 42 | } 43 | 44 | // Select mode of service. options is "RandomSelect","RoundRobin","WeightedRoundRobin", 45 | // "WeightedICMP","ConsistentHash","Closest". 46 | func SelectMode(s string) Option { 47 | return func(options *Options) { 48 | options.SelectMode = s 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /utils/timer/example_test.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func ExampleTimer() { 9 | d := NewDispatcher(10) 10 | 11 | // timer 1 12 | d.AfterFunc(1, func() { 13 | fmt.Println("My name is Leaf") 14 | }) 15 | 16 | // timer 2 17 | t := d.AfterFunc(1, func() { 18 | fmt.Println("will not print") 19 | }) 20 | t.Stop() 21 | 22 | // dispatch 23 | (<-d.ChanTimer).Cb() 24 | 25 | // Output: 26 | // My name is Leaf 27 | } 28 | 29 | func ExampleCronExpr() { 30 | cronExpr, err := NewCronExpr("0 * * * *") 31 | if err != nil { 32 | return 33 | } 34 | 35 | fmt.Println(cronExpr.Next(time.Date( 36 | 2000, 1, 1, 37 | 20, 10, 5, 38 | 0, time.UTC, 39 | ))) 40 | 41 | // Output: 42 | // 2000-01-01 21:00:00 +0000 UTC 43 | } 44 | 45 | func ExampleCron() { 46 | d := NewDispatcher(10) 47 | 48 | // cron expr 49 | cronExpr, err := NewCronExpr("* * * * * *") 50 | if err != nil { 51 | return 52 | } 53 | 54 | // cron 55 | var c *Cron 56 | c = d.CronFunc(cronExpr, func() { 57 | fmt.Println("My name is Leaf") 58 | c.Stop() 59 | }) 60 | 61 | // dispatch 62 | (<-d.ChanTimer).Cb() 63 | 64 | // Output: 65 | // My name is Leaf 66 | } 67 | -------------------------------------------------------------------------------- /Chinese.md: -------------------------------------------------------------------------------- 1 | # Kudos 2 | Kudos是一款基于微服务架构的简洁,高性能,易扩展,易部署的分布式游戏服务框架。基于rpcx的rpc,支持pomelo通信协议,轻松应用于游戏开发。 3 | 4 | 5 | ## 特点 6 | - **简单**:容易上手,游戏开发需要基本组件和服务都已集成,直接调用。对于熟悉pomelo的特别友好。 7 | - **组件化**:功能分为一个个组件,按需要加载。 8 | - **分布式**:可以分成多个节点分布式部署,也可以打包一起作为一个进程部署。 9 | - **微服务架构,支持服务发现**:consul,etcd,zookeeper等主流注册中心。 10 | - **基于rpcx的rpc**:rpcx是一款高性能的rpc框架。其性能远远高于 Dubbo、Motan、Thrift等框架,是gRPC性能的两倍。支持服务治理。更多功能请参考:[http://rpcx.io](http://rpcx.io/) 11 | - **跨语言**:除go外,还可以访问其它语言实现的节点服务。得益于rpcx。 12 | - **支持pomelo通信协议**:该协议广泛用于各种游戏开发中,支持多端,多种语言版本。 13 | - **易部署**:各服务器独立,无依赖,可以单独启动。 14 | 15 | ## 安装 16 | 17 | `go get -u -v github.com/kudoochui/kudos` 18 | 19 | ## 开发脚手架(示例) 20 | [kudosServer](https://github.com/kudoochui/kudosServer) 21 | 22 | ## 游戏架构参考 23 | [游戏微服务架构设计:MMORPG](https://www.toutiao.com/i6798800455955644935/) 24 | 25 | [游戏微服务架构设计:挂机类游戏](https://www.toutiao.com/i6798814918574342660/) 26 | 27 | [游戏微服务架构设计:棋牌游戏](https://www.toutiao.com/i6798815085935460876/) 28 | 29 | [游戏微服务架构设计:io游戏](https://www.toutiao.com/i6798815271386612231/) 30 | 31 | ## Roadmap 32 | - 添加更多连接器 33 | - Actor支持 34 | 35 | ## 交流 36 | [wiki](https://github.com/kudoochui/kudos/wiki) 37 | 38 | QQ交流群:77584553 39 | 40 | 关注头条号:丁玲隆咚呛 41 | 分享更多内容 42 | 43 | ## 证书 44 | MIT License 45 | -------------------------------------------------------------------------------- /rpcx/client/peer2peer_discovery.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // Peer2PeerDiscovery is a peer-to-peer service discovery. 4 | // It always returns the static server. 5 | type Peer2PeerDiscovery struct { 6 | server string 7 | metadata string 8 | } 9 | 10 | // NewPeer2PeerDiscovery returns a new Peer2PeerDiscovery. 11 | func NewPeer2PeerDiscovery(server, metadata string) (ServiceDiscovery, error) { 12 | return &Peer2PeerDiscovery{server: server, metadata: metadata}, nil 13 | } 14 | 15 | // Clone clones this ServiceDiscovery with new servicePath. 16 | func (d *Peer2PeerDiscovery) Clone(servicePath string) (ServiceDiscovery, error) { 17 | return d, nil 18 | } 19 | 20 | // SetFilter sets the filer. 21 | func (d *Peer2PeerDiscovery) SetFilter(filter ServiceDiscoveryFilter) { 22 | 23 | } 24 | 25 | // GetServices returns the static server 26 | func (d *Peer2PeerDiscovery) GetServices() []*KVPair { 27 | return []*KVPair{&KVPair{Key: d.server, Value: d.metadata}} 28 | } 29 | 30 | // WatchService returns a nil chan. 31 | func (d *Peer2PeerDiscovery) WatchService() chan []*KVPair { 32 | return nil 33 | } 34 | 35 | func (d *Peer2PeerDiscovery) RemoveWatcher(ch chan []*KVPair) {} 36 | 37 | func (d *Peer2PeerDiscovery) Close() { 38 | 39 | } 40 | -------------------------------------------------------------------------------- /component/timers/timers.go: -------------------------------------------------------------------------------- 1 | package timers 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/utils/timer" 5 | "time" 6 | ) 7 | 8 | type Timers struct { 9 | opts *Options 10 | dispatcher *timer.Dispatcher 11 | } 12 | 13 | func NewTimer(opts ...Option) *Timers { 14 | options := newOptions(opts...) 15 | 16 | return &Timers{ 17 | opts: options, 18 | } 19 | } 20 | 21 | func (t *Timers) OnInit() { 22 | 23 | } 24 | 25 | func (t *Timers) OnDestroy() { 26 | 27 | } 28 | 29 | func (t *Timers) OnRun(closeSig chan bool) { 30 | go func() { 31 | t.dispatcher = timer.NewDispatcher(t.opts.TimerDispatcherLen) 32 | 33 | for { 34 | select { 35 | case <-closeSig: 36 | return 37 | case tt := <-t.dispatcher.ChanTimer: 38 | tt.Cb() 39 | } 40 | } 41 | }() 42 | } 43 | 44 | func (t *Timers) AfterFunc(d time.Duration, cb func()) *timer.Timer { 45 | if t.opts.TimerDispatcherLen == 0 { 46 | panic("invalid TimerDispatcherLen") 47 | } 48 | 49 | return t.dispatcher.AfterFunc(d, cb) 50 | } 51 | 52 | func (t *Timers) CronFunc(cronExpr *timer.CronExpr, cb func()) *timer.Cron { 53 | if t.opts.TimerDispatcherLen == 0 { 54 | panic("invalid TimerDispatcherLen") 55 | } 56 | 57 | return t.dispatcher.CronFunc(cronExpr, cb) 58 | } -------------------------------------------------------------------------------- /rpcx/protocol/testdata/benchmark.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package testdata; 4 | 5 | option optimize_for = SPEED; 6 | 7 | 8 | message BenchmarkMessage { 9 | string field1 = 1; 10 | string field9 = 9; 11 | string field18 = 18; 12 | bool field80 = 80; 13 | bool field81 = 81; 14 | int32 field2 = 2; 15 | int32 field3 = 3; 16 | int32 field280 = 280; 17 | int32 field6 = 6; 18 | int64 field22 = 22; 19 | string field4 = 4; 20 | repeated fixed64 field5 = 5; 21 | bool field59 = 59; 22 | string field7 = 7; 23 | int32 field16 = 16; 24 | int32 field130 = 130; 25 | bool field12 = 12; 26 | bool field17 = 17; 27 | bool field13 = 13; 28 | bool field14 = 14; 29 | int32 field104 = 104; 30 | int32 field100 = 100; 31 | int32 field101 = 101; 32 | string field102 = 102; 33 | string field103 = 103; 34 | int32 field29 = 29; 35 | bool field30 = 30; 36 | int32 field60 = 60; 37 | int32 field271 = 271; 38 | int32 field272 = 272; 39 | int32 field150 = 150; 40 | int32 field23 = 23; 41 | bool field24 = 24; 42 | int32 field25 = 25; 43 | bool field78 = 78; 44 | int32 field67 = 67; 45 | int32 field68 = 68; 46 | int32 field128 = 128; 47 | string field129 = 129; 48 | int32 field131 = 131; 49 | } -------------------------------------------------------------------------------- /rpcx/tool/xgen/README.md: -------------------------------------------------------------------------------- 1 | # xgen 2 | 3 | `xgen` isn a tool that can help you generate a server stub for rpcx services. 4 | 5 | It search structs in your specified files and add them as services. Currently it doesn't support registring functions. 6 | 7 | ## Usage 8 | 9 | ```sh 10 | # install 11 | go get -u github.com/smallnest/rpcx/tool/xgen/... 12 | 13 | # run 14 | xgen -o server.go .go 15 | ``` 16 | 17 | The above will generate server.go containing a rpcx which registers all exported struct types contained in `.go`. 18 | 19 | 20 | ## Options 21 | 22 | ``` 23 | -o string 24 | specify the filename of the output 25 | -pkg 26 | process the whole package instead of just the given file 27 | -r string 28 | registry type. support etcd, consul, zookeeper, mdns (default "etcd") 29 | -tags string 30 | build tags to add to generated file 31 | ``` 32 | 33 | You can run as: 34 | 35 | ```sh 36 | xgen [options] .go .go .go 37 | ``` 38 | 39 | for example, `xgen -o server.go a.go b.go /User/abc/go/src/github.com/abc/aaa/c.go` 40 | 41 | or 42 | 43 | ```sh 44 | xgen [options] 45 | ``` 46 | 47 | for example, `xgen -o server.go github.com/abc/aaa github.com/abc/bbb github.com/abc/ccc` -------------------------------------------------------------------------------- /rpcx/client/circuit_breaker_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestConsecCircuitBreaker(t *testing.T) { 10 | count := -1 11 | fn := func() error { 12 | count++ 13 | if count >= 5 && count < 10 { 14 | return nil 15 | } 16 | 17 | return errors.New("test error") 18 | } 19 | 20 | cb := NewConsecCircuitBreaker(5, 100*time.Millisecond) 21 | 22 | for i := 0; i < 25; i++ { 23 | err := cb.Call(fn, 200*time.Millisecond) 24 | switch { 25 | case i < 5: 26 | if err.Error() != "test error" { 27 | t.Fatalf("expect %v, got %v", "test error", err) 28 | } 29 | case i >= 5 && i < 10: 30 | if err != ErrBreakerOpen { 31 | t.Fatalf("expect %v, got %v", ErrBreakerOpen, err) 32 | } 33 | case i >= 10 && i < 15: 34 | if err != nil { 35 | t.Fatalf("expect success, got %v", err) 36 | } 37 | case i >= 15 && i < 20: 38 | if err.Error() != "test error" { 39 | t.Fatalf("expect %v, got %v", "test error", err) 40 | } 41 | case i >= 20 && i < 25: 42 | if err != ErrBreakerOpen { 43 | t.Fatalf("expect %v, got %v", ErrBreakerOpen, err) 44 | } 45 | } 46 | 47 | if i == 9 { // expired 48 | time.Sleep(150 * time.Millisecond) 49 | } 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/server.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/log" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | type Server interface { 10 | OnStart() 11 | Run(closeSig chan bool) 12 | OnStop() 13 | } 14 | 15 | 16 | type server struct { 17 | s Server 18 | closeSig chan bool 19 | wg sync.WaitGroup 20 | } 21 | 22 | var servers []*server 23 | 24 | func Register(s Server) { 25 | m := new(server) 26 | m.s = s 27 | m.closeSig = make(chan bool, 1) 28 | 29 | servers = append(servers, m) 30 | } 31 | 32 | func Init() { 33 | for i := 0; i < len(servers); i++ { 34 | servers[i].s.OnStart() 35 | } 36 | 37 | for i := 0; i < len(servers); i++ { 38 | s := servers[i] 39 | s.wg.Add(1) 40 | go run(s) 41 | } 42 | } 43 | 44 | func Destroy() { 45 | for i := len(servers) - 1; i >= 0; i-- { 46 | m := servers[i] 47 | //m.closeSig <- true 48 | close(m.closeSig) 49 | m.wg.Wait() 50 | destroy(m) 51 | } 52 | } 53 | 54 | func run(m *server) { 55 | m.s.Run(m.closeSig) 56 | m.wg.Done() 57 | } 58 | 59 | func destroy(m *server) { 60 | defer func() { 61 | if r := recover(); r != nil { 62 | buf := make([]byte, 1024) 63 | l := runtime.Stack(buf, false) 64 | log.Error("%v: %s", r, buf[:l]) 65 | } 66 | }() 67 | 68 | m.s.OnStop() 69 | } -------------------------------------------------------------------------------- /rpcx/serverplugin/req_rate_limiting.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/juju/ratelimit" 8 | "github.com/syndtr/goleveldb/leveldb/errors" 9 | ) 10 | 11 | var ErrReqReachLimit = errors.New("request reached rate limit") 12 | 13 | // ReqRateLimitingPlugin can limit requests per unit time 14 | type ReqRateLimitingPlugin struct { 15 | FillInterval time.Duration 16 | Capacity int64 17 | bucket *ratelimit.Bucket 18 | block bool // blocks or return error if reach the limit 19 | } 20 | 21 | // NewReqRateLimitingPlugin creates a new RateLimitingPlugin 22 | func NewReqRateLimitingPlugin(fillInterval time.Duration, capacity int64, block bool) *ReqRateLimitingPlugin { 23 | tb := ratelimit.NewBucket(fillInterval, capacity) 24 | 25 | return &ReqRateLimitingPlugin{ 26 | FillInterval: fillInterval, 27 | Capacity: capacity, 28 | bucket: tb, 29 | block: block, 30 | } 31 | } 32 | 33 | // PreReadRequest can limit request processing. 34 | func (plugin *ReqRateLimitingPlugin) PreReadRequest(ctx context.Context) error { 35 | if plugin.block { 36 | plugin.bucket.Wait(1) 37 | return nil 38 | } 39 | 40 | count := plugin.bucket.TakeAvailable(1) 41 | if count == 1 { 42 | return nil 43 | } 44 | return ErrReqReachLimit 45 | } 46 | -------------------------------------------------------------------------------- /rpcx/server/converter_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/kudoochui/kudos/rpcx/codec" 9 | "github.com/kudoochui/kudos/rpcx/share" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestHTTPRequest2RpcxRequest(t *testing.T) { 14 | 15 | cc := &codec.MsgpackCodec{} 16 | 17 | args := &Args{ 18 | A: 10, 19 | B: 20, 20 | } 21 | 22 | data, _ := cc.Encode(args) 23 | 24 | req, err := http.NewRequest("POST", "http://127.0.0.1:8972/", bytes.NewReader(data)) 25 | if err != nil { 26 | t.Fatal("failed to create request: ", err) 27 | return 28 | } 29 | 30 | h := req.Header 31 | h.Set(XMessageID, "10000") 32 | h.Set(XHeartbeat, "0") 33 | h.Set(XOneway, "0") 34 | h.Set(XSerializeType, "3") 35 | h.Set(XMeta, "Meta") 36 | h.Set("Authorization", "Authorization") 37 | h.Set(XServicePath, "ProxyServer") 38 | h.Set(XServiceMethod, "GetAdData") 39 | 40 | rpcxReq, err := HTTPRequest2RpcxRequest(req) 41 | if err != nil { 42 | t.Fatal("HTTPRequest2RpcxRequest() error") 43 | } 44 | 45 | assert.NotNil(t, rpcxReq.Metadata) 46 | 47 | assert.Equal(t, h.Get("Authorization"), rpcxReq.Metadata[share.AuthKey]) 48 | 49 | assert.Equal(t, h.Get(XServicePath), rpcxReq.ServicePath) 50 | 51 | assert.Equal(t, h.Get(XServiceMethod), rpcxReq.ServiceMethod) 52 | } 53 | -------------------------------------------------------------------------------- /network/ws_conn.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "bytes" 5 | "github.com/gorilla/websocket" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type WebsocketConnSet map[*websocket.Conn]struct{} 11 | 12 | type WSConn struct { 13 | sync.Mutex 14 | conn *websocket.Conn 15 | } 16 | 17 | func newWSConn(conn *websocket.Conn, pendingWriteNum int, maxMsgLen uint32) *WSConn { 18 | wsConn := new(WSConn) 19 | wsConn.conn = conn 20 | 21 | return wsConn 22 | } 23 | 24 | func (wsConn *WSConn) Close() { 25 | wsConn.Lock() 26 | defer wsConn.Unlock() 27 | wsConn.conn.UnderlyingConn().(*net.TCPConn).SetLinger(0) 28 | wsConn.conn.Close() 29 | } 30 | 31 | func (wsConn *WSConn) LocalAddr() net.Addr { 32 | return wsConn.conn.LocalAddr() 33 | } 34 | 35 | func (wsConn *WSConn) RemoteAddr() net.Addr { 36 | return wsConn.conn.RemoteAddr() 37 | } 38 | 39 | func (wsConn *WSConn) Read(buffer []byte) (int, error) { 40 | return 0, nil 41 | } 42 | 43 | // goroutine not safe 44 | func (wsConn *WSConn) ReadMsg(buf *bytes.Buffer) error { 45 | //_, b, err := wsConn.conn.ReadMessage() 46 | messageType, r, err := wsConn.conn.NextReader() 47 | _ = messageType 48 | if err != nil { 49 | return err 50 | } 51 | _, err = buf.ReadFrom(r) 52 | return err 53 | } 54 | 55 | func (wsConn *WSConn) WriteMessage(buf []byte) error { 56 | return wsConn.conn.WriteMessage(websocket.BinaryMessage, buf) 57 | } -------------------------------------------------------------------------------- /rpcx/server/pool.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | ) 7 | 8 | var UsePool bool 9 | 10 | // Reset defines Reset method for pooled object. 11 | type Reset interface { 12 | Reset() 13 | } 14 | 15 | var argsReplyPools = &typePools{ 16 | pools: make(map[reflect.Type]*sync.Pool), 17 | New: func(t reflect.Type) interface{} { 18 | var argv reflect.Value 19 | 20 | if t.Kind() == reflect.Ptr { // reply must be ptr 21 | argv = reflect.New(t.Elem()) 22 | } else { 23 | argv = reflect.New(t) 24 | } 25 | 26 | return argv.Interface() 27 | }, 28 | } 29 | 30 | type typePools struct { 31 | mu sync.RWMutex 32 | pools map[reflect.Type]*sync.Pool 33 | New func(t reflect.Type) interface{} 34 | } 35 | 36 | func (p *typePools) Init(t reflect.Type) { 37 | tp := &sync.Pool{} 38 | tp.New = func() interface{} { 39 | return p.New(t) 40 | } 41 | p.mu.Lock() 42 | defer p.mu.Unlock() 43 | p.pools[t] = tp 44 | } 45 | 46 | func (p *typePools) Put(t reflect.Type, x interface{}) { 47 | if !UsePool { 48 | return 49 | } 50 | if o, ok := x.(Reset); ok { 51 | o.Reset() 52 | } 53 | 54 | p.mu.RLock() 55 | pool := p.pools[t] 56 | p.mu.RUnlock() 57 | pool.Put(x) 58 | } 59 | 60 | func (p *typePools) Get(t reflect.Type) interface{} { 61 | if !UsePool { 62 | return p.New(t) 63 | } 64 | p.mu.RLock() 65 | pool := p.pools[t] 66 | p.mu.RUnlock() 67 | 68 | return pool.Get() 69 | } 70 | -------------------------------------------------------------------------------- /rpcx/server/listener.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | var makeListeners = make(map[string]MakeListener) 10 | 11 | func init() { 12 | makeListeners["tcp"] = tcpMakeListener("tcp") 13 | makeListeners["tcp4"] = tcpMakeListener("tcp4") 14 | makeListeners["tcp6"] = tcpMakeListener("tcp6") 15 | makeListeners["http"] = tcpMakeListener("tcp") 16 | } 17 | 18 | // RegisterMakeListener registers a MakeListener for network. 19 | func RegisterMakeListener(network string, ml MakeListener) { 20 | makeListeners[network] = ml 21 | } 22 | 23 | // MakeListener defines a listener generator. 24 | type MakeListener func(s *Server, address string) (ln net.Listener, err error) 25 | 26 | // block can be nil if the caller wishes to skip encryption in kcp. 27 | // tlsConfig can be nil iff we are not using network "quic". 28 | func (s *Server) makeListener(network, address string) (ln net.Listener, err error) { 29 | ml := makeListeners[network] 30 | if ml == nil { 31 | return nil, fmt.Errorf("can not make listener for %s", network) 32 | } 33 | return ml(s, address) 34 | } 35 | 36 | func tcpMakeListener(network string) MakeListener { 37 | return func(s *Server, address string) (ln net.Listener, err error) { 38 | if s.tlsConfig == nil { 39 | ln, err = net.Listen(network, address) 40 | } else { 41 | ln, err = tls.Listen(network, address, s.tlsConfig) 42 | } 43 | 44 | return ln, err 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /log/beego/alils/log_config.go: -------------------------------------------------------------------------------- 1 | package alils 2 | 3 | // InputDetail define log detail 4 | type InputDetail struct { 5 | LogType string `json:"logType"` 6 | LogPath string `json:"logPath"` 7 | FilePattern string `json:"filePattern"` 8 | LocalStorage bool `json:"localStorage"` 9 | TimeFormat string `json:"timeFormat"` 10 | LogBeginRegex string `json:"logBeginRegex"` 11 | Regex string `json:"regex"` 12 | Keys []string `json:"key"` 13 | FilterKeys []string `json:"filterKey"` 14 | FilterRegex []string `json:"filterRegex"` 15 | TopicFormat string `json:"topicFormat"` 16 | } 17 | 18 | // OutputDetail define the output detail 19 | type OutputDetail struct { 20 | Endpoint string `json:"endpoint"` 21 | LogStoreName string `json:"logstoreName"` 22 | } 23 | 24 | // LogConfig define Log Config 25 | type LogConfig struct { 26 | Name string `json:"configName"` 27 | InputType string `json:"inputType"` 28 | InputDetail InputDetail `json:"inputDetail"` 29 | OutputType string `json:"outputType"` 30 | OutputDetail OutputDetail `json:"outputDetail"` 31 | 32 | CreateTime uint32 33 | LastModifyTime uint32 34 | 35 | project *LogProject 36 | } 37 | 38 | // GetAppliedMachineGroup returns applied machine group of this config. 39 | func (c *LogConfig) GetAppliedMachineGroup(confName string) (groupNames []string, err error) { 40 | groupNames, err = c.project.GetAppliedMachineGroups(c.Name) 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /rpcx/client/failmode_enumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=FailMode"; DO NOT EDIT. 2 | 3 | package client 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | const _FailModeName = "FailoverFailfastFailtryFailbackup" 10 | 11 | var _FailModeIndex = [...]uint8{0, 8, 16, 23, 33} 12 | 13 | func (i FailMode) String() string { 14 | if i < 0 || i >= FailMode(len(_FailModeIndex)-1) { 15 | return fmt.Sprintf("FailMode(%d)", i) 16 | } 17 | return _FailModeName[_FailModeIndex[i]:_FailModeIndex[i+1]] 18 | } 19 | 20 | var _FailModeValues = []FailMode{0, 1, 2, 3} 21 | 22 | var _FailModeNameToValueMap = map[string]FailMode{ 23 | _FailModeName[0:8]: 0, 24 | _FailModeName[8:16]: 1, 25 | _FailModeName[16:23]: 2, 26 | _FailModeName[23:33]: 3, 27 | } 28 | 29 | // FailModeString retrieves an enum value from the enum constants string name. 30 | // Throws an error if the param is not part of the enum. 31 | func FailModeString(s string) (FailMode, error) { 32 | if val, ok := _FailModeNameToValueMap[s]; ok { 33 | return val, nil 34 | } 35 | return 0, fmt.Errorf("%s does not belong to FailMode values", s) 36 | } 37 | 38 | // FailModeValues returns all values of the enum 39 | func FailModeValues() []FailMode { 40 | return _FailModeValues 41 | } 42 | 43 | // IsAFailMode returns "true" if the value is listed in the enum definition. "false" otherwise 44 | func (i FailMode) IsAFailMode() bool { 45 | for _, v := range _FailModeValues { 46 | if i == v { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | -------------------------------------------------------------------------------- /rpcx/protocol/message_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestMessage(t *testing.T) { 10 | req := NewMessage() 11 | req.SetVersion(0) 12 | req.SetMessageType(Request) 13 | req.SetHeartbeat(false) 14 | req.SetOneway(false) 15 | req.SetCompressType(None) 16 | req.SetMessageStatusType(Normal) 17 | req.SetSerializeType(JSON) 18 | 19 | req.SetSeq(1234567890) 20 | 21 | m := make(map[string]string) 22 | req.ServicePath = "Arith" 23 | req.ServiceMethod = "Add" 24 | m["__ID"] = "6ba7b810-9dad-11d1-80b4-00c04fd430c9" 25 | req.Metadata = m 26 | 27 | payload := `{ 28 | "A": 1, 29 | "B": 2, 30 | } 31 | ` 32 | req.Payload = []byte(payload) 33 | 34 | var buf bytes.Buffer 35 | _, err := req.WriteTo(&buf) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | fmt.Println(buf.Bytes()) 41 | 42 | res, err := Read(&buf) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | res.SetMessageType(Response) 47 | 48 | if res.Version() != 0 { 49 | t.Errorf("expect 0 but got %d", res.Version()) 50 | } 51 | 52 | if res.Seq() != 1234567890 { 53 | t.Errorf("expect 1234567890 but got %d", res.Seq()) 54 | } 55 | 56 | if res.ServicePath != "Arith" || res.ServiceMethod != "Add" || res.Metadata["__ID"] != "6ba7b810-9dad-11d1-80b4-00c04fd430c9" { 57 | t.Errorf("got wrong metadata: %v", res.Metadata) 58 | } 59 | 60 | if string(res.Payload) != payload { 61 | t.Errorf("got wrong payload: %v", string(res.Payload)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /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 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestFormatHeader_0(t *testing.T) { 23 | tm := time.Now() 24 | if tm.Year() >= 2100 { 25 | t.FailNow() 26 | } 27 | dur := time.Second 28 | for { 29 | if tm.Year() >= 2100 { 30 | break 31 | } 32 | h, _, _ := formatTimeHeader(tm) 33 | if tm.Format("2006/01/02 15:04:05.000 ") != string(h) { 34 | t.Log(tm) 35 | t.FailNow() 36 | } 37 | tm = tm.Add(dur) 38 | dur *= 2 39 | } 40 | } 41 | 42 | func TestFormatHeader_1(t *testing.T) { 43 | tm := time.Now() 44 | year := tm.Year() 45 | dur := time.Second 46 | for { 47 | if tm.Year() >= year+1 { 48 | break 49 | } 50 | h, _, _ := formatTimeHeader(tm) 51 | if tm.Format("2006/01/02 15:04:05.000 ") != string(h) { 52 | t.Log(tm) 53 | t.FailNow() 54 | } 55 | tm = tm.Add(dur) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /rpcx/client/hash_utils.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "hash/fnv" 6 | ) 7 | 8 | // Hash consistently chooses a hash bucket number in the range [0, numBuckets) for the given key. numBuckets must be >= 1. 9 | func Hash(key uint64, buckets int32) int32 { 10 | if buckets <= 0 { 11 | buckets = 1 12 | } 13 | 14 | var b, j int64 15 | 16 | for j < int64(buckets) { 17 | b = j 18 | key = key*2862933555777941757 + 1 19 | j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) 20 | } 21 | 22 | return int32(b) 23 | } 24 | 25 | // HashString get a hash value of a string 26 | func HashString(s string) uint64 { 27 | h := fnv.New64a() 28 | h.Write([]byte(s)) 29 | return h.Sum64() 30 | } 31 | 32 | // HashServiceAndArgs define a hash function 33 | type HashServiceAndArgs func(len int, options ...interface{}) int 34 | 35 | // ConsistentFunction define a hash function 36 | // Return service address, like "tcp@127.0.0.1:8970" 37 | type ConsistentAddrStrFunction func(options ...interface{}) string 38 | 39 | func genKey(options ...interface{}) uint64 { 40 | keyString := "" 41 | for _, opt := range options { 42 | keyString = keyString + "/" + toString(opt) 43 | } 44 | 45 | return HashString(keyString) 46 | } 47 | 48 | // JumpConsistentHash selects a server by serviceMethod and args 49 | func JumpConsistentHash(len int, options ...interface{}) int { 50 | return int(Hash(genKey(options...), int32(len))) 51 | } 52 | 53 | func toString(obj interface{}) string { 54 | return fmt.Sprintf("%v", obj) 55 | } 56 | -------------------------------------------------------------------------------- /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 | ```golang 20 | import ( 21 | "github.com/astaxie/beego/logs" 22 | ) 23 | ``` 24 | 25 | Then init a Log (example with console adapter) 26 | 27 | ```golang 28 | log := logs.NewLogger(10000) 29 | log.SetLogger("console", "") 30 | ``` 31 | 32 | > the first params stand for how many channel 33 | 34 | Use it like this: 35 | 36 | ```golang 37 | log.Trace("trace") 38 | log.Info("info") 39 | log.Warn("warning") 40 | log.Debug("debug") 41 | log.Critical("critical") 42 | ``` 43 | 44 | ## File adapter 45 | 46 | Configure file adapter like this: 47 | 48 | ```golang 49 | log := NewLogger(10000) 50 | log.SetLogger("file", `{"filename":"test.log"}`) 51 | ``` 52 | 53 | ## Conn adapter 54 | 55 | Configure like this: 56 | 57 | ```golang 58 | log := NewLogger(1000) 59 | log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) 60 | log.Info("info") 61 | ``` 62 | 63 | ## Smtp adapter 64 | 65 | Configure like this: 66 | 67 | ```golang 68 | log := NewLogger(10000) 69 | log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) 70 | log.Critical("sendmail critical") 71 | time.Sleep(time.Second * 30) 72 | ``` 73 | -------------------------------------------------------------------------------- /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 | // Flush implementing method. empty. 51 | func (s *SLACKWriter) Flush() { 52 | } 53 | 54 | // Destroy implementing method. empty. 55 | func (s *SLACKWriter) Destroy() { 56 | } 57 | 58 | func init() { 59 | Register(AdapterSlack, newSLACKWriter) 60 | } 61 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | beegolog "github.com/kudoochui/kudos/log/beego" 5 | "sync" 6 | ) 7 | 8 | var beego *beegolog.BeeLogger 9 | var once sync.Once 10 | 11 | func LogBeego() *beegolog.BeeLogger { 12 | once.Do(func() { 13 | beego = beegolog.NewLogger() 14 | }) 15 | return beego 16 | } 17 | 18 | 19 | // Emergency logs a message at emergency level. 20 | func Emergency(f interface{}, v ...interface{}) { 21 | LogBeego().Emergency(beegolog.FormatLog(f, v...)) 22 | } 23 | 24 | // Alert logs a message at alert level. 25 | func Alert(f interface{}, v ...interface{}) { 26 | LogBeego().Alert(beegolog.FormatLog(f, v...)) 27 | } 28 | 29 | // Critical logs a message at critical level. 30 | func Critical(f interface{}, v ...interface{}) { 31 | LogBeego().Critical(beegolog.FormatLog(f, v...)) 32 | } 33 | 34 | // Error logs a message at error level. 35 | func Error(f interface{}, v ...interface{}) { 36 | LogBeego().Error(beegolog.FormatLog(f, v...)) 37 | } 38 | 39 | // Warning logs a message at warning level. 40 | func Warning(f interface{}, v ...interface{}) { 41 | LogBeego().Warn(beegolog.FormatLog(f, v...)) 42 | } 43 | 44 | // Notice logs a message at notice level. 45 | func Notice(f interface{}, v ...interface{}) { 46 | LogBeego().Notice(beegolog.FormatLog(f, v...)) 47 | } 48 | 49 | // Info compatibility alias for Warning() 50 | func Info(f interface{}, v ...interface{}) { 51 | LogBeego().Info(beegolog.FormatLog(f, v...)) 52 | } 53 | 54 | // Debug logs a message at debug level. 55 | func Debug(f interface{}, v ...interface{}) { 56 | LogBeego().Debug(beegolog.FormatLog(f, v...)) 57 | } 58 | -------------------------------------------------------------------------------- /config/config_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 config 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | ) 21 | 22 | func TestExpandValueEnv(t *testing.T) { 23 | 24 | testCases := []struct { 25 | item string 26 | want string 27 | }{ 28 | {"", ""}, 29 | {"$", "$"}, 30 | {"{", "{"}, 31 | {"{}", "{}"}, 32 | {"${}", ""}, 33 | {"${|}", ""}, 34 | {"${}", ""}, 35 | {"${{}}", ""}, 36 | {"${{||}}", "}"}, 37 | {"${pwd||}", ""}, 38 | {"${pwd||}", ""}, 39 | {"${pwd||}", ""}, 40 | {"${pwd||}}", "}"}, 41 | {"${pwd||{{||}}}", "{{||}}"}, 42 | {"${GOPATH}", os.Getenv("GOPATH")}, 43 | {"${GOPATH||}", os.Getenv("GOPATH")}, 44 | {"${GOPATH||root}", os.Getenv("GOPATH")}, 45 | {"${GOPATH_NOT||root}", "root"}, 46 | {"${GOPATH_NOT||||root}", "||root"}, 47 | } 48 | 49 | for _, c := range testCases { 50 | if got := ExpandValueEnv(c.item); got != c.want { 51 | t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got) 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /rpcx/util/buffer_pool_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLimitedPool_findPool(t *testing.T) { 9 | pool := NewLimitedPool(512, 4096) 10 | 11 | tests := []struct { 12 | args int 13 | want int 14 | }{ 15 | {200, 512}, 16 | {512, 512}, 17 | {1000, 1024}, 18 | {2000, 2048}, 19 | {2048, 2048}, 20 | {4095, 4096}, 21 | {4096, 4096}, 22 | {4097, -1}, 23 | } 24 | for _, tt := range tests { 25 | t.Run(fmt.Sprintf("bytes-%d", tt.args), func(t *testing.T) { 26 | got := pool.findPool(tt.args) 27 | if got == nil { 28 | if tt.want > 0 { 29 | fmt.Printf("expect %d pool but got nil", tt.want) 30 | } 31 | return 32 | } 33 | 34 | if got.size != tt.want { 35 | fmt.Printf("expect %d pool but got %d pool", tt.want, got.size) 36 | } 37 | }) 38 | } 39 | } 40 | 41 | func TestLimitedPool_findPutPool(t *testing.T) { 42 | pool := NewLimitedPool(512, 4096) 43 | 44 | tests := []struct { 45 | args int 46 | want int 47 | }{ 48 | {200, -1}, //too small so we discard it 49 | {512, 512}, 50 | {1000, 512}, 51 | {2000, 1024}, 52 | {2048, 2048}, 53 | {4095, 2048}, 54 | {4096, 4096}, 55 | {4097, -1}, // too big so we discard it 56 | } 57 | for _, tt := range tests { 58 | t.Run(fmt.Sprintf("bytes-%d", tt.args), func(t *testing.T) { 59 | got := pool.findPutPool(tt.args) 60 | if got == nil { 61 | if tt.want > 0 { 62 | fmt.Printf("expect %d pool but got nil", tt.want) 63 | } 64 | return 65 | } 66 | 67 | if got.size != tt.want { 68 | fmt.Printf("expect %d pool but got %d pool", tt.want, got.size) 69 | } 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /rpcx/client/selectmode_enumer.go: -------------------------------------------------------------------------------- 1 | // Code generated by "enumer -type=SelectMode"; DO NOT EDIT. 2 | 3 | package client 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | const _SelectModeName = "RandomSelectRoundRobinWeightedRoundRobinWeightedICMPConsistentHashClosest" 10 | 11 | var _SelectModeIndex = [...]uint8{0, 12, 22, 40, 52, 66, 73} 12 | 13 | func (i SelectMode) String() string { 14 | if i < 0 || i >= SelectMode(len(_SelectModeIndex)-1) { 15 | return fmt.Sprintf("SelectMode(%d)", i) 16 | } 17 | return _SelectModeName[_SelectModeIndex[i]:_SelectModeIndex[i+1]] 18 | } 19 | 20 | var _SelectModeValues = []SelectMode{0, 1, 2, 3, 4, 5} 21 | 22 | var _SelectModeNameToValueMap = map[string]SelectMode{ 23 | _SelectModeName[0:12]: 0, 24 | _SelectModeName[12:22]: 1, 25 | _SelectModeName[22:40]: 2, 26 | _SelectModeName[40:52]: 3, 27 | _SelectModeName[52:66]: 4, 28 | _SelectModeName[66:73]: 5, 29 | } 30 | 31 | // SelectModeString retrieves an enum value from the enum constants string name. 32 | // Throws an error if the param is not part of the enum. 33 | func SelectModeString(s string) (SelectMode, error) { 34 | if val, ok := _SelectModeNameToValueMap[s]; ok { 35 | return val, nil 36 | } 37 | return 0, fmt.Errorf("%s does not belong to SelectMode values", s) 38 | } 39 | 40 | // SelectModeValues returns all values of the enum 41 | func SelectModeValues() []SelectMode { 42 | return _SelectModeValues 43 | } 44 | 45 | // IsASelectMode returns "true" if the value is listed in the enum definition. "false" otherwise 46 | func (i SelectMode) IsASelectMode() bool { 47 | for _, v := range _SelectModeValues { 48 | if i == v { 49 | return true 50 | } 51 | } 52 | return false 53 | } 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /protocol/protobuf/pkg/package.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/kudoochui/kudos/protocol" 6 | ) 7 | 8 | const ( 9 | PKG_SIZE_BYTES = 4 10 | PKG_TYPE_BYTES = 4 11 | ) 12 | 13 | var gLittleEndian bool = false 14 | 15 | func SetByteOrder(littleEndian bool) { 16 | gLittleEndian = littleEndian 17 | } 18 | 19 | func GetByteOrder() bool { 20 | return gLittleEndian 21 | } 22 | /** 23 | * Package protocol encode. 24 | * 25 | * +------+------+------------------+ 26 | * | size | type | body | 27 | * +------+------+------------------+ 28 | * 29 | */ 30 | func Encode(pkgType uint32, body []byte) *[]byte { 31 | length := 0 32 | length = len(body) + PKG_TYPE_BYTES 33 | buffer := protocol.GetPoolBuffer(length + PKG_SIZE_BYTES) 34 | if gLittleEndian { 35 | binary.LittleEndian.PutUint32(*buffer, uint32(length)) 36 | binary.LittleEndian.PutUint32((*buffer)[PKG_SIZE_BYTES:], pkgType) 37 | } else { 38 | binary.BigEndian.PutUint32(*buffer, uint32(length)) 39 | binary.BigEndian.PutUint32((*buffer)[PKG_SIZE_BYTES:], pkgType) 40 | } 41 | copy((*buffer)[PKG_SIZE_BYTES + PKG_TYPE_BYTES:], body) 42 | return buffer 43 | } 44 | 45 | /** 46 | * Package protocol decode. 47 | * See encode for package format. 48 | */ 49 | func Decode(buffer []byte) (length int, pkgType uint32, body []byte) { 50 | if gLittleEndian { 51 | length = int(binary.LittleEndian.Uint32(buffer)) 52 | pkgType = binary.LittleEndian.Uint32(buffer[PKG_SIZE_BYTES:]) 53 | } else { 54 | length = int(binary.BigEndian.Uint32(buffer)) 55 | pkgType = binary.BigEndian.Uint32(buffer[PKG_SIZE_BYTES:]) 56 | } 57 | body = buffer[PKG_SIZE_BYTES+PKG_TYPE_BYTES:] 58 | return 59 | } -------------------------------------------------------------------------------- /protocol/pomelo/pkg/package.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "github.com/kudoochui/kudos/protocol" 4 | 5 | const ( 6 | TYPE_NULL = iota 7 | TYPE_HANDSHAKE //1 8 | TYPE_HANDSHAKE_ACK //2 9 | TYPE_HEARTBEAT //3 10 | TYPE_DATA //4 11 | TYPE_KICK //5 12 | 13 | PKG_HEAD_BYTES = 4 14 | ) 15 | 16 | /** 17 | * Package protocol encode. 18 | * 19 | * Pomelo package format: 20 | * +------+-------------+------------------+ 21 | * | type | body length | body | 22 | * +------+-------------+------------------+ 23 | * 24 | * Head: 4bytes 25 | * 0: package type, 26 | * 1 - handshake, 27 | * 2 - handshake ack, 28 | * 3 - heartbeat, 29 | * 4 - data 30 | * 5 - kick 31 | * 1 - 3: big-endian body length 32 | * Body: body length bytes 33 | */ 34 | func Encode(pkgType int, body []byte) *[]byte { 35 | length := 0 36 | var buffer *[]byte 37 | if pkgType == TYPE_DATA { 38 | length = len(body) - PKG_HEAD_BYTES 39 | buffer = &body 40 | (*buffer)[0] = byte(pkgType & 0xff) 41 | (*buffer)[1] = byte(length >> 16) 42 | (*buffer)[2] = byte(length >> 8) 43 | (*buffer)[3] = byte(length) 44 | } else { 45 | length = len(body) 46 | buffer = protocol.GetPoolBuffer(length + PKG_HEAD_BYTES) 47 | (*buffer)[0] = byte(pkgType & 0xff) 48 | (*buffer)[1] = byte(length >> 16) 49 | (*buffer)[2] = byte(length >> 8) 50 | (*buffer)[3] = byte(length) 51 | copy((*buffer)[PKG_HEAD_BYTES:], body) 52 | } 53 | return buffer 54 | } 55 | 56 | /** 57 | * Package protocol decode. 58 | * See encode for package format. 59 | */ 60 | func Decode(buffer []byte) (pkgType int, body []byte) { 61 | pkgType = int(buffer[0]) 62 | body = buffer[PKG_HEAD_BYTES:] 63 | return 64 | } -------------------------------------------------------------------------------- /rpcx/util/compress.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "io/ioutil" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | spWriter sync.Pool 12 | spReader sync.Pool 13 | spBuffer sync.Pool 14 | ) 15 | 16 | func init() { 17 | spWriter = sync.Pool{New: func() interface{} { 18 | return new(gzip.Writer) 19 | }} 20 | spReader = sync.Pool{New: func() interface{} { 21 | return new(gzip.Reader) 22 | }} 23 | spBuffer = sync.Pool{New: func() interface{} { 24 | return bytes.NewBuffer(nil) 25 | }} 26 | } 27 | 28 | // Unzip unzips data. 29 | func Unzip(data []byte) ([]byte, error) { 30 | buf := spBuffer.Get().(*bytes.Buffer) 31 | defer func() { 32 | buf.Reset() 33 | spBuffer.Put(buf) 34 | }() 35 | 36 | _, err := buf.Write(data) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | gr := spReader.Get().(*gzip.Reader) 42 | defer func() { 43 | spReader.Put(gr) 44 | }() 45 | err = gr.Reset(buf) 46 | if err != nil { 47 | return nil, err 48 | } 49 | defer gr.Close() 50 | 51 | data, err = ioutil.ReadAll(gr) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return data, err 56 | } 57 | 58 | // Zip zips data. 59 | func Zip(data []byte) ([]byte, error) { 60 | buf := spBuffer.Get().(*bytes.Buffer) 61 | w := spWriter.Get().(*gzip.Writer) 62 | w.Reset(buf) 63 | 64 | defer func() { 65 | buf.Reset() 66 | spBuffer.Put(buf) 67 | w.Close() 68 | spWriter.Put(w) 69 | }() 70 | _, err := w.Write(data) 71 | if err != nil { 72 | return nil, err 73 | } 74 | err = w.Flush() 75 | if err != nil { 76 | return nil, err 77 | } 78 | err = w.Close() 79 | if err != nil { 80 | return nil, err 81 | } 82 | dec := buf.Bytes() 83 | return dec, nil 84 | } 85 | -------------------------------------------------------------------------------- /rpcx/util/compress_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestZip(t *testing.T) { 8 | s := "%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D" 9 | t.Logf("origin len: %d", len(s)) 10 | data, err := Zip([]byte(s)) 11 | if err != nil { 12 | t.Fatalf("failed to zip: %v", err) 13 | } 14 | t.Logf("zipped len: %d", len(data)) 15 | s2, err := Unzip(data) 16 | if err != nil { 17 | t.Fatalf("failed to unzip: %v", err) 18 | } 19 | 20 | if string(s2) != s { 21 | t.Fatalf("unzip data is wrong") 22 | } 23 | } 24 | 25 | func BenchmarkZip(b *testing.B) { 26 | s := "%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D" 27 | b.RunParallel(func(pb *testing.PB) { 28 | for pb.Next() { 29 | data, err := Zip([]byte(s)) 30 | if err != nil { 31 | b.Errorf("failed to zip: %v", err) 32 | } 33 | _ = data 34 | } 35 | }) 36 | } 37 | 38 | func BenchmarkUnzip(b *testing.B) { 39 | s := "%5B%7B%22service%22%3A%22AttrDict%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%2C%7B%22service%22%3A%22BrasInfo%22%2C%22service_address%22%3A%22udp%40127.0.0.1%3A5353%22%7D%5D" 40 | data, err := Zip([]byte(s)) 41 | if err != nil { 42 | b.Fatalf("failed to zip: %v", err) 43 | } 44 | 45 | b.RunParallel(func(pb *testing.PB) { 46 | for pb.Next() { 47 | s2, err := Unzip(data) 48 | if err != nil { 49 | b.Errorf("failed to zip: %v", err) 50 | } 51 | _ = s2 52 | } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /rpcx/client/opencensus.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/kudoochui/kudos/rpcx/share" 7 | "go.opencensus.io/trace" 8 | ) 9 | 10 | type OpenCensusPlugin struct{} 11 | 12 | func (p *OpenCensusPlugin) PreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error { 13 | var span1 *trace.Span 14 | 15 | // if it is called in rpc service in case that a service calls antoher service, 16 | // we uses the span in the service context as the parent span. 17 | parentSpan := ctx.Value(share.OpencensusSpanServerKey) 18 | if parentSpan != nil { 19 | _, span1 = trace.StartSpanWithRemoteParent(ctx, "rpcx.client."+servicePath+"."+serviceMethod, 20 | parentSpan.(*trace.Span).SpanContext()) 21 | } else { 22 | parentContext, err := share.GetOpencensusSpanContextFromContext(ctx) 23 | if err == nil && parentContext != nil { //try to parse span from request 24 | _, span1 = trace.StartSpanWithRemoteParent(ctx, "rpcx.client."+servicePath+"."+serviceMethod, 25 | *parentContext) 26 | } else { // parse span from context or create root context 27 | _, span1 = trace.StartSpan(ctx, "rpcx.client."+servicePath+"."+serviceMethod) 28 | } 29 | } 30 | 31 | if rpcxContext, ok := ctx.(*share.Context); ok { 32 | rpcxContext.SetValue(share.OpencensusSpanClientKey, span1) 33 | } 34 | return nil 35 | } 36 | func (p *OpenCensusPlugin) PostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error { 37 | if rpcxContext, ok := ctx.(*share.Context); ok { 38 | span1 := rpcxContext.Value(share.OpencensusSpanClientKey) 39 | if span1 != nil { 40 | span1.(*trace.Span).End() 41 | } 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /rpcx/protocol/compressor.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | 7 | "github.com/golang/snappy" 8 | "github.com/kudoochui/kudos/rpcx/util" 9 | ) 10 | 11 | // Compressor defines a common compression interface. 12 | type Compressor interface { 13 | Zip([]byte) ([]byte, error) 14 | Unzip([]byte) ([]byte, error) 15 | } 16 | 17 | // GzipCompressor implements gzip compressor. 18 | type GzipCompressor struct { 19 | } 20 | 21 | func (c GzipCompressor) Zip(data []byte) ([]byte, error) { 22 | return util.Zip(data) 23 | } 24 | 25 | func (c GzipCompressor) Unzip(data []byte) ([]byte, error) { 26 | return util.Unzip(data) 27 | } 28 | 29 | type RawDataCompressor struct { 30 | } 31 | 32 | func (c RawDataCompressor) Zip(data []byte) ([]byte, error) { 33 | return data, nil 34 | } 35 | 36 | func (c RawDataCompressor) Unzip(data []byte) ([]byte, error) { 37 | return data, nil 38 | } 39 | 40 | // SnappyCompressor implements snappy compressor 41 | type SnappyCompressor struct { 42 | } 43 | 44 | func (c *SnappyCompressor) Zip(data []byte) ([]byte, error) { 45 | if len(data) == 0 { 46 | return data, nil 47 | } 48 | 49 | var buffer bytes.Buffer 50 | writer := snappy.NewBufferedWriter(&buffer) 51 | _, err := writer.Write(data) 52 | if err != nil { 53 | writer.Close() 54 | return nil, err 55 | } 56 | err = writer.Close() 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return buffer.Bytes(), nil 62 | } 63 | 64 | func (c *SnappyCompressor) Unzip(data []byte) ([]byte, error) { 65 | if len(data) == 0 { 66 | return data, nil 67 | } 68 | 69 | reader := snappy.NewReader(bytes.NewReader(data)) 70 | out, err := ioutil.ReadAll(reader) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | return out, err 76 | } 77 | -------------------------------------------------------------------------------- /utils/timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/log" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | // one dispatcher per goroutine (goroutine not safe) 10 | type Dispatcher struct { 11 | ChanTimer chan *Timer 12 | } 13 | 14 | func NewDispatcher(l int) *Dispatcher { 15 | disp := new(Dispatcher) 16 | disp.ChanTimer = make(chan *Timer, l) 17 | return disp 18 | } 19 | 20 | // Timer 21 | type Timer struct { 22 | t *time.Timer 23 | cb func() 24 | } 25 | 26 | func (t *Timer) Stop() { 27 | t.t.Stop() 28 | t.cb = nil 29 | } 30 | 31 | func (t *Timer) Cb() { 32 | defer func() { 33 | t.cb = nil 34 | if r := recover(); r != nil { 35 | buf := make([]byte, 1024) 36 | l := runtime.Stack(buf, false) 37 | log.Error("%v: %s", r, buf[:l]) 38 | } 39 | }() 40 | 41 | if t.cb != nil { 42 | t.cb() 43 | } 44 | } 45 | 46 | func (disp *Dispatcher) AfterFunc(d time.Duration, cb func()) *Timer { 47 | t := new(Timer) 48 | t.cb = cb 49 | t.t = time.AfterFunc(d, func() { 50 | disp.ChanTimer <- t 51 | }) 52 | return t 53 | } 54 | 55 | // Cron 56 | type Cron struct { 57 | t *Timer 58 | } 59 | 60 | func (c *Cron) Stop() { 61 | if c.t != nil { 62 | c.t.Stop() 63 | } 64 | } 65 | 66 | func (disp *Dispatcher) CronFunc(cronExpr *CronExpr, _cb func()) *Cron { 67 | c := new(Cron) 68 | 69 | now := time.Now() 70 | nextTime := cronExpr.Next(now) 71 | if nextTime.IsZero() { 72 | return c 73 | } 74 | 75 | // callback 76 | var cb func() 77 | cb = func() { 78 | defer _cb() 79 | 80 | now := time.Now() 81 | nextTime := cronExpr.Next(now) 82 | if nextTime.IsZero() { 83 | return 84 | } 85 | c.t = disp.AfterFunc(nextTime.Sub(now), cb) 86 | } 87 | 88 | c.t = disp.AfterFunc(nextTime.Sub(now), cb) 89 | return c 90 | } 91 | -------------------------------------------------------------------------------- /log/beego/alils/request.go: -------------------------------------------------------------------------------- 1 | package alils 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | // request sends a request to SLS. 11 | func request(project *LogProject, method, uri string, headers map[string]string, 12 | body []byte) (resp *http.Response, err error) { 13 | 14 | // The caller should provide 'x-sls-bodyrawsize' header 15 | if _, ok := headers["x-sls-bodyrawsize"]; !ok { 16 | err = fmt.Errorf("Can't find 'x-sls-bodyrawsize' header") 17 | return 18 | } 19 | 20 | // SLS public request headers 21 | headers["Host"] = project.Name + "." + project.Endpoint 22 | headers["Date"] = nowRFC1123() 23 | headers["x-sls-apiversion"] = version 24 | headers["x-sls-signaturemethod"] = signatureMethod 25 | if body != nil { 26 | bodyMD5 := fmt.Sprintf("%X", md5.Sum(body)) 27 | headers["Content-MD5"] = bodyMD5 28 | 29 | if _, ok := headers["Content-Type"]; !ok { 30 | err = fmt.Errorf("Can't find 'Content-Type' header") 31 | return 32 | } 33 | } 34 | 35 | // Calc Authorization 36 | // Authorization = "SLS :" 37 | digest, err := signature(project, method, uri, headers) 38 | if err != nil { 39 | return 40 | } 41 | auth := fmt.Sprintf("SLS %v:%v", project.AccessKeyID, digest) 42 | headers["Authorization"] = auth 43 | 44 | // Initialize http request 45 | reader := bytes.NewReader(body) 46 | urlStr := fmt.Sprintf("http://%v.%v%v", project.Name, project.Endpoint, uri) 47 | req, err := http.NewRequest(method, urlStr, reader) 48 | if err != nil { 49 | return 50 | } 51 | for k, v := range headers { 52 | req.Header.Add(k, v) 53 | } 54 | 55 | // Get ready to do request 56 | resp, err = http.DefaultClient.Do(req) 57 | if err != nil { 58 | return 59 | } 60 | 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /component/web/options.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import "time" 4 | 5 | type Option func(*Options) 6 | 7 | type Options struct { 8 | ListenIp string 9 | ListenPort int 10 | WriteTimeout time.Duration 11 | ReadTimeout time.Duration 12 | IdleTimeout time.Duration 13 | CloseTimeout time.Duration 14 | } 15 | 16 | func newOptions(opts ...Option) *Options { 17 | opt := &Options{ 18 | ListenIp: "0.0.0.0", 19 | ListenPort: 5050, 20 | WriteTimeout: 15 * time.Second, 21 | ReadTimeout: 15 * time.Second, 22 | IdleTimeout: 60 * time.Second, 23 | CloseTimeout: 5 * time.Second, 24 | } 25 | 26 | for _,o := range opts { 27 | o(opt) 28 | } 29 | return opt 30 | } 31 | 32 | // Ip of listening. Default is "0.0.0.0" 33 | func ListenIp(ip string) Option { 34 | return func(options *Options) { 35 | options.ListenIp = ip 36 | } 37 | } 38 | 39 | // Port of listening. Default is 5050 40 | func ListenPort(port int) Option { 41 | return func(options *Options) { 42 | options.ListenPort = port 43 | } 44 | } 45 | 46 | // Maximum duration before timing out writes of the response. Default is 15s 47 | func WriteTimeout(t time.Duration) Option { 48 | return func(options *Options) { 49 | options.WriteTimeout = t 50 | } 51 | } 52 | 53 | // Maximum duration for reading the entire request. Default is 15s 54 | func ReadTimeout(t time.Duration) Option { 55 | return func(options *Options) { 56 | options.ReadTimeout = t 57 | } 58 | } 59 | 60 | // Maximum amount of time to wait for the next request. Default is 60s 61 | func IdleTimeout(t time.Duration) Option { 62 | return func(options *Options) { 63 | options.IdleTimeout = t 64 | } 65 | } 66 | 67 | // Deadline to wait for closing. Default is 5s 68 | func CloseTimeout(t time.Duration) Option { 69 | return func(options *Options) { 70 | options.CloseTimeout = t 71 | } 72 | } -------------------------------------------------------------------------------- /rpcx/client/opentracing.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | opentracing "github.com/opentracing/opentracing-go" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "github.com/kudoochui/kudos/rpcx/share" 9 | ) 10 | 11 | type OpenTracingPlugin struct{} 12 | 13 | func (p *OpenTracingPlugin) PreCall(ctx context.Context, servicePath, serviceMethod string, args interface{}) error { 14 | var span1 opentracing.Span 15 | 16 | // if it is called in rpc service in case that a service calls antoher service, 17 | // we uses the span in the service context as the parent span. 18 | parentSpan := ctx.Value(share.OpentracingSpanServerKey) 19 | if parentSpan != nil { 20 | span1 = opentracing.StartSpan( 21 | "rpcx.client."+servicePath+"."+serviceMethod, 22 | opentracing.ChildOf(parentSpan.(opentracing.Span).Context())) 23 | } else { 24 | wireContext, err := share.GetSpanContextFromContext(ctx) 25 | if err == nil && wireContext != nil { //try to parse span from request 26 | span1 = opentracing.StartSpan( 27 | "rpcx.client."+servicePath+"."+serviceMethod, 28 | ext.RPCServerOption(wireContext)) 29 | } else { // parse span from context or create root context 30 | span1, _ = opentracing.StartSpanFromContext(ctx, "rpcx.client."+servicePath+"."+serviceMethod) 31 | } 32 | } 33 | 34 | if rpcxContext, ok := ctx.(*share.Context); ok { 35 | rpcxContext.SetValue(share.OpentracingSpanClientKey, span1) 36 | } 37 | return nil 38 | } 39 | func (p *OpenTracingPlugin) PostCall(ctx context.Context, servicePath, serviceMethod string, args interface{}, reply interface{}, err error) error { 40 | if rpcxContext, ok := ctx.(*share.Context); ok { 41 | span1 := rpcxContext.Value(share.OpentracingSpanClientKey) 42 | if span1 != nil { 43 | span1.(opentracing.Span).Finish() 44 | } 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /log/beego/es/es.go: -------------------------------------------------------------------------------- 1 | package es 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "time" 10 | 11 | "github.com/OwnLocal/goes" 12 | logs "github.com/kudoochui/kudos/log/beego" 13 | 14 | ) 15 | 16 | // NewES return a LoggerInterface 17 | func NewES() logs.Logger { 18 | cw := &esLogger{ 19 | Level: logs.LevelDebug, 20 | } 21 | return cw 22 | } 23 | 24 | type esLogger struct { 25 | *goes.Client 26 | DSN string `json:"dsn"` 27 | Level int `json:"level"` 28 | } 29 | 30 | // {"dsn":"http://localhost:9200/","level":1} 31 | func (el *esLogger) Init(jsonconfig string) error { 32 | err := json.Unmarshal([]byte(jsonconfig), el) 33 | if err != nil { 34 | return err 35 | } 36 | if el.DSN == "" { 37 | return errors.New("empty dsn") 38 | } else if u, err := url.Parse(el.DSN); err != nil { 39 | return err 40 | } else if u.Path == "" { 41 | return errors.New("missing prefix") 42 | } else if host, port, err := net.SplitHostPort(u.Host); err != nil { 43 | return err 44 | } else { 45 | conn := goes.NewClient(host, port) 46 | el.Client = conn 47 | } 48 | return nil 49 | } 50 | 51 | // WriteMsg will write the msg and level into es 52 | func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error { 53 | if level > el.Level { 54 | return nil 55 | } 56 | 57 | vals := make(map[string]interface{}) 58 | vals["@timestamp"] = when.Format(time.RFC3339) 59 | vals["@msg"] = msg 60 | d := goes.Document{ 61 | Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()), 62 | Type: "logs", 63 | Fields: vals, 64 | } 65 | _, err := el.Index(d, nil) 66 | return err 67 | } 68 | 69 | // Destroy is a empty method 70 | func (el *esLogger) Destroy() { 71 | 72 | } 73 | 74 | // Flush is a empty method 75 | func (el *esLogger) Flush() { 76 | 77 | } 78 | 79 | func init() { 80 | logs.Register(logs.AdapterEs, NewES) 81 | } 82 | 83 | -------------------------------------------------------------------------------- /rpcx/share/context_test.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "go.opencensus.io/trace" 9 | ) 10 | 11 | var ( 12 | TheAnswer = "Answer to the Ultimate Question of Life, the Universe, and Everything" 13 | MagicNumber = 42 14 | ) 15 | 16 | func TestContext(t *testing.T) { 17 | rpcxContext := NewContext(context.Background()) 18 | assert.NotNil(t, rpcxContext.Context) 19 | assert.NotNil(t, rpcxContext.tags) 20 | 21 | 22 | rpcxContext.SetValue("string", TheAnswer) 23 | rpcxContext.SetValue(42, MagicNumber) 24 | assert.Equal(t, MagicNumber, rpcxContext.Value(42)) 25 | assert.Equal(t, TheAnswer, rpcxContext.Value("string")) 26 | 27 | 28 | rpcxContext.SetValue("string", TheAnswer) 29 | t.Log(rpcxContext.String()) 30 | } 31 | 32 | func TestGetOpencensusSpanContextFromContext(t *testing.T) { 33 | var ctx Context 34 | ctx.SetValue("key", "value") 35 | ctx.SetValue(ReqMetaDataKey, make(map[string]string)) 36 | 37 | spanCtx, err := GetOpencensusSpanContextFromContext(&ctx) 38 | assert.Nil(t, spanCtx) 39 | assert.Equal(t, "key not found", err.Error()) 40 | 41 | PI := "3141592653589793238462643383279" 42 | 43 | ctx.SetValue(ReqMetaDataKey, map[string]string{ 44 | OpencensusSpanRequestKey: PI, 45 | }) 46 | 47 | spanCtx, err = GetOpencensusSpanContextFromContext(&ctx) 48 | var exceptTraceID [16]byte 49 | copy(exceptTraceID[:], []byte(PI)[:16]) 50 | assert.Equal(t, trace.TraceID(exceptTraceID), spanCtx.TraceID) 51 | assert.Nil(t, err) 52 | } 53 | 54 | func TestWithValue(t *testing.T) { 55 | ctx := WithValue(context.Background(), "key", "value") 56 | assert.NotNil(t, ctx.tags) 57 | } 58 | 59 | func TestWithLocalValue(t *testing.T) { 60 | var c Context 61 | c.SetValue("key", "value") 62 | 63 | ctx := WithLocalValue(&c, "MagicNumber", "42") 64 | 65 | assert.Equal(t, "value", ctx.tags["key"]) 66 | assert.Equal(t, "42", ctx.tags["MagicNumber"]) 67 | } -------------------------------------------------------------------------------- /rpcx/log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | const ( 9 | calldepth = 3 10 | ) 11 | 12 | var l Logger = &defaultLogger{log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)} 13 | 14 | type Logger interface { 15 | Debug(v ...interface{}) 16 | Debugf(format string, v ...interface{}) 17 | 18 | Info(v ...interface{}) 19 | Infof(format string, v ...interface{}) 20 | 21 | Warn(v ...interface{}) 22 | Warnf(format string, v ...interface{}) 23 | 24 | Error(v ...interface{}) 25 | Errorf(format string, v ...interface{}) 26 | 27 | Fatal(v ...interface{}) 28 | Fatalf(format string, v ...interface{}) 29 | 30 | Panic(v ...interface{}) 31 | Panicf(format string, v ...interface{}) 32 | } 33 | 34 | type Handler interface { 35 | Handle(v ...interface{}) 36 | } 37 | 38 | func SetLogger(logger Logger) { 39 | l = logger 40 | } 41 | 42 | func SetDummyLogger() { 43 | l = &dummyLogger{} 44 | } 45 | 46 | func Debug(v ...interface{}) { 47 | l.Debug(v...) 48 | } 49 | func Debugf(format string, v ...interface{}) { 50 | l.Debugf(format, v...) 51 | } 52 | 53 | func Info(v ...interface{}) { 54 | l.Info(v...) 55 | } 56 | func Infof(format string, v ...interface{}) { 57 | l.Infof(format, v...) 58 | } 59 | 60 | func Warn(v ...interface{}) { 61 | l.Warn(v...) 62 | } 63 | func Warnf(format string, v ...interface{}) { 64 | l.Warnf(format, v...) 65 | } 66 | 67 | func Error(v ...interface{}) { 68 | l.Error(v...) 69 | } 70 | func Errorf(format string, v ...interface{}) { 71 | l.Errorf(format, v...) 72 | } 73 | 74 | func Fatal(v ...interface{}) { 75 | l.Fatal(v...) 76 | } 77 | func Fatalf(format string, v ...interface{}) { 78 | l.Fatalf(format, v...) 79 | } 80 | 81 | func Panic(v ...interface{}) { 82 | l.Panic(v...) 83 | } 84 | func Panicf(format string, v ...interface{}) { 85 | l.Panicf(format, v...) 86 | } 87 | 88 | func Handle(v ...interface{}) { 89 | if handle, ok := l.(Handler); ok { 90 | handle.Handle(v...) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /component/connector/timer.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/utils/timer" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | 10 | type Timers struct { 11 | dispatcher *timer.Dispatcher 12 | timerDispatcherLen int 13 | chanJob chan *TimeJob 14 | chanStop chan *timer.Timer 15 | closed bool 16 | lock sync.RWMutex 17 | } 18 | 19 | func NewTimer() *Timers { 20 | return &Timers{ 21 | timerDispatcherLen: 20, 22 | dispatcher: timer.NewDispatcher(20), 23 | chanJob: make(chan *TimeJob, 5), 24 | chanStop: make(chan *timer.Timer, 20), 25 | } 26 | } 27 | 28 | type TimeJob struct { 29 | timeout time.Duration 30 | cronExpr *timer.CronExpr 31 | f func() 32 | } 33 | 34 | func (t *Timers) RunTimer(closeSig chan bool) { 35 | //t.dispatcher = timer.NewDispatcher(t.timerDispatcherLen) 36 | 37 | for { 38 | select { 39 | case <-closeSig: 40 | t.lock.Lock() 41 | t.closed = true 42 | t.lock.Unlock() 43 | return 44 | case tt := <-t.dispatcher.ChanTimer: 45 | tt.Cb() 46 | case job := <-t.chanJob: 47 | t.work(job) 48 | case handler := <-t.chanStop: 49 | handler.Stop() 50 | } 51 | } 52 | } 53 | func (t *Timers) work(job *TimeJob) { 54 | if job.cronExpr != nil { 55 | 56 | } else { 57 | 58 | } 59 | } 60 | 61 | func (t *Timers) AfterFunc(d time.Duration, cb func()) *timer.Timer { 62 | t.lock.RLock() 63 | if t.closed { 64 | t.lock.RUnlock() 65 | return nil 66 | } 67 | t.lock.RUnlock() 68 | return t.dispatcher.AfterFunc(d, cb) 69 | } 70 | 71 | func (t *Timers) CronFunc(cronExpr *timer.CronExpr, cb func()) *timer.Cron { 72 | t.lock.RLock() 73 | if t.closed { 74 | t.lock.RUnlock() 75 | return nil 76 | } 77 | t.lock.RUnlock() 78 | return t.dispatcher.CronFunc(cronExpr, cb) 79 | } 80 | 81 | func (t *Timers) ClearTimeout(handler *timer.Timer){ 82 | t.lock.RLock() 83 | if t.closed { 84 | t.lock.RUnlock() 85 | return 86 | } 87 | t.lock.RUnlock() 88 | t.chanStop <- handler 89 | } -------------------------------------------------------------------------------- /log/beego/jianliao.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 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 | return json.Unmarshal([]byte(jsonconfig), s) 29 | } 30 | 31 | // WriteMsg write message in smtp writer. 32 | // it will send an email with subject and only this message. 33 | func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error { 34 | if level > s.Level { 35 | return nil 36 | } 37 | 38 | text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg) 39 | 40 | form := url.Values{} 41 | form.Add("authorName", s.AuthorName) 42 | form.Add("title", s.Title) 43 | form.Add("text", text) 44 | if s.RedirectURL != "" { 45 | form.Add("redirectUrl", s.RedirectURL) 46 | } 47 | if s.ImageURL != "" { 48 | form.Add("imageUrl", s.ImageURL) 49 | } 50 | 51 | resp, err := http.PostForm(s.WebhookURL, form) 52 | if err != nil { 53 | return err 54 | } 55 | defer resp.Body.Close() 56 | if resp.StatusCode != http.StatusOK { 57 | return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) 58 | } 59 | return nil 60 | } 61 | 62 | // Flush implementing method. empty. 63 | func (s *JLWriter) Flush() { 64 | } 65 | 66 | // Destroy implementing method. empty. 67 | func (s *JLWriter) Destroy() { 68 | } 69 | 70 | func init() { 71 | Register(AdapterJianLiao, newJLWriter) 72 | } 73 | -------------------------------------------------------------------------------- /rpcx/mailbox/queue/mpsc/mpsc.go: -------------------------------------------------------------------------------- 1 | // Package mpsc provides an efficient implementation of a multi-producer, single-consumer lock-free queue. 2 | // 3 | // The Push function is safe to call from multiple goroutines. The Pop and Empty APIs must only be 4 | // called from a single, consumer goroutine. 5 | // 6 | package mpsc 7 | 8 | // This implementation is based on http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue 9 | 10 | import ( 11 | "sync/atomic" 12 | "unsafe" 13 | ) 14 | 15 | type node struct { 16 | next *node 17 | val interface{} 18 | } 19 | 20 | type Queue struct { 21 | head, tail *node 22 | } 23 | 24 | func New() *Queue { 25 | q := &Queue{} 26 | stub := &node{} 27 | q.head = stub 28 | q.tail = stub 29 | return q 30 | } 31 | 32 | // Push adds x to the back of the queue. 33 | // 34 | // Push can be safely called from multiple goroutines 35 | func (q *Queue) Push(x interface{}) { 36 | n := new(node) 37 | n.val = x 38 | // current producer acquires head node 39 | prev := (*node)(atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&q.head)), unsafe.Pointer(n))) 40 | 41 | // release node to consumer 42 | atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&prev.next)), unsafe.Pointer(n)) 43 | } 44 | 45 | // Pop removes the item from the front of the queue or nil if the queue is empty 46 | // 47 | // Pop must be called from a single, consumer goroutine 48 | func (q *Queue) Pop() interface{} { 49 | tail := q.tail 50 | next := (*node)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tail.next)))) // acquire 51 | if next != nil { 52 | q.tail = next 53 | v := next.val 54 | next.val = nil 55 | return v 56 | } 57 | return nil 58 | } 59 | 60 | // Empty returns true if the queue is empty 61 | // 62 | // Empty must be called from a single, consumer goroutine 63 | func (q *Queue) Empty() bool { 64 | tail := q.tail 65 | next := (*node)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&tail.next)))) 66 | return next == nil 67 | } 68 | -------------------------------------------------------------------------------- /rpcx/server/plugin_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/kudoochui/kudos/rpcx/client" 11 | "github.com/kudoochui/kudos/rpcx/protocol" 12 | ) 13 | 14 | type HeartbeatHandler struct{} 15 | 16 | func (h *HeartbeatHandler) HeartbeatRequest(ctx context.Context, req *protocol.Message) error { 17 | conn := ctx.Value(RemoteConnContextKey).(net.Conn) 18 | println("OnHeartbeat:", conn.RemoteAddr().String()) 19 | return nil 20 | } 21 | 22 | // TestPluginHeartbeat: go test -v -test.run TestPluginHeartbeat 23 | func TestPluginHeartbeat(t *testing.T) { 24 | h := &HeartbeatHandler{} 25 | s := NewServer( 26 | WithReadTimeout(time.Duration(5)*time.Second), 27 | WithWriteTimeout(time.Duration(5)*time.Second), 28 | ) 29 | s.Plugins.Add(h) 30 | s.RegisterName("Arith", new(Arith), "") 31 | wg := sync.WaitGroup{} 32 | wg.Add(2) 33 | go func() { 34 | // server 35 | defer wg.Done() 36 | err := s.Serve("tcp", "127.0.0.1:9001") 37 | if err != nil { 38 | t.Log(err.Error()) 39 | } 40 | }() 41 | go func() { 42 | // wait for server start complete 43 | time.Sleep(time.Second) 44 | defer wg.Done() 45 | // client 46 | opts := client.DefaultOption 47 | opts.Heartbeat = true 48 | opts.HeartbeatInterval = time.Second 49 | opts.IdleTimeout = time.Duration(5) * time.Second 50 | opts.ConnectTimeout = time.Duration(5) * time.Second 51 | // PeerDiscovery 52 | d, err := client.NewPeer2PeerDiscovery("tcp@127.0.0.1:9001", "") 53 | if err != nil { 54 | t.Fatalf("failed to NewPeer2PeerDiscovery: %v", err) 55 | } 56 | 57 | c := client.NewXClient("Arith", client.Failtry, client.RoundRobin, d, opts) 58 | i := 0 59 | for { 60 | i++ 61 | resp := &Reply{} 62 | c.Call(context.Background(), "Mul", &Args{A: 1, B: 5}, resp) 63 | t.Log("call Mul resp:", resp.C) 64 | time.Sleep(time.Second) 65 | if i > 10 { 66 | break 67 | } 68 | } 69 | c.Close() 70 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 71 | defer cancel() 72 | s.Shutdown(ctx) 73 | }() 74 | wg.Wait() 75 | } 76 | -------------------------------------------------------------------------------- /rpcx/serverplugin/trace.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | // import ( 4 | // "context" 5 | // "net" 6 | // "reflect" 7 | // "runtime" 8 | 9 | // "github.com/kudoochui/kudos/rpcx/protocol" 10 | // "golang.org/x/net/trace" 11 | // ) 12 | 13 | // type TracePlugin struct { 14 | // } 15 | 16 | // func (p *TracePlugin) Register(name string, rcvr interface{}, metadata string) error { 17 | // tr := trace.New("rpcx.Server", "Register") 18 | // defer tr.Finish() 19 | // tr.LazyPrintf("register %s: %T", name, rcvr) 20 | // return nil 21 | // } 22 | 23 | // func (p *TracePlugin) RegisterFunction(serviceName, fname string, fn interface{}, metadata string) error { 24 | // tr := trace.New("rpcx.Server", "RegisterFunction") 25 | // defer tr.Finish() 26 | // tr.LazyPrintf("register %s.%s: %T", serviceName, fname, GetFunctionName(fn)) 27 | // return nil 28 | // } 29 | 30 | // func GetFunctionName(i interface{}) string { 31 | // return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() 32 | // } 33 | 34 | // func (p *TracePlugin) PostConnAccept(conn net.Conn) (net.Conn, bool) { 35 | // tr := trace.New("rpcx.Server", "Accept") 36 | // defer tr.Finish() 37 | // tr.LazyPrintf("accept conn %s", conn.RemoteAddr().String()) 38 | // return conn, true 39 | // } 40 | 41 | // func (p *TracePlugin) PostReadRequest(ctx context.Context, r *protocol.Message, e error) error { 42 | // tr := trace.New("rpcx.Server", "ReadRequest") 43 | // defer tr.Finish() 44 | // tr.LazyPrintf("read request %s.%s, seq: %d", r.ServicePath, r.ServiceMethod, r.Seq()) 45 | // return nil 46 | // } 47 | 48 | // func (p *TracePlugin) PostWriteResponse(ctx context.Context, req *protocol.Message, res *protocol.Message, err error) error { 49 | // tr := trace.New("rpcx.Server", "WriteResponse") 50 | // defer tr.Finish() 51 | // if err == nil { 52 | // tr.LazyPrintf("succeed to call %s.%s, seq: %d", req.ServicePath, req.ServiceMethod, req.Seq()) 53 | // } else { 54 | // tr.LazyPrintf("failed to call %s.%s, seq: %d : %v", req.Seq, req.ServicePath, req.ServiceMethod, req.Seq(), err) 55 | // tr.SetError() 56 | // } 57 | 58 | // return nil 59 | // } 60 | -------------------------------------------------------------------------------- /component/web/webServer.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gorilla/mux" 7 | "github.com/kudoochui/kudos/log" 8 | "net/http" 9 | ) 10 | 11 | type Handler func(http.ResponseWriter, *http.Request) 12 | 13 | // ServeHTTP calls f(w, r). 14 | func (f Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 15 | f(w, r) 16 | } 17 | 18 | type WebServer struct { 19 | opts *Options 20 | server *http.Server 21 | router *mux.Router 22 | routeMap map[string]Handler 23 | prefixRouteMap map[string]http.Handler 24 | } 25 | 26 | func NewWebServer(opts ...Option) *WebServer { 27 | options := newOptions(opts...) 28 | 29 | web := &WebServer{ 30 | opts: options, 31 | routeMap: map[string]Handler{}, 32 | prefixRouteMap: map[string]http.Handler{}, 33 | } 34 | 35 | return web 36 | } 37 | 38 | func (w *WebServer) Route(path string, f Handler) { 39 | w.routeMap[path] = f 40 | } 41 | 42 | func (w *WebServer) PrefixRoute(path string, f http.Handler) { 43 | w.prefixRouteMap[path] = f 44 | } 45 | 46 | func (w *WebServer) OnInit() { 47 | 48 | } 49 | 50 | func (w *WebServer) OnDestroy() { 51 | // Create a deadline to wait for. 52 | ctx, _ := context.WithTimeout(context.Background(), w.opts.CloseTimeout) 53 | // Doesn't block if no connections, but will otherwise wait 54 | // until the timeout deadline. 55 | w.server.Shutdown(ctx) 56 | } 57 | 58 | func (w *WebServer) OnRun(closeSig chan bool) { 59 | w.router = mux.NewRouter() 60 | for p,f := range w.routeMap { 61 | w.router.HandleFunc(p, f) 62 | } 63 | 64 | for p,f := range w.prefixRouteMap { 65 | w.router.PathPrefix(p).Handler(f) 66 | } 67 | 68 | w.server = &http.Server{ 69 | Addr: fmt.Sprintf("%s:%d", w.opts.ListenIp, w.opts.ListenPort), 70 | WriteTimeout: w.opts.WriteTimeout, 71 | ReadTimeout: w.opts.ReadTimeout, 72 | IdleTimeout: w.opts.IdleTimeout, 73 | Handler: w.router, 74 | } 75 | 76 | log.Info("web server listen at: %s:%d", w.opts.ListenIp, w.opts.ListenPort) 77 | 78 | go func() { 79 | if err := w.server.ListenAndServe(); err != nil { 80 | log.Info("web server: %s", err) 81 | } 82 | }() 83 | } -------------------------------------------------------------------------------- /config/env/env_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // Copyright 2017 Faissal Elamraoui. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | package env 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | ) 21 | 22 | func TestEnvGet(t *testing.T) { 23 | gopath := Get("GOPATH", "") 24 | if gopath != os.Getenv("GOPATH") { 25 | t.Error("expected GOPATH not empty.") 26 | } 27 | 28 | noExistVar := Get("NOEXISTVAR", "foo") 29 | if noExistVar != "foo" { 30 | t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar) 31 | } 32 | } 33 | 34 | func TestEnvMustGet(t *testing.T) { 35 | gopath, err := MustGet("GOPATH") 36 | if err != nil { 37 | t.Error(err) 38 | } 39 | 40 | if gopath != os.Getenv("GOPATH") { 41 | t.Errorf("expected GOPATH to be the same, got %s.", gopath) 42 | } 43 | 44 | _, err = MustGet("NOEXISTVAR") 45 | if err == nil { 46 | t.Error("expected error to be non-nil") 47 | } 48 | } 49 | 50 | func TestEnvSet(t *testing.T) { 51 | Set("MYVAR", "foo") 52 | myVar := Get("MYVAR", "bar") 53 | if myVar != "foo" { 54 | t.Errorf("expected MYVAR to equal foo, got %s.", myVar) 55 | } 56 | } 57 | 58 | func TestEnvMustSet(t *testing.T) { 59 | err := MustSet("FOO", "bar") 60 | if err != nil { 61 | t.Error(err) 62 | } 63 | 64 | fooVar := os.Getenv("FOO") 65 | if fooVar != "bar" { 66 | t.Errorf("expected FOO variable to equal bar, got %s.", fooVar) 67 | } 68 | } 69 | 70 | func TestEnvGetAll(t *testing.T) { 71 | envMap := GetAll() 72 | if len(envMap) == 0 { 73 | t.Error("expected environment not empty.") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rpcx/log/default_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/fatih/color" 9 | ) 10 | 11 | type defaultLogger struct { 12 | *log.Logger 13 | } 14 | 15 | func (l *defaultLogger) Debug(v ...interface{}) { 16 | _ = l.Output(calldepth, header("DEBUG", fmt.Sprint(v...))) 17 | } 18 | 19 | func (l *defaultLogger) Debugf(format string, v ...interface{}) { 20 | _ = l.Output(calldepth, header("DEBUG", fmt.Sprintf(format, v...))) 21 | } 22 | 23 | func (l *defaultLogger) Info(v ...interface{}) { 24 | _ = l.Output(calldepth, header(color.GreenString("INFO "), fmt.Sprint(v...))) 25 | } 26 | 27 | func (l *defaultLogger) Infof(format string, v ...interface{}) { 28 | _ = l.Output(calldepth, header(color.GreenString("INFO "), fmt.Sprintf(format, v...))) 29 | } 30 | 31 | func (l *defaultLogger) Warn(v ...interface{}) { 32 | _ = l.Output(calldepth, header(color.YellowString("WARN "), fmt.Sprint(v...))) 33 | } 34 | 35 | func (l *defaultLogger) Warnf(format string, v ...interface{}) { 36 | _ = l.Output(calldepth, header(color.YellowString("WARN "), fmt.Sprintf(format, v...))) 37 | } 38 | 39 | func (l *defaultLogger) Error(v ...interface{}) { 40 | _ = l.Output(calldepth, header(color.RedString("ERROR"), fmt.Sprint(v...))) 41 | } 42 | 43 | func (l *defaultLogger) Errorf(format string, v ...interface{}) { 44 | _ = l.Output(calldepth, header(color.RedString("ERROR"), fmt.Sprintf(format, v...))) 45 | } 46 | 47 | func (l *defaultLogger) Fatal(v ...interface{}) { 48 | _ = l.Output(calldepth, header(color.MagentaString("FATAL"), fmt.Sprint(v...))) 49 | os.Exit(1) 50 | } 51 | 52 | func (l *defaultLogger) Fatalf(format string, v ...interface{}) { 53 | _ = l.Output(calldepth, header(color.MagentaString("FATAL"), fmt.Sprintf(format, v...))) 54 | os.Exit(1) 55 | } 56 | 57 | func (l *defaultLogger) Panic(v ...interface{}) { 58 | l.Logger.Panic(v...) 59 | } 60 | 61 | func (l *defaultLogger) Panicf(format string, v ...interface{}) { 62 | l.Logger.Panicf(format, v...) 63 | } 64 | 65 | func (l *defaultLogger) Handle(v ...interface{}) { 66 | l.Error(v...) 67 | } 68 | 69 | func header(lvl, msg string) string { 70 | return fmt.Sprintf("%s: %s", lvl, msg) 71 | } 72 | -------------------------------------------------------------------------------- /utils/file_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 utils 16 | 17 | import ( 18 | "path/filepath" 19 | "reflect" 20 | "testing" 21 | ) 22 | 23 | var noExistedFile = "/tmp/not_existed_file" 24 | 25 | func TestSelfPath(t *testing.T) { 26 | path := SelfPath() 27 | if path == "" { 28 | t.Error("path cannot be empty") 29 | } 30 | t.Logf("SelfPath: %s", path) 31 | } 32 | 33 | func TestSelfDir(t *testing.T) { 34 | dir := SelfDir() 35 | t.Logf("SelfDir: %s", dir) 36 | } 37 | 38 | func TestFileExists(t *testing.T) { 39 | if !FileExists("./file.go") { 40 | t.Errorf("./file.go should exists, but it didn't") 41 | } 42 | 43 | if FileExists(noExistedFile) { 44 | t.Errorf("Weird, how could this file exists: %s", noExistedFile) 45 | } 46 | } 47 | 48 | func TestSearchFile(t *testing.T) { 49 | path, err := SearchFile(filepath.Base(SelfPath()), SelfDir()) 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | t.Log(path) 54 | 55 | _, err = SearchFile(noExistedFile, ".") 56 | if err == nil { 57 | t.Errorf("err shouldnt be nil, got path: %s", SelfDir()) 58 | } 59 | } 60 | 61 | func TestGrepFile(t *testing.T) { 62 | _, err := GrepFile("", noExistedFile) 63 | if err == nil { 64 | t.Error("expect file-not-existed error, but got nothing") 65 | } 66 | 67 | path := filepath.Join(".", "testdata", "grepe.test") 68 | lines, err := GrepFile(`^\s*[^#]+`, path) 69 | if err != nil { 70 | t.Error(err) 71 | } 72 | if !reflect.DeepEqual(lines, []string{"hello", "world"}) { 73 | t.Errorf("expect [hello world], but receive %v", lines) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /component/connector/pomelo/options.go: -------------------------------------------------------------------------------- 1 | package pomelo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Option func(*Options) 8 | 9 | type Options struct { 10 | MaxConnNum int 11 | MaxMsgLen uint32 12 | HeartbeatTimeout time.Duration 13 | 14 | // websocket 15 | WSAddr string 16 | HTTPTimeout time.Duration 17 | CertFile string 18 | KeyFile string 19 | 20 | // tcp 21 | TCPAddr string 22 | } 23 | 24 | func newOptions(opts...Option) *Options { 25 | opt := &Options{ 26 | MaxConnNum: 20000, 27 | MaxMsgLen: 4096, 28 | HTTPTimeout: 10 * time.Second, 29 | HeartbeatTimeout: 20 * time.Second, 30 | } 31 | 32 | for _,o := range opts { 33 | o(opt) 34 | } 35 | 36 | return opt 37 | } 38 | 39 | // Max connections support. Default is 20000 40 | func MaxConnNum(num int) Option { 41 | return func(options *Options) { 42 | options.MaxConnNum = num 43 | } 44 | } 45 | 46 | // Max message length. If a message exceeds the limit, the connection sends a close message to the peer. Default is 4096 47 | func MaxMsgLen(length uint32) Option { 48 | return func(options *Options) { 49 | options.MaxMsgLen = length 50 | } 51 | } 52 | 53 | // Address of tcp server as "host:port" 54 | func TCPAddr(addr string) Option { 55 | return func(options *Options) { 56 | options.TCPAddr = addr 57 | } 58 | } 59 | 60 | // Address of websocket server as "host:port" 61 | func WSAddr(addr string) Option { 62 | return func(options *Options) { 63 | options.WSAddr = addr 64 | } 65 | } 66 | 67 | // Timeout for http handshake. Default is 10s 68 | func HTTPTimeout(t time.Duration) Option { 69 | return func(options *Options) { 70 | options.HTTPTimeout = t 71 | } 72 | } 73 | 74 | // Cert file for https 75 | func CertFile(f string) Option { 76 | return func(options *Options) { 77 | options.CertFile = f 78 | } 79 | } 80 | 81 | // Key file for https 82 | func KeyFile(f string) Option { 83 | return func(options *Options) { 84 | options.KeyFile = f 85 | } 86 | } 87 | 88 | // Heartbeat timeout. Default is 10s. Disconnect if after 2*t 89 | func HeartbeatTimeout(t time.Duration) Option { 90 | return func(options *Options) { 91 | options.HeartbeatTimeout = t 92 | } 93 | } -------------------------------------------------------------------------------- /component/connector/protobuf/options.go: -------------------------------------------------------------------------------- 1 | package protobuf 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Option func(*Options) 8 | 9 | type Options struct { 10 | MaxConnNum int 11 | MaxMsgLen uint32 12 | HeartbeatTimeout time.Duration 13 | 14 | // websocket 15 | WSAddr string 16 | HTTPTimeout time.Duration 17 | CertFile string 18 | KeyFile string 19 | 20 | // tcp 21 | TCPAddr string 22 | } 23 | 24 | func newOptions(opts...Option) *Options { 25 | opt := &Options{ 26 | MaxConnNum: 20000, 27 | MaxMsgLen: 4096, 28 | HTTPTimeout: 10 * time.Second, 29 | HeartbeatTimeout: 20 * time.Second, 30 | } 31 | 32 | for _,o := range opts { 33 | o(opt) 34 | } 35 | 36 | return opt 37 | } 38 | 39 | // Max connections support. Default is 20000 40 | func MaxConnNum(num int) Option { 41 | return func(options *Options) { 42 | options.MaxConnNum = num 43 | } 44 | } 45 | 46 | // Max message length. If a message exceeds the limit, the connection sends a close message to the peer. Default is 4096 47 | func MaxMsgLen(length uint32) Option { 48 | return func(options *Options) { 49 | options.MaxMsgLen = length 50 | } 51 | } 52 | 53 | // Address of tcp server as "host:port" 54 | func TCPAddr(addr string) Option { 55 | return func(options *Options) { 56 | options.TCPAddr = addr 57 | } 58 | } 59 | 60 | // Address of websocket server as "host:port" 61 | func WSAddr(addr string) Option { 62 | return func(options *Options) { 63 | options.WSAddr = addr 64 | } 65 | } 66 | 67 | // Timeout for http handshake. Default is 10s 68 | func HTTPTimeout(t time.Duration) Option { 69 | return func(options *Options) { 70 | options.HTTPTimeout = t 71 | } 72 | } 73 | 74 | // Cert file for https 75 | func CertFile(f string) Option { 76 | return func(options *Options) { 77 | options.CertFile = f 78 | } 79 | } 80 | 81 | // Key file for https 82 | func KeyFile(f string) Option { 83 | return func(options *Options) { 84 | options.KeyFile = f 85 | } 86 | } 87 | 88 | // Heartbeat timeout. Default is 10s. Disconnect if after 2*t 89 | func HeartbeatTimeout(t time.Duration) Option { 90 | return func(options *Options) { 91 | options.HeartbeatTimeout = t 92 | } 93 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kudos 2 | Kudos is a simple, high-performance, easy to expand and easy to deploy distributed game service framework 3 | based on microservice architecture, It is based on RPC of rpcx, supports pomelo communication protocol and 4 | can be easily applied to game development. 5 | 6 | [中文文档](Chinese.md) 7 | 8 | ## Features 9 | -**Easy to use**: Game development requires that basic components and services have been integrated and called directly. Especially friendly to those who are familiar with pomelo. 10 | 11 | -**Componentization**: The functions are divided into components and loaded as required. 12 | 13 | -**Distributed**: It can be deployed in multiple nodes or packaged together as a process. 14 | 15 | -**Microservice architecture, supporting service discovery**: Mainstream registries such as consult, etcd, zookeeper, etc. 16 | 17 | -**RPC based on rpcx**: rpcx is a high-performance RPC framework. Its performance is much higher than Dubbo, Motan, thrift and other frameworks, which is twice the performance of grpc. Support service governance. For more functions, please refer to:[ http://rpcx.io ] http://rpcx.io/ ) 18 | 19 | -**Cross language**: In addition to go, you can also access node services implemented in other languages. Thanks to rpcx. 20 | 21 | -**Support pomelo communication protocol**: The protocol is widely used in various game development, supporting multi terminal and multi language versions. 22 | 23 | -**Easy to deploy**: Each server is independent and independent and can be started independently. 24 | 25 | ## Installation 26 | `go get -u -v github.com/kudoochui/kudos` 27 | 28 | ## Getting started(开发脚手架) 29 | [kudosServer](https://github.com/kudoochui/kudosServer) 30 | 31 | ## 游戏架构参考 32 | [游戏微服务架构设计:MMORPG](https://www.toutiao.com/i6798800455955644935/) 33 | 34 | [游戏微服务架构设计:挂机类游戏](https://www.toutiao.com/i6798814918574342660/) 35 | 36 | [游戏微服务架构设计:棋牌游戏](https://www.toutiao.com/i6798815085935460876/) 37 | 38 | [游戏微服务架构设计:io游戏](https://www.toutiao.com/i6798815271386612231/) 39 | 40 | ## Roadmap 41 | - Add more connector 42 | - Actor support 43 | 44 | ## Community 45 | [wiki](https://github.com/kudoochui/kudos/wiki) 46 | 47 | QQ交流群:77584553 48 | 49 | 关注头条号:丁玲隆咚呛 50 | 分享更多内容 51 | 52 | ## 证书 53 | MIT License -------------------------------------------------------------------------------- /rpcx/client/ping_utils.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "strings" 7 | 8 | ping "github.com/go-ping/ping" 9 | ) 10 | 11 | func newWeightedICMPSelector(servers map[string]string) Selector { 12 | ss := createICMPWeighted(servers) 13 | return &weightedICMPSelector{servers: ss} 14 | } 15 | 16 | func (s weightedICMPSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string { 17 | ss := s.servers 18 | if len(ss) == 0 { 19 | return "" 20 | } 21 | w := nextWeighted(ss) 22 | if w == nil { 23 | return "" 24 | } 25 | return w.Server 26 | } 27 | 28 | func (s *weightedICMPSelector) UpdateServer(servers map[string]string) { 29 | ss := createICMPWeighted(servers) 30 | s.servers = ss 31 | } 32 | 33 | func createICMPWeighted(servers map[string]string) []*Weighted { 34 | var ss = make([]*Weighted, 0, len(servers)) 35 | for k := range servers { 36 | w := &Weighted{Server: k, Weight: 1, EffectiveWeight: 1} 37 | server := strings.Split(k, "@") 38 | host, _, _ := net.SplitHostPort(server[1]) 39 | rtt, _ := Ping(host) 40 | rtt = CalculateWeight(rtt) 41 | w.Weight = rtt 42 | w.EffectiveWeight = rtt 43 | ss = append(ss, w) 44 | } 45 | return ss 46 | } 47 | 48 | // Ping gets network traffic by ICMP 49 | func Ping(host string) (rtt int, err error) { 50 | rtt = 1000 //default and timeout is 1000 ms 51 | 52 | pinger, err := ping.NewPinger(host) 53 | if err != nil { 54 | return rtt, err 55 | } 56 | pinger.Count = 3 57 | stats := pinger.Statistics() 58 | rtt = int(stats.AvgRtt) 59 | 60 | return rtt, err 61 | } 62 | 63 | // CalculateWeight converts the rtt to weighted by: 64 | // 1. weight=191 if t <= 10 65 | // 2. weight=201 -t if 10 < t <=200 66 | // 3. weight=1 if 200 < t < 1000 67 | // 4. weight = 0 if t >= 1000 68 | // 69 | // It means servers that ping time t < 10 will be preferred 70 | // and servers won't be selected if t > 1000. 71 | // It is hard coded based on Ops experience. 72 | func CalculateWeight(rtt int) int { 73 | switch { 74 | case rtt >= 0 && rtt <= 10: 75 | return 191 76 | case rtt > 10 && rtt <= 200: 77 | return 201 - rtt 78 | case rtt > 100 && rtt < 1000: 79 | return 1 80 | default: 81 | return 0 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rpcx/util/buffer_pool.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math" 5 | "sync" 6 | ) 7 | 8 | type levelPool struct { 9 | size int 10 | pool sync.Pool 11 | } 12 | 13 | func newLevelPool(size int) *levelPool { 14 | return &levelPool{ 15 | size: size, 16 | pool: sync.Pool{ 17 | New: func() interface{} { 18 | data := make([]byte, size) 19 | return &data 20 | }, 21 | }, 22 | } 23 | } 24 | 25 | type LimitedPool struct { 26 | minSize int 27 | maxSize int 28 | pools []*levelPool 29 | } 30 | 31 | func NewLimitedPool(minSize, maxSize int) *LimitedPool { 32 | if maxSize < minSize { 33 | panic("maxSize can't be less than minSize") 34 | } 35 | const multiplier = 2 36 | var pools []*levelPool 37 | curSize := minSize 38 | for curSize < maxSize { 39 | pools = append(pools, newLevelPool(curSize)) 40 | curSize *= multiplier 41 | } 42 | pools = append(pools, newLevelPool(maxSize)) 43 | return &LimitedPool{ 44 | minSize: minSize, 45 | maxSize: maxSize, 46 | pools: pools, 47 | } 48 | } 49 | 50 | func (p *LimitedPool) findPool(size int) *levelPool { 51 | if size > p.maxSize { 52 | return nil 53 | } 54 | idx := int(math.Ceil(math.Log2(float64(size) / float64(p.minSize)))) 55 | if idx < 0 { 56 | idx = 0 57 | } 58 | if idx > len(p.pools)-1 { 59 | return nil 60 | } 61 | return p.pools[idx] 62 | } 63 | 64 | func (p *LimitedPool) findPutPool(size int) *levelPool { 65 | if size > p.maxSize { 66 | return nil 67 | } 68 | if size < p.minSize { 69 | return nil 70 | } 71 | 72 | idx := int(math.Floor(math.Log2(float64(size) / float64(p.minSize)))) 73 | if idx < 0 { 74 | idx = 0 75 | } 76 | if idx > len(p.pools)-1 { 77 | return nil 78 | } 79 | return p.pools[idx] 80 | } 81 | 82 | func (p *LimitedPool) Get(size int) *[]byte { 83 | sp := p.findPool(size) 84 | if sp == nil { 85 | data := make([]byte, size) 86 | return &data 87 | } 88 | buf := sp.pool.Get().(*[]byte) 89 | *buf = (*buf)[:size] 90 | return buf 91 | } 92 | 93 | func (p *LimitedPool) Put(b *[]byte) { 94 | sp := p.findPutPool(cap(*b)) 95 | if sp == nil { 96 | return 97 | } 98 | *b = (*b)[:cap(*b)] 99 | sp.pool.Put(b) 100 | } 101 | -------------------------------------------------------------------------------- /rpcx/client/circuit_breaker.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | var ( 10 | ErrBreakerOpen = errors.New("breaker open") 11 | ErrBreakerTimeout = errors.New("breaker time out") 12 | ) 13 | 14 | // ConsecCircuitBreaker is window sliding CircuitBreaker with failure threshold. 15 | type ConsecCircuitBreaker struct { 16 | lastFailureTime time.Time 17 | failures uint64 18 | failureThreshold uint64 19 | window time.Duration 20 | } 21 | 22 | // NewConsecCircuitBreaker returns a new ConsecCircuitBreaker. 23 | func NewConsecCircuitBreaker(failureThreshold uint64, window time.Duration) *ConsecCircuitBreaker { 24 | return &ConsecCircuitBreaker{ 25 | failureThreshold: failureThreshold, 26 | window: window, 27 | } 28 | } 29 | 30 | // Call Circuit function 31 | func (cb *ConsecCircuitBreaker) Call(fn func() error, d time.Duration) error { 32 | var err error 33 | 34 | if !cb.ready() { 35 | return ErrBreakerOpen 36 | } 37 | 38 | if d == 0 { 39 | err = fn() 40 | } else { 41 | c := make(chan error, 1) 42 | go func() { 43 | c <- fn() 44 | close(c) 45 | }() 46 | 47 | t := time.NewTimer(d) 48 | select { 49 | case e := <-c: 50 | err = e 51 | case <-t.C: 52 | err = ErrBreakerTimeout 53 | } 54 | t.Stop() 55 | } 56 | 57 | if err == nil { 58 | cb.success() 59 | } else { 60 | cb.fail() 61 | } 62 | 63 | return err 64 | } 65 | 66 | func (cb *ConsecCircuitBreaker) ready() bool { 67 | if time.Since(cb.lastFailureTime) > cb.window { 68 | cb.reset() 69 | return true 70 | } 71 | 72 | failures := atomic.LoadUint64(&cb.failures) 73 | return failures < cb.failureThreshold 74 | } 75 | 76 | func (cb *ConsecCircuitBreaker) success() { 77 | cb.reset() 78 | } 79 | func (cb *ConsecCircuitBreaker) fail() { 80 | atomic.AddUint64(&cb.failures, 1) 81 | cb.lastFailureTime = time.Now() 82 | } 83 | 84 | func (cb *ConsecCircuitBreaker) Success() { 85 | cb.success() 86 | } 87 | func (cb *ConsecCircuitBreaker) Fail() { 88 | cb.fail() 89 | } 90 | 91 | func (cb *ConsecCircuitBreaker) Ready() bool { 92 | return cb.ready() 93 | } 94 | 95 | func (cb *ConsecCircuitBreaker) reset() { 96 | atomic.StoreUint64(&cb.failures, 0) 97 | cb.lastFailureTime = time.Now() 98 | } 99 | -------------------------------------------------------------------------------- /rpcx/serverplugin/opencensus.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/kudoochui/kudos/rpcx/protocol" 8 | "github.com/kudoochui/kudos/rpcx/server" 9 | "github.com/kudoochui/kudos/rpcx/share" 10 | "go.opencensus.io/trace" 11 | ) 12 | 13 | type OpenCensusPlugin struct{} 14 | 15 | func (p OpenCensusPlugin) Register(name string, rcvr interface{}, metadata string) error { 16 | _, span := trace.StartSpan(context.Background(), "rpcx.Register") 17 | defer span.End() 18 | 19 | span.AddAttributes(trace.StringAttribute("register_service", name)) 20 | 21 | return nil 22 | } 23 | 24 | func (p OpenCensusPlugin) RegisterFunction(serviceName, fname string, fn interface{}, metadata string) error { 25 | _, span := trace.StartSpan(context.Background(), "rpcx.RegisterFunction") 26 | defer span.End() 27 | 28 | span.AddAttributes(trace.StringAttribute("register_function", serviceName+"."+fname)) 29 | return nil 30 | } 31 | 32 | func (p OpenCensusPlugin) PostConnAccept(conn net.Conn) (net.Conn, bool) { 33 | _, span := trace.StartSpan(context.Background(), "rpcx.AcceptConn") 34 | defer span.End() 35 | 36 | span.AddAttributes(trace.StringAttribute("remote_addr", conn.RemoteAddr().String())) 37 | return conn, true 38 | } 39 | 40 | func (p OpenCensusPlugin) PreHandleRequest(ctx context.Context, r *protocol.Message) error { 41 | parentContext, err := share.GetOpencensusSpanContextFromContext(ctx) 42 | if err != nil || parentContext == nil { 43 | return err 44 | } 45 | 46 | _, span1 := trace.StartSpanWithRemoteParent(ctx, "rpcx.service."+r.ServicePath+"."+r.ServiceMethod, *parentContext) 47 | clientConn := ctx.Value(server.RemoteConnContextKey).(net.Conn) 48 | span1.AddAttributes(trace.StringAttribute("remote_addr", clientConn.RemoteAddr().String())) 49 | 50 | if rpcxContext, ok := ctx.(*share.Context); ok { 51 | rpcxContext.SetValue(share.OpencensusSpanServerKey, span1) 52 | } 53 | return nil 54 | } 55 | 56 | func (p OpenCensusPlugin) PostWriteResponse(ctx context.Context, req *protocol.Message, res *protocol.Message, err error) error { 57 | if rpcxContext, ok := ctx.(*share.Context); ok { 58 | span1 := rpcxContext.Value(share.OpencensusSpanServerKey) 59 | if span1 != nil { 60 | span1.(*trace.Span).End() 61 | } 62 | } 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /protocol/pomelo/message/message_test.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "gotest.tools/assert" 5 | "testing" 6 | ) 7 | 8 | func TestMsgIdLen(t *testing.T) { 9 | assert.Equal(t, caculateMsgIdBytes(1), 1) 10 | assert.Equal(t, caculateMsgIdBytes(127), 1) 11 | assert.Equal(t, caculateMsgIdBytes(128), 2) 12 | var b []byte = nil 13 | t.Log(len(b)) 14 | } 15 | 16 | func Test_EncodeMsgId(t *testing.T) { 17 | buffer := make([]byte, 10) 18 | assert.Equal(t, encodeMsgId(1,1,buffer,1), 2) 19 | assert.DeepEqual(t, buffer, []byte{0, 1, 0, 0, 0, 0, 0, 0, 0, 0}) 20 | 21 | buffer1 := make([]byte, 10) 22 | assert.Equal(t, encodeMsgId(128,2,buffer1,1), 3) 23 | assert.DeepEqual(t, buffer1, []byte{0, 129, 0, 0, 0, 0, 0, 0, 0, 0}) 24 | } 25 | 26 | func Test_Encode(t *testing.T) { 27 | msg := []byte{1,2,3,4,5,6,7,8,9} 28 | packer := NewMessagePacker() 29 | assert.DeepEqual(t, packer.Encode(1,0,3,msg), []byte{1, 1, 0, 3, 1, 2, 3, 4, 5, 6, 7, 8, 9}) 30 | } 31 | 32 | func Test_Main(t *testing.T) { 33 | msg := []byte{1,2,3,4,5,6,7,8,9} 34 | packer := NewMessagePacker() 35 | buffer := packer.Encode(1,0,3, msg) 36 | t.Log(buffer) 37 | t.Log(Decode(buffer)) 38 | } 39 | 40 | func benchmarkEncode(b *testing.B, buffer []byte) { 41 | b.ReportAllocs() 42 | b.SetBytes(int64(len(buffer))) 43 | packer := NewMessagePacker() 44 | for i := 0; i < b.N; i++ { 45 | packer.Encode(1,0,3, buffer) 46 | } 47 | } 48 | 49 | func BenchmarkEncode(b *testing.B) { 50 | buffer := make([]byte, 1024*1024) 51 | 52 | b.Run("Encode=16", func(bb *testing.B) { benchmarkEncode(bb, buffer[:16])}) 53 | b.Run("Encode=32", func(bb *testing.B) { benchmarkEncode(bb, buffer[:32])}) 54 | b.Run("Encode=64", func(bb *testing.B) { benchmarkEncode(bb, buffer[:64])}) 55 | b.Run("Encode=128", func(bb *testing.B) { benchmarkEncode(bb, buffer[:128])}) 56 | b.Run("Encode=512", func(bb *testing.B) { benchmarkEncode(bb, buffer[:512])}) 57 | b.Run("Encode=1k", func(bb *testing.B) { benchmarkEncode(bb, buffer[:1024])}) 58 | b.Run("Encode=10k", func(bb *testing.B) { benchmarkEncode(bb, buffer[:1024*10])}) 59 | b.Run("Encode=64k", func(bb *testing.B) { benchmarkEncode(bb, buffer[:1024*64])}) 60 | b.Run("Encode=100k", func(bb *testing.B) { benchmarkEncode(bb, buffer[:1024*100])}) 61 | b.Run("Encode=1M", func(bb *testing.B) { benchmarkEncode(bb, buffer[:1024*1024])}) 62 | } -------------------------------------------------------------------------------- /rpcx/serverplugin/opentracing.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | opentracing "github.com/opentracing/opentracing-go" 8 | "github.com/opentracing/opentracing-go/ext" 9 | "github.com/opentracing/opentracing-go/log" 10 | "github.com/kudoochui/kudos/rpcx/protocol" 11 | "github.com/kudoochui/kudos/rpcx/server" 12 | "github.com/kudoochui/kudos/rpcx/share" 13 | ) 14 | 15 | type OpenTracingPlugin struct{} 16 | 17 | func (p OpenTracingPlugin) Register(name string, rcvr interface{}, metadata string) error { 18 | span1 := opentracing.StartSpan( 19 | "rpcx.Register") 20 | defer span1.Finish() 21 | 22 | span1.LogFields(log.String("register_service", name)) 23 | 24 | return nil 25 | } 26 | 27 | func (p OpenTracingPlugin) RegisterFunction(serviceName, fname string, fn interface{}, metadata string) error { 28 | span1 := opentracing.StartSpan( 29 | "rpcx.RegisterFunction") 30 | defer span1.Finish() 31 | 32 | span1.LogFields(log.String("register_function", serviceName+"."+fname)) 33 | return nil 34 | } 35 | 36 | func (p OpenTracingPlugin) PostConnAccept(conn net.Conn) (net.Conn, bool) { 37 | span1 := opentracing.StartSpan( 38 | "rpcx.AcceptConn") 39 | defer span1.Finish() 40 | 41 | span1.LogFields(log.String("remote_addr", conn.RemoteAddr().String())) 42 | return conn, true 43 | } 44 | 45 | func (p OpenTracingPlugin) PreHandleRequest(ctx context.Context, r *protocol.Message) error { 46 | wireContext, err := share.GetSpanContextFromContext(ctx) 47 | if err != nil || wireContext == nil { 48 | return err 49 | } 50 | span1 := opentracing.StartSpan( 51 | "rpcx.service."+r.ServicePath+"."+r.ServiceMethod, 52 | ext.RPCServerOption(wireContext)) 53 | 54 | clientConn := ctx.Value(server.RemoteConnContextKey).(net.Conn) 55 | span1.LogFields(log.String("remote_addr", clientConn.RemoteAddr().String())) 56 | 57 | if rpcxContext, ok := ctx.(*share.Context); ok { 58 | rpcxContext.SetValue(share.OpentracingSpanServerKey, span1) 59 | } 60 | return nil 61 | } 62 | 63 | func (p OpenTracingPlugin) PostWriteResponse(ctx context.Context, req *protocol.Message, res *protocol.Message, err error) error { 64 | if rpcxContext, ok := ctx.(*share.Context); ok { 65 | span1 := rpcxContext.Value(share.OpentracingSpanServerKey) 66 | if span1 != nil { 67 | span1.(opentracing.Span).Finish() 68 | } 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /service/channelService/channel.go: -------------------------------------------------------------------------------- 1 | package channelService 2 | 3 | import ( 4 | "errors" 5 | "github.com/kudoochui/kudos/log" 6 | "github.com/kudoochui/kudos/rpc" 7 | "github.com/kudoochui/kudos/rpcx/server" 8 | "github.com/kudoochui/kudos/service/codecService" 9 | "sync" 10 | ) 11 | 12 | type Channel struct { 13 | name string 14 | group map[int64]*server.ServerSession //uid => session 15 | lock sync.RWMutex 16 | } 17 | 18 | func NewChannel(name string) *Channel { 19 | return &Channel{ 20 | name: name, 21 | group: map[int64]*server.ServerSession{}, 22 | } 23 | } 24 | 25 | // Add user to channel. 26 | func (c *Channel) Add(s *server.ServerSession) error { 27 | c.lock.Lock() 28 | defer c.lock.Unlock() 29 | 30 | if _,ok := c.group[s.GetUserId()]; ok { 31 | return errors.New("already in channel") 32 | } 33 | c.group[s.GetUserId()] = s 34 | return nil 35 | } 36 | 37 | // Remove user from channel. 38 | func (c *Channel) Leave(uid int64) { 39 | c.lock.Lock() 40 | defer c.lock.Unlock() 41 | 42 | s := c.group[uid] 43 | if s == nil { 44 | return 45 | } 46 | 47 | delete(c.group, uid) 48 | } 49 | 50 | // Get userId array 51 | func (c *Channel) GetMembers() []int64 { 52 | c.lock.RLock() 53 | defer c.lock.RUnlock() 54 | 55 | array := make([]int64, 0) 56 | for k,_ := range c.group { 57 | array = append(array, k) 58 | } 59 | return array 60 | } 61 | 62 | func (c *Channel) GetSessions() map[int64]*server.ServerSession { 63 | c.lock.RLock() 64 | defer c.lock.RUnlock() 65 | 66 | m := make(map[int64]*server.ServerSession, 0) 67 | for k,v := range c.group { 68 | m[k] = v 69 | } 70 | return m 71 | } 72 | 73 | // Push message to all the members in the channel, exclude uid in the excludeUid. 74 | func (c *Channel) PushMessage(route string, msg interface{}, excludeUid []int64) { 75 | data, err := codecService.GetCodecService().Marshal(msg) 76 | if err != nil { 77 | log.Error("marshal error: %v", err) 78 | } 79 | 80 | excludeMap := make(map[int64]bool, len(excludeUid)) 81 | for _, uid := range excludeUid { 82 | excludeMap[uid] = true 83 | } 84 | args := &rpc.ArgsGroup{ 85 | Route: route, 86 | Payload: data, 87 | } 88 | 89 | c.lock.RLock() 90 | defer c.lock.RUnlock() 91 | for uid, session := range c.group { 92 | if !excludeMap[uid] { 93 | session.PushMessage("ChannelRemote", "PushMessage", args) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rpcx/client/multiple_servers_discovery.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/kudoochui/kudos/rpcx/log" 8 | ) 9 | 10 | // MultipleServersDiscovery is a multiple servers service discovery. 11 | // It always returns the current servers and uses can change servers dynamically. 12 | type MultipleServersDiscovery struct { 13 | pairsMu sync.RWMutex 14 | pairs []*KVPair 15 | chans []chan []*KVPair 16 | 17 | mu sync.Mutex 18 | } 19 | 20 | // NewMultipleServersDiscovery returns a new MultipleServersDiscovery. 21 | func NewMultipleServersDiscovery(pairs []*KVPair) (ServiceDiscovery, error) { 22 | return &MultipleServersDiscovery{ 23 | pairs: pairs, 24 | }, nil 25 | } 26 | 27 | // Clone clones this ServiceDiscovery with new servicePath. 28 | func (d *MultipleServersDiscovery) Clone(servicePath string) (ServiceDiscovery, error) { 29 | return d, nil 30 | } 31 | 32 | // SetFilter sets the filer. 33 | func (d *MultipleServersDiscovery) SetFilter(filter ServiceDiscoveryFilter) { 34 | } 35 | 36 | // GetServices returns the configured server 37 | func (d *MultipleServersDiscovery) GetServices() []*KVPair { 38 | d.pairsMu.RLock() 39 | defer d.pairsMu.RUnlock() 40 | 41 | return d.pairs 42 | } 43 | 44 | // WatchService returns a nil chan. 45 | func (d *MultipleServersDiscovery) WatchService() chan []*KVPair { 46 | d.mu.Lock() 47 | defer d.mu.Unlock() 48 | 49 | ch := make(chan []*KVPair, 10) 50 | d.chans = append(d.chans, ch) 51 | return ch 52 | } 53 | 54 | func (d *MultipleServersDiscovery) RemoveWatcher(ch chan []*KVPair) { 55 | d.mu.Lock() 56 | defer d.mu.Unlock() 57 | 58 | var chans []chan []*KVPair 59 | for _, c := range d.chans { 60 | if c == ch { 61 | continue 62 | } 63 | 64 | chans = append(chans, c) 65 | } 66 | 67 | d.chans = chans 68 | } 69 | 70 | // Update is used to update servers at runtime. 71 | func (d *MultipleServersDiscovery) Update(pairs []*KVPair) { 72 | d.mu.Lock() 73 | defer d.mu.Unlock() 74 | 75 | for _, ch := range d.chans { 76 | ch := ch 77 | go func() { 78 | defer func() { 79 | recover() 80 | }() 81 | select { 82 | case ch <- pairs: 83 | case <-time.After(time.Minute): 84 | log.Warn("chan is full and new change has been dropped") 85 | } 86 | }() 87 | } 88 | 89 | d.pairsMu.Lock() 90 | d.pairs = pairs 91 | d.pairsMu.Unlock() 92 | } 93 | 94 | func (d *MultipleServersDiscovery) Close() { 95 | } 96 | -------------------------------------------------------------------------------- /log/beego/alils/machine_group.go: -------------------------------------------------------------------------------- 1 | package alils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/http/httputil" 9 | ) 10 | 11 | // MachineGroupAttribute define the Attribute 12 | type MachineGroupAttribute struct { 13 | ExternalName string `json:"externalName"` 14 | TopicName string `json:"groupTopic"` 15 | } 16 | 17 | // MachineGroup define the machine Group 18 | type MachineGroup struct { 19 | Name string `json:"groupName"` 20 | Type string `json:"groupType"` 21 | MachineIDType string `json:"machineIdentifyType"` 22 | MachineIDList []string `json:"machineList"` 23 | 24 | Attribute MachineGroupAttribute `json:"groupAttribute"` 25 | 26 | CreateTime uint32 27 | LastModifyTime uint32 28 | 29 | project *LogProject 30 | } 31 | 32 | // Machine define the Machine 33 | type Machine struct { 34 | IP string 35 | UniqueID string `json:"machine-uniqueid"` 36 | UserdefinedID string `json:"userdefined-id"` 37 | } 38 | 39 | // MachineList define the Machine List 40 | type MachineList struct { 41 | Total int 42 | Machines []*Machine 43 | } 44 | 45 | // ListMachines returns machine list of this machine group. 46 | func (m *MachineGroup) ListMachines() (ms []*Machine, total int, err error) { 47 | h := map[string]string{ 48 | "x-sls-bodyrawsize": "0", 49 | } 50 | 51 | uri := fmt.Sprintf("/machinegroups/%v/machines", m.Name) 52 | r, err := request(m.project, "GET", uri, h, nil) 53 | if err != nil { 54 | return 55 | } 56 | 57 | buf, err := ioutil.ReadAll(r.Body) 58 | if err != nil { 59 | return 60 | } 61 | 62 | if r.StatusCode != http.StatusOK { 63 | errMsg := &errorMessage{} 64 | err = json.Unmarshal(buf, errMsg) 65 | if err != nil { 66 | err = fmt.Errorf("failed to remove config from machine group") 67 | dump, _ := httputil.DumpResponse(r, true) 68 | fmt.Println(dump) 69 | return 70 | } 71 | err = fmt.Errorf("%v:%v", errMsg.Code, errMsg.Message) 72 | return 73 | } 74 | 75 | body := &MachineList{} 76 | err = json.Unmarshal(buf, body) 77 | if err != nil { 78 | return 79 | } 80 | 81 | ms = body.Machines 82 | total = body.Total 83 | 84 | return 85 | } 86 | 87 | // GetAppliedConfigs returns applied configs of this machine group. 88 | func (m *MachineGroup) GetAppliedConfigs() (confNames []string, err error) { 89 | confNames, err = m.project.GetAppliedConfigs(m.Name) 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /utils/safemap.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 utils 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 | defer m.lock.RUnlock() 39 | if val, ok := m.bm[k]; ok { 40 | return val 41 | } 42 | return nil 43 | } 44 | 45 | // Set Maps the given key and value. Returns false 46 | // if the key is already in the map and changes nothing. 47 | func (m *BeeMap) Set(k interface{}, v interface{}) bool { 48 | m.lock.Lock() 49 | defer m.lock.Unlock() 50 | if val, ok := m.bm[k]; !ok { 51 | m.bm[k] = v 52 | } else if val != v { 53 | m.bm[k] = v 54 | } else { 55 | return false 56 | } 57 | return true 58 | } 59 | 60 | // Check Returns true if k is exist in the map. 61 | func (m *BeeMap) Check(k interface{}) bool { 62 | m.lock.RLock() 63 | defer m.lock.RUnlock() 64 | _, ok := m.bm[k] 65 | return ok 66 | } 67 | 68 | // Delete the given key and value. 69 | func (m *BeeMap) Delete(k interface{}) { 70 | m.lock.Lock() 71 | defer m.lock.Unlock() 72 | delete(m.bm, k) 73 | } 74 | 75 | // Items returns all items in safemap. 76 | func (m *BeeMap) Items() map[interface{}]interface{} { 77 | m.lock.RLock() 78 | defer m.lock.RUnlock() 79 | r := make(map[interface{}]interface{}) 80 | for k, v := range m.bm { 81 | r[k] = v 82 | } 83 | return r 84 | } 85 | 86 | // Count returns the number of items within the map. 87 | func (m *BeeMap) Count() int { 88 | m.lock.RLock() 89 | defer m.lock.RUnlock() 90 | return len(m.bm) 91 | } 92 | -------------------------------------------------------------------------------- /rpcx/serverplugin/alias.go: -------------------------------------------------------------------------------- 1 | package serverplugin 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/kudoochui/kudos/rpcx/protocol" 7 | ) 8 | 9 | var aliasAppliedKey = "__aliasAppliedKey" 10 | 11 | type aliasPair struct { 12 | servicePath, serviceMethod string 13 | } 14 | 15 | //AliasPlugin can be used to set aliases for services 16 | type AliasPlugin struct { 17 | Aliases map[string]*aliasPair 18 | ReseverseAliases map[string]*aliasPair 19 | } 20 | 21 | // Alias sets a alias for the serviceMethod. 22 | // For example Alias("anewpath&method", "Arith.mul") 23 | func (p *AliasPlugin) Alias(aliasServicePath, aliasServiceMethod string, servicePath, serviceMethod string) { 24 | p.Aliases[aliasServicePath+"."+aliasServiceMethod] = &aliasPair{ 25 | servicePath: servicePath, 26 | serviceMethod: serviceMethod, 27 | } 28 | p.ReseverseAliases[servicePath+"."+serviceMethod] = &aliasPair{ 29 | servicePath: aliasServicePath, 30 | serviceMethod: aliasServiceMethod, 31 | } 32 | } 33 | 34 | // NewAliasPlugin creates a new NewAliasPlugin 35 | func NewAliasPlugin() *AliasPlugin { 36 | return &AliasPlugin{ 37 | Aliases: make(map[string]*aliasPair), 38 | ReseverseAliases: make(map[string]*aliasPair), 39 | } 40 | } 41 | 42 | // PostReadRequest converts the alias of this service. 43 | func (p *AliasPlugin) PostReadRequest(ctx context.Context, r *protocol.Message, e error) error { 44 | var sp = r.ServicePath 45 | var sm = r.ServiceMethod 46 | 47 | k := sp + "." + sm 48 | if p.Aliases != nil { 49 | if pm := p.Aliases[k]; pm != nil { 50 | r.ServicePath = pm.servicePath 51 | r.ServiceMethod = pm.serviceMethod 52 | if r.Metadata == nil { 53 | r.Metadata = make(map[string]string) 54 | } 55 | r.Metadata[aliasAppliedKey] = "true" 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | // PreWriteResponse restore servicePath and serviceMethod. 62 | func (p *AliasPlugin) PreWriteResponse(ctx context.Context, r *protocol.Message, res *protocol.Message) error { 63 | if r.Metadata[aliasAppliedKey] != "true" { 64 | return nil 65 | } 66 | var sp = r.ServicePath 67 | var sm = r.ServiceMethod 68 | 69 | k := sp + "." + sm 70 | if p.ReseverseAliases != nil { 71 | if pm := p.ReseverseAliases[k]; pm != nil { 72 | r.ServicePath = pm.servicePath 73 | r.ServiceMethod = pm.serviceMethod 74 | delete(r.Metadata, aliasAppliedKey) 75 | if res != nil { 76 | res.ServicePath = pm.servicePath 77 | res.ServiceMethod = pm.serviceMethod 78 | } 79 | } 80 | } 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /service/msgService/msgService.go: -------------------------------------------------------------------------------- 1 | package msgService 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/log" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | 10 | var msgMgrSingleton *msgMgr 11 | var once sync.Once 12 | var gRouteId uint32 = 0 13 | 14 | func GetMsgService() *msgMgr { 15 | once.Do(func() { 16 | msgMgrSingleton = &msgMgr{ 17 | msgMap: map[string]uint32{}, 18 | //msgArray: make([]*MsgInfo,0), 19 | idMap: map[uint32]*MsgInfo{}, 20 | } 21 | }) 22 | return msgMgrSingleton 23 | } 24 | 25 | type msgMgr struct { 26 | msgMap map[string]uint32 27 | //msgArray []*MsgInfo 28 | idMap map[uint32]*MsgInfo 29 | } 30 | 31 | type MsgInfo struct { 32 | Route string 33 | RespId uint32 34 | MsgReqType reflect.Type 35 | MsgRespType reflect.Type 36 | } 37 | 38 | func (m *msgMgr) Register(route string, reqId uint32, respId uint32, msgReq interface{}, msgResp interface{}) { 39 | msgType := reflect.TypeOf(msgReq) 40 | if msgType == nil || msgType.Kind() != reflect.Ptr { 41 | log.Error("message request pointer required") 42 | } 43 | 44 | if _, ok := m.msgMap[route]; ok { 45 | log.Warning("route %s is already registered", route) 46 | return 47 | } 48 | 49 | msgRespType := reflect.TypeOf(msgResp) 50 | if msgRespType == nil || msgRespType.Kind() != reflect.Ptr { 51 | log.Error("message response pointer required") 52 | } 53 | 54 | i := new(MsgInfo) 55 | i.Route = route 56 | i.RespId = respId 57 | i.MsgReqType = msgType 58 | i.MsgRespType = msgRespType 59 | 60 | id := reqId 61 | if reqId == 0 { 62 | gRouteId++ 63 | id = gRouteId 64 | } 65 | m.msgMap[route] = id 66 | m.idMap[id] = i 67 | } 68 | 69 | func (m *msgMgr) RegisterPush(route string, routeId uint32) { 70 | if _, ok := m.msgMap[route]; ok { 71 | log.Warning("route %s is already registered", route) 72 | return 73 | } 74 | 75 | i := new(MsgInfo) 76 | i.Route = route 77 | 78 | id := routeId 79 | if routeId == 0 { 80 | gRouteId++ 81 | id = gRouteId 82 | } 83 | m.msgMap[route] = id 84 | m.idMap[id] = i 85 | } 86 | 87 | func (m *msgMgr) GetMsgByRouteId(routeId uint32) *MsgInfo { 88 | return m.idMap[routeId] 89 | } 90 | 91 | func (m *msgMgr) GetMsgByRoute(route string) *MsgInfo { 92 | routeId := m.msgMap[route] 93 | return m.idMap[routeId] 94 | } 95 | 96 | func (m *msgMgr) GetRouteId(route string) uint32 { 97 | return m.msgMap[route] 98 | } 99 | 100 | func (m *msgMgr) GetMsgMap() map[string]uint32 { 101 | return m.msgMap 102 | } 103 | -------------------------------------------------------------------------------- /component/connector/sessionRemote.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "context" 5 | "github.com/kudoochui/kudos/log" 6 | "github.com/kudoochui/kudos/rpc" 7 | "runtime" 8 | ) 9 | 10 | type SessionRemote struct { 11 | connector Connector 12 | } 13 | 14 | func NewSessionRemote(c Connector) *SessionRemote { 15 | return &SessionRemote{ 16 | connector: c, 17 | } 18 | } 19 | 20 | func (s *SessionRemote) Bind(ctx context.Context, session *rpc.Session, args *rpc.Args, reply *rpc.Reply) error { 21 | sessioinId := session.GetSessionId() 22 | agent,err := s.connector.GetSessionMap().GetAgent(sessioinId) 23 | if err != nil { 24 | log.Error("Bind can't find session:%s", sessioinId) 25 | return nil 26 | } 27 | agent.GetSession().SetUserId(args.MsgReq.(int64)) 28 | log.Debug("Bind success: %d", agent.GetSession().GetUserId()) 29 | return nil 30 | } 31 | 32 | func (s *SessionRemote) UnBind(ctx context.Context, session *rpc.Session, args *rpc.Args, reply *rpc.Reply) error { 33 | sessioinId := session.GetSessionId() 34 | agent,err := s.connector.GetSessionMap().GetAgent(sessioinId) 35 | if err != nil { 36 | log.Error("UnBind can't find session:%s", sessioinId) 37 | return nil 38 | } 39 | agent.GetSession().SetUserId(0) 40 | log.Debug("UnBind success: %d", agent.GetSession().GetUserId()) 41 | return nil 42 | } 43 | 44 | func (s *SessionRemote) Push(ctx context.Context, session *rpc.Session, args *rpc.Args, reply *rpc.Reply) error { 45 | sessioinId := session.GetSessionId() 46 | agent,err := s.connector.GetSessionMap().GetAgent(sessioinId) 47 | if err != nil { 48 | log.Error("Push can't find session:%s", sessioinId) 49 | return nil 50 | } 51 | settings := args.MsgReq.(map[string]interface{}) 52 | agent.GetSession().SyncSettings(settings) 53 | log.Debug("Push success: %v", settings) 54 | return nil 55 | } 56 | 57 | func (s *SessionRemote) KickBySid(ctx context.Context, session *rpc.Session, args *rpc.Args, reply *rpc.Reply) error { 58 | sessioinId := session.GetSessionId() 59 | agent,err := s.connector.GetSessionMap().GetAgent(sessioinId) 60 | if err != nil { 61 | log.Error("KickBySid can't find session:%s", sessioinId) 62 | return err 63 | } 64 | reason := args.MsgReq.(string) 65 | agent.KickMessage(reason) 66 | 67 | runtime.Gosched() 68 | agent.Close() 69 | return nil 70 | } 71 | 72 | func (s *SessionRemote) GetSessionCount(ctx context.Context, session *rpc.Session, args *rpc.Args, reply *rpc.Reply) error { 73 | count := s.connector.GetSessionMap().GetSessionCount() 74 | reply.MsgResp = count 75 | return nil 76 | } -------------------------------------------------------------------------------- /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 utils 16 | 17 | import "testing" 18 | 19 | var safeMap *BeeMap 20 | 21 | func TestNewBeeMap(t *testing.T) { 22 | safeMap = NewBeeMap() 23 | if safeMap == nil { 24 | t.Fatal("expected to return non-nil BeeMap", "got", safeMap) 25 | } 26 | } 27 | 28 | func TestSet(t *testing.T) { 29 | safeMap = NewBeeMap() 30 | if ok := safeMap.Set("astaxie", 1); !ok { 31 | t.Error("expected", true, "got", false) 32 | } 33 | } 34 | 35 | func TestReSet(t *testing.T) { 36 | safeMap := NewBeeMap() 37 | if ok := safeMap.Set("astaxie", 1); !ok { 38 | t.Error("expected", true, "got", false) 39 | } 40 | // set diff value 41 | if ok := safeMap.Set("astaxie", -1); !ok { 42 | t.Error("expected", true, "got", false) 43 | } 44 | 45 | // set same value 46 | if ok := safeMap.Set("astaxie", -1); ok { 47 | t.Error("expected", false, "got", true) 48 | } 49 | } 50 | 51 | func TestCheck(t *testing.T) { 52 | if exists := safeMap.Check("astaxie"); !exists { 53 | t.Error("expected", true, "got", false) 54 | } 55 | } 56 | 57 | func TestGet(t *testing.T) { 58 | if val := safeMap.Get("astaxie"); val.(int) != 1 { 59 | t.Error("expected value", 1, "got", val) 60 | } 61 | } 62 | 63 | func TestDelete(t *testing.T) { 64 | safeMap.Delete("astaxie") 65 | if exists := safeMap.Check("astaxie"); exists { 66 | t.Error("expected element to be deleted") 67 | } 68 | } 69 | 70 | func TestItems(t *testing.T) { 71 | safeMap := NewBeeMap() 72 | safeMap.Set("astaxie", "hello") 73 | for k, v := range safeMap.Items() { 74 | key := k.(string) 75 | value := v.(string) 76 | if key != "astaxie" { 77 | t.Error("expected the key should be astaxie") 78 | } 79 | if value != "hello" { 80 | t.Error("expected the value should be hello") 81 | } 82 | } 83 | } 84 | 85 | func TestCount(t *testing.T) { 86 | if count := safeMap.Count(); count != 0 { 87 | t.Error("expected count to be", 0, "got", count) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /config/env/env.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // Copyright 2017 Faissal Elamraoui. All Rights Reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // Package env is used to parse environment. 17 | package env 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "strings" 23 | 24 | "github.com/kudoochui/kudos/utils" 25 | ) 26 | 27 | var env *utils.BeeMap 28 | 29 | func init() { 30 | env = utils.NewBeeMap() 31 | for _, e := range os.Environ() { 32 | splits := strings.Split(e, "=") 33 | env.Set(splits[0], os.Getenv(splits[0])) 34 | } 35 | } 36 | 37 | // Get returns a value by key. 38 | // If the key does not exist, the default value will be returned. 39 | func Get(key string, defVal string) string { 40 | if val := env.Get(key); val != nil { 41 | return val.(string) 42 | } 43 | return defVal 44 | } 45 | 46 | // MustGet returns a value by key. 47 | // If the key does not exist, it will return an error. 48 | func MustGet(key string) (string, error) { 49 | if val := env.Get(key); val != nil { 50 | return val.(string), nil 51 | } 52 | return "", fmt.Errorf("no env variable with %s", key) 53 | } 54 | 55 | // Set sets a value in the ENV copy. 56 | // This does not affect the child process environment. 57 | func Set(key string, value string) { 58 | env.Set(key, value) 59 | } 60 | 61 | // MustSet sets a value in the ENV copy and the child process environment. 62 | // It returns an error in case the set operation failed. 63 | func MustSet(key string, value string) error { 64 | err := os.Setenv(key, value) 65 | if err != nil { 66 | return err 67 | } 68 | env.Set(key, value) 69 | return nil 70 | } 71 | 72 | // GetAll returns all keys/values in the current child process environment. 73 | func GetAll() map[string]string { 74 | items := env.Items() 75 | envs := make(map[string]string, env.Count()) 76 | 77 | for key, val := range items { 78 | switch key := key.(type) { 79 | case string: 80 | switch val := val.(type) { 81 | case string: 82 | envs[key] = val 83 | } 84 | } 85 | } 86 | return envs 87 | } 88 | -------------------------------------------------------------------------------- /rpcx/mailbox/queue/goring/queue_test.go: -------------------------------------------------------------------------------- 1 | package goring 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "runtime" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestPushPop(t *testing.T) { 16 | q := New(10) 17 | q.Push("hello") 18 | res, _ := q.Pop() 19 | assert.Equal(t, "hello", res) 20 | assert.True(t, q.Empty()) 21 | } 22 | 23 | func TestPushPopRepeated(t *testing.T) { 24 | q := New(10) 25 | for i := 0; i < 100; i++ { 26 | q.Push("hello") 27 | res, _ := q.Pop() 28 | assert.Equal(t, "hello", res) 29 | assert.True(t, q.Empty()) 30 | } 31 | } 32 | 33 | func TestPushPopMany(t *testing.T) { 34 | q := New(10) 35 | for i := 0; i < 10000; i++ { 36 | item := fmt.Sprintf("hello%v", i) 37 | q.Push(item) 38 | res, _ := q.Pop() 39 | assert.Equal(t, item, res) 40 | } 41 | assert.True(t, q.Empty()) 42 | } 43 | 44 | func TestPushPopMany2(t *testing.T) { 45 | q := New(10) 46 | for i := 0; i < 10000; i++ { 47 | item := fmt.Sprintf("hello%v", i) 48 | q.Push(item) 49 | } 50 | for i := 0; i < 10000; i++ { 51 | item := fmt.Sprintf("hello%v", i) 52 | res, _ := q.Pop() 53 | assert.Equal(t, item, res) 54 | } 55 | assert.True(t, q.Empty()) 56 | } 57 | 58 | func TestLfQueueConsistency(t *testing.T) { 59 | max := 1000000 60 | c := 100 61 | var wg sync.WaitGroup 62 | wg.Add(1) 63 | q := New(2) 64 | go func() { 65 | i := 0 66 | seen := make(map[string]string) 67 | for { 68 | r, ok := q.Pop() 69 | if !ok { 70 | runtime.Gosched() 71 | 72 | continue 73 | } 74 | i++ 75 | if r == nil { 76 | log.Printf("%#v, %#v", q, q.content) 77 | panic("consistency failure") 78 | } 79 | s := r.(string) 80 | _, present := seen[s] 81 | if present { 82 | log.Printf("item have already been seen %v", s) 83 | t.FailNow() 84 | } 85 | seen[s] = s 86 | 87 | if i == max { 88 | wg.Done() 89 | return 90 | } 91 | } 92 | }() 93 | 94 | for j := 0; j < c; j++ { 95 | jj := j 96 | cmax := max / c 97 | go func() { 98 | for i := 0; i < cmax; i++ { 99 | if rand.Intn(10) == 0 { 100 | time.Sleep(time.Duration(rand.Intn(1000))) 101 | } 102 | q.Push(fmt.Sprintf("%v %v", jj, i)) 103 | } 104 | }() 105 | } 106 | 107 | wg.Wait() 108 | time.Sleep(500 * time.Millisecond) 109 | // queue should be empty 110 | for i := 0; i < 100; i++ { 111 | r, ok := q.Pop() 112 | if ok { 113 | log.Printf("unexpected result %+v", r) 114 | t.FailNow() 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /rpcx/mailbox/queue/goring/queue.go: -------------------------------------------------------------------------------- 1 | package goring 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | type ringBuffer struct { 9 | buffer []interface{} 10 | head int64 11 | tail int64 12 | mod int64 13 | } 14 | 15 | type Queue struct { 16 | len int64 17 | content *ringBuffer 18 | lock sync.Mutex 19 | } 20 | 21 | func New(initialSize int64) *Queue { 22 | return &Queue{ 23 | content: &ringBuffer{ 24 | buffer: make([]interface{}, initialSize), 25 | head: 0, 26 | tail: 0, 27 | mod: initialSize, 28 | }, 29 | len: 0, 30 | } 31 | } 32 | 33 | // 满了就扩容为原来的2倍大小,有点像redis hash存储,不过它是渐近式扩容,这是一次性扩容到位。 34 | func (q *Queue) Push(item interface{}) { 35 | q.lock.Lock() 36 | c := q.content 37 | c.tail = (c.tail + 1) % c.mod 38 | if c.tail == c.head { 39 | var fillFactor int64 = 2 40 | // we need to resize 41 | 42 | newLen := c.mod * fillFactor 43 | newBuff := make([]interface{}, newLen) 44 | 45 | for i := int64(0); i < c.mod; i++ { 46 | buffIndex := (c.tail + i) % c.mod 47 | newBuff[i] = c.buffer[buffIndex] 48 | } 49 | // set the new buffer and reset head and tail 50 | newContent := &ringBuffer{ 51 | buffer: newBuff, 52 | head: 0, 53 | tail: c.mod, 54 | mod: newLen, 55 | } 56 | q.content = newContent 57 | } 58 | atomic.AddInt64(&q.len, 1) 59 | q.content.buffer[q.content.tail] = item 60 | q.lock.Unlock() 61 | } 62 | 63 | func (q *Queue) Length() int64 { 64 | return atomic.LoadInt64(&q.len) 65 | } 66 | 67 | func (q *Queue) Empty() bool { 68 | return q.Length() == 0 69 | } 70 | 71 | // single consumer 72 | func (q *Queue) Pop() (interface{}, bool) { 73 | if q.Empty() { 74 | return nil, false 75 | } 76 | // as we are a single consumer, no other thread can have poped the items there are guaranteed to be items now 77 | 78 | q.lock.Lock() 79 | c := q.content 80 | c.head = (c.head + 1) % c.mod 81 | res := c.buffer[c.head] 82 | c.buffer[c.head] = nil 83 | atomic.AddInt64(&q.len, -1) 84 | q.lock.Unlock() 85 | return res, true 86 | } 87 | 88 | func (q *Queue) PopMany(count int64) ([]interface{}, bool) { 89 | if q.Empty() { 90 | return nil, false 91 | } 92 | 93 | q.lock.Lock() 94 | c := q.content 95 | 96 | if count >= q.len { 97 | count = q.len 98 | } 99 | atomic.AddInt64(&q.len, -count) 100 | 101 | buffer := make([]interface{}, count) 102 | for i := int64(0); i < count; i++ { 103 | pos := (c.head + 1 + i) % c.mod 104 | buffer[i] = c.buffer[pos] 105 | c.buffer[pos] = nil 106 | } 107 | c.head = (c.head + count) % c.mod 108 | 109 | q.lock.Unlock() 110 | return buffer, true 111 | } 112 | -------------------------------------------------------------------------------- /rpcx/tool/xgen/parser/parser.go: -------------------------------------------------------------------------------- 1 | // Package parser parses Go code and keeps track of all the types defined 2 | // and provides access to all the constants defined for an int type. 3 | package parser 4 | 5 | import ( 6 | "go/ast" 7 | "go/build" 8 | "go/parser" 9 | "go/token" 10 | "unicode" 11 | "unicode/utf8" 12 | ) 13 | 14 | type Parser struct { 15 | PkgPath string 16 | PkgName string 17 | PkgFullName string 18 | StructNames map[string]bool 19 | } 20 | 21 | type visitor struct { 22 | *Parser 23 | 24 | name string 25 | explicit bool 26 | } 27 | 28 | func isExported(name string) bool { 29 | rune, _ := utf8.DecodeRuneInString(name) 30 | return unicode.IsUpper(rune) 31 | } 32 | 33 | func (v *visitor) Visit(n ast.Node) (w ast.Visitor) { 34 | switch n := n.(type) { 35 | case *ast.Package: 36 | return v 37 | case *ast.File: 38 | v.PkgName = n.Name.String() 39 | return v 40 | 41 | case *ast.GenDecl: 42 | return v 43 | case *ast.TypeSpec: 44 | v.name = n.Name.String() 45 | 46 | // Allow to specify non-structs explicitly independent of '-all' flag. 47 | if v.explicit { 48 | v.StructNames[v.name] = true 49 | return nil 50 | } 51 | return v 52 | case *ast.StructType: 53 | // if isExported(v.name) { 54 | //fmt.Printf("@@@@%s: %s\n", v.name, pretty.Sprint(n.Fields)) 55 | //v.StructNames = append(v.StructNames, v.name) 56 | // } 57 | return nil 58 | case *ast.FuncDecl: 59 | if isExported(v.name) { 60 | if n.Type.Params.NumFields() == 3 && n.Type.Results.NumFields() == 1 { 61 | params := n.Type.Params.List 62 | result := n.Type.Results.List[0] 63 | 64 | if result.Type.(*ast.Ident).Name != "error" { 65 | return nil 66 | } 67 | 68 | if p, ok := params[0].Type.(*ast.SelectorExpr); ok { 69 | x := p.X.(*ast.Ident) 70 | s := p.Sel 71 | 72 | if x.Name+"."+s.Name == "context.Context" { 73 | v.StructNames[v.name] = true 74 | } 75 | } 76 | } 77 | 78 | } 79 | return nil 80 | } 81 | return nil 82 | } 83 | 84 | func (p *Parser) Parse(fname string, isDir bool) error { 85 | p.PkgPath = build.Default.GOPATH 86 | 87 | fset := token.NewFileSet() 88 | if isDir { 89 | packages, err := parser.ParseDir(fset, fname, nil, parser.ParseComments) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | for _, pckg := range packages { 95 | ast.Walk(&visitor{Parser: p}, pckg) 96 | } 97 | } else { 98 | f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | ast.Walk(&visitor{Parser: p}, f) 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /rpc/session.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/service/idService" 5 | "sync" 6 | ) 7 | 8 | type Session struct { 9 | NodeId string 10 | SessionId int64 11 | 12 | UserId int64 13 | mu sync.RWMutex 14 | Settings map[string]string 15 | cachedSettings map[string]string 16 | } 17 | 18 | func NewSession(nodeId string) *Session { 19 | return &Session{ 20 | NodeId: nodeId, 21 | SessionId: idService.GenerateID().Int64(), 22 | Settings: map[string]string{}, 23 | cachedSettings: map[string]string{}, 24 | } 25 | } 26 | 27 | func NewSessionFromRpc(nodeId string, sessionId int64, userId int64) *Session { 28 | return &Session{ 29 | NodeId: nodeId, 30 | SessionId: sessionId, 31 | UserId: userId, 32 | Settings: map[string]string{}, 33 | cachedSettings: map[string]string{}, 34 | } 35 | } 36 | 37 | func (s *Session) GetNodeId() string { 38 | return s.NodeId 39 | } 40 | 41 | func (s *Session) GetSessionId() int64 { 42 | return s.SessionId 43 | } 44 | 45 | func (s *Session) GetUserId() int64 { 46 | s.mu.RLock() 47 | defer s.mu.RUnlock() 48 | return s.UserId 49 | } 50 | 51 | func (s *Session) SetUserId(userId int64) { 52 | s.mu.Lock() 53 | defer s.mu.Unlock() 54 | s.UserId = userId 55 | } 56 | 57 | func (s *Session) SyncSettings(settings map[string]interface{}) { 58 | _settings := make(map[string]string) 59 | for k,v := range settings { 60 | _settings[k] = v.(string) 61 | } 62 | s.Settings = _settings 63 | } 64 | 65 | func (s *Session) Get(key string) string { 66 | return s.Settings[key] 67 | } 68 | 69 | func (s *Session) Set(key, value string) { 70 | if s.Settings == nil { 71 | s.Settings = make(map[string]string) 72 | } 73 | s.Settings[key] = value 74 | } 75 | 76 | func (s *Session) Remove(key string) { 77 | delete(s.Settings, key) 78 | } 79 | 80 | func (s *Session) GetCache(key string) string { 81 | return s.cachedSettings[key] 82 | } 83 | 84 | func (s *Session) SetCache(key, value string) { 85 | if s.cachedSettings == nil { 86 | s.cachedSettings = make(map[string]string) 87 | } 88 | s.cachedSettings[key] = value 89 | } 90 | 91 | func (s *Session) RemoveCache(key string) { 92 | delete(s.cachedSettings, key) 93 | } 94 | 95 | func (s *Session) Clone() *Session { 96 | session := &Session{ 97 | NodeId: s.NodeId, 98 | SessionId: s.SessionId, 99 | UserId: s.UserId, 100 | Settings: map[string]string{}, 101 | cachedSettings: map[string]string{}, 102 | } 103 | 104 | for k,v := range s.Settings { 105 | session.Settings[k] = v 106 | } 107 | 108 | for k,v := range s.cachedSettings { 109 | session.cachedSettings[k] = v 110 | } 111 | return session 112 | } -------------------------------------------------------------------------------- /component/connector/protobuf/agentHandler.go: -------------------------------------------------------------------------------- 1 | package protobuf 2 | 3 | import ( 4 | "github.com/kudoochui/kudos/log" 5 | "github.com/kudoochui/kudos/protocol/protobuf/pkg" 6 | "github.com/kudoochui/kudos/rpc" 7 | "github.com/kudoochui/kudos/service/msgService" 8 | "github.com/kudoochui/kudos/utils/timer" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | type agentHandler struct { 14 | agent *agent 15 | timerHandler *timer.Timer 16 | } 17 | 18 | func NewAgentHandler(a *agent) *agentHandler { 19 | return &agentHandler{agent:a} 20 | } 21 | 22 | func (h *agentHandler) Handle(pkgType uint32, body []byte) { 23 | switch pkgType { 24 | case uint32(pkg.EMsgType_TYPE_HEARTBEAT): 25 | h.handleHeartbeat(pkgType, body) 26 | default: 27 | h.handleData(pkgType, body) 28 | } 29 | } 30 | 31 | func (h *agentHandler) handleHeartbeat(pkgType uint32, body []byte) { 32 | buffer := pkg.Encode(uint32(pkg.EMsgType_TYPE_HEARTBEAT), nil) 33 | h.agent.Write(buffer) 34 | 35 | if h.timerHandler != nil { 36 | h.agent.connector.timers.ClearTimeout(h.timerHandler) 37 | } 38 | 39 | //heartbeat timeout close the socket 40 | h.timerHandler = h.agent.connector.timers.AfterFunc(2*h.agent.connector.opts.HeartbeatTimeout, func() { 41 | log.Debug("heart beat overtime") 42 | h.agent.Close() 43 | }) 44 | } 45 | 46 | func (h *agentHandler) handleData(pkgType uint32, body []byte) { 47 | msgId, data := pkgType, body 48 | 49 | msgInfo := msgService.GetMsgService().GetMsgByRouteId(msgId) 50 | if msgInfo == nil { 51 | log.Error("invalid route id") 52 | return 53 | } 54 | 55 | args := &rpc.Args{ 56 | MsgId: int(msgInfo.RespId), 57 | MsgReq: data, 58 | } 59 | 60 | msgResp := reflect.New(msgInfo.MsgRespType.Elem()).Interface() 61 | rr := strings.Split(msgInfo.Route, ".") 62 | if len(rr) < 3 { 63 | log.Error("route format error") 64 | return 65 | } 66 | nodeName := rr[0] 67 | servicePath := rr[1] 68 | serviceName := rr[2] 69 | 70 | if h.agent.connector.customerRoute != nil { 71 | var err error 72 | servicePath, err = h.agent.connector.customerRoute(h.agent.session, servicePath, serviceName) 73 | if err != nil { 74 | log.Error("customer route error: %v", err) 75 | reply := &pkg.RespResult{ 76 | Code: int32(pkg.EErrorCode_ERROR_ROUTE_ID), 77 | Msg: err.Error(), 78 | } 79 | h.agent.WriteResponse(int(pkg.EMsgType_TYPE_COMMON_RESULT), reply) 80 | return 81 | } 82 | } 83 | if h.agent.connector.handlerFilter != nil { 84 | h.agent.connector.handlerFilter.Before(servicePath+"."+serviceName, args) 85 | } 86 | h.agent.connector.proxy.Go(nodeName, servicePath, serviceName, h.agent.session, args, msgResp, h.agent.chanRet) 87 | //rpcClientService.GetRpcClientService().Go(nodeName, servicePath, serviceName, h.agent.session, args, msgResp, h.agent.chanRet) 88 | } -------------------------------------------------------------------------------- /utils/file.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 utils 16 | 17 | import ( 18 | "bufio" 19 | "errors" 20 | "io" 21 | "os" 22 | "path/filepath" 23 | "regexp" 24 | ) 25 | 26 | // SelfPath gets compiled executable file absolute path 27 | func SelfPath() string { 28 | path, _ := filepath.Abs(os.Args[0]) 29 | return path 30 | } 31 | 32 | // SelfDir gets compiled executable file directory 33 | func SelfDir() string { 34 | return filepath.Dir(SelfPath()) 35 | } 36 | 37 | // FileExists reports whether the named file or directory exists. 38 | func FileExists(name string) bool { 39 | if _, err := os.Stat(name); err != nil { 40 | if os.IsNotExist(err) { 41 | return false 42 | } 43 | } 44 | return true 45 | } 46 | 47 | // SearchFile Search a file in paths. 48 | // this is often used in search config file in /etc ~/ 49 | func SearchFile(filename string, paths ...string) (fullpath string, err error) { 50 | for _, path := range paths { 51 | if fullpath = filepath.Join(path, filename); FileExists(fullpath) { 52 | return 53 | } 54 | } 55 | err = errors.New(fullpath + " not found in paths") 56 | return 57 | } 58 | 59 | // GrepFile like command grep -E 60 | // for example: GrepFile(`^hello`, "hello.txt") 61 | // \n is striped while read 62 | func GrepFile(patten string, filename string) (lines []string, err error) { 63 | re, err := regexp.Compile(patten) 64 | if err != nil { 65 | return 66 | } 67 | 68 | fd, err := os.Open(filename) 69 | if err != nil { 70 | return 71 | } 72 | lines = make([]string, 0) 73 | reader := bufio.NewReader(fd) 74 | prefix := "" 75 | var isLongLine bool 76 | for { 77 | byteLine, isPrefix, er := reader.ReadLine() 78 | if er != nil && er != io.EOF { 79 | return nil, er 80 | } 81 | if er == io.EOF { 82 | break 83 | } 84 | line := string(byteLine) 85 | if isPrefix { 86 | prefix += line 87 | continue 88 | } else { 89 | isLongLine = true 90 | } 91 | 92 | line = prefix + line 93 | if isLongLine { 94 | prefix = "" 95 | } 96 | if re.MatchString(line) { 97 | lines = append(lines, line) 98 | } 99 | } 100 | return lines, nil 101 | } 102 | -------------------------------------------------------------------------------- /log/beego/accesslog.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 | "bytes" 19 | "strings" 20 | "encoding/json" 21 | "fmt" 22 | "time" 23 | ) 24 | 25 | const ( 26 | apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s" 27 | apacheFormat = "APACHE_FORMAT" 28 | jsonFormat = "JSON_FORMAT" 29 | ) 30 | 31 | // AccessLogRecord struct for holding access log data. 32 | type AccessLogRecord struct { 33 | RemoteAddr string `json:"remote_addr"` 34 | RequestTime time.Time `json:"request_time"` 35 | RequestMethod string `json:"request_method"` 36 | Request string `json:"request"` 37 | ServerProtocol string `json:"server_protocol"` 38 | Host string `json:"host"` 39 | Status int `json:"status"` 40 | BodyBytesSent int64 `json:"body_bytes_sent"` 41 | ElapsedTime time.Duration `json:"elapsed_time"` 42 | HTTPReferrer string `json:"http_referrer"` 43 | HTTPUserAgent string `json:"http_user_agent"` 44 | RemoteUser string `json:"remote_user"` 45 | } 46 | 47 | func (r *AccessLogRecord) json() ([]byte, error) { 48 | buffer := &bytes.Buffer{} 49 | encoder := json.NewEncoder(buffer) 50 | disableEscapeHTML(encoder) 51 | 52 | err := encoder.Encode(r) 53 | return buffer.Bytes(), err 54 | } 55 | 56 | func disableEscapeHTML(i interface{}) { 57 | if e, ok := i.(interface { 58 | SetEscapeHTML(bool) 59 | }); ok { 60 | e.SetEscapeHTML(false) 61 | } 62 | } 63 | 64 | // AccessLog - Format and print access log. 65 | func AccessLog(r *AccessLogRecord, format string) { 66 | var msg string 67 | switch format { 68 | case apacheFormat: 69 | timeFormatted := r.RequestTime.Format("02/Jan/2006 03:04:05") 70 | msg = fmt.Sprintf(apacheFormatPattern, r.RemoteAddr, timeFormatted, r.Request, r.Status, r.BodyBytesSent, 71 | r.ElapsedTime.Seconds(), r.HTTPReferrer, r.HTTPUserAgent) 72 | case jsonFormat: 73 | fallthrough 74 | default: 75 | jsonData, err := r.json() 76 | if err != nil { 77 | msg = fmt.Sprintf(`{"Error": "%s"}`, err) 78 | } else { 79 | msg = string(jsonData) 80 | } 81 | } 82 | beeLogger.writeMsg(levelLoggerImpl, strings.TrimSpace(msg)) 83 | } 84 | -------------------------------------------------------------------------------- /network/tcp_server.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "net" 5 | "github.com/kudoochui/kudos/log" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type TCPServer struct { 11 | Addr string 12 | MaxConnNum int 13 | NewAgent func(*TCPConn) Agent 14 | ln net.Listener 15 | conns ConnSet 16 | mutexConns sync.Mutex 17 | wgLn sync.WaitGroup 18 | wgConns sync.WaitGroup 19 | 20 | // msg parser 21 | //LenMsgLen int 22 | //MinMsgLen uint32 23 | //MaxMsgLen uint32 24 | //LittleEndian bool 25 | //msgParser *MsgParser 26 | } 27 | 28 | func (server *TCPServer) Start() { 29 | server.init() 30 | go server.run() 31 | } 32 | 33 | func (server *TCPServer) init() { 34 | ln, err := net.Listen("tcp", server.Addr) 35 | if err != nil { 36 | log.Error("%v", err) 37 | } 38 | 39 | if server.MaxConnNum <= 0 { 40 | server.MaxConnNum = 10000 41 | log.Warning("invalid MaxConnNum, reset to %v", server.MaxConnNum) 42 | } 43 | if server.NewAgent == nil { 44 | log.Error("NewAgent must not be nil") 45 | } 46 | 47 | server.ln = ln 48 | server.conns = make(ConnSet) 49 | } 50 | 51 | func (server *TCPServer) run() { 52 | server.wgLn.Add(1) 53 | defer server.wgLn.Done() 54 | 55 | var tempDelay time.Duration 56 | for { 57 | conn, err := server.ln.Accept() 58 | if err != nil { 59 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 60 | if tempDelay == 0 { 61 | tempDelay = 5 * time.Millisecond 62 | } else { 63 | tempDelay *= 2 64 | } 65 | if max := 1 * time.Second; tempDelay > max { 66 | tempDelay = max 67 | } 68 | log.Warning("accept error: %v; retrying in %v", err, tempDelay) 69 | time.Sleep(tempDelay) 70 | continue 71 | } 72 | return 73 | } 74 | tempDelay = 0 75 | 76 | server.mutexConns.Lock() 77 | if len(server.conns) >= server.MaxConnNum { 78 | server.mutexConns.Unlock() 79 | conn.Close() 80 | log.Debug("too many connections") 81 | continue 82 | } 83 | server.conns[conn] = struct{}{} 84 | server.mutexConns.Unlock() 85 | 86 | server.wgConns.Add(1) 87 | 88 | tcpConn := newTCPConn(conn) 89 | agent := server.NewAgent(tcpConn) 90 | go func() { 91 | agent.Run() 92 | 93 | // cleanup 94 | tcpConn.Close() 95 | server.mutexConns.Lock() 96 | delete(server.conns, conn) 97 | server.mutexConns.Unlock() 98 | agent.OnClose() 99 | 100 | server.wgConns.Done() 101 | }() 102 | } 103 | } 104 | 105 | func (server *TCPServer) Close() { 106 | server.ln.Close() 107 | server.wgLn.Wait() 108 | 109 | server.mutexConns.Lock() 110 | for conn := range server.conns { 111 | conn.Close() 112 | } 113 | server.conns = nil 114 | server.mutexConns.Unlock() 115 | server.wgConns.Wait() 116 | } 117 | -------------------------------------------------------------------------------- /rpcx/client/oneclient_pool.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "sync/atomic" 5 | 6 | "github.com/kudoochui/kudos/rpcx/protocol" 7 | ) 8 | 9 | // OneClientPool is a oneclient pool with fixed size. 10 | // It uses roundrobin algorithm to call its xclients. 11 | // All oneclients share the same configurations such as ServiceDiscovery and serverMessageChan. 12 | type OneClientPool struct { 13 | count uint64 14 | index uint64 15 | oneclients []*OneClient 16 | auth string 17 | 18 | failMode FailMode 19 | selectMode SelectMode 20 | discovery ServiceDiscovery 21 | option Option 22 | serverMessageChan chan<- *protocol.Message 23 | } 24 | 25 | // NewOneClientPool creates a fixed size OneClient pool. 26 | func NewOneClientPool(count int, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option) *OneClientPool { 27 | pool := &OneClientPool{ 28 | count: uint64(count), 29 | oneclients: make([]*OneClient, count), 30 | failMode: failMode, 31 | selectMode: selectMode, 32 | discovery: discovery, 33 | option: option, 34 | } 35 | 36 | for i := 0; i < count; i++ { 37 | oneclient := NewOneClient(failMode, selectMode, discovery, option) 38 | pool.oneclients[i] = oneclient 39 | } 40 | return pool 41 | } 42 | 43 | // NewBidirectionalOneClientPool creates a BidirectionalOneClient pool with fixed size. 44 | func NewBidirectionalOneClientPool(count int, failMode FailMode, selectMode SelectMode, discovery ServiceDiscovery, option Option, serverMessageChan chan<- *protocol.Message) *OneClientPool { 45 | pool := &OneClientPool{ 46 | count: uint64(count), 47 | oneclients: make([]*OneClient, count), 48 | failMode: failMode, 49 | selectMode: selectMode, 50 | discovery: discovery, 51 | option: option, 52 | serverMessageChan: serverMessageChan, 53 | } 54 | 55 | for i := 0; i < count; i++ { 56 | oneclient := NewBidirectionalOneClient(failMode, selectMode, discovery, option, serverMessageChan) 57 | pool.oneclients[i] = oneclient 58 | } 59 | return pool 60 | } 61 | 62 | // Auth sets s token for Authentication. 63 | func (c *OneClientPool) Auth(auth string) { 64 | c.auth = auth 65 | 66 | for _, v := range c.oneclients { 67 | v.Auth(auth) 68 | } 69 | } 70 | 71 | // Get returns a OneClient. 72 | // It does not remove this OneClient from its cache so you don't need to put it back. 73 | // Don't close this OneClient because maybe other goroutines are using this OneClient. 74 | func (p *OneClientPool) Get() *OneClient { 75 | i := atomic.AddUint64(&p.index, 1) 76 | picked := int(i % p.count) 77 | return p.oneclients[picked] 78 | } 79 | 80 | // Close this pool. 81 | // Please make sure it won't be used any more. 82 | func (p OneClientPool) Close() { 83 | for _, c := range p.oneclients { 84 | c.Close() 85 | } 86 | p.oneclients = nil 87 | } 88 | -------------------------------------------------------------------------------- /network/tcp_msg.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "github.com/kudoochui/kudos/protocol/protobuf/pkg" 7 | "io" 8 | ) 9 | 10 | // -------------- 11 | // | len | data | 12 | // -------------- 13 | type MsgParser struct { 14 | lenMsgLen uint32 15 | minMsgLen uint32 16 | maxMsgLen uint32 17 | littleEndian bool 18 | } 19 | 20 | func NewMsgParser() *MsgParser { 21 | p := new(MsgParser) 22 | p.lenMsgLen = 4 23 | p.minMsgLen = 1 24 | p.maxMsgLen = 4096 25 | p.littleEndian = false 26 | 27 | return p 28 | } 29 | 30 | // It's dangerous to call the method on reading or writing 31 | func (p *MsgParser) SetMsgLen(lenMsgLen int, minMsgLen uint32, maxMsgLen uint32) { 32 | p.lenMsgLen, p.minMsgLen, p.maxMsgLen = uint32(lenMsgLen), minMsgLen, maxMsgLen 33 | } 34 | 35 | // It's dangerous to call the method on reading or writing 36 | func (p *MsgParser) SetByteOrder(littleEndian bool) { 37 | p.littleEndian = littleEndian 38 | } 39 | 40 | // goroutine safe 41 | func (p *MsgParser) Read(conn *TCPConn) (uint32, []byte, error) { 42 | var b [4]byte 43 | 44 | // read len 45 | if _, err := io.ReadFull(conn, b[:]); err != nil { 46 | return 0, nil, err 47 | } 48 | 49 | // parse len 50 | var msgLen uint32 51 | if p.littleEndian { 52 | msgLen = binary.LittleEndian.Uint32(b[:]) 53 | } else { 54 | msgLen = binary.BigEndian.Uint32(b[:]) 55 | } 56 | 57 | // check len 58 | if msgLen > p.maxMsgLen { 59 | return 0, nil, errors.New("message too long") 60 | } else if msgLen < p.minMsgLen { 61 | return 0, nil, errors.New("message too short") 62 | } 63 | 64 | // data 65 | msgData := make([]byte, msgLen) 66 | if _, err := io.ReadFull(conn, msgData); err != nil { 67 | return 0, nil, err 68 | } 69 | 70 | var pkgType uint32 71 | if p.littleEndian { 72 | pkgType = binary.LittleEndian.Uint32(msgData[:pkg.PKG_TYPE_BYTES]) 73 | } else { 74 | pkgType = binary.BigEndian.Uint32(msgData[:pkg.PKG_TYPE_BYTES]) 75 | } 76 | body := msgData[pkg.PKG_TYPE_BYTES:] 77 | 78 | return pkgType, body, nil 79 | } 80 | 81 | // goroutine safe 82 | func (p *MsgParser) Write(conn *TCPConn, respId uint32, data []byte) error { 83 | // get len 84 | var msgLen = uint32(len(data) + 4) 85 | 86 | // check len 87 | if msgLen > p.maxMsgLen { 88 | return errors.New("message too long") 89 | } else if msgLen < p.minMsgLen { 90 | return errors.New("message too short") 91 | } 92 | 93 | msg := make([]byte, uint32(p.lenMsgLen)+msgLen) 94 | 95 | // write len 96 | if p.littleEndian { 97 | binary.LittleEndian.PutUint32(msg, msgLen) 98 | binary.LittleEndian.PutUint32(msg[p.lenMsgLen:], respId) 99 | } else { 100 | binary.BigEndian.PutUint32(msg, msgLen) 101 | binary.BigEndian.PutUint32(msg[p.lenMsgLen:], respId) 102 | } 103 | 104 | // write data 105 | l := p.lenMsgLen + 4 106 | copy(msg[l:], data) 107 | 108 | conn.WriteMessage(msg) 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /rpcx/share/context.go: -------------------------------------------------------------------------------- 1 | package share 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | 9 | opentracing "github.com/opentracing/opentracing-go" 10 | "go.opencensus.io/trace" 11 | ) 12 | 13 | // var _ context.Context = &Context{} 14 | 15 | // Context is a rpcx customized Context that can contains multiple values. 16 | type Context struct { 17 | tags map[interface{}]interface{} 18 | context.Context 19 | } 20 | 21 | func NewContext(ctx context.Context) *Context { 22 | return &Context{ 23 | Context: ctx, 24 | tags: make(map[interface{}]interface{}), 25 | } 26 | } 27 | 28 | func (c *Context) Value(key interface{}) interface{} { 29 | if c.tags == nil { 30 | c.tags = make(map[interface{}]interface{}) 31 | } 32 | 33 | if v, ok := c.tags[key]; ok { 34 | return v 35 | } 36 | return c.Context.Value(key) 37 | } 38 | 39 | func (c *Context) SetValue(key, val interface{}) { 40 | if c.tags == nil { 41 | c.tags = make(map[interface{}]interface{}) 42 | } 43 | c.tags[key] = val 44 | } 45 | 46 | func (c *Context) String() string { 47 | return fmt.Sprintf("%v.WithValue(%v)", c.Context, c.tags) 48 | } 49 | 50 | func WithValue(parent context.Context, key, val interface{}) *Context { 51 | if key == nil { 52 | panic("nil key") 53 | } 54 | if !reflect.TypeOf(key).Comparable() { 55 | panic("key is not comparable") 56 | } 57 | 58 | tags := make(map[interface{}]interface{}) 59 | tags[key] = val 60 | return &Context{Context: parent, tags: tags} 61 | } 62 | 63 | func WithLocalValue(ctx *Context, key, val interface{}) *Context { 64 | if key == nil { 65 | panic("nil key") 66 | } 67 | if !reflect.TypeOf(key).Comparable() { 68 | panic("key is not comparable") 69 | } 70 | 71 | if ctx.tags == nil { 72 | ctx.tags = make(map[interface{}]interface{}) 73 | } 74 | 75 | ctx.tags[key] = val 76 | return ctx 77 | } 78 | 79 | // GetSpanContextFromContext get opentracing.SpanContext from context.Context. 80 | func GetSpanContextFromContext(ctx context.Context) (opentracing.SpanContext, error) { 81 | reqMeta, ok := ctx.Value(ReqMetaDataKey).(map[string]string) 82 | if !ok { 83 | return nil, nil 84 | } 85 | return opentracing.GlobalTracer().Extract( 86 | opentracing.TextMap, 87 | opentracing.TextMapCarrier(reqMeta)) 88 | } 89 | 90 | // GetOpencensusSpanContextFromContext get opencensus.trace.SpanContext from context.Context. 91 | func GetOpencensusSpanContextFromContext(ctx context.Context) (*trace.SpanContext, error) { 92 | reqMeta, ok := ctx.Value(ReqMetaDataKey).(map[string]string) 93 | if !ok { 94 | return nil, nil 95 | } 96 | spanKey := reqMeta[OpencensusSpanRequestKey] 97 | if spanKey == "" { 98 | return nil, errors.New("key not found") 99 | } 100 | 101 | data := []byte(spanKey) 102 | _ = data[23] 103 | 104 | t := &trace.SpanContext{} 105 | copy(t.TraceID[:], data[:16]) 106 | copy(t.SpanID[:], data[16:24]) 107 | 108 | return t, nil 109 | } 110 | -------------------------------------------------------------------------------- /network/tcp_client.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "net" 5 | "github.com/kudoochui/kudos/log" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type TCPClient struct { 11 | sync.Mutex 12 | Addr string 13 | ConnNum int 14 | ConnectInterval time.Duration 15 | AutoReconnect bool 16 | NewAgent func(*TCPConn) Agent 17 | conns ConnSet 18 | wg sync.WaitGroup 19 | closeFlag bool 20 | 21 | // msg parser 22 | LenMsgLen int 23 | MinMsgLen uint32 24 | MaxMsgLen uint32 25 | LittleEndian bool 26 | MsgParser *MsgParser 27 | } 28 | 29 | func (client *TCPClient) Start() { 30 | client.init() 31 | 32 | for i := 0; i < client.ConnNum; i++ { 33 | client.wg.Add(1) 34 | go client.connect() 35 | } 36 | } 37 | 38 | func (client *TCPClient) init() { 39 | client.Lock() 40 | defer client.Unlock() 41 | 42 | if client.ConnNum <= 0 { 43 | client.ConnNum = 1 44 | log.Warning("invalid ConnNum, reset to %v", client.ConnNum) 45 | } 46 | if client.ConnectInterval <= 0 { 47 | client.ConnectInterval = 3 * time.Second 48 | log.Warning("invalid ConnectInterval, reset to %v", client.ConnectInterval) 49 | } 50 | if client.NewAgent == nil { 51 | log.Error("NewAgent must not be nil") 52 | } 53 | if client.conns != nil { 54 | log.Error("client is running") 55 | } 56 | 57 | client.conns = make(ConnSet) 58 | client.closeFlag = false 59 | 60 | // msg parser 61 | msgParser := NewMsgParser() 62 | msgParser.SetMsgLen(client.LenMsgLen, client.MinMsgLen, client.MaxMsgLen) 63 | msgParser.SetByteOrder(client.LittleEndian) 64 | client.MsgParser = msgParser 65 | } 66 | 67 | func (client *TCPClient) dial() net.Conn { 68 | for { 69 | conn, err := net.Dial("tcp", client.Addr) 70 | if err == nil || client.closeFlag { 71 | return conn 72 | } 73 | 74 | log.Warning("connect to %v error: %v", client.Addr, err) 75 | time.Sleep(client.ConnectInterval) 76 | continue 77 | } 78 | } 79 | 80 | func (client *TCPClient) connect() { 81 | defer client.wg.Done() 82 | 83 | reconnect: 84 | conn := client.dial() 85 | if conn == nil { 86 | return 87 | } 88 | 89 | client.Lock() 90 | if client.closeFlag { 91 | client.Unlock() 92 | conn.Close() 93 | return 94 | } 95 | client.conns[conn] = struct{}{} 96 | client.Unlock() 97 | 98 | tcpConn := newTCPConn(conn) 99 | agent := client.NewAgent(tcpConn) 100 | agent.Run() 101 | 102 | // cleanup 103 | tcpConn.Close() 104 | client.Lock() 105 | delete(client.conns, conn) 106 | client.Unlock() 107 | agent.OnClose() 108 | 109 | if client.AutoReconnect { 110 | time.Sleep(client.ConnectInterval) 111 | goto reconnect 112 | } 113 | } 114 | 115 | func (client *TCPClient) Close() { 116 | client.Lock() 117 | client.closeFlag = true 118 | for conn := range client.conns { 119 | conn.Close() 120 | } 121 | client.conns = nil 122 | client.Unlock() 123 | 124 | client.wg.Wait() 125 | } 126 | --------------------------------------------------------------------------------