├── .ignore ├── _config_local └── .ignore ├── im_service ├── proto │ ├── compile.sh │ ├── api.proto │ └── sub.proto ├── client │ ├── gateway_rpc_client.go │ ├── subscription_rpc_client.go │ ├── mixed_rpc_impl.go │ ├── gateway_rpc_impl.go │ └── subscription_rpc_impl.go └── server │ └── rpc_server.go ├── internal ├── world_channel │ ├── message.go │ └── world_channel.go ├── pkg │ └── db │ │ ├── redis.go │ │ └── mysql.go └── message_store_db │ ├── chan.go │ └── message_store.go ├── pkg ├── conn │ ├── server.go │ ├── tcp_server.go │ ├── tcp_conn.go │ ├── ws_server_test.go │ ├── connection.go │ ├── ws_server.go │ └── ws_conn.go ├── messaging │ ├── heartbeat.go │ ├── internal.go │ ├── cli_custom.go │ ├── handler_test.go │ ├── misc.go │ ├── internal_action.go │ ├── sub.go │ ├── offline_handler.go │ ├── user_state_sub.go │ ├── chat.go │ ├── handler.go │ └── messaging.go ├── gate │ ├── id_test.go │ ├── errors.go │ ├── id.go │ ├── client_test.go │ ├── reader.go │ ├── client_impl_test.go │ ├── client.go │ ├── authenticator.go │ ├── gateway.go │ └── client_impl.go ├── store │ ├── idle.go │ ├── kafka_store_producer_test.go │ ├── kafka_store_consumer_test.go │ ├── store.go │ ├── kafka_store_producer.go │ └── kafka_store_consumer.go ├── messages │ ├── hello.go │ ├── message_test.go │ ├── codec.go │ ├── actions.go │ ├── messages.go │ └── message.go ├── subscription │ ├── chan_info.go │ ├── message.go │ ├── subscription_impl │ │ ├── perm_test.go │ │ ├── perm.go │ │ ├── message_test.go │ │ ├── message.go │ │ ├── wrap.go │ │ ├── subscription_test.go │ │ ├── subscription.go │ │ └── chan_test.go │ ├── chan.go │ └── interface.go ├── hash │ ├── hash_test.go │ ├── hash.go │ ├── consisten_hash_test.go │ └── consisten_hash.go ├── rpc │ ├── service_router.go │ ├── selector.go │ ├── context.go │ ├── server.go │ └── client.go ├── logger │ └── log.go └── timingwheel │ ├── timingwheel_test.go │ └── timingwheel.go ├── README.md ├── config ├── config.toml └── viper.go ├── docs ├── 消息鉴权.md └── documents.md ├── example ├── kafka_store.go ├── api │ └── api_example.go └── client │ └── rpc_client_example.go ├── LICENSE ├── cmd └── im_service │ └── main.go └── go.mod /.ignore: -------------------------------------------------------------------------------- 1 | /temp 2 | /.idea 3 | .idea -------------------------------------------------------------------------------- /_config_local/.ignore: -------------------------------------------------------------------------------- 1 | *.* 2 | * -------------------------------------------------------------------------------- /im_service/proto/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | protoc --proto_path=./ --go_out=./../../ ./*.proto -------------------------------------------------------------------------------- /internal/world_channel/message.go: -------------------------------------------------------------------------------- 1 | package world_channel 2 | 3 | type Event struct { 4 | Action string 5 | Data interface{} 6 | } 7 | -------------------------------------------------------------------------------- /pkg/conn/server.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | type ConnectionHandler func(conn Connection) 4 | 5 | type Server interface { 6 | SetConnHandler(handler ConnectionHandler) 7 | Run(host string, port int) error 8 | } 9 | -------------------------------------------------------------------------------- /pkg/messaging/heartbeat.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/messages" 6 | ) 7 | 8 | func handleHeartbeat(cliInfo *gate.Info, message *messages.GlideMessage) error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/gate/id_test.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGenTempID(t *testing.T) { 9 | 10 | id, err := GenTempID("test") 11 | 12 | assert.Nil(t, err) 13 | assert.NotEmpty(t, id) 14 | assert.True(t, id.IsTemp()) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/messaging/internal.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/messages" 6 | ) 7 | 8 | type InternalHandler struct { 9 | } 10 | 11 | func (c *InternalHandler) Handle(h *MessageInterfaceImpl, cliInfo *gate.Info, message *messages.GlideMessage) bool { 12 | return message.GetAction().IsInternal() 13 | } 14 | -------------------------------------------------------------------------------- /pkg/store/idle.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | ) 6 | 7 | var _ MessageStore = &IdleMessageStore{} 8 | 9 | type IdleMessageStore struct { 10 | } 11 | 12 | func (i *IdleMessageStore) StoreOffline(message *messages.ChatMessage) error { 13 | return nil 14 | } 15 | 16 | func (i *IdleMessageStore) StoreMessage(*messages.ChatMessage) error { 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/messaging/cli_custom.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/messages" 6 | ) 7 | 8 | type ClientCustomMessageHandler struct { 9 | } 10 | 11 | func (c *ClientCustomMessageHandler) Handle(h *MessageInterfaceImpl, ci *gate.Info, m *messages.GlideMessage) bool { 12 | if m.Action != messages.ActionClientCustom { 13 | return false 14 | } 15 | dispatch2AllDevice(h, m.To, m) 16 | return true 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## glide 2 | 3 | **[立即体验](http://im.dengzii.com/)** 4 | 5 | **几个较为重要的模块** 6 | 7 | - `gate`: 长连接消息网关抽象, 所有消息的入口, 提供管理网关中客户端的接口, 如设置 id, 退出, 推送消息等. 8 | - `messaging`: 消息路由层, 处理来自 `gate` 的消息, 并根据消息类型进行转发给相应的消息处理器. 9 | - `subscription`: 提供适用于群聊, 实时订阅等场景的接口. 10 | 11 | **公共消息的定义** 12 | 13 | `messages` 包提供了客户端和服务端通讯的消息实体, 类型及消息编解码器. `GlideMessage` 为最基本的公共消息实体. 14 | 15 | **相关项目** 16 | 17 | [TypeScript WebApp](https://github.com/glide-im/glide_ts_sdk) 18 | 19 | [业务 HTTP API接口](https://github.com/glide-im/api) 20 | -------------------------------------------------------------------------------- /pkg/messages/hello.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | type Hello struct { 4 | ClientVersion string `json:"client_version,omitempty"` 5 | ClientName string `json:"client_name,omitempty"` 6 | ClientType string `json:"client_type,omitempty"` 7 | } 8 | 9 | type ServerHello struct { 10 | ServerVersion string `json:"server_version,omitempty"` 11 | TempID string `json:"temp_id,omitempty"` 12 | HeartbeatInterval int `json:"heartbeat_interval,omitempty"` 13 | Protocols []string `json:"protocols,omitempty"` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/subscription/chan_info.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | type ChanType int32 4 | 5 | //goland:noinspection GoUnusedConst 6 | const ( 7 | ChanTypeUnknown ChanType = 0 8 | ) 9 | 10 | type ChanInfo struct { 11 | ID ChanID 12 | Type ChanType 13 | 14 | Muted bool 15 | Blocked bool 16 | Closed bool 17 | 18 | Secret string 19 | 20 | Parent *ChanID 21 | Child []ChanID 22 | } 23 | 24 | func NewChanInfo(id ChanID, type_ ChanType) *ChanInfo { 25 | return &ChanInfo{ 26 | ID: id, 27 | Type: type_, 28 | } 29 | } 30 | 31 | type Chan struct { 32 | } 33 | -------------------------------------------------------------------------------- /pkg/messaging/handler_test.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "github.com/glide-im/glide/pkg/store" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | func TestMessageHandler_InitDefaultHandler(t *testing.T) { 11 | 12 | handler, err := NewHandlerWithOptions(nil, &MessageHandlerOptions{ 13 | MessageStore: &store.IdleMessageStore{}, 14 | DontInitDefaultHandler: true, 15 | }) 16 | assert.NoError(t, err) 17 | 18 | handler.InitDefaultHandler(func(action messages.Action, fn HandlerFunc) HandlerFunc { 19 | t.Log(action) 20 | return fn 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/store/kafka_store_producer_test.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestNewProducer(t *testing.T) { 10 | 11 | producer, err := NewKafkaProducer([]string{"localhost:9092"}) 12 | defer producer.Close() 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | 17 | err = producer.StoreMessage(&messages.ChatMessage{ 18 | CliMid: "1", 19 | Mid: 1, 20 | Seq: 1, 21 | From: "2", 22 | To: "2", 23 | Type: 2, 24 | Content: "2", 25 | SendAt: 2, 26 | }) 27 | 28 | if err != nil { 29 | t.Error(err) 30 | } 31 | 32 | time.Sleep(time.Second) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /im_service/proto/api.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package im_service.glide_im.github.com; 3 | 4 | option go_package = "im_service/proto"; 5 | 6 | message Response { 7 | enum ResponseCode { 8 | OK = 0; 9 | ERROR = 1; 10 | } 11 | 12 | int32 code = 1; 13 | string msg = 2; 14 | } 15 | 16 | message UpdateClient { 17 | enum UpdateType { 18 | _ = 0; 19 | UpdateID = 1; 20 | Close = 2; 21 | Kick = 3; 22 | UpdateSecret = 4; 23 | } 24 | string id = 1; 25 | string newId = 3; 26 | string secret = 4; 27 | string message = 5; 28 | UpdateType type = 6; 29 | } 30 | 31 | message EnqueueMessageRequest { 32 | string id = 1; 33 | bytes msg = 2; 34 | } -------------------------------------------------------------------------------- /pkg/hash/hash_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import "testing" 4 | 5 | func TestHash(t *testing.T) { 6 | type args struct { 7 | data []byte 8 | seed uint32 9 | } 10 | tests := []struct { 11 | name string 12 | args args 13 | want uint32 14 | }{ 15 | { 16 | name: "", 17 | args: struct { 18 | data []byte 19 | seed uint32 20 | }{ 21 | data: []byte("ABCD"), 22 | seed: 1, 23 | }, 24 | want: 3027734286, 25 | }, 26 | } 27 | for _, tt := range tests { 28 | t.Run(tt.name, func(t *testing.T) { 29 | if got := Hash(tt.args.data, tt.args.seed); got != tt.want { 30 | t.Errorf("Hash() = %v, want %v", got, tt.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/subscription/message.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import "github.com/glide-im/glide/pkg/messages" 4 | 5 | // Message is a message that can be publishing to channel. 6 | type Message interface { 7 | GetFrom() SubscriberID 8 | 9 | // GetChatMessage convert message body to *messages.ChatMessage 10 | GetChatMessage() (*messages.ChatMessage, error) 11 | } 12 | 13 | const ( 14 | NotifyTypeOffline = 1 15 | NotifyTypeOnline = 2 16 | NotifyTypeJoin = 3 17 | NotifyTypeLeave = 4 18 | 19 | NotifyOnlineMembers = 5 20 | ) 21 | 22 | type NotifyMessage struct { 23 | From string `json:"from"` 24 | Type int `json:"type"` 25 | Body interface{} `json:"body"` 26 | } 27 | -------------------------------------------------------------------------------- /internal/pkg/db/redis.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-redis/redis" 6 | ) 7 | 8 | var Redis *redis.Client 9 | 10 | type RedisConfig struct { 11 | Host string 12 | Port int 13 | Password string 14 | PoolSize int 15 | MinIdleConn int 16 | Db int 17 | } 18 | 19 | func initRedis(c *RedisConfig) { 20 | 21 | addr := fmt.Sprintf("%s:%d", c.Host, c.Port) 22 | 23 | opt := &redis.Options{ 24 | Addr: addr, 25 | Password: c.Password, 26 | DB: c.Db, 27 | } 28 | if c.MinIdleConn > 0 { 29 | opt.MinIdleConns = c.MinIdleConn 30 | } 31 | if c.PoolSize > 0 { 32 | opt.PoolSize = c.PoolSize 33 | } 34 | Redis = redis.NewClient(opt) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/gate/errors.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | const ( 4 | errClientClosed = "client closed" 5 | errClientNotExist = "client does not exist" 6 | errClientAlreadyExist = "id already exist" 7 | ) 8 | 9 | func IsClientClosed(err error) bool { 10 | return err != nil && err.Error() == errClientClosed 11 | } 12 | 13 | func IsClientNotExist(err error) bool { 14 | return err != nil && err.Error() == errClientNotExist 15 | } 16 | 17 | // IsIDAlreadyExist returns true if the error is caused by the ID of the client already exist. 18 | // Returns when SetClientID is called with the existing new ID. 19 | func IsIDAlreadyExist(err error) bool { 20 | return err != nil && err.Error() == errClientAlreadyExist 21 | } 22 | -------------------------------------------------------------------------------- /pkg/conn/tcp_server.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import "net" 4 | 5 | type TcpServer struct { 6 | handler ConnectionHandler 7 | } 8 | 9 | func NewTcpServer() *TcpServer { 10 | return &TcpServer{} 11 | } 12 | 13 | func (t *TcpServer) SetConnHandler(handler ConnectionHandler) { 14 | t.handler = handler 15 | } 16 | 17 | func (t *TcpServer) Run(host string, port int) error { 18 | tcp, err := net.ListenTCP("tcp", &net.TCPAddr{ 19 | IP: net.ParseIP(host), 20 | Port: port, 21 | }) 22 | if err != nil { 23 | return err 24 | } 25 | for { 26 | acceptTCP, err := tcp.AcceptTCP() 27 | if err != nil { 28 | return err 29 | } 30 | conn := ConnectionProxy{ 31 | conn: NewTcpConn(acceptTCP), 32 | } 33 | t.handler(conn) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/config.toml: -------------------------------------------------------------------------------- 1 | [CommonConf] 2 | StoreMessageHistory = false # 是否保存消息到数据库 3 | StoreOfflineMessage = false # 是否保存离线消息(用户不在线时保存, 上线后推送并删除) 4 | SecretKey = "secret_key" # 服务秘钥 5 | 6 | [WsServer] # WebSocket 服务配置 7 | Addr = "0.0.0.0" 8 | Port = 8083 9 | JwtSecret = "secret" # Jwt 生成的密匙 10 | ID = "node1" # 单机部署忽略 11 | 12 | [IMRpcServer] # RPC 接口服务配置 13 | Addr = "0.0.0.0" 14 | Port = 8092 15 | Network = "tcp" 16 | Etcd = [] # 单机部署忽略 17 | Name = "im_rpc_server" # 单机部署忽略 18 | 19 | [MySql] # 不保存消息历史时可不配置 20 | Host = "localhost" 21 | Port = 3306 22 | Username = "root" 23 | Password = "root" 24 | Db = "im-service" 25 | Charset = "utf8mb4" 26 | 27 | [Kafka] 28 | address = [] 29 | 30 | [Redis] # 不保存离线消息时可不配置 31 | Host = "" 32 | Port = 6789 33 | Db = 8 34 | Password = "" -------------------------------------------------------------------------------- /docs/消息鉴权.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - 用户关系鉴权, 消息接收黑名单, 白名单 4 | - 接口权限鉴权 5 | - 聊天服务用户鉴权 6 | 7 | --- 8 | 9 | - 聊天服务将对所有接收到的消息进行鉴权, 若鉴权失败, 则服务器将拒绝接收该消息, 或断开连接 10 | - 聊天服务在每次启动时生成一个随机的`消息服务密钥`, 作为母密钥, 生成其他鉴权令牌 11 | 12 | - 消息服务登录鉴权 13 | - 用户通过业务系统登录时, 客户端随机生成连接id, 由`消息服务密钥`+连接id+用户id加密生成 `登录令牌` 14 | - 用户连接消息服务时, 需要将`登录令牌` 和 连接id 发送给服务器 15 | - 用户聊天消息鉴权 16 | - 方案1 17 | - 用户登录消息服务后首次给会话发送消息时, 需要先向业务系统获取一个`会话令牌`, 该令牌用于本次连接对该会话的所有消息鉴权 18 | - `会话令牌` 由`聊天服务鉴权密钥`+会话id 加密生成 19 | - 问题: 若用户在线期间, 会话权限发生变化, 如何使之前令牌失效 20 | - 方案2 21 | - 用户注册时为用户生成一个 `会话秘钥` , 创建会话时根据权限使用该秘钥对会话id进行签名 22 | - 用户发送消息时需要携带签名后的 签名id, 签名id 不会过期, 服务端不保存该 id 23 | - 登录消息服务时, 消息服务缓存用户 `会话秘钥`, 收到消息时, 使用 `会话秘钥` 对消息中的 `签名id` 进行验证 24 | - 发生不一致时说明用户 `会话秘钥` 发生变化, 拒收该消息, 重新向服务器请求生成新的 `签名会话id` 25 | - 签名颁发 -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/perm_test.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestPermission_Allows(t *testing.T) { 9 | p := PermRead | PermWrite 10 | assert.True(t, p.allows(MaskPermRead, MaskPermWrite)) 11 | } 12 | 13 | func TestPermission_AllowsFalse(t *testing.T) { 14 | p := PermRead | PermWrite 15 | assert.False(t, p.allows(MaskPermAdmin)) 16 | assert.False(t, p.allows(MaskPermWrite, MaskPermAdmin)) 17 | } 18 | 19 | func TestPermission_Denies(t *testing.T) { 20 | p := PermRead | PermWrite 21 | assert.True(t, p.denies(MaskPermAdmin)) 22 | } 23 | 24 | func TestPermission_DeniesFalse(t *testing.T) { 25 | p := PermRead | PermWrite 26 | assert.False(t, p.denies(MaskPermRead)) 27 | } 28 | -------------------------------------------------------------------------------- /im_service/client/gateway_rpc_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "github.com/glide-im/glide/im_service/proto" 6 | "github.com/glide-im/glide/im_service/server" 7 | "github.com/glide-im/glide/pkg/rpc" 8 | ) 9 | 10 | var _ server.GatewayRpcServer = &GatewayRpcClient{} 11 | 12 | type GatewayRpcClient struct { 13 | cli *rpc.BaseClient 14 | } 15 | 16 | func (I *GatewayRpcClient) UpdateClient(ctx context.Context, request *proto.UpdateClient, response *proto.Response) error { 17 | return I.cli.Call(ctx, "UpdateClient", request, response) 18 | } 19 | 20 | func (I *GatewayRpcClient) EnqueueMessage(ctx context.Context, request *proto.EnqueueMessageRequest, response *proto.Response) error { 21 | return I.cli.Call(ctx, "EnqueueMessage", request, response) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/conn/tcp_conn.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import "net" 4 | 5 | type TcpConnection struct { 6 | c *net.TCPConn 7 | } 8 | 9 | func NewTcpConn(c *net.TCPConn) *TcpConnection { 10 | return &TcpConnection{c: c} 11 | } 12 | 13 | func (t TcpConnection) Write(data []byte) error { 14 | _, err := t.c.Write(data) 15 | return err 16 | } 17 | 18 | func (t TcpConnection) Read() ([]byte, error) { 19 | var b []byte 20 | _, err := t.c.Read(b) 21 | return b, err 22 | } 23 | 24 | func (t TcpConnection) Close() error { 25 | return t.c.Close() 26 | } 27 | 28 | func (t TcpConnection) GetConnInfo() *ConnectionInfo { 29 | addr := t.c.RemoteAddr().(*net.TCPAddr) 30 | return &ConnectionInfo{ 31 | Ip: addr.IP.String(), 32 | Port: addr.Port, 33 | Addr: t.c.RemoteAddr().String(), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/perm.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | type PermMask int 4 | 5 | const ( 6 | MaskPermRead PermMask = 1 << iota 7 | MaskPermWrite = 1 << iota 8 | MaskPermAdmin = 1 << iota 9 | MaskPermSystem = 1 << iota 10 | ) 11 | 12 | const ( 13 | PermNone Permission = 0 14 | PermRead Permission = 1 << MaskPermRead 15 | PermWrite Permission = 1 << MaskPermWrite 16 | PermAdmin Permission = 1 << MaskPermAdmin 17 | ) 18 | 19 | type Permission int64 20 | 21 | func (perm Permission) allows(masks ...PermMask) bool { 22 | for _, m := range masks { 23 | if perm.denies(m) { 24 | return false 25 | } 26 | } 27 | return true 28 | } 29 | 30 | func (perm Permission) denies(mask PermMask) bool { 31 | b := perm >> mask 32 | return b&1 != 1 33 | } 34 | -------------------------------------------------------------------------------- /pkg/subscription/chan.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | //goland:noinspection GoUnusedGlobalVariable 4 | var ( 5 | ErrChanNotExist = "channel does not exist" 6 | ErrChanAlreadyExists = "channel already exists" 7 | ErrChanClosed = "subscribe channel is closed" 8 | ErrAlreadySubscribed = "already subscribed" 9 | ErrNotSubscribed = "not subscribed" 10 | ErrNotPublisher = "not publisher" 11 | ) 12 | 13 | type Subscriber struct { 14 | ID string 15 | Type string 16 | } 17 | 18 | func (s *Subscriber) Notify(msg Message) error { 19 | return nil 20 | } 21 | 22 | type Channel interface { 23 | Subscribe(id SubscriberID, extra interface{}) error 24 | 25 | Unsubscribe(id SubscriberID) error 26 | 27 | Update(i *ChanInfo) error 28 | 29 | Publish(msg Message) error 30 | 31 | GetSubscribers() []string 32 | 33 | Close() error 34 | } 35 | -------------------------------------------------------------------------------- /example/kafka_store.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "github.com/glide-im/glide/pkg/store" 8 | ) 9 | 10 | func main() { 11 | 12 | consumer, err := store.NewKafkaConsumer([]string{"192.168.99.191:9092"}) 13 | if err != nil { 14 | panic(err) 15 | } 16 | consumer.ConsumeChatMessage(func(m *messages.ChatMessage) { 17 | j, _ := json.Marshal(m) 18 | logger.D("on chat message: %s", string(j)) 19 | }) 20 | consumer.ConsumeOfflineMessage(func(m *messages.ChatMessage) { 21 | j, _ := json.Marshal(m) 22 | logger.D("on offline message: %s", string(j)) 23 | 24 | }) 25 | consumer.ConsumeChannelMessage(func(m *messages.ChatMessage) { 26 | j, _ := json.Marshal(m) 27 | logger.D("on channel message: %s", string(j)) 28 | }) 29 | select {} 30 | } 31 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/message_test.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import ( 4 | "errors" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestIsUnknownMessageType(t *testing.T) { 10 | type args struct { 11 | err error 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want bool 17 | }{ 18 | { 19 | name: "unknown message type", 20 | args: args{ 21 | err: errors.New(errUnknownMessageType), 22 | }, 23 | want: true, 24 | }, 25 | { 26 | name: "unknown message type false", 27 | args: args{ 28 | err: errors.New(""), 29 | }, 30 | want: false, 31 | }, 32 | } 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | assert.Equalf(t, tt.want, IsUnknownMessageType(tt.args.err), "IsUnknownMessageType(%v)", tt.args.err) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/conn/ws_server_test.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestConnect(t *testing.T) { 10 | 11 | dialer := websocket.Dialer{ 12 | ReadBufferSize: 1024, 13 | WriteBufferSize: 1024, 14 | } 15 | 16 | conn, _, err := dialer.Dial("ws://127.0.0.1:8080/ws", nil) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | go func() { 21 | for { 22 | messageType, p, e := conn.ReadMessage() 23 | if e != nil { 24 | break 25 | } 26 | t.Log(messageType, string(p)) 27 | } 28 | 29 | }() 30 | 31 | order := 0 32 | for true { 33 | time.Sleep(time.Second * 3) 34 | msg := struct { 35 | Order int 36 | Msg string 37 | }{Order: order, Msg: "msg here"} 38 | order++ 39 | e := conn.WriteJSON(msg) 40 | if e != nil { 41 | t.Error(e) 42 | } 43 | } 44 | 45 | time.Sleep(time.Hour) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/store/kafka_store_consumer_test.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestNewConsumer(t *testing.T) { 12 | 13 | consumer, err := NewKafkaConsumer([]string{"localhost:9092"}) 14 | if err != nil { 15 | t.Error(consumer) 16 | } 17 | defer consumer.Close() 18 | 19 | consumer.ConsumeChatMessage(func(m *messages.ChatMessage) { 20 | b, _ := json.Marshal(m) 21 | logger.D("message: %s", string(b)) 22 | }) 23 | consumer.ConsumeChannelMessage(func(m *messages.ChatMessage) { 24 | b, _ := json.Marshal(m) 25 | logger.D("message: %s", string(b)) 26 | }) 27 | consumer.ConsumeOfflineMessage(func(m *messages.ChatMessage) { 28 | b, _ := json.Marshal(m) 29 | logger.D("message: %s", string(b)) 30 | }) 31 | 32 | time.Sleep(time.Second * 5) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/messaging/misc.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/messages" 6 | ) 7 | 8 | func (d *MessageHandlerImpl) handleHeartbeat(cInfo *gate.Info, msg *messages.GlideMessage) error { 9 | return nil 10 | } 11 | 12 | // handleAckRequest 处理接收者收到消息发回来的确认消息 13 | func (d *MessageHandlerImpl) handleAckRequest(c *gate.Info, msg *messages.GlideMessage) error { 14 | ackMsg := new(messages.AckRequest) 15 | if !d.unmarshalData(c, msg, ackMsg) { 16 | return nil 17 | } 18 | ackNotify := messages.NewMessage(0, messages.ActionAckNotify, ackMsg) 19 | 20 | // 通知发送者, 对方已收到消息 21 | d.dispatchAllDevice(ackMsg.To, ackNotify) 22 | return nil 23 | } 24 | 25 | func (d *MessageHandlerImpl) handleAckOffline(c *gate.Info, msg *messages.GlideMessage) error { 26 | if c.ID.IsTemp() { 27 | return nil 28 | } 29 | AckOfflineMessage(c.ID.UID()) 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /internal/message_store_db/chan.go: -------------------------------------------------------------------------------- 1 | package message_store_db 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "github.com/glide-im/glide/pkg/subscription" 6 | "math" 7 | ) 8 | 9 | type SubscriptionMessageStore struct { 10 | } 11 | 12 | func (c *SubscriptionMessageStore) NextSegmentSequence(id subscription.ChanID, info subscription.ChanInfo) (int64, int64, error) { 13 | return 1, math.MaxInt64, nil 14 | } 15 | 16 | func (c *SubscriptionMessageStore) StoreChannelMessage(ch subscription.ChanID, msg *messages.ChatMessage) error { 17 | return nil 18 | } 19 | 20 | type IdleSubscriptionStore struct { 21 | } 22 | 23 | func (i *IdleSubscriptionStore) NextSegmentSequence(id subscription.ChanID, info subscription.ChanInfo) (int64, int64, error) { 24 | return 1, math.MaxInt64, nil 25 | } 26 | 27 | func (i *IdleSubscriptionStore) StoreChannelMessage(ch subscription.ChanID, msg *messages.ChatMessage) error { 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/store/store.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "github.com/glide-im/glide/pkg/subscription" 6 | ) 7 | 8 | // MessageStore is a store for messages, used to store chat messages in messaging.Interface, its many be called multiple times, 9 | // but only the last updates will be stored. 10 | type MessageStore interface { 11 | 12 | // StoreMessage stores chat message to database 13 | StoreMessage(message *messages.ChatMessage) error 14 | 15 | StoreOffline(message *messages.ChatMessage) error 16 | } 17 | 18 | type SubscriptionStore interface { 19 | 20 | // NextSegmentSequence return the next segment of specified channel, and segment length. 21 | NextSegmentSequence(id subscription.ChanID, info subscription.ChanInfo) (int64, int64, error) 22 | 23 | // StoreChannelMessage stores a published message. 24 | StoreChannelMessage(ch subscription.ChanID, msg *messages.ChatMessage) error 25 | } 26 | -------------------------------------------------------------------------------- /pkg/rpc/service_router.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "github.com/smallnest/rpcx/client" 6 | ) 7 | 8 | // RoundRobinSelector selects servers with roundrobin. 9 | type RoundRobinSelector struct { 10 | servers []string 11 | i int 12 | } 13 | 14 | func NewRoundRobinSelector() client.Selector { 15 | return &RoundRobinSelector{servers: []string{}} 16 | } 17 | 18 | func (s *RoundRobinSelector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string { 19 | return s.SelectNext() 20 | } 21 | 22 | func (s *RoundRobinSelector) SelectNext() string { 23 | ss := s.servers 24 | if len(ss) == 0 { 25 | return "" 26 | } 27 | i := s.i 28 | i = i % len(ss) 29 | s.i = i + 1 30 | return ss[i] 31 | } 32 | 33 | func (s *RoundRobinSelector) UpdateServer(servers map[string]string) { 34 | ss := make([]string, 0, len(servers)) 35 | for k := range servers { 36 | ss = append(ss, k) 37 | } 38 | 39 | s.servers = ss 40 | } 41 | -------------------------------------------------------------------------------- /pkg/gate/id.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "encoding/hex" 5 | "io" 6 | "math/rand" 7 | "time" 8 | ) 9 | 10 | // GenTempID generates a temporary ID. 11 | // ID implementation is a UUID v4, which is generated by random number generator. 12 | func GenTempID(gateID string) (ID, error) { 13 | uuid, err := newUUID() 14 | if err != nil { 15 | return "", err 16 | } 17 | return NewID(gateID, tempIdPrefix+uuid, ""), nil 18 | } 19 | 20 | func newUUID() (string, error) { 21 | var uuid [16]byte 22 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 23 | _, err := io.ReadFull(r, uuid[:]) 24 | if err != nil { 25 | return "", err 26 | } 27 | uuid[6] = (uuid[6] & 0x0f) | 0x40 28 | uuid[8] = (uuid[8] & 0x3f) | 0x80 29 | 30 | var buf1 [36]byte 31 | buf := buf1[:] 32 | hex.Encode(buf, uuid[:4]) 33 | buf[8] = '-' 34 | hex.Encode(buf[9:13], uuid[4:6]) 35 | buf[13] = '-' 36 | hex.Encode(buf[14:18], uuid[6:8]) 37 | buf[18] = '-' 38 | hex.Encode(buf[19:23], uuid[8:10]) 39 | buf[23] = '-' 40 | hex.Encode(buf[24:], uuid[10:]) 41 | return string(buf[:]), nil 42 | } 43 | -------------------------------------------------------------------------------- /im_service/proto/sub.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package im_service.glide_im.github.com; 3 | 4 | option go_package = "pkg/proto"; 5 | 6 | message SubscribeRequest { 7 | string channelID = 1; 8 | string subscriberID = 2; 9 | bytes extra = 3; 10 | } 11 | 12 | message UnsubscribeRequest { 13 | string channelID = 1; 14 | string subscriberID = 2; 15 | } 16 | 17 | message UpdateSubscriberRequest { 18 | string channelID = 1; 19 | string subscriberID = 2; 20 | bytes extra = 3; 21 | } 22 | 23 | message RemoveChannelRequest { 24 | string channelID = 1; 25 | } 26 | 27 | message ChannelInfo { 28 | string ID = 1; 29 | 30 | int32 type = 4; 31 | bool muted = 5; 32 | bool blocked = 6; 33 | bool closed = 7; 34 | 35 | string parent = 8; 36 | repeated string children = 9; 37 | } 38 | 39 | message CreateChannelRequest { 40 | string channelID = 1; 41 | ChannelInfo channelInfo = 2; 42 | } 43 | 44 | message UpdateChannelRequest { 45 | string channelID = 1; 46 | ChannelInfo channelInfo = 2; 47 | } 48 | 49 | message PublishRequest { 50 | string channelID = 1; 51 | bytes message = 2; 52 | } -------------------------------------------------------------------------------- /pkg/messaging/internal_action.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/config" 5 | "github.com/glide-im/glide/internal/world_channel" 6 | "github.com/glide-im/glide/pkg/gate" 7 | "github.com/glide-im/glide/pkg/logger" 8 | "github.com/glide-im/glide/pkg/messages" 9 | "time" 10 | ) 11 | 12 | func (d *MessageHandlerImpl) handleInternalOffline(c *gate.Info, m *messages.GlideMessage) error { 13 | go world_channel.OnUserOffline(c.ID) 14 | 15 | d.userState.onUserOffline(c.ID) 16 | 17 | return nil 18 | } 19 | 20 | func (d *MessageHandlerImpl) handleInternalOnline(c *gate.Info, m *messages.GlideMessage) error { 21 | 22 | d.userState.onUserOnline(c.ID) 23 | 24 | go func() { 25 | defer func() { 26 | err, ok := recover().(error) 27 | if err != nil && ok { 28 | logger.ErrE("push offline message error", err) 29 | } 30 | }() 31 | go func() { 32 | time.Sleep(time.Second * 1) 33 | world_channel.OnUserOnline(c.ID) 34 | }() 35 | 36 | if config.Common.StoreOfflineMessage { 37 | // message_handler.PushOfflineMessage(h, cliInfo.ID.UID()) 38 | } 39 | }() 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 glide 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 | -------------------------------------------------------------------------------- /pkg/subscription/interface.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import ( 4 | "errors" 5 | "github.com/glide-im/glide/pkg/gate" 6 | ) 7 | 8 | const ( 9 | SubscriberSubscribe int64 = 1 10 | SubscriberUnsubscribe = 2 11 | SubscriberUpdate = 5 12 | ) 13 | 14 | const ( 15 | ChanCreate int64 = 1 16 | ChanDelete = 2 17 | ChanUpdate = 3 18 | ) 19 | 20 | var ( 21 | ErrUnknownFlag = errors.New("unknown flag") 22 | ) 23 | 24 | // ChanID is a unique identifier for a channel. 25 | type ChanID string 26 | 27 | type SubscriberID string 28 | 29 | type Update struct { 30 | Flag int64 31 | ID SubscriberID 32 | 33 | Extra interface{} 34 | } 35 | 36 | type ChannelUpdate struct { 37 | Flag int64 38 | 39 | Extra interface{} 40 | } 41 | 42 | type Interface interface { 43 | PublishMessage(id ChanID, message Message) error 44 | } 45 | 46 | type Subscribe interface { 47 | Interface 48 | 49 | SetGateInterface(gate gate.DefaultGateway) 50 | 51 | UpdateSubscriber(id ChanID, updates []Update) error 52 | 53 | UpdateChannel(id ChanID, update ChannelUpdate) error 54 | } 55 | 56 | type Server interface { 57 | Subscribe 58 | 59 | Run() error 60 | } 61 | -------------------------------------------------------------------------------- /pkg/logger/log.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | ) 6 | 7 | var Zap *zap.Logger 8 | 9 | var sugar *zap.SugaredLogger 10 | 11 | func init() { 12 | var err error 13 | Zap, err = zap.NewDevelopment( 14 | zap.Development(), 15 | zap.AddCallerSkip(1), 16 | zap.WithCaller(true), 17 | zap.AddCaller(), 18 | ) 19 | if err != nil { 20 | panic(err) 21 | } 22 | sugar = Zap.Sugar() 23 | } 24 | 25 | func E(format string, logs ...interface{}) { 26 | sugar.Errorf(format, logs...) 27 | } 28 | 29 | func I(format string, args ...interface{}) { 30 | sugar.Infof(format, args...) 31 | } 32 | 33 | func D(format string, args ...interface{}) { 34 | sugar.Debugf(format, args...) 35 | } 36 | 37 | func W(format string, args ...interface{}) { 38 | sugar.Warnf(format, args) 39 | } 40 | 41 | func ErrE(msg string, e error) { 42 | Zap.Error(msg, zap.Error(e)) 43 | } 44 | 45 | func ErrStr(msg string, k string, v string) { 46 | Zap.Error(msg, zap.String(k, v)) 47 | } 48 | 49 | func ErrInt(msg string, k string, v int64) { 50 | Zap.Error(msg, zap.Int64(k, v)) 51 | } 52 | 53 | func DebugStr(msg string, k string, v string) { 54 | Zap.Debug(msg, zap.String(k, v)) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/messages/message_test.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestGlideMessage_Decode(t *testing.T) { 9 | cm := AckMessage{ 10 | Mid: 1, 11 | Seq: 2, 12 | } 13 | message := NewMessage(1, ActionHeartbeat, &cm) 14 | bytes, err := JsonCodec.Encode(message) 15 | assert.Nil(t, err) 16 | 17 | m := NewEmptyMessage() 18 | err = JsonCodec.Decode(bytes, m) 19 | assert.Nil(t, err) 20 | 21 | assert.Equal(t, m.Action, message.Action) 22 | } 23 | 24 | func TestData_Deserialize(t *testing.T) { 25 | m := NewMessage(1, ActionHello, &ChatMessage{ 26 | Mid: 11, 27 | Seq: 2, 28 | From: "4", 29 | }) 30 | cm := ChatMessage{} 31 | err := m.Data.Deserialize(&cm) 32 | if err != nil { 33 | t.Error(err) 34 | } 35 | assert.Equal(t, cm.From, m.From) 36 | } 37 | 38 | func TestData_MarshalJSON(t *testing.T) { 39 | 40 | data := NewData("foo") 41 | encode, err := JsonCodec.Encode(data) 42 | assert.Nil(t, err) 43 | 44 | d := Data{} 45 | err = JsonCodec.Decode(encode, &d) 46 | assert.Nil(t, err) 47 | 48 | var s string 49 | err = d.Deserialize(&s) 50 | assert.Nil(t, err) 51 | 52 | assert.Equal(t, s, data.des) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/rpc/selector.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/smallnest/rpcx/client" 7 | "github.com/smallnest/rpcx/share" 8 | ) 9 | 10 | type selector struct { 11 | services map[string]string 12 | round client.Selector 13 | tags map[string]string 14 | } 15 | 16 | func newSelector() *selector { 17 | s := map[string]string{} 18 | return &selector{ 19 | services: s, 20 | round: NewRoundRobinSelector(), 21 | tags: map[string]string{}, 22 | } 23 | } 24 | 25 | func (r *selector) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string { 26 | 27 | m := ctx.Value(share.ReqMetaDataKey).(map[string]string) 28 | 29 | if target, ok := m["ExtraTarget"]; ok { 30 | if _, ok := r.services[target]; ok { 31 | return target 32 | } 33 | logger.E("unknown service addr, ExtraTarget:", target) 34 | } 35 | 36 | if tag, ok := m["ExtraTag"]; ok { 37 | if path, ok := r.tags[tag]; ok { 38 | if _, ok := r.services[path]; ok { 39 | logger.D("route by tag: %s=%s", tag, path) 40 | return path 41 | } 42 | } 43 | } 44 | return r.round.Select(ctx, servicePath, serviceMethod, args) 45 | } 46 | 47 | func (r *selector) UpdateServer(servers map[string]string) { 48 | r.round.UpdateServer(servers) 49 | for k, v := range servers { 50 | r.services[k] = v 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pkg/conn/connection.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrForciblyClosed = errors.New("connection was forcibly closed") 9 | ErrClosed = errors.New("closed") 10 | ErrConnectionClosed = errors.New("connection closed") 11 | ErrBadPackage = errors.New("bad package data") 12 | ErrReadTimeout = errors.New("i/o timeout") 13 | ) 14 | 15 | type ConnectionInfo struct { 16 | Ip string 17 | Port int 18 | Addr string 19 | } 20 | 21 | // Connection expression a network keep-alive connection, WebSocket, tcp etc 22 | type Connection interface { 23 | // Write message to the connection. 24 | Write(data []byte) error 25 | // Read message from the connection. 26 | Read() ([]byte, error) 27 | // Close the connection. 28 | Close() error 29 | // GetConnInfo return the connection info 30 | GetConnInfo() *ConnectionInfo 31 | } 32 | 33 | // ConnectionProxy expression a binder of Connection. 34 | type ConnectionProxy struct { 35 | conn Connection 36 | } 37 | 38 | func (c ConnectionProxy) Write(data []byte) error { 39 | return c.conn.Write(data) 40 | } 41 | 42 | func (c ConnectionProxy) Read() ([]byte, error) { 43 | return c.conn.Read() 44 | } 45 | 46 | func (c ConnectionProxy) Close() error { 47 | return c.conn.Close() 48 | } 49 | 50 | func (c ConnectionProxy) GetConnInfo() *ConnectionInfo { 51 | return c.conn.GetConnInfo() 52 | } 53 | -------------------------------------------------------------------------------- /pkg/hash/hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "math/bits" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | c1_32 uint32 = 0xcc9e2d51 12 | c2_32 uint32 = 0x1b873593 13 | ) 14 | 15 | func SHA1(str string) string { 16 | h := sha1.New() 17 | _, _ = h.Write([]byte(str)) 18 | return hex.EncodeToString(h.Sum(nil)) 19 | } 20 | 21 | func Hash(data []byte, seed uint32) uint32 { 22 | 23 | h1 := seed 24 | 25 | nblocks := len(data) / 4 26 | var p uintptr 27 | if len(data) > 0 { 28 | p = uintptr(unsafe.Pointer(&data[0])) 29 | } 30 | p1 := p + uintptr(4*nblocks) 31 | for ; p < p1; p += 4 { 32 | k1 := *(*uint32)(unsafe.Pointer(p)) 33 | 34 | k1 *= c1_32 35 | k1 = bits.RotateLeft32(k1, 15) 36 | k1 *= c2_32 37 | 38 | h1 ^= k1 39 | h1 = bits.RotateLeft32(h1, 13) 40 | h1 = h1*4 + h1 + 0xe6546b64 41 | } 42 | 43 | tail := data[nblocks*4:] 44 | 45 | var k1 uint32 46 | switch len(tail) & 3 { 47 | case 3: 48 | k1 ^= uint32(tail[2]) << 16 49 | fallthrough 50 | case 2: 51 | k1 ^= uint32(tail[1]) << 8 52 | fallthrough 53 | case 1: 54 | k1 ^= uint32(tail[0]) 55 | k1 *= c1_32 56 | k1 = bits.RotateLeft32(k1, 15) 57 | k1 *= c2_32 58 | h1 ^= k1 59 | } 60 | 61 | h1 ^= uint32(len(data)) 62 | 63 | h1 ^= h1 >> 16 64 | h1 *= 0x85ebca6b 65 | h1 ^= h1 >> 13 66 | h1 *= 0xc2b2ae35 67 | h1 ^= h1 >> 16 68 | 69 | return h1 70 | } 71 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/message.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "github.com/glide-im/glide/pkg/subscription" 6 | ) 7 | 8 | const ( 9 | errUnknownMessageType = "unknown message type" 10 | ) 11 | 12 | const ( 13 | typeUnknown = iota 14 | 15 | // TypeNotify is the notification message type. 16 | TypeNotify 17 | 18 | // TypeMessage is the chat message type. 19 | TypeMessage 20 | 21 | // TypeSystem is the system message type. 22 | TypeSystem 23 | ) 24 | 25 | // PublishMessage is the message published to the channel. 26 | type PublishMessage struct { 27 | // From the message sender. 28 | From subscription.SubscriberID 29 | // To specified receiver, empty express all subscribers will be received. 30 | To []subscription.SubscriberID 31 | Seq int64 32 | // Type the message type. 33 | Type int 34 | Message *messages.GlideMessage 35 | } 36 | 37 | func (p *PublishMessage) GetFrom() subscription.SubscriberID { 38 | return p.From 39 | } 40 | 41 | func (p *PublishMessage) GetChatMessage() (*messages.ChatMessage, error) { 42 | cm := &messages.ChatMessage{} 43 | err := p.Message.Data.Deserialize(cm) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return cm, nil 48 | } 49 | 50 | func IsUnknownMessageType(err error) bool { 51 | return err.Error() == errUnknownMessageType 52 | } 53 | 54 | func isValidMessageType(t int) bool { 55 | return t > typeUnknown && t <= TypeSystem 56 | } 57 | -------------------------------------------------------------------------------- /pkg/messages/codec.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "google.golang.org/protobuf/proto" 7 | "strings" 8 | ) 9 | 10 | var ProtoBuffCodec = protobufCodec{} 11 | var JsonCodec = jsonCodec{} 12 | var DefaultCodec = JsonCodec 13 | 14 | var errDecode = "message decode error: " 15 | 16 | func IsDecodeError(err error) bool { 17 | return err != nil && strings.HasPrefix(err.Error(), errDecode) 18 | } 19 | 20 | type Codec interface { 21 | Decode(data []byte, i interface{}) error 22 | Encode(i interface{}) ([]byte, error) 23 | } 24 | 25 | type protobufCodec struct { 26 | } 27 | 28 | func (p protobufCodec) Decode(data []byte, i interface{}) error { 29 | message, ok := i.(proto.Message) 30 | if !ok { 31 | return errors.New("illegal argument, not implement proto.GlideMessage") 32 | } 33 | return proto.Unmarshal(data, message) 34 | } 35 | 36 | func (p protobufCodec) Encode(i interface{}) ([]byte, error) { 37 | message, ok := i.(proto.Message) 38 | if !ok { 39 | return nil, errors.New("illegal argument, not implement proto.GlideMessage") 40 | } 41 | return proto.Marshal(message) 42 | } 43 | 44 | type jsonCodec struct { 45 | } 46 | 47 | func (j jsonCodec) Decode(data []byte, i interface{}) error { 48 | err := json.Unmarshal(data, i) 49 | if err != nil { 50 | return errors.New(errDecode + err.Error()) 51 | } 52 | return nil 53 | } 54 | 55 | func (j jsonCodec) Encode(i interface{}) ([]byte, error) { 56 | return json.Marshal(i) 57 | } 58 | 59 | type GlideProtocol struct { 60 | } 61 | -------------------------------------------------------------------------------- /pkg/messages/actions.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import "strings" 4 | 5 | // Action is the type of action that is being performed. 6 | type Action string 7 | 8 | const ( 9 | ActionHello Action = "hello" 10 | ActionHeartbeat = "heartbeat" 11 | ActionNotifyUnknownAction = "notify.unknown.action" 12 | 13 | ActionChatMessage = "message.chat" 14 | ActionChatMessageResend = "message.chat.resend" 15 | ActionGroupMessage = "message.group" 16 | ActionGroupNotify = "message.group.notify" 17 | ActionClientCustom = "message.cli" 18 | 19 | ActionAuthenticate = "authenticate" 20 | ActionNotifyError = "notify.error" 21 | ActionNotifySuccess = "notify.success" 22 | ActionNotifyKickOut = "notify.kickout" 23 | ActionNotifyForbidden = "notify.forbidden" 24 | ActionNotifyUnauthenticated = "notify.unauthenticated" 25 | ActionNotifyUserState = "notify.state" 26 | 27 | ActionAckRequest = "ack.request" 28 | ActionAckGroupMsg = "ack.group.msg" 29 | ActionAckMessage = "ack.message" 30 | ActionAckNotify = "ack.notify" 31 | AckOffline = "ack.offline" 32 | 33 | ActionApiGroupMembers = "api.group.members" 34 | ActionApiSubUserState = "api.state.sub" 35 | ActionApiFailed = "api.failed" 36 | ActionApiSuccess = "api.success" 37 | 38 | ActionInternalOnline = "internal.online" 39 | ActionInternalOffline = "internal.offline" 40 | ) 41 | 42 | func (a Action) IsInternal() bool { 43 | return strings.HasPrefix(string(a), "internal.") 44 | } 45 | -------------------------------------------------------------------------------- /pkg/timingwheel/timingwheel_test.go: -------------------------------------------------------------------------------- 1 | package timingwheel 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "math/rand" 7 | "strconv" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestNewTimingWheel(t *testing.T) { 13 | 14 | tw := NewTimingWheel(time.Millisecond*100, 3, 20) 15 | assert.NotNil(t, tw) 16 | assert.Equal(t, 20, tw.wheel.slot.len) 17 | assert.Equal(t, 400, tw.wheel.slotCap) 18 | } 19 | 20 | func (s *slot) tasks() [][]*Task { 21 | sl := s 22 | for sl.index != 0 { 23 | sl = sl.next 24 | } 25 | var t [][]*Task 26 | 27 | t = append(t, sl.valueArray()) 28 | sl = sl.next 29 | if sl.index != 0 { 30 | t = append(t, sl.valueArray()) 31 | sl = sl.next 32 | } 33 | t = append(t, sl.valueArray()) 34 | return t 35 | } 36 | 37 | func (w *wheel) status() string { 38 | var s []string 39 | sl := w.slot 40 | for ; sl.index != sl.len-1; sl = sl.next { 41 | 42 | } 43 | for i := 0; i != sl.len; i++ { 44 | sl = sl.next 45 | if sl.index == w.slot.index { 46 | s = append(s, strconv.Itoa(i)) 47 | continue 48 | } 49 | if sl.isEmpty() { 50 | s = append(s, "_") 51 | } else { 52 | s = append(s, "#") 53 | } 54 | } 55 | 56 | var ts []string 57 | for _, tasks := range w.slot.tasks() { 58 | var tt []string 59 | for _, t := range tasks { 60 | tt = append(tt, strconv.Itoa(t.offset)) 61 | } 62 | ts = append(ts, fmt.Sprintf("%v", tt)) 63 | } 64 | 65 | return fmt.Sprintf("%v %v %d", s, ts, w.remain) 66 | } 67 | 68 | func sleepRndMilleSec(start int32, end int32) { 69 | n := rand.Int31n(end - start) 70 | n = start + n 71 | time.Sleep(time.Duration(n) * time.Millisecond) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/rpc/context.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "github.com/smallnest/rpcx/share" 6 | ) 7 | 8 | type ExtraContext struct { 9 | context.Context 10 | } 11 | 12 | func NewContextFrom(c context.Context) *ExtraContext { 13 | return &ExtraContext{c} 14 | } 15 | 16 | func NewContext() *ExtraContext { 17 | return NewContextFrom(context.Background()) 18 | } 19 | 20 | func (c *ExtraContext) PutReqExtra(k string, v string) *ExtraContext { 21 | mate := c.Context.Value(share.ReqMetaDataKey) 22 | if mate == nil { 23 | mate = map[string]string{} 24 | c.Context = context.WithValue(c.Context, share.ReqMetaDataKey, mate) 25 | } 26 | m := c.Context.Value(share.ReqMetaDataKey).(map[string]string) 27 | m[k] = v 28 | return c 29 | } 30 | 31 | func (c *ExtraContext) PutResExtra(k string, v string) *ExtraContext { 32 | mate := c.Context.Value(share.ResMetaDataKey) 33 | if mate == nil { 34 | mate = map[string]string{} 35 | c.Context = context.WithValue(c.Context, share.ResMetaDataKey, mate) 36 | } 37 | m := c.Context.Value(share.ResMetaDataKey).(map[string]string) 38 | m[k] = v 39 | return c 40 | } 41 | 42 | func (c *ExtraContext) GetReqExtra(k string) (string, bool) { 43 | mate := c.Context.Value(share.ReqMetaDataKey) 44 | if mate == nil { 45 | return "", false 46 | } 47 | m := c.Context.Value(share.ReqMetaDataKey).(map[string]string) 48 | v, ok := m[k] 49 | return v, ok 50 | } 51 | 52 | func (c *ExtraContext) GetResExtra(k string) (string, bool) { 53 | mate := c.Context.Value(share.ResMetaDataKey) 54 | if mate == nil { 55 | return "", false 56 | } 57 | m := c.Context.Value(share.ResMetaDataKey).(map[string]string) 58 | v, ok := m[k] 59 | return v, ok 60 | } 61 | -------------------------------------------------------------------------------- /pkg/conn/ws_server.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorilla/websocket" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | type WsServerOptions struct { 11 | ReadTimeout time.Duration 12 | WriteTimeout time.Duration 13 | } 14 | 15 | type WsServer struct { 16 | options *WsServerOptions 17 | upgrader websocket.Upgrader 18 | handler ConnectionHandler 19 | } 20 | 21 | // NewWsServer options can be nil, use default value when nil. 22 | func NewWsServer(options *WsServerOptions) Server { 23 | 24 | if options == nil { 25 | options = &WsServerOptions{ 26 | ReadTimeout: 8 * time.Minute, 27 | WriteTimeout: 8 * time.Minute, 28 | } 29 | } 30 | ws := new(WsServer) 31 | ws.options = options 32 | ws.upgrader = websocket.Upgrader{ 33 | ReadBufferSize: 1024, 34 | WriteBufferSize: 65536, 35 | CheckOrigin: func(r *http.Request) bool { 36 | return true 37 | }, 38 | } 39 | return ws 40 | } 41 | 42 | func (ws *WsServer) handleWebSocketRequest(writer http.ResponseWriter, request *http.Request) { 43 | 44 | conn, err := ws.upgrader.Upgrade(writer, request, nil) 45 | if err != nil { 46 | // logger.E("upgrade http to ws error", err) 47 | return 48 | } 49 | 50 | proxy := ConnectionProxy{ 51 | conn: NewWsConnection(conn, ws.options), 52 | } 53 | ws.handler(proxy) 54 | } 55 | 56 | func (ws *WsServer) SetConnHandler(handler ConnectionHandler) { 57 | ws.handler = handler 58 | } 59 | 60 | func (ws *WsServer) Run(host string, port int) error { 61 | 62 | http.HandleFunc("/ws", ws.handleWebSocketRequest) 63 | 64 | addr := fmt.Sprintf("%s:%d", host, port) 65 | if err := http.ListenAndServe(addr, nil); err != nil { 66 | return err 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /internal/pkg/db/mysql.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "gorm.io/driver/mysql" 6 | "gorm.io/gorm" 7 | "gorm.io/gorm/schema" 8 | "time" 9 | ) 10 | 11 | var DB *gorm.DB 12 | 13 | type MySQLConfig struct { 14 | Host string 15 | Port int 16 | User string 17 | Password string 18 | Database string 19 | Charset string 20 | MaxOpenConn int 21 | MaxIdleConn int 22 | MaxLifeTimeMin int 23 | } 24 | 25 | func Init(mysqlConf *MySQLConfig, redisConf *RedisConfig) error { 26 | 27 | if mysqlConf != nil { 28 | err := initMySQL(mysqlConf) 29 | if err != nil { 30 | return err 31 | } 32 | } 33 | if redisConf != nil { 34 | initRedis(redisConf) 35 | } 36 | return nil 37 | } 38 | 39 | func initMySQL(mysqlConf *MySQLConfig) error { 40 | 41 | url := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=true", 42 | mysqlConf.User, mysqlConf.Password, mysqlConf.Host, mysqlConf.Port, mysqlConf.Database, mysqlConf.Charset) 43 | 44 | var err error 45 | DB, err = gorm.Open(mysql.Open(url), &gorm.Config{ 46 | NamingStrategy: schema.NamingStrategy{ 47 | TablePrefix: "im_", 48 | SingularTable: true, 49 | //NameReplacer: nil, 50 | //NoLowerCase: false, 51 | }, 52 | }) 53 | if err != nil { 54 | return err 55 | } 56 | db, err := DB.DB() 57 | if err != nil { 58 | return err 59 | } 60 | if mysqlConf.MaxOpenConn > 0 { 61 | db.SetMaxOpenConns(mysqlConf.MaxOpenConn) 62 | } 63 | if mysqlConf.MaxLifeTimeMin > 0 { 64 | db.SetConnMaxLifetime(time.Duration(mysqlConf.MaxLifeTimeMin) * time.Minute) 65 | } 66 | if mysqlConf.MaxIdleConn > 0 { 67 | db.SetMaxIdleConns(mysqlConf.MaxIdleConn) 68 | } 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /docs/documents.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - [go 实现的一个简单客户端](https://github.com/glide-im/glide_cli) 4 | 5 | 本项目是 glide 的单体服务实现, 包含了长连接网关, 群聊功能, 并提供了管理长连接及群聊的接口客户端. 6 | 7 | 本项目依赖 `glide`, 关于 `glide` 更多信息请查看此项目文档. 8 | 9 | `internal` 包中为本项目的业务实现, `pkg` 包中为提供给外部系统的接口及 rpc 客户端实现. 10 | 11 | `im-service` 需要配合接口项目 `api` 使用, 本项目只提供长连接的消息收发, 登录鉴权等需要通过 `api` 实现, `api` 通过 本项目提供的 rpc 接口进行交互, 例如给指定用户设置 id, 12 | 给指定id用户推送消息, 断开指定链接, 判断用户是否在线等. 13 | 14 | 本项目需要在消息收发时保存消息, 且消息直接通过 mysql 持久化, 离线消息通过 redis 保存. 15 | 16 | ## 简单介绍 17 | 18 | > 2022年9月22日17:49:47 19 | 20 | 这个项目是一个单体聊天服务器, 包含群聊和单聊功能, 启用聊天历史(StoreMessageHistory.StoreMessageHistory=true)的情况下需要依赖 mysql, 启用离线( 21 | StoreMessageHistory.StoreOfflineMessage=true)则还需要 redis, 如果关闭离线和历史消息则不要要配置也可运行, 配置文件中的 [Redis] 和 [MySql] 不需要配置即可. 22 | 23 | 客户端连接到服务后, 会收到一条 `Action` 为 `hello` 的消息, 里面包含了一些配置的一个临时 id 用来标记当前客户端, 这个 id 在每次连接到服务时都不一样. 24 | 25 | 如果客户端需要登录, 鉴权, 需要发送 `Action` 为 `api.auth` 消息进行鉴权, 具体协议查看源码. 鉴权后一个连接即会绑定一个用户id `uid`, 设备 id `device`, 鉴权 token 类型为 jwt. 26 | 27 | 聊天服务不提供用户, 用户关系, 群等管理功能, 只处理消息转发和消息保存等和消息推送转发流程相关业务, 其他功能在 http 接口项目 `api` 中有具体的实现及演示. 28 | 29 | 客户端鉴权, 管理等功能通过 HTTP API 实现, 后端 HTTP 服务再通过 `im_service` 提供的 RPC 接口进行踢人, 推送消息, 管理群等功能, 项目根目录下 `pkg` 包中列出了所有聊天服务提供的接口 及 rpc 30 | 客户端实现, 外围管理服务直接依赖本项目, 再通过 pkg 中的 rpc 客户端即可对聊天服务管理. 31 | 32 | ## 运行 33 | 34 | > 2022年9月28日17:48:30 35 | 36 | - [配置文件](../config/config.toml) 37 | - [程序入口](../cmd/im_service/main.go) 38 | - [如何调用本服务提供的RPC接口](../example/client/rpc_client_example.go) 39 | 40 | ## 测试一下发消息 41 | 42 | > 2022年9月28日17:37:46 43 | 44 | 为了方便测试发消息等服务端的流程, 可以使用用 go 实现的一个简单客户端 [glide-cli](https://github.com/glide-im/glide_cli) 进行消息收发. 45 | `glide_cli` 中 `example` 目录中有一个简单的例子发消息, 使用时只需要将 jwt 的 secret 配置与 `im_service` 一致即可, uid 可以随意填, 这样就免去了麻烦 API 鉴权过程, 46 | 只需要运行一个 `im_service` 即可进行消息收发的测试. -------------------------------------------------------------------------------- /pkg/messaging/sub.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "github.com/glide-im/glide/pkg/subscription" 8 | "github.com/glide-im/glide/pkg/subscription/subscription_impl" 9 | ) 10 | 11 | // handleGroupMsg 分发群消息 12 | func (d *MessageHandlerImpl) handleGroupMsg(c *gate.Info, msg *messages.GlideMessage) error { 13 | 14 | id := subscription.ChanID(msg.To) 15 | 16 | cm := messages.ChatMessage{} 17 | e := msg.Data.Deserialize(&cm) 18 | if e != nil { 19 | return e 20 | } 21 | 22 | m := subscription_impl.PublishMessage{ 23 | From: subscription.SubscriberID(msg.From), 24 | Message: msg, 25 | Type: subscription_impl.TypeMessage, 26 | } 27 | err := d.def.GetGroupInterface().PublishMessage(id, &m) 28 | 29 | if err != nil { 30 | logger.E("dispatch group message error: %v", err) 31 | notify := messages.NewMessage(msg.GetSeq(), messages.ActionNotifyError, err.Error()) 32 | d.enqueueMessage(c.ID, notify) 33 | } else { 34 | _ = d.ackChatMessage(c, &cm) 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func (d *MessageHandlerImpl) handleApiGroupMembers(c *gate.Info, msg *messages.GlideMessage) error { 41 | //id := subscription.ChanID(msg.To) 42 | // 43 | //cm := messages.ChatMessage{} 44 | //e := msg.Data.Deserialize(&cm) 45 | //if e != nil { 46 | // return e 47 | //} 48 | 49 | return nil 50 | } 51 | 52 | func (d *MessageHandlerImpl) handleAckGroupMsgRequest(c *gate.Info, msg *messages.GlideMessage) error { 53 | ack := new(messages.AckGroupMessage) 54 | if !d.unmarshalData(c, msg, ack) { 55 | return nil 56 | } 57 | //err := msgdao.UpdateGroupMemberMsgState(ack.Gid, 0, ack.Mid, ack.Seq) 58 | //if err != nil { 59 | // 60 | //} 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /im_service/client/subscription_rpc_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "github.com/glide-im/glide/im_service/proto" 6 | "github.com/glide-im/glide/pkg/rpc" 7 | ) 8 | 9 | type subscriptionRpcClient struct { 10 | cli *rpc.BaseClient 11 | } 12 | 13 | func (s *subscriptionRpcClient) Close() error { 14 | return s.cli.Close() 15 | } 16 | 17 | func (s *subscriptionRpcClient) Subscribe(ctx context.Context, request *proto.SubscribeRequest, response *proto.Response) error { 18 | return s.cli.Call(ctx, "Subscribe", request, response) 19 | } 20 | 21 | func (s *subscriptionRpcClient) UnSubscribe(ctx context.Context, request *proto.UnsubscribeRequest, response *proto.Response) error { 22 | return s.cli.Call(ctx, "UnSubscribe", request, response) 23 | } 24 | 25 | func (s *subscriptionRpcClient) UpdateSubscriber(ctx context.Context, request *proto.UpdateSubscriberRequest, response *proto.Response) error { 26 | return s.cli.Call(ctx, "UpdateSubscriber", request, response) 27 | } 28 | 29 | func (s *subscriptionRpcClient) RemoveChannel(ctx context.Context, request *proto.RemoveChannelRequest, response *proto.Response) error { 30 | return s.cli.Call(ctx, "RemoveChannel", request, response) 31 | } 32 | 33 | func (s *subscriptionRpcClient) CreateChannel(ctx context.Context, request *proto.CreateChannelRequest, response *proto.Response) error { 34 | return s.cli.Call(ctx, "CreateChannel", request, response) 35 | } 36 | 37 | func (s *subscriptionRpcClient) UpdateChannel(ctx context.Context, request *proto.UpdateChannelRequest, response *proto.Response) error { 38 | return s.cli.Call(ctx, "UpdateChannel", request, response) 39 | } 40 | 41 | func (s *subscriptionRpcClient) Publish(ctx context.Context, request *proto.PublishRequest, response *proto.Response) error { 42 | return s.cli.Call(ctx, "Publish", request, response) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/hash/consisten_hash_test.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestConsistentHash_Add(t *testing.T) { 10 | c := NewConsistentHash() 11 | _ = c.Add("A") 12 | _ = c.Add("B") 13 | _ = c.Add("C") 14 | _ = c.Add("D") 15 | _ = c.Add("E") 16 | _ = c.Add("F") 17 | _ = c.Add("G") 18 | 19 | //for _, n := range hash.nodes { 20 | // t.Log(n.Val, n.hash, n.virtual) 21 | //} 22 | 23 | rates := map[string]int{ 24 | "A": 0, 25 | "B": 0, 26 | "C": 0, 27 | "D": 0, 28 | "E": 0, 29 | "F": 0, 30 | "G": 0, 31 | } 32 | 33 | count := 10000 34 | 35 | for i := 0; i < count; i++ { 36 | s := strconv.FormatInt(rand.Int63n(100000), 10) 37 | nd, _ := c.Get(s) 38 | r := rates[nd.Val] 39 | rates[nd.Val] = r + 1 40 | } 41 | 42 | for k, v := range rates { 43 | t.Log(k, v, int(float64(v)/float64(count)*float64(100))) 44 | } 45 | } 46 | 47 | func TestConsistentHash_Remove(t *testing.T) { 48 | c := NewConsistentHash() 49 | _ = c.Add("A") 50 | _ = c.Add("B") 51 | _ = c.Add("C") 52 | _ = c.Add("D") 53 | _ = c.Add("E") 54 | _ = c.Add("F") 55 | //for _, n := range hash.nodes { 56 | // t.Log(n.Val, n.hash, n.virtual) 57 | //} 58 | e := c.Remove("A") 59 | if e != nil { 60 | t.Error(e) 61 | } 62 | //t.Log("=====================") 63 | //for _, n := range hash.nodes { 64 | // t.Log(n.Val, n.hash, n.virtual) 65 | //} 66 | } 67 | 68 | func TestAdd(t *testing.T) { 69 | c := NewConsistentHash2(1) 70 | _ = c.Add("A") 71 | _ = c.Add("B") 72 | 73 | for i := 0; i < 5; i++ { 74 | s := strconv.FormatInt(int64(i), 10) 75 | n, _ := c.Get(s) 76 | t.Log(i, ":", n.Val) 77 | } 78 | 79 | t.Log("===================") 80 | _ = c.Add("C") 81 | for i := 0; i < 5; i++ { 82 | s := strconv.FormatInt(int64(i), 10) 83 | n, _ := c.Get(s) 84 | t.Log(i, ":", n.Val) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /im_service/client/mixed_rpc_impl.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/messages" 6 | "github.com/glide-im/glide/pkg/rpc" 7 | "github.com/glide-im/glide/pkg/subscription" 8 | ) 9 | 10 | type Client struct { 11 | sub *SubscriptionRpcImpl 12 | gate *GatewayRpcImpl 13 | } 14 | 15 | func NewClient(opts *rpc.ClientOptions) (*Client, error) { 16 | cli, err := rpc.NewBaseClient(opts) 17 | if err != nil { 18 | return nil, err 19 | } 20 | c := Client{ 21 | sub: NewSubscriptionRpcImplWithClient(cli), 22 | gate: NewGatewayRpcImplWithClient(cli), 23 | } 24 | return &c, nil 25 | } 26 | 27 | func (c *Client) SetClientID(old gate.ID, new_ gate.ID) error { 28 | return c.gate.SetClientID(old, new_) 29 | } 30 | 31 | func (c *Client) ExitClient(id gate.ID) error { 32 | return c.gate.ExitClient(id) 33 | } 34 | 35 | func (c *Client) EnqueueMessage(id gate.ID, message *messages.GlideMessage) error { 36 | return c.gate.EnqueueMessage(id, message) 37 | } 38 | 39 | func (c *Client) Subscribe(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error { 40 | return c.sub.Subscribe(ch, id, extra) 41 | } 42 | 43 | func (c *Client) UnSubscribe(ch subscription.ChanID, id subscription.SubscriberID) error { 44 | return c.sub.UnSubscribe(ch, id) 45 | } 46 | 47 | func (c *Client) UpdateSubscriber(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error { 48 | return c.sub.UpdateSubscriber(ch, id, extra) 49 | } 50 | 51 | func (c *Client) RemoveChannel(ch subscription.ChanID) error { 52 | return c.sub.RemoveChannel(ch) 53 | } 54 | 55 | func (c *Client) CreateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error { 56 | return c.sub.CreateChannel(ch, update) 57 | } 58 | 59 | func (c *Client) UpdateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error { 60 | return c.sub.UpdateChannel(ch, update) 61 | } 62 | 63 | func (c *Client) Publish(ch subscription.ChanID, msg subscription.Message) error { 64 | return c.sub.Publish(ch, msg) 65 | } 66 | -------------------------------------------------------------------------------- /example/api/api_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/glide-im/glide/im_service/client" 6 | "github.com/glide-im/glide/pkg/auth/jwt_auth" 7 | "github.com/glide-im/glide/pkg/rpc" 8 | "github.com/glide-im/glide/pkg/subscription" 9 | "github.com/glide-im/glide/pkg/subscription/subscription_impl" 10 | ) 11 | 12 | var imServiceRpcCli *client.Client 13 | 14 | func initialize() { 15 | var err error 16 | imServiceRpcCli, err = client.NewClient(&rpc.ClientOptions{ 17 | Addr: "127.0.0.1", 18 | Port: 8092, 19 | Name: "im_rpc_server", 20 | }) 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | func main() { 27 | 28 | //ExampleUserLogin("1") 29 | 30 | initialize() 31 | ExampleCreateGroup("c1", "1") 32 | ExampleAddMember("c1", "2") 33 | } 34 | 35 | func ExampleUserLogin(uid string) { 36 | // 用户输入账号密码, 查询到 uid, 用 uid 生成 jwt token, 返回给客户端, 客户端使用该 token 登录聊天服务 37 | token, err := jwt_auth.NewAuthorizeImpl("secret").GetToken(&jwt_auth.JwtAuthInfo{ 38 | UID: uid, 39 | Device: "0", 40 | ExpiredHour: 10, 41 | }) 42 | if err != nil { 43 | panic(err) 44 | } 45 | fmt.Println(token) 46 | } 47 | 48 | func ExampleCreateGroup(channelId, adminId string) { 49 | 50 | info := &subscription.ChanInfo{ 51 | ID: subscription.ChanID(channelId), 52 | Muted: false, 53 | Blocked: false, 54 | Closed: false, 55 | } 56 | // 创建频道 57 | err := imServiceRpcCli.CreateChannel(info.ID, info) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | // 添加创建者到频道 63 | err = imServiceRpcCli.Subscribe(subscription.ChanID(channelId), subscription.SubscriberID(adminId), 64 | &subscription_impl.SubscriberOptions{Perm: subscription_impl.PermAdmin}) 65 | if err != nil { 66 | panic(err) 67 | } 68 | // 邀请其他成员 69 | // ... 70 | } 71 | 72 | func ExampleAddMember(channelId, memberId string) { 73 | 74 | err := imServiceRpcCli.Subscribe(subscription.ChanID(channelId), subscription.SubscriberID(memberId), 75 | &subscription_impl.SubscriberOptions{Perm: subscription_impl.PermRead}) 76 | if err != nil { 77 | panic(err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /pkg/gate/client_test.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "crypto/sha512" 5 | "github.com/glide-im/glide/pkg/hash" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestNewID(t *testing.T) { 12 | id := NewID("gate", "uid", "dev") 13 | assert.Equal(t, "gate_uid_dev", string(id)) 14 | } 15 | 16 | func TestID_SetGateway(t *testing.T) { 17 | id := NewID2("empty-uid") 18 | suc := id.SetGateway("gateway") 19 | 20 | assert.True(t, suc) 21 | assert.Equal(t, "gateway_empty-uid_", string(id)) 22 | } 23 | 24 | func TestID_SetDevice(t *testing.T) { 25 | id := NewID2("empty-uid") 26 | suc := id.SetDevice("device") 27 | 28 | assert.True(t, suc) 29 | assert.Equal(t, "_empty-uid_device", string(id)) 30 | } 31 | 32 | func TestID_IsTemp(t *testing.T) { 33 | id := NewID2(tempIdPrefix + "temp-uid") 34 | assert.True(t, id.IsTemp()) 35 | 36 | id = NewID2("uid") 37 | assert.False(t, id.IsTemp()) 38 | } 39 | 40 | func TestID_UID(t *testing.T) { 41 | id := NewID2("uid") 42 | assert.Equal(t, "uid", id.UID()) 43 | } 44 | 45 | func TestID_Gateway(t *testing.T) { 46 | id := NewID("gate", "uid", "dev") 47 | assert.Equal(t, "gate", id.Gateway()) 48 | 49 | id = NewID2("uid") 50 | assert.Equal(t, "", id.Gateway()) 51 | } 52 | 53 | func TestAesCBC_Decrypt(t *testing.T) { 54 | 55 | key := sha512.New().Sum([]byte("secret_key")) 56 | cbcCrypto := NewAesCBCCrypto(key) 57 | 58 | credentials := ClientAuthCredentials{ 59 | Type: 1, 60 | UserID: "", 61 | DeviceID: "1", 62 | DeviceName: "iPhone 6s", 63 | Secrets: &ClientSecrets{ 64 | MessageDeliverSecret: "secret", 65 | }, 66 | ConnectionID: "1", 67 | Timestamp: time.Now().UnixMilli(), 68 | } 69 | encryptCredentials, err := cbcCrypto.EncryptCredentials(&credentials) 70 | assert.NoError(t, err) 71 | 72 | t.Log(string(encryptCredentials)) 73 | decryptCredentials, err := cbcCrypto.DecryptCredentials(encryptCredentials) 74 | assert.NoError(t, err) 75 | 76 | assert.Equal(t, decryptCredentials.UserID, credentials.UserID) 77 | } 78 | 79 | func TestGenerateTicket(t *testing.T) { 80 | 81 | secret := "secret" 82 | sum1 := hash.SHA1(secret + "to") 83 | expectTicket := hash.SHA1(secret + "from" + sum1) 84 | 85 | t.Log(expectTicket) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/rpc/server.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/rcrowley/go-metrics" 6 | "github.com/rpcxio/rpcx-etcd/serverplugin" 7 | "github.com/smallnest/rpcx/server" 8 | "time" 9 | ) 10 | 11 | const ( 12 | BaseServicePath = "/im_service" 13 | ) 14 | 15 | type ServerOptions struct { 16 | Name string 17 | Network string 18 | Addr string 19 | Port int 20 | MaxRecvMsgSize int 21 | MaxSendMsgSize int 22 | EtcdServers []string 23 | } 24 | 25 | type BaseServer struct { 26 | Srv *server.Server 27 | 28 | Options *ServerOptions 29 | etcdRegister *serverplugin.EtcdV3RegisterPlugin 30 | reg []func(srv *BaseServer) error 31 | id string 32 | } 33 | 34 | func NewBaseServer(options *ServerOptions) *BaseServer { 35 | ret := &BaseServer{ 36 | Srv: server.NewServer(), 37 | id: fmt.Sprintf("%s@%s:%d", options.Name, options.Addr, options.Port), 38 | } 39 | 40 | if options.Network == "" { 41 | options.Network = "tcp" 42 | } 43 | 44 | ret.Options = options 45 | if len(options.EtcdServers) != 0 { 46 | ret.etcdRegister = &serverplugin.EtcdV3RegisterPlugin{ 47 | EtcdServers: options.EtcdServers, 48 | BasePath: BaseServicePath, 49 | Metrics: metrics.NewRegistry(), 50 | UpdateInterval: time.Minute, 51 | } 52 | } 53 | return ret 54 | } 55 | 56 | func (s *BaseServer) GetServerID() string { 57 | if len(s.id) == 0 { 58 | s.id = fmt.Sprintf("%s@%s:%d", s.Options.Name, s.Options.Addr, s.Options.Port) 59 | } 60 | return s.id 61 | } 62 | 63 | func (s *BaseServer) Register(name string, sv interface{}) { 64 | s.reg = append(s.reg, func(srv *BaseServer) error { 65 | return srv.Srv.RegisterName(name, sv, "") 66 | }) 67 | } 68 | 69 | func (s *BaseServer) Run() error { 70 | 71 | addr := fmt.Sprintf("%s:%d", s.Options.Addr, s.Options.Port) 72 | 73 | if s.etcdRegister != nil { 74 | s.etcdRegister.ServiceAddress = s.Options.Network + "@" + addr 75 | 76 | err := s.etcdRegister.Start() 77 | if err != nil { 78 | return err 79 | } 80 | s.Srv.Plugins.Add(s.etcdRegister) 81 | } 82 | 83 | for _, f := range s.reg { 84 | if er := f(s); er != nil { 85 | return er 86 | } 87 | } 88 | 89 | return s.Srv.Serve(s.Options.Network, addr) 90 | } 91 | -------------------------------------------------------------------------------- /internal/message_store_db/message_store.go: -------------------------------------------------------------------------------- 1 | package message_store_db 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/glide-im/glide/config" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "github.com/glide-im/glide/pkg/store" 9 | _ "github.com/go-sql-driver/mysql" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | var _ store.MessageStore = &ChatMessageStore{} 15 | 16 | type ChatMessageStore struct { 17 | db *sql.DB 18 | } 19 | 20 | func New(conf *config.MySqlConf) (*ChatMessageStore, error) { 21 | mysqlUrl := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", conf.Username, conf.Password, conf.Host, conf.Port, conf.Db) 22 | db, err := sql.Open("mysql", mysqlUrl) 23 | if err != nil { 24 | return nil, err 25 | } 26 | err = db.Ping() 27 | if err != nil { 28 | return nil, err 29 | } 30 | m := &ChatMessageStore{ 31 | db: db, 32 | } 33 | return m, nil 34 | } 35 | 36 | func (D *ChatMessageStore) StoreOffline(message *messages.ChatMessage) error { 37 | //TODO implement me 38 | panic("implement me") 39 | } 40 | 41 | func (D *ChatMessageStore) StoreMessage(m *messages.ChatMessage) error { 42 | 43 | from, err := strconv.ParseInt(m.From, 10, 64) 44 | if err != nil { 45 | return nil 46 | } 47 | to, err := strconv.ParseInt(m.To, 10, 64) 48 | if err != nil { 49 | return nil 50 | } 51 | 52 | lg := from 53 | sm := to 54 | if lg < sm { 55 | lg, sm = sm, lg 56 | } 57 | sid := fmt.Sprintf("%d_%d", lg, sm) 58 | 59 | // todo update the type of user id to string 60 | //mysql only 61 | s, e := D.db.Exec( 62 | "INSERT INTO im_chat_message (`session_id`, `from`, `to`, `type`, `content`, `send_at`, `create_at`, `cli_seq`, `status`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)ON DUPLICATE KEY UPDATE send_at=?", 63 | sid, from, to, m.Type, m.Content, m.SendAt, time.Now().Unix(), 0, 0, m.SendAt) 64 | if e != nil { 65 | return e 66 | } 67 | m.Mid, _ = s.LastInsertId() 68 | return nil 69 | } 70 | 71 | var _ store.MessageStore = &IdleChatMessageStore{} 72 | 73 | type IdleChatMessageStore struct { 74 | } 75 | 76 | func (i *IdleChatMessageStore) StoreOffline(message *messages.ChatMessage) error { 77 | return nil 78 | } 79 | 80 | func (i *IdleChatMessageStore) StoreMessage(message *messages.ChatMessage) error { 81 | message.Mid = time.Now().Unix() 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/messaging/offline_handler.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/internal/pkg/db" 5 | "github.com/glide-im/glide/pkg/gate" 6 | "github.com/glide-im/glide/pkg/logger" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "time" 9 | ) 10 | 11 | const ( 12 | KeyRedisOfflineMsgPrefix = "im:msg:offline:" 13 | ) 14 | 15 | var StoreOfflineMessage = false 16 | 17 | func offlineMessageHandler(_ *MessageHandlerImpl, _ *gate.Info, m *messages.GlideMessage) { 18 | if !StoreOfflineMessage { 19 | return 20 | } 21 | if m.GetAction() == messages.ActionChatMessage || m.GetAction() == messages.ActionChatMessageResend { 22 | c := messages.ChatMessage{} 23 | err := m.Data.Deserialize(&c) 24 | if err != nil { 25 | logger.E("deserialize chat message error: %v", err) 26 | return 27 | } 28 | bytes, err := messages.JsonCodec.Encode(m) 29 | if err != nil { 30 | logger.E("deserialize chat message error: %v", err) 31 | return 32 | } 33 | storeOfflineMessage(m.To, string(bytes)) 34 | } 35 | } 36 | 37 | func storeOfflineMessage(to string, msg string) { 38 | key := KeyRedisOfflineMsgPrefix + to 39 | db.Redis.SAdd(key, msg) 40 | // TODO 2022-6-22 16:56:57 do not reset expire on new offline message arrived 41 | // use fixed time segment save offline msg reset segment only. 42 | db.Redis.Expire(key, time.Hour*24*2) 43 | } 44 | 45 | func PushOfflineMessage(h *MessageInterfaceImpl, id string) { 46 | key := KeyRedisOfflineMsgPrefix + id 47 | members, err := db.Redis.SMembers(key).Result() 48 | if err != nil { 49 | logger.ErrE("push offline msg error", err) 50 | return 51 | } 52 | for _, member := range members { 53 | msg := messages.NewEmptyMessage() 54 | err := messages.JsonCodec.Decode([]byte(member), msg) 55 | if err != nil { 56 | logger.ErrE("deserialize redis offline msg error", err) 57 | continue 58 | } 59 | id2 := gate.NewID2(id) 60 | _ = h.GetClientInterface().EnqueueMessage(id2, msg) 61 | } 62 | 63 | // AckOfflineMessage(id) 64 | } 65 | 66 | func AckOfflineMessage(id string) { 67 | key := KeyRedisOfflineMsgPrefix + id 68 | result, err := db.Redis.Del(key).Result() 69 | if err != nil { 70 | logger.ErrE("remove offline message error", err) 71 | } 72 | logger.I("user %s ack %d offline messages", id, result) 73 | } 74 | -------------------------------------------------------------------------------- /config/viper.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/spf13/viper" 4 | 5 | var ( 6 | Common *CommonConf 7 | MySql *MySqlConf 8 | WsServer *WsServerConf 9 | IMService *IMRpcServerConf 10 | Redis *RedisConf 11 | Kafka *KafkaConf 12 | ) 13 | 14 | type CommonConf struct { 15 | StoreOfflineMessage bool 16 | StoreMessageHistory bool 17 | SecretKey string 18 | } 19 | 20 | type WsServerConf struct { 21 | ID string 22 | Addr string 23 | Port int 24 | JwtSecret string 25 | } 26 | 27 | type ApiHttpConf struct { 28 | Addr string 29 | Port int 30 | } 31 | 32 | type IMRpcServerConf struct { 33 | Addr string 34 | Port int 35 | Network string 36 | Etcd []string 37 | Name string 38 | } 39 | 40 | type KafkaConf struct { 41 | Address []string 42 | } 43 | 44 | type MySqlConf struct { 45 | Host string 46 | Port int 47 | Username string 48 | Password string 49 | Db string 50 | Charset string 51 | } 52 | 53 | type RedisConf struct { 54 | Host string 55 | Port int 56 | Password string 57 | Db int 58 | } 59 | 60 | func MustLoad() { 61 | 62 | viper.SetConfigName("config.toml") 63 | viper.SetConfigType("toml") 64 | viper.AddConfigPath(".") 65 | viper.AddConfigPath("./_config_local") 66 | viper.AddConfigPath("./config") 67 | viper.AddConfigPath("/etc/") 68 | viper.AddConfigPath("$HOME/.config/") 69 | 70 | err := viper.ReadInConfig() 71 | if err != nil { 72 | panic(err) 73 | } 74 | c := struct { 75 | MySql *MySqlConf 76 | Redis *RedisConf 77 | WsServer *WsServerConf 78 | IMRpcServer *IMRpcServerConf 79 | CommonConf *CommonConf 80 | Kafka *KafkaConf 81 | }{} 82 | 83 | err = viper.Unmarshal(&c) 84 | if err != nil { 85 | panic(err) 86 | } 87 | MySql = c.MySql 88 | WsServer = c.WsServer 89 | IMService = c.IMRpcServer 90 | Common = c.CommonConf 91 | Redis = c.Redis 92 | Kafka = c.Kafka 93 | 94 | if Common == nil { 95 | panic("CommonConf is nil") 96 | } 97 | if c.MySql == nil { 98 | panic("mysql config is nil") 99 | } 100 | if c.WsServer == nil { 101 | panic("ws server config is nil") 102 | } 103 | if c.IMRpcServer == nil { 104 | panic("im rpc server config is nil") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pkg/conn/ws_conn.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "net" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type WsConnection struct { 11 | options *WsServerOptions 12 | conn *websocket.Conn 13 | } 14 | 15 | func NewWsConnection(conn *websocket.Conn, options *WsServerOptions) *WsConnection { 16 | c := new(WsConnection) 17 | c.conn = conn 18 | c.options = options 19 | c.conn.SetCloseHandler(func(code int, text string) error { 20 | return ErrClosed 21 | }) 22 | return c 23 | } 24 | 25 | func (c *WsConnection) Write(data []byte) error { 26 | deadLine := time.Now().Add(c.options.WriteTimeout) 27 | _ = c.conn.SetWriteDeadline(deadLine) 28 | 29 | err := c.conn.WriteMessage(websocket.TextMessage, data) 30 | return c.wrapError(err) 31 | } 32 | 33 | func (c *WsConnection) Read() ([]byte, error) { 34 | 35 | deadLine := time.Now().Add(c.options.ReadTimeout) 36 | _ = c.conn.SetReadDeadline(deadLine) 37 | 38 | msgType, bytes, err := c.conn.ReadMessage() 39 | if err != nil { 40 | return nil, c.wrapError(err) 41 | } 42 | 43 | switch msgType { 44 | case websocket.TextMessage: 45 | case websocket.PingMessage: 46 | case websocket.BinaryMessage: 47 | default: 48 | return nil, ErrBadPackage 49 | } 50 | 51 | return bytes, err 52 | } 53 | 54 | func (c *WsConnection) Close() error { 55 | return c.wrapError(c.conn.Close()) 56 | } 57 | 58 | func (c *WsConnection) GetConnInfo() *ConnectionInfo { 59 | c.conn.UnderlyingConn() 60 | remoteAddr := c.conn.RemoteAddr().(*net.TCPAddr) 61 | info := ConnectionInfo{ 62 | Ip: remoteAddr.IP.String(), 63 | Port: remoteAddr.Port, 64 | Addr: c.conn.RemoteAddr().String(), 65 | } 66 | return &info 67 | } 68 | 69 | func (c *WsConnection) wrapError(err error) error { 70 | if err == nil { 71 | return nil 72 | } 73 | if websocket.IsUnexpectedCloseError(err) { 74 | return ErrClosed 75 | } 76 | if websocket.IsCloseError(err) { 77 | return ErrClosed 78 | } 79 | if strings.Contains(err.Error(), "An existing connection was forcibly closed by the remote host") { 80 | _ = c.conn.Close() 81 | return ErrClosed 82 | } 83 | if strings.Contains(err.Error(), "use of closed network conn") { 84 | _ = c.conn.Close() 85 | return ErrClosed 86 | } 87 | if strings.Contains(err.Error(), "i/o timeout") { 88 | return ErrReadTimeout 89 | } 90 | return err 91 | } 92 | -------------------------------------------------------------------------------- /pkg/rpc/client.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/glide-im/glide/pkg/logger" 7 | etcd "github.com/rpcxio/rpcx-etcd/client" 8 | "github.com/smallnest/rpcx/client" 9 | "github.com/smallnest/rpcx/protocol" 10 | ) 11 | 12 | type Cli interface { 13 | Call(ctx context.Context, fn string, request, reply interface{}) error 14 | Broadcast(fn string, request, reply interface{}) error 15 | Run() error 16 | Close() error 17 | } 18 | 19 | type ClientOptions struct { 20 | client.Option 21 | 22 | Addr string 23 | Port int 24 | Name string 25 | EtcdServers []string 26 | Selector client.Selector 27 | } 28 | 29 | type BaseClient struct { 30 | cli client.XClient 31 | options *ClientOptions 32 | id string 33 | } 34 | 35 | func NewBaseClient(options *ClientOptions) (*BaseClient, error) { 36 | ret := &BaseClient{ 37 | options: options, 38 | } 39 | 40 | var discovery client.ServiceDiscovery 41 | var err error 42 | 43 | if options.EtcdServers != nil { 44 | discovery, err = etcd.NewEtcdV3Discovery(BaseServicePath, options.Name, options.EtcdServers, false, nil) 45 | if err != nil { 46 | return nil, err 47 | } 48 | } else { 49 | srv := fmt.Sprintf("%s@%s:%d", "tcp", options.Addr, options.Port) 50 | discovery, _ = client.NewPeer2PeerDiscovery(srv, "") 51 | } 52 | 53 | if options.SerializeType == protocol.SerializeNone { 54 | // using protobuffer serializer by default 55 | options.SerializeType = protocol.ProtoBuffer 56 | } 57 | ret.cli = client.NewXClient(options.Name, client.Failtry, client.RoundRobin, discovery, options.Option) 58 | 59 | if options.Selector != nil { 60 | ret.cli.SetSelector(options.Selector) 61 | } else { 62 | // using round robbin selector by default 63 | ret.cli.SetSelector(NewRoundRobinSelector()) 64 | } 65 | return ret, nil 66 | } 67 | 68 | func (c *BaseClient) Call2(fn string, arg interface{}, reply interface{}) error { 69 | return c.Call(context.Background(), fn, arg, reply) 70 | } 71 | 72 | func (c *BaseClient) Broadcast(fn string, request, reply interface{}) error { 73 | return c.cli.Broadcast(context.Background(), fn, request, reply) 74 | } 75 | 76 | func (c *BaseClient) Call(ctx context.Context, fn string, arg interface{}, reply interface{}) error { 77 | logger.D("rpc call %s, args=%v", fn, arg) 78 | err := c.cli.Call(ctx, fn, arg, reply) 79 | return err 80 | } 81 | 82 | func (c *BaseClient) Run() error { 83 | return nil 84 | } 85 | 86 | func (c *BaseClient) Close() error { 87 | return c.cli.Close() 88 | } 89 | -------------------------------------------------------------------------------- /pkg/gate/reader.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/conn" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "sync" 8 | ) 9 | 10 | var messageReader MessageReader 11 | 12 | // var codec messages.Codec = message.ProtobufCodec{} 13 | var codec messages.Codec = messages.DefaultCodec 14 | 15 | // recyclePool 回收池, 减少临时对象, 回收复用 readerRes 16 | var recyclePool sync.Pool 17 | 18 | func init() { 19 | recyclePool = sync.Pool{ 20 | New: func() interface{} { 21 | return &readerRes{} 22 | }, 23 | } 24 | SetMessageReader(&defaultReader{}) 25 | } 26 | 27 | func SetMessageReader(s MessageReader) { 28 | messageReader = s 29 | } 30 | 31 | type readerRes struct { 32 | err error 33 | m *messages.GlideMessage 34 | } 35 | 36 | // Recycle 回收当前对象, 一定要在用完后调用这个方法, 否则无法回收 37 | func (r *readerRes) Recycle() { 38 | r.m = nil 39 | r.err = nil 40 | recyclePool.Put(r) 41 | } 42 | 43 | // MessageReader 表示一个从连接中(Connection)读取消息的读取者, 可以用于定义如何从连接中读取并解析消息. 44 | type MessageReader interface { 45 | 46 | // Read 阻塞读取, 会阻塞当前协程 47 | Read(conn conn.Connection) (*messages.GlideMessage, error) 48 | 49 | // ReadCh 返回两个管道, 第一个用于读取内容, 第二个用于发送停止读取, 停止读取时切记要发送停止信号 50 | ReadCh(conn conn.Connection) (<-chan *readerRes, chan<- interface{}) 51 | } 52 | 53 | type defaultReader struct{} 54 | 55 | func (d *defaultReader) ReadCh(conn conn.Connection) (<-chan *readerRes, chan<- interface{}) { 56 | c := make(chan *readerRes, 5) 57 | done := make(chan interface{}) 58 | 59 | go func() { 60 | defer func() { 61 | e := recover() 62 | if e != nil { 63 | logger.E("error on runRead msg from connection %v", e) 64 | } 65 | }() 66 | for { 67 | select { 68 | case <-done: 69 | goto CLOSE 70 | default: 71 | m, err := d.Read(conn) 72 | res := recyclePool.Get().(*readerRes) 73 | if err != nil { 74 | res.err = err 75 | c <- res 76 | if messages.IsDecodeError(err) { 77 | continue 78 | } 79 | goto CLOSE 80 | } else { 81 | res.m = m 82 | c <- res 83 | } 84 | } 85 | } 86 | CLOSE: 87 | close(c) 88 | }() 89 | return c, done 90 | } 91 | 92 | func (d *defaultReader) Read(conn conn.Connection) (*messages.GlideMessage, error) { 93 | // TODO 2021-12-3 校验数据包 94 | bytes, err := conn.Read() 95 | if err != nil { 96 | return nil, err 97 | } 98 | m := messages.NewEmptyMessage() 99 | err = codec.Decode(bytes, m) 100 | return m, err 101 | } 102 | -------------------------------------------------------------------------------- /pkg/messages/messages.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | // ChatMessage chat message in single/group chat 4 | type ChatMessage struct { 5 | /// client message id to identity unique a message. 6 | /// for identity a message 7 | /// and wait for the server ack receipt and return `mid` for it. 8 | CliMid string `json:"cliMid,omitempty"` 9 | /// server message id in the database. 10 | // when a client sends a message for the first time or client retry to send a message that 11 | // the server does not ack, the 'Mid' is empty. 12 | /// if this field is not empty that this message is server acked, need not store to database again. 13 | Mid int64 `json:"mid,omitempty"` 14 | /// message sequence for a chat, use to check message whether the message lost. 15 | Seq int64 `json:"seq,omitempty"` 16 | /// message sender 17 | From string `json:"from,omitempty"` 18 | /// message send to 19 | To string `json:"to,omitempty"` 20 | /// message type 21 | Type int32 `json:"type,omitempty"` 22 | /// message content 23 | Content string `json:"content,omitempty"` 24 | /// message send time, server store message time. 25 | SendAt int64 `json:"sendAt,omitempty"` 26 | } 27 | 28 | // ClientCustom client custom message, server does not store to database. 29 | type ClientCustom struct { 30 | Type string `json:"type,omitempty"` 31 | Content interface{} `json:"content,omitempty"` 32 | } 33 | 34 | // AckRequest 接收者回复给服务端确认收到消息 35 | type AckRequest struct { 36 | CliMid string `json:"cli_mid,omitempty"` 37 | Seq int64 `json:"seq,omitempty"` 38 | Mid int64 `json:"mid,omitempty"` 39 | From string `json:"from,omitempty"` 40 | To string `json:"to,omitempty"` 41 | } 42 | 43 | // AckGroupMessage 发送群消息服务器回执 44 | type AckGroupMessage struct { 45 | CliMid string `json:"cli_mid,omitempty"` 46 | Gid int64 `json:"gid,omitempty"` 47 | Mid int64 `json:"mid,omitempty"` 48 | Seq int64 `json:"seq,omitempty"` 49 | } 50 | 51 | // AckMessage 服务端通知发送者的服务端收到消息 52 | type AckMessage struct { 53 | CliMid string `json:"cli_mid,omitempty"` 54 | /// message id to tall the client 55 | Mid int64 `json:"mid,omitempty"` 56 | From string `json:"from,omitempty"` 57 | Seq int64 `json:"seq,omitempty"` 58 | } 59 | 60 | // AckNotify 服务端下发给发送者的消息送达通知 61 | type AckNotify struct { 62 | CliMid string `json:"cli_mid,omitempty"` 63 | Seq int64 `json:"seq,omitempty"` 64 | Mid int64 `json:"mid,omitempty"` 65 | From string `json:"from,omitempty"` 66 | } 67 | 68 | type KickOutNotify struct { 69 | DeviceId string `json:"device_id,omitempty"` 70 | DeviceName string `json:"device_name,omitempty"` 71 | } 72 | -------------------------------------------------------------------------------- /internal/world_channel/world_channel.go: -------------------------------------------------------------------------------- 1 | package world_channel 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/glide-im/glide/pkg/gate" 6 | "github.com/glide-im/glide/pkg/logger" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "github.com/glide-im/glide/pkg/subscription" 9 | "github.com/glide-im/glide/pkg/subscription/subscription_impl" 10 | "time" 11 | ) 12 | 13 | var sub subscription_impl.SubscribeWrap 14 | var chanId = subscription.ChanID("the_world_channel") 15 | 16 | func EnableWorldChannel(subscribe subscription_impl.SubscribeWrap) error { 17 | sub = subscribe 18 | err := sub.CreateChannel(chanId, &subscription.ChanInfo{ 19 | ID: chanId, 20 | }) 21 | if err != nil { 22 | return err 23 | } 24 | err = sub.Subscribe(chanId, "system", &subscription_impl.SubscriberOptions{Perm: subscription_impl.PermWrite}) 25 | return err 26 | } 27 | 28 | func OnUserOnline(id gate.ID) { 29 | if id.IsTemp() { 30 | return 31 | } 32 | myId := subscription.SubscriberID(id.UID()) 33 | err := sub.Subscribe(chanId, myId, 34 | &subscription_impl.SubscriberOptions{Perm: subscription_impl.PermRead | subscription_impl.PermWrite}) 35 | if err == nil { 36 | 37 | time.Sleep(time.Second) 38 | b := &messages.ChatMessage{ 39 | Mid: time.Now().UnixNano(), 40 | Seq: 0, 41 | From: "system", 42 | To: string(chanId), 43 | Type: 100, 44 | Content: id.UID(), 45 | SendAt: time.Now().Unix(), 46 | } 47 | _ = sub.Publish(chanId, &subscription_impl.PublishMessage{ 48 | From: "system", 49 | Type: subscription_impl.TypeMessage, 50 | Message: messages.NewMessage(0, messages.ActionGroupMessage, b), 51 | }) 52 | 53 | time.Sleep(time.Millisecond * 400) 54 | _ = sub.Publish(chanId, &subscription_impl.PublishMessage{ 55 | From: "system", 56 | Type: subscription_impl.TypeMessage, 57 | To: []subscription.SubscriberID{myId}, 58 | Message: messages.NewMessage(0, messages.ActionGroupMessage, b), 59 | }) 60 | } else { 61 | logger.E("$v", err) 62 | } 63 | } 64 | 65 | func OnUserOffline(id gate.ID) { 66 | if id.IsTemp() { 67 | return 68 | } 69 | err := sub.UnSubscribe(chanId, subscription.SubscriberID(id.UID())) 70 | if err != nil { 71 | logger.E("$v", err) 72 | } 73 | b, _ := json.Marshal(&messages.ChatMessage{ 74 | Mid: time.Now().UnixNano(), 75 | Seq: 0, 76 | From: "system", 77 | To: string(chanId), 78 | Type: 101, 79 | Content: id.UID(), 80 | SendAt: time.Now().Unix(), 81 | }) 82 | _ = sub.Publish(chanId, &subscription_impl.PublishMessage{ 83 | From: "system", 84 | Type: subscription_impl.TypeMessage, 85 | Message: messages.NewMessage(0, messages.ActionGroupMessage, b), 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/wrap.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import "github.com/glide-im/glide/pkg/subscription" 4 | 5 | // SubscribeWrap the wrapper for subscription.Subscribe implementation, for convenience. 6 | type SubscribeWrap interface { 7 | Subscribe(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error 8 | 9 | UnSubscribe(ch subscription.ChanID, id subscription.SubscriberID) error 10 | 11 | UpdateSubscriber(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error 12 | 13 | RemoveChannel(ch subscription.ChanID) error 14 | 15 | CreateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error 16 | 17 | UpdateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error 18 | 19 | Publish(ch subscription.ChanID, msg subscription.Message) error 20 | } 21 | 22 | func NewSubscribeWrap(subscribe subscription.Subscribe) SubscribeWrap { 23 | return &wrap{ 24 | fac: subscribe, 25 | } 26 | } 27 | 28 | var _ SubscribeWrap = (*wrap)(nil) 29 | 30 | type wrap struct { 31 | fac subscription.Subscribe 32 | } 33 | 34 | func (w *wrap) Subscribe(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error { 35 | return w.fac.UpdateSubscriber(ch, []subscription.Update{ 36 | { 37 | Flag: subscription.SubscriberSubscribe, 38 | ID: id, 39 | Extra: extra, 40 | }, 41 | }) 42 | } 43 | 44 | func (w *wrap) UnSubscribe(ch subscription.ChanID, id subscription.SubscriberID) error { 45 | return w.fac.UpdateSubscriber(ch, []subscription.Update{ 46 | { 47 | Flag: subscription.SubscriberUnsubscribe, 48 | ID: id, 49 | Extra: nil, 50 | }, 51 | }) 52 | } 53 | 54 | func (w *wrap) UpdateSubscriber(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error { 55 | return w.fac.UpdateSubscriber(ch, []subscription.Update{ 56 | { 57 | Flag: subscription.SubscriberUpdate, 58 | ID: id, 59 | Extra: extra, 60 | }, 61 | }) 62 | } 63 | 64 | func (w *wrap) RemoveChannel(ch subscription.ChanID) error { 65 | return w.fac.UpdateChannel(ch, subscription.ChannelUpdate{ 66 | Flag: subscription.ChanDelete, 67 | Extra: nil, 68 | }) 69 | } 70 | 71 | func (w *wrap) CreateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error { 72 | return w.fac.UpdateChannel(ch, subscription.ChannelUpdate{ 73 | Flag: subscription.ChanCreate, 74 | Extra: update, 75 | }) 76 | } 77 | 78 | func (w *wrap) UpdateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error { 79 | return w.fac.UpdateChannel(ch, subscription.ChannelUpdate{ 80 | Flag: subscription.ChanUpdate, 81 | Extra: update, 82 | }) 83 | } 84 | 85 | func (w *wrap) Publish(ch subscription.ChanID, msg subscription.Message) error { 86 | return w.fac.PublishMessage(ch, msg) 87 | } 88 | -------------------------------------------------------------------------------- /pkg/messages/message.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | ) 9 | 10 | var messageVersion int64 = 1 11 | 12 | // GlideMessage common data of all message 13 | type GlideMessage struct { 14 | Ver int64 `json:"ver,omitempty"` 15 | Seq int64 `json:"seq,omitempty"` 16 | Action string `json:"action"` 17 | From string `json:"from,omitempty"` 18 | To string `json:"to,omitempty"` 19 | Data *Data `json:"data,omitempty"` 20 | Msg string `json:"msg,omitempty"` 21 | 22 | Ticket string `json:"ticket,omitempty"` 23 | Sign string `json:"sign,omitempty"` 24 | 25 | Extra map[string]string `json:"extra,omitempty"` 26 | } 27 | 28 | func NewMessage(seq int64, action Action, data interface{}) *GlideMessage { 29 | return &GlideMessage{ 30 | Ver: messageVersion, 31 | Seq: seq, 32 | Action: string(action), 33 | Data: NewData(data), 34 | Extra: nil, 35 | } 36 | } 37 | 38 | func NewEmptyMessage() *GlideMessage { 39 | return &GlideMessage{ 40 | Ver: messageVersion, 41 | Data: nil, 42 | Extra: nil, 43 | } 44 | } 45 | 46 | func (g *GlideMessage) GetSeq() int64 { 47 | return g.Seq 48 | } 49 | 50 | func (g *GlideMessage) GetAction() Action { 51 | return Action(g.Action) 52 | } 53 | 54 | func (g *GlideMessage) SetSeq(seq int64) { 55 | g.Seq = seq 56 | } 57 | 58 | func (g *GlideMessage) String() string { 59 | if g == nil { 60 | return "" 61 | } 62 | return fmt.Sprintf("&Message{Ver:%d, Action:%s, Data:%s}", g.Ver, g.Action, g.Data) 63 | } 64 | 65 | // Data used to wrap message data. 66 | // Server received a message, the data type is []byte, it's waiting for deserialize to specified struct. 67 | // When server push a message to client, the data type is specific struct. 68 | type Data struct { 69 | des interface{} 70 | } 71 | 72 | func NewData(d interface{}) *Data { 73 | return &Data{ 74 | des: d, 75 | } 76 | } 77 | 78 | func (d *Data) UnmarshalJSON(bytes []byte) error { 79 | d.des = bytes 80 | return nil 81 | } 82 | 83 | func (d *Data) MarshalJSON() ([]byte, error) { 84 | bytes, ok := d.des.([]byte) 85 | if ok { 86 | return bytes, nil 87 | } 88 | return JsonCodec.Encode(d.des) 89 | } 90 | 91 | func (d *Data) GetData() interface{} { 92 | return d.des 93 | } 94 | 95 | func (d *Data) Deserialize(i interface{}) error { 96 | if d == nil { 97 | return errors.New("data is nil") 98 | } 99 | s, ok := d.des.([]byte) 100 | if ok { 101 | return JsonCodec.Decode(s, i) 102 | } else { 103 | t1 := reflect.TypeOf(i) 104 | t2 := reflect.TypeOf(d.des) 105 | if t1 == t2 { 106 | reflect.ValueOf(i).Elem().Set(reflect.ValueOf(d.des).Elem()) 107 | return nil 108 | } 109 | } 110 | return errors.New("deserialize message data failed") 111 | } 112 | 113 | func (d *Data) String() string { 114 | b, ok := d.des.([]byte) 115 | var s interface{} 116 | if ok { 117 | s = string(b) 118 | } else { 119 | if d.des == nil { 120 | s = "" 121 | } else { 122 | s, _ = json.Marshal(d.des) 123 | } 124 | } 125 | return fmt.Sprintf("%s", s) 126 | } 127 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/subscription_test.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "github.com/glide-im/glide/pkg/subscription" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | ) 9 | 10 | type mockStore struct { 11 | } 12 | 13 | func (m *mockStore) NextSegmentSequence(id subscription.ChanID, info subscription.ChanInfo) (int64, int64, error) { 14 | return 1, 10000, nil 15 | } 16 | 17 | func (m *mockStore) StoreChannelMessage(ch subscription.ChanID, msg *messages.ChatMessage) error { 18 | return nil 19 | } 20 | 21 | func TestRealSubscription_Publish(t *testing.T) { 22 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 23 | m := PublishMessage{Type: TypeNotify} 24 | id := subscription.ChanID("test") 25 | err := sbp.CreateChannel(id, nil) 26 | assert.Nil(t, err) 27 | err = sbp.Publish(id, &m) 28 | assert.Nil(t, err) 29 | } 30 | 31 | func TestRealSubscription_PublishNotExist(t *testing.T) { 32 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 33 | m := PublishMessage{} 34 | id := subscription.ChanID("test") 35 | err := sbp.Publish(id, &m) 36 | assert.ErrorContains(t, err, subscription.ErrChanNotExist) 37 | } 38 | 39 | func TestRealSubscription_CreateChannelExist(t *testing.T) { 40 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 41 | id := subscription.ChanID("test") 42 | err := sbp.CreateChannel(id, nil) 43 | assert.Nil(t, err) 44 | err = sbp.CreateChannel(id, nil) 45 | assert.ErrorContains(t, err, subscription.ErrChanAlreadyExists) 46 | } 47 | 48 | func TestRealSubscription_CreateChannel(t *testing.T) { 49 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 50 | id := subscription.ChanID("test") 51 | err := sbp.CreateChannel(id, nil) 52 | assert.Nil(t, err) 53 | } 54 | 55 | func TestRealSubscription_RemoveChannel(t *testing.T) { 56 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 57 | id := subscription.ChanID("test") 58 | err := sbp.CreateChannel(id, nil) 59 | assert.Nil(t, err) 60 | err = sbp.RemoveChannel(id) 61 | assert.Nil(t, err) 62 | } 63 | 64 | func TestRealSubscription_RemoveChannelNotExit(t *testing.T) { 65 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 66 | id := subscription.ChanID("test") 67 | err := sbp.RemoveChannel(id) 68 | assert.ErrorContains(t, err, subscription.ErrChanNotExist) 69 | } 70 | 71 | func TestRealSubscription_Subscribe(t *testing.T) { 72 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 73 | id := subscription.ChanID("test") 74 | err := sbp.CreateChannel(id, nil) 75 | assert.Nil(t, err) 76 | err = sbp.Subscribe(id, "test", &SubscriberOptions{}) 77 | assert.Nil(t, err) 78 | } 79 | 80 | func TestRealSubscription_UnSubscribe(t *testing.T) { 81 | var sbp = NewSubscribeWrap(NewSubscription(&mockStore{}, &mockStore{})) 82 | id := subscription.ChanID("test") 83 | err := sbp.CreateChannel(id, nil) 84 | assert.Nil(t, err) 85 | err = sbp.Subscribe(id, "test", &SubscriberOptions{}) 86 | assert.Nil(t, err) 87 | err = sbp.UnSubscribe(id, "test") 88 | assert.Nil(t, err) 89 | } 90 | -------------------------------------------------------------------------------- /pkg/store/kafka_store_producer.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/Shopify/sarama" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "github.com/glide-im/glide/pkg/subscription" 8 | "time" 9 | ) 10 | 11 | const ( 12 | KafkaChatMessageTopic = "getaway_chat_message" 13 | KafkaChatOfflineMessageTopic = "getaway_chat_offline_message" 14 | KafkaChannelMessageTopic = "gateway_channel_message" 15 | ) 16 | 17 | var _ MessageStore = &KafkaMessageStore{} 18 | var _ SubscriptionStore = &KafkaMessageStore{} 19 | 20 | type KafkaMessageStore struct { 21 | producer sarama.AsyncProducer 22 | } 23 | 24 | func NewKafkaProducer(address []string) (*KafkaMessageStore, error) { 25 | 26 | config := sarama.NewConfig() 27 | config.Producer.RequiredAcks = sarama.WaitForAll 28 | config.Producer.Partitioner = sarama.NewRandomPartitioner 29 | //config.Producer.Return.Successes = true 30 | 31 | producer, err := sarama.NewAsyncProducer(address, config) 32 | 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return &KafkaMessageStore{ 38 | producer: producer, 39 | }, nil 40 | } 41 | 42 | func (k *KafkaMessageStore) Close() error { 43 | return k.producer.Close() 44 | } 45 | 46 | type msg struct { 47 | data []byte 48 | } 49 | 50 | func (m *msg) Encode() ([]byte, error) { 51 | return m.data, nil 52 | } 53 | 54 | func (m *msg) Length() int { 55 | return len(m.data) 56 | } 57 | 58 | func (k *KafkaMessageStore) StoreOffline(message *messages.ChatMessage) error { 59 | msgBytes, err := json.Marshal(message) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | cm := &sarama.ProducerMessage{ 65 | Topic: KafkaChatOfflineMessageTopic, 66 | Value: &msg{data: msgBytes}, 67 | Headers: nil, 68 | Metadata: nil, 69 | Offset: 0, 70 | Partition: 0, 71 | Timestamp: time.Now(), 72 | } 73 | k.producer.Input() <- cm 74 | return nil 75 | } 76 | 77 | func (k *KafkaMessageStore) NextSegmentSequence(id subscription.ChanID, info subscription.ChanInfo) (int64, int64, error) { 78 | //TODO implement me 79 | return 0, 0, nil 80 | } 81 | 82 | func (k *KafkaMessageStore) StoreChannelMessage(ch subscription.ChanID, m *messages.ChatMessage) error { 83 | msgBytes, err := json.Marshal(m) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | cm := &sarama.ProducerMessage{ 89 | Topic: KafkaChannelMessageTopic, 90 | Value: &msg{data: msgBytes}, 91 | Headers: nil, 92 | Metadata: nil, 93 | Offset: 0, 94 | Partition: 0, 95 | Timestamp: time.Now(), 96 | } 97 | k.producer.Input() <- cm 98 | return nil 99 | } 100 | 101 | func (k *KafkaMessageStore) StoreMessage(message *messages.ChatMessage) error { 102 | 103 | msgBytes, err := json.Marshal(message) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | cm := &sarama.ProducerMessage{ 109 | Topic: KafkaChatMessageTopic, 110 | Value: &msg{data: msgBytes}, 111 | Headers: nil, 112 | Metadata: nil, 113 | Offset: 0, 114 | Partition: 0, 115 | Timestamp: time.Now(), 116 | } 117 | k.producer.Input() <- cm 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /example/client/rpc_client_example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/glide-im/glide/im_service/client" 5 | "github.com/glide-im/glide/pkg/gate" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "github.com/glide-im/glide/pkg/rpc" 8 | "github.com/glide-im/glide/pkg/subscription/subscription_impl" 9 | ) 10 | 11 | /// 如何控制消息服务器, 用户连接, 发布订阅接口 12 | /// 用于 HTTP API 接口等非 IM 业务配合使用 13 | 14 | func main() { 15 | 16 | RpcGatewayClientUpdateClient() 17 | return 18 | 19 | // 消息网关接口(用户管理用户连接, 用户状态) 20 | RpcGatewayClientExample() 21 | 22 | // 发布订阅(群聊) 23 | RpcSubscriberClientExample() 24 | } 25 | 26 | func RpcGatewayClientUpdateClient() { 27 | 28 | options := &rpc.ClientOptions{ 29 | Addr: "127.0.0.1", 30 | Port: 8092, 31 | Name: "im_rpc_server", 32 | } 33 | cli, err := client.NewGatewayRpcImpl(options) 34 | 35 | defer cli.Close() 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | err = cli.UpdateClient(gate.NewID2("544607"), &gate.ClientSecrets{MessageDeliverSecret: "secret"}) 41 | //err = cli.EnqueueMessage(gate.NewID2("544607"), messages.NewEmptyMessage()) 42 | //err = cli.ExitClient(gate.NewID2("544607")) 43 | if err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | func RpcGatewayClientExample() { 49 | 50 | // 消息网关 RPC 客户端配置 51 | options := &rpc.ClientOptions{ 52 | Addr: "127.0.0.1", 53 | Port: 8092, 54 | Name: "im_rpc_server", 55 | } 56 | 57 | // 创建消息网关接口客户端 58 | cli, err := client.NewGatewayRpcImpl(options) 59 | // 长时间不用完记得关闭 60 | defer cli.Close() 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | // 给网关中指定 id 的链接推送一条消息 (例如加好友通知, 多设备登录通知等等) 66 | err = cli.EnqueueMessage(gate.NewID2("1"), messages.NewEmptyMessage()) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | // 设置网关中连接新 id 72 | err = cli.SetClientID(gate.NewID2("1"), gate.NewID2("2")) 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | // 断开 uid 为 1 的设备 1 78 | // 单体情况, 网关 id 传空即可 79 | _ = cli.ExitClient(gate.NewID("", "1", "1")) 80 | } 81 | 82 | func RpcSubscriberClientExample() { 83 | options := &rpc.ClientOptions{ 84 | Addr: "127.0.0.1", 85 | Port: 8092, 86 | Name: "im_rpc_server", 87 | } 88 | cli, err := client.NewSubscriptionRpcImpl(options) 89 | defer cli.Close() 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | //err = cli.CreateChannel("1", &subscription.ChanInfo{ 95 | // ID: "1", 96 | // Type: 0, 97 | //}) 98 | //if err != nil { 99 | // panic(err) 100 | //} 101 | 102 | // 用户订阅某个频道的消息(用户上线, 开始接受群消息) 103 | err = cli.Subscribe("1", "1", &subscription_impl.SubscriberOptions{ 104 | Perm: subscription_impl.PermRead | subscription_impl.PermWrite, 105 | }) 106 | if err != nil { 107 | panic(err) 108 | } 109 | 110 | // 移除指定 id 频道 (解散群, 删除频道等) 111 | _ = cli.RemoveChannel("1") 112 | 113 | msg := &subscription_impl.PublishMessage{ 114 | From: "1", 115 | Seq: 1, 116 | Type: subscription_impl.TypeMessage, 117 | Message: messages.NewMessage(0, "1", &messages.ChatMessage{}), 118 | } 119 | // 推送消息到指定频道 (发送一条系统消息, 群通知等) 120 | err = cli.Publish("1", msg) 121 | if err != nil { 122 | panic(err) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /pkg/store/kafka_store_consumer.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "github.com/Shopify/sarama" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | ) 8 | 9 | type KafkaConsumer struct { 10 | consumer sarama.Consumer 11 | cf func(m *messages.ChatMessage) 12 | offlineCf func(m *messages.ChatMessage) 13 | channelCf func(m *messages.ChatMessage) 14 | } 15 | 16 | func NewKafkaConsumer(address []string) (*KafkaConsumer, error) { 17 | 18 | consumer, err := sarama.NewConsumer(address, sarama.NewConfig()) 19 | if err != nil { 20 | return nil, err 21 | } 22 | c := &KafkaConsumer{ 23 | consumer: consumer, 24 | } 25 | if err = c.run(); err != nil { 26 | return nil, err 27 | } 28 | 29 | return c, nil 30 | } 31 | 32 | func (c *KafkaConsumer) run() error { 33 | 34 | partitions, err2 := c.consumer.Partitions(KafkaChatMessageTopic) 35 | 36 | if err2 != nil { 37 | return err2 38 | } 39 | 40 | for _, partition := range partitions { 41 | 42 | consumer, err := c.consumer.ConsumePartition(KafkaChatMessageTopic, partition, sarama.OffsetNewest) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | go func(pc sarama.PartitionConsumer) { 48 | for m := range pc.Messages() { 49 | var cm = messages.ChatMessage{} 50 | err2 := messages.JsonCodec.Decode(m.Value, &cm) 51 | if err2 != nil { 52 | logger.E("message decode error %v", err2) 53 | continue 54 | } 55 | if c.cf != nil { 56 | c.cf(&cm) 57 | } 58 | } 59 | }(consumer) 60 | 61 | consumer2, err := c.consumer.ConsumePartition(KafkaChannelMessageTopic, partition, sarama.OffsetNewest) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | go func(pc sarama.PartitionConsumer) { 67 | for m := range pc.Messages() { 68 | var cm = messages.ChatMessage{} 69 | err2 := messages.JsonCodec.Decode(m.Value, &cm) 70 | if err2 != nil { 71 | logger.E("message decode error %v", err2) 72 | continue 73 | } 74 | if c.channelCf != nil { 75 | c.channelCf(&cm) 76 | } 77 | } 78 | }(consumer2) 79 | 80 | consumer3, err := c.consumer.ConsumePartition(KafkaChatOfflineMessageTopic, partition, sarama.OffsetNewest) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | go func(pc sarama.PartitionConsumer) { 86 | for m := range pc.Messages() { 87 | var cm = messages.ChatMessage{} 88 | err2 := messages.JsonCodec.Decode(m.Value, &cm) 89 | if err2 != nil { 90 | logger.E("message decode error %v", err2) 91 | continue 92 | } 93 | if c.offlineCf != nil { 94 | c.offlineCf(&cm) 95 | } 96 | } 97 | }(consumer3) 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func (c *KafkaConsumer) Close() error { 104 | return c.consumer.Close() 105 | } 106 | 107 | func (c *KafkaConsumer) ConsumeChatMessage(cf func(m *messages.ChatMessage)) { 108 | c.cf = cf 109 | } 110 | 111 | func (c *KafkaConsumer) ConsumeChannelMessage(cf func(m *messages.ChatMessage)) { 112 | c.channelCf = cf 113 | } 114 | 115 | func (c *KafkaConsumer) ConsumeOfflineMessage(cf func(m *messages.ChatMessage)) { 116 | c.offlineCf = cf 117 | } 118 | -------------------------------------------------------------------------------- /im_service/client/gateway_rpc_impl.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/glide-im/glide/im_service/proto" 9 | "github.com/glide-im/glide/pkg/gate" 10 | "github.com/glide-im/glide/pkg/messages" 11 | "github.com/glide-im/glide/pkg/rpc" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | errRpcInvocation = "gate invocation error: " 17 | ) 18 | 19 | type IMServiceError struct { 20 | Code int32 21 | Message string 22 | } 23 | 24 | func (e *IMServiceError) Error() string { 25 | return fmt.Sprintf("IM Service Error: %d, %s", e.Code, e.Message) 26 | } 27 | 28 | // IsRpcInvocationError 29 | // Rpc invocation failed errors are returned by the gate client when the rpc call fails. 30 | func IsRpcInvocationError(err error) bool { 31 | return err != nil && strings.HasPrefix(err.Error(), errRpcInvocation) 32 | } 33 | 34 | type GatewayRpcImpl struct { 35 | gate *GatewayRpcClient 36 | } 37 | 38 | func NewGatewayRpcImplWithClient(client *rpc.BaseClient) *GatewayRpcImpl { 39 | return &GatewayRpcImpl{ 40 | gate: &GatewayRpcClient{ 41 | cli: client, 42 | }, 43 | } 44 | } 45 | 46 | func NewGatewayRpcImpl(opts *rpc.ClientOptions) (*GatewayRpcImpl, error) { 47 | cli, err := rpc.NewBaseClient(opts) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return NewGatewayRpcImplWithClient(cli), nil 52 | } 53 | 54 | func (i *GatewayRpcImpl) SetClientID(old gate.ID, new_ gate.ID) error { 55 | response := proto.Response{} 56 | ctx := context.TODO() 57 | request := proto.UpdateClient{ 58 | Type: proto.UpdateClient_UpdateID, 59 | Id: string(old), 60 | NewId: string(new_), 61 | } 62 | return i.gate.UpdateClient(ctx, &request, &response) 63 | } 64 | 65 | func (i *GatewayRpcImpl) UpdateClient(id gate.ID, info *gate.ClientSecrets) error { 66 | response := proto.Response{} 67 | ctx := context.TODO() 68 | request := proto.UpdateClient{ 69 | Type: proto.UpdateClient_UpdateSecret, 70 | Id: string(id), 71 | Secret: info.MessageDeliverSecret, 72 | } 73 | return i.gate.UpdateClient(ctx, &request, &response) 74 | } 75 | 76 | func (i *GatewayRpcImpl) ExitClient(id gate.ID) error { 77 | response := proto.Response{} 78 | ctx := context.TODO() 79 | request := proto.UpdateClient{ 80 | Type: proto.UpdateClient_Close, 81 | Id: string(id), 82 | } 83 | return i.gate.UpdateClient(ctx, &request, &response) 84 | } 85 | 86 | func (i *GatewayRpcImpl) EnqueueMessage(id gate.ID, message *messages.GlideMessage) error { 87 | 88 | marshal, err := json.Marshal(message) 89 | if err != nil { 90 | return err 91 | } 92 | ctx := context.TODO() 93 | request := proto.EnqueueMessageRequest{ 94 | Id: string(id), 95 | Msg: marshal, 96 | } 97 | response := proto.Response{} 98 | err = i.gate.EnqueueMessage(ctx, &request, &response) 99 | if err != nil { 100 | return errors.New(errRpcInvocation + err.Error()) 101 | } 102 | return getResponseError(&response) 103 | } 104 | 105 | func (i *GatewayRpcImpl) Close() error { 106 | return i.gate.cli.Close() 107 | } 108 | 109 | func getResponseError(response *proto.Response) error { 110 | if proto.Response_ResponseCode(response.GetCode()) != proto.Response_OK { 111 | return &IMServiceError{ 112 | Code: response.GetCode(), 113 | Message: response.GetMsg(), 114 | } 115 | } 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /pkg/messaging/user_state_sub.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | type UserStateData struct { 12 | Uid string `json:"uid,omitempty"` 13 | Online bool `json:"online,omitempty" json:"online,omitempty"` 14 | } 15 | 16 | type StateSubscribeData struct { 17 | Uids []string `json:"uids,omitempty"` 18 | } 19 | 20 | type UserState struct { 21 | subscribers map[string]map[string]byte 22 | mySubs map[string]map[string]byte 23 | 24 | mu *sync.Mutex 25 | gateway gate.Gateway 26 | 27 | logStateAt int64 28 | } 29 | 30 | func NewUserState(gateway gate.Gateway) *UserState { 31 | return &UserState{ 32 | subscribers: map[string]map[string]byte{}, 33 | mySubs: map[string]map[string]byte{}, 34 | gateway: gateway, 35 | mu: &sync.Mutex{}, 36 | } 37 | } 38 | 39 | func (u *UserState) onUserOnline(id gate.ID) { 40 | u.mu.Lock() 41 | defer u.mu.Unlock() 42 | 43 | mySubList, ok := u.subscribers[id.UID()] 44 | if !ok { 45 | mySubList = map[string]byte{} 46 | u.subscribers[id.UID()] = mySubList 47 | } 48 | u.notifyOnline(id, mySubList) 49 | } 50 | 51 | func (u *UserState) onUserOffline(id gate.ID) { 52 | u.mu.Lock() 53 | defer u.mu.Unlock() 54 | 55 | mySubList, ok := u.subscribers[id.UID()] 56 | if !ok || len(mySubList) == 0 { 57 | return 58 | } 59 | u.notifyOffline(id, mySubList) 60 | 61 | myId := id.UID() 62 | sub, ok := u.mySubs[myId] 63 | if !ok { 64 | return 65 | } 66 | for uid := range sub { 67 | target, ok2 := u.subscribers[uid] 68 | if !ok2 { 69 | continue 70 | } 71 | delete(target, uid) 72 | } 73 | delete(u.mySubs, myId) 74 | } 75 | 76 | func (u *UserState) subUserStateApi(c *gate.Info, m *messages.GlideMessage) error { 77 | data := StateSubscribeData{} 78 | err := m.Data.Deserialize(&data) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | myId := c.ID.UID() 84 | u.mu.Lock() 85 | defer u.mu.Unlock() 86 | 87 | mySubs, ok := u.mySubs[myId] 88 | if !ok { 89 | mySubs = map[string]byte{} 90 | u.mySubs[myId] = mySubs 91 | } 92 | 93 | for _, uid := range data.Uids { 94 | subscribers, ok := u.subscribers[uid] 95 | if !ok { 96 | subscribers = map[string]byte{} 97 | u.subscribers[uid] = subscribers 98 | } 99 | u.subscribers[uid][myId] = 0 100 | mySubs[uid] = 0 101 | } 102 | return nil 103 | } 104 | 105 | func (u *UserState) notifyOnline(src gate.ID, to map[string]byte) { 106 | notify := messages.NewMessage(0, messages.ActionNotifyUserState, UserStateData{ 107 | Uid: src.UID(), 108 | Online: false, 109 | }) 110 | for uid := range to { 111 | _ = u.gateway.EnqueueMessage(gate.NewID2(uid), notify) 112 | } 113 | 114 | var s = time.Now().Unix() - u.logStateAt 115 | if s > 900 { 116 | u.logStateAt = time.Now().Unix() 117 | logger.D("[UserState] online users: %d, subscribes: %d", len(u.mySubs), len(u.subscribers)) 118 | } 119 | } 120 | 121 | func (u *UserState) notifyOffline(src gate.ID, to map[string]byte) { 122 | notify := messages.NewMessage(0, messages.ActionNotifyUserState, UserStateData{ 123 | Uid: src.UID(), 124 | Online: true, 125 | }) 126 | for uid := range to { 127 | _ = u.gateway.EnqueueMessage(gate.NewID2(uid), notify) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /cmd/im_service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/glide-im/glide/config" 5 | "github.com/glide-im/glide/im_service/server" 6 | "github.com/glide-im/glide/internal/message_store_db" 7 | "github.com/glide-im/glide/internal/pkg/db" 8 | "github.com/glide-im/glide/internal/world_channel" 9 | "github.com/glide-im/glide/pkg/gate" 10 | "github.com/glide-im/glide/pkg/logger" 11 | "github.com/glide-im/glide/pkg/messages" 12 | "github.com/glide-im/glide/pkg/messaging" 13 | "github.com/glide-im/glide/pkg/rpc" 14 | "github.com/glide-im/glide/pkg/store" 15 | "github.com/glide-im/glide/pkg/subscription/subscription_impl" 16 | ) 17 | 18 | func main() { 19 | 20 | config.MustLoad() 21 | 22 | err := db.Init(nil, &db.RedisConfig{ 23 | Host: config.Redis.Host, 24 | Port: config.Redis.Port, 25 | Password: config.Redis.Password, 26 | Db: config.Redis.Db, 27 | }) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | gateway := gate.NewWebsocketServer( 33 | config.WsServer.ID, 34 | config.WsServer.Addr, 35 | config.WsServer.Port, 36 | config.Common.SecretKey, 37 | ) 38 | 39 | var cStore store.MessageStore = &message_store_db.IdleChatMessageStore{} 40 | var sStore store.SubscriptionStore = &message_store_db.IdleSubscriptionStore{} 41 | 42 | if config.Common.StoreMessageHistory { 43 | if config.Kafka != nil && len(config.Kafka.Address) != 0 { 44 | producer, err := store.NewKafkaProducer(config.Kafka.Address) 45 | if err != nil { 46 | panic(err) 47 | } 48 | cStore = producer 49 | sStore = producer 50 | logger.D("Kafka is configured, all message will push to kafka: %v", config.Kafka.Address) 51 | } else { 52 | dbStore, err := message_store_db.New(config.MySql) 53 | if err != nil { 54 | panic(err) 55 | } 56 | cStore = dbStore 57 | sStore = &message_store_db.SubscriptionMessageStore{} 58 | } 59 | 60 | } else { 61 | logger.D("Common.StoreMessageHistory is false, message history will not be stored") 62 | } 63 | 64 | handler, err := messaging.NewHandlerWithOptions(gateway, &messaging.MessageHandlerOptions{ 65 | MessageStore: cStore, 66 | DontInitDefaultHandler: false, 67 | NotifyOnErr: true, 68 | }) 69 | if err != nil { 70 | panic(err) 71 | } 72 | messaging.StoreOfflineMessage = config.Common.StoreOfflineMessage 73 | 74 | subscription := subscription_impl.NewSubscription(sStore, sStore) 75 | subscription.SetGateInterface(gateway) 76 | 77 | handler.SetSubscription(subscription) 78 | handler.SetGate(gateway) 79 | 80 | go func() { 81 | logger.D("websocket listening on %s:%d", config.WsServer.Addr, config.WsServer.Port) 82 | 83 | gateway.SetMessageHandler(func(cliInfo *gate.Info, message *messages.GlideMessage) { 84 | e := handler.Handle(cliInfo, message) 85 | if e != nil { 86 | logger.E("error: %v", e) 87 | } 88 | }) 89 | 90 | err = gateway.Run() 91 | if err != nil { 92 | panic(err) 93 | } 94 | }() 95 | 96 | err = world_channel.EnableWorldChannel(subscription_impl.NewSubscribeWrap(subscription)) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | rpcOpts := rpc.ServerOptions{ 102 | Name: config.IMService.Name, 103 | Network: config.IMService.Network, 104 | Addr: config.IMService.Addr, 105 | Port: config.IMService.Port, 106 | } 107 | logger.D("rpc %s listening on %s %s:%d", rpcOpts.Name, rpcOpts.Network, rpcOpts.Addr, rpcOpts.Port) 108 | err = server.RunRpcService(&rpcOpts, gateway, subscription) 109 | if err != nil { 110 | panic(err) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /pkg/messaging/chat.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | ) 8 | 9 | // handleChatMessage 分发用户单聊消息 10 | func (d *MessageHandlerImpl) handleChatMessage(c *gate.Info, m *messages.GlideMessage) error { 11 | msg := new(messages.ChatMessage) 12 | if !d.unmarshalData(c, m, msg) { 13 | return nil 14 | } 15 | msg.From = c.ID.UID() 16 | msg.To = m.To 17 | 18 | if msg.Mid == 0 && m.Action != messages.ActionChatMessageResend { 19 | // 当客户端发送一条 mid 为 0 的消息时表示这条消息未被服务端收到过, 或客户端未收到服务端的确认回执 20 | err := d.store.StoreMessage(msg) 21 | if err != nil { 22 | logger.E("store chat message error %v", err) 23 | return err 24 | } 25 | } 26 | // sender resend message to receiver, server has already acked it 27 | // does the server should not ack it again ? 28 | err := d.ackChatMessage(c, msg) 29 | if err != nil { 30 | logger.E("ack chat message error %v", err) 31 | } 32 | 33 | pushMsg := messages.NewMessage(0, messages.ActionChatMessage, msg) 34 | 35 | if !d.dispatchAllDevice(msg.To, pushMsg) { 36 | // receiver offline, send offline message, and ack message 37 | err := d.ackNotifyMessage(c, msg) 38 | if err != nil { 39 | logger.E("ack notify message error %v", err) 40 | } 41 | return d.dispatchOffline(c, msg) 42 | } 43 | return nil 44 | } 45 | 46 | func (d *MessageHandlerImpl) handleChatRecallMessage(c *gate.Info, msg *messages.GlideMessage) error { 47 | return d.handleChatMessage(c, msg) 48 | } 49 | 50 | func (d *MessageHandlerImpl) ackNotifyMessage(c *gate.Info, m *messages.ChatMessage) error { 51 | ackNotify := messages.AckNotify{ 52 | CliMid: m.CliMid, 53 | Mid: m.Mid, 54 | Seq: m.Seq, 55 | From: m.From, 56 | } 57 | msg := messages.NewMessage(0, messages.ActionAckNotify, &ackNotify) 58 | return d.def.GetClientInterface().EnqueueMessage(gate.NewID2(m.To), msg) 59 | } 60 | 61 | func (d *MessageHandlerImpl) ackChatMessage(c *gate.Info, msg *messages.ChatMessage) error { 62 | ackMsg := messages.AckMessage{ 63 | CliMid: msg.CliMid, 64 | Mid: msg.Mid, 65 | Seq: msg.Seq, 66 | From: msg.To, 67 | } 68 | ack := messages.NewMessage(0, messages.ActionAckMessage, &ackMsg) 69 | return d.def.GetClientInterface().EnqueueMessage(c.ID, ack) 70 | } 71 | 72 | // dispatchOffline 接收者不在线, 离线推送 73 | func (d *MessageHandlerImpl) dispatchOffline(c *gate.Info, message *messages.ChatMessage) error { 74 | logger.D("dispatch offline message %v %v", c.ID, message) 75 | err := d.store.StoreOffline(message) 76 | if err != nil { 77 | logger.E("store chat message error %v", err) 78 | return err 79 | } 80 | return nil 81 | } 82 | 83 | // dispatchOnline 接收者在线, 直接投递消息 84 | func (d *MessageHandlerImpl) dispatchOnline(c *gate.Info, msg *messages.ChatMessage) error { 85 | receiverMsg := msg 86 | msg.From = c.ID.UID() 87 | dispatchMsg := messages.NewMessage(-1, messages.ActionChatMessage, receiverMsg) 88 | return d.def.GetClientInterface().EnqueueMessage(c.ID, dispatchMsg) 89 | } 90 | 91 | // TODO optimize 2022-6-20 11:18:24 92 | func (d *MessageHandlerImpl) dispatchAllDevice(uid string, m *messages.GlideMessage) bool { 93 | devices := []string{"", "1", "2", "3"} 94 | 95 | var ok = false 96 | for _, device := range devices { 97 | id := gate.NewID("", uid, device) 98 | err := d.def.GetClientInterface().EnqueueMessage(id, m) 99 | if err != nil { 100 | if !gate.IsClientNotExist(err) { 101 | logger.E("dispatch message error %v", err) 102 | } 103 | } else { 104 | ok = true 105 | } 106 | } 107 | return ok 108 | } 109 | -------------------------------------------------------------------------------- /pkg/messaging/handler.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/gate" 5 | "github.com/glide-im/glide/pkg/logger" 6 | "github.com/glide-im/glide/pkg/messages" 7 | "github.com/glide-im/glide/pkg/store" 8 | "github.com/glide-im/glide/pkg/subscription" 9 | ) 10 | 11 | var _ Messaging = (*MessageHandlerImpl)(nil) 12 | 13 | type MessageHandlerOptions struct { 14 | // MessageStore chat message store 15 | MessageStore store.MessageStore 16 | 17 | // DontInitDefaultHandler true will not init default action offlineMessageHandler, MessageHandlerImpl.InitDefaultHandler 18 | DontInitDefaultHandler bool 19 | 20 | // NotifyOnErr true express notify client on server error. 21 | NotifyOnErr bool 22 | } 23 | 24 | // MessageHandlerImpl . 25 | type MessageHandlerImpl struct { 26 | def *MessageInterfaceImpl 27 | store store.MessageStore 28 | 29 | userState *UserState 30 | } 31 | 32 | func NewHandlerWithOptions(gateway gate.Gateway, opts *MessageHandlerOptions) (*MessageHandlerImpl, error) { 33 | impl, err := NewDefaultImpl(&Options{ 34 | NotifyServerError: true, 35 | MaxMessageConcurrency: 10_0000, 36 | }) 37 | if err != nil { 38 | return nil, err 39 | } 40 | impl.SetNotifyErrorOnServer(opts.NotifyOnErr) 41 | 42 | ret := &MessageHandlerImpl{ 43 | def: impl, 44 | store: opts.MessageStore, 45 | userState: NewUserState(gateway), 46 | } 47 | if !opts.DontInitDefaultHandler { 48 | ret.InitDefaultHandler(nil) 49 | } 50 | return ret, nil 51 | } 52 | 53 | // InitDefaultHandler 54 | // 初始化 message.Action 对应的默认 Handler, 部分类型的 Action 才有默认 Handler, 若要修改特定 Action 的默认 Handler 则可以在 55 | // callback 回调中返回你需要的即可, callback 参数 fn 既是该 action 对的默认 Handler. 56 | func (d *MessageHandlerImpl) InitDefaultHandler(callback func(action messages.Action, fn HandlerFunc) HandlerFunc) { 57 | 58 | m := map[messages.Action]HandlerFunc{ 59 | messages.ActionChatMessage: d.handleChatMessage, 60 | messages.ActionGroupMessage: d.handleGroupMsg, 61 | messages.ActionApiGroupMembers: d.handleApiGroupMembers, 62 | messages.ActionAckRequest: d.handleAckRequest, 63 | messages.ActionAckGroupMsg: d.handleAckGroupMsgRequest, 64 | messages.AckOffline: d.handleAckOffline, 65 | messages.ActionHeartbeat: d.handleHeartbeat, 66 | messages.ActionInternalOnline: d.handleInternalOnline, 67 | messages.ActionInternalOffline: d.handleInternalOffline, 68 | messages.ActionApiSubUserState: d.userState.subUserStateApi, 69 | } 70 | for action, handlerFunc := range m { 71 | if callback != nil { 72 | handlerFunc = callback(action, handlerFunc) 73 | } 74 | d.def.AddHandler(NewActionHandler(action, handlerFunc)) 75 | } 76 | 77 | d.def.AddHandler(&ClientCustomMessageHandler{}) 78 | d.def.AddHandler(NewActionHandler(messages.ActionHeartbeat, handleHeartbeat)) 79 | } 80 | 81 | func (d *MessageHandlerImpl) AddHandler(i MessageHandler) { 82 | d.def.AddHandler(i) 83 | } 84 | 85 | func (d *MessageHandlerImpl) Handle(cInfo *gate.Info, msg *messages.GlideMessage) error { 86 | return d.def.Handle(cInfo, msg) 87 | } 88 | 89 | func (d *MessageHandlerImpl) SetGate(g gate.Gateway) { 90 | d.def.SetGate(g) 91 | } 92 | 93 | func (d *MessageHandlerImpl) SetSubscription(s subscription.Interface) { 94 | d.def.SetSubscription(s) 95 | } 96 | 97 | func (d *MessageHandlerImpl) dispatchGroupMessage(gid int64, msg *messages.ChatMessage) error { 98 | return d.def.GetGroupInterface().PublishMessage("", nil) 99 | } 100 | 101 | func (d *MessageHandlerImpl) enqueueMessage(id gate.ID, message *messages.GlideMessage) { 102 | err := d.def.GetClientInterface().EnqueueMessage(id, message) 103 | if err != nil { 104 | logger.E("%v", err) 105 | } 106 | } 107 | func (d *MessageHandlerImpl) unmarshalData(c *gate.Info, msg *messages.GlideMessage, to interface{}) bool { 108 | err := msg.Data.Deserialize(to) 109 | if err != nil { 110 | logger.E("sender chat senderMsg %v", err) 111 | return false 112 | } 113 | return true 114 | } 115 | 116 | func dispatch2AllDevice(h *MessageInterfaceImpl, uid string, m *messages.GlideMessage) bool { 117 | devices := []string{"", "1", "2", "3"} 118 | for _, device := range devices { 119 | id := gate.NewID("", uid, device) 120 | err := h.GetClientInterface().EnqueueMessage(id, m) 121 | if err != nil && !gate.IsClientNotExist(err) { 122 | logger.E("dispatch message error %v", err) 123 | } 124 | } 125 | return true 126 | } 127 | -------------------------------------------------------------------------------- /pkg/hash/consisten_hash.go: -------------------------------------------------------------------------------- 1 | package hash 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | duplicateVirtual = 100 // 1_000_000 11 | seed = 0xabcd1234 12 | ) 13 | 14 | var ( 15 | errNodeExist = errors.New("node already exist") 16 | ) 17 | 18 | type Node struct { 19 | Val string 20 | hash uint32 21 | virtual bool 22 | real *Node 23 | } 24 | 25 | type Nodes struct { 26 | nd Node 27 | virtual []Node 28 | hit int64 29 | } 30 | 31 | func (n *Nodes) appendVirtual(node Node) { 32 | n.virtual = append(n.virtual, node) 33 | } 34 | 35 | type ConsistentHash struct { 36 | nodes []Node 37 | nodeMap map[string]*Nodes 38 | virtual int 39 | 40 | mu sync.RWMutex 41 | } 42 | 43 | func NewConsistentHash() *ConsistentHash { 44 | return NewConsistentHash2(duplicateVirtual) 45 | } 46 | 47 | func NewConsistentHash2(virtual int) *ConsistentHash { 48 | hash := &ConsistentHash{ 49 | nodes: []Node{}, 50 | nodeMap: map[string]*Nodes{}, 51 | virtual: virtual, 52 | mu: sync.RWMutex{}, 53 | } 54 | return hash 55 | } 56 | 57 | // Remove node by id, include virtual node. 58 | func (c *ConsistentHash) Remove(id string) error { 59 | nodes, ok := c.nodeMap[id] 60 | if !ok { 61 | return errors.New("node does not exist, id:" + id) 62 | } 63 | for _, vNd := range nodes.virtual { 64 | ndIndex, exist := c.findIndex(vNd.hash) 65 | if exist { 66 | ndIndex-- 67 | } else { 68 | return errors.New("virtual node does not exist, id:" + vNd.Val) 69 | } 70 | c.mu.RLock() 71 | nd := c.nodes[ndIndex] 72 | c.mu.RUnlock() 73 | if nd.hash != vNd.hash { 74 | return errors.New("could not find virtual node, id:" + vNd.Val) 75 | } else { 76 | c.removeIndex(ndIndex) 77 | } 78 | } 79 | index, exist := c.findIndex(nodes.nd.hash) 80 | if !exist { 81 | return errors.New("real node not fund") 82 | } 83 | index-- 84 | c.removeIndex(index) 85 | delete(c.nodeMap, id) 86 | return nil 87 | } 88 | 89 | func (c *ConsistentHash) Get(data string) (*Node, error) { 90 | hash := Hash([]byte(data), seed) 91 | index, _ := c.findIndex(hash) 92 | return c.get(index) 93 | } 94 | 95 | func (c *ConsistentHash) Add(id string) error { 96 | _, ok := c.nodeMap[id] 97 | if ok { 98 | return errors.New("node already exist, id=" + id) 99 | } 100 | hash := Hash([]byte(id), seed) 101 | nd := Node{ 102 | Val: id, 103 | hash: hash, 104 | virtual: false, 105 | real: nil, 106 | } 107 | c.nodeMap[id] = &Nodes{ 108 | nd: nd, 109 | virtual: []Node{}, 110 | } 111 | c.addNode(nd) 112 | c.addVirtual(&nd, c.virtual) 113 | return nil 114 | } 115 | 116 | func (c *ConsistentHash) get(index int) (*Node, error) { 117 | c.mu.RLock() 118 | defer c.mu.RUnlock() 119 | 120 | if len(c.nodes) == 0 { 121 | return nil, errNodeExist 122 | } 123 | if index == len(c.nodes) { 124 | index = len(c.nodes) - 1 125 | } 126 | n := c.nodes[index] 127 | if n.virtual { 128 | return n.real, nil 129 | } 130 | return &n, nil 131 | } 132 | 133 | func (c *ConsistentHash) addVirtual(real *Node, duplicate int) { 134 | for i := 0; i < duplicate; i++ { 135 | vNodeID := fmt.Sprintf("%s_#%d", real.Val, i) 136 | hash := Hash([]byte(vNodeID), seed) 137 | vNode := Node{ 138 | Val: vNodeID, 139 | hash: hash, 140 | virtual: true, 141 | real: real, 142 | } 143 | c.addNode(vNode) 144 | nds := c.nodeMap[real.Val] 145 | nds.appendVirtual(vNode) 146 | } 147 | } 148 | 149 | func (c *ConsistentHash) addNode(nd Node) { 150 | 151 | index, _ := c.findIndex(nd.hash) 152 | 153 | c.mu.Lock() 154 | defer c.mu.Unlock() 155 | 156 | p1 := c.nodes[:index] 157 | p2 := c.nodes[index:] 158 | n := make([]Node, len(p1)) 159 | copy(n, p1) 160 | n = append(n, nd) 161 | for _, i := range p2 { 162 | n = append(n, i) 163 | } 164 | c.nodes = n 165 | } 166 | 167 | func (c *ConsistentHash) removeIndex(index int) { 168 | c.mu.Lock() 169 | defer c.mu.Unlock() 170 | 171 | if index == len(c.nodes)-1 { 172 | c.nodes = c.nodes[:len(c.nodes)-1] 173 | return 174 | } 175 | 176 | p2 := c.nodes[index+1:] 177 | c.nodes = c.nodes[:index] 178 | for _, n := range p2 { 179 | c.nodes = append(c.nodes, n) 180 | } 181 | } 182 | 183 | func (c *ConsistentHash) findIndex(h uint32) (int, bool) { 184 | c.mu.RLock() 185 | defer c.mu.RUnlock() 186 | 187 | left := 0 188 | right := len(c.nodes) 189 | exist := false 190 | 191 | LOOP: 192 | if left < right { 193 | middle := (left + right) / 2 194 | hash := c.nodes[middle].hash 195 | if hash < h { 196 | left = middle + 1 197 | } else if hash == h { 198 | left = middle + 1 199 | exist = true 200 | } else { 201 | right = middle 202 | } 203 | goto LOOP 204 | } 205 | return left, exist 206 | } 207 | -------------------------------------------------------------------------------- /im_service/client/subscription_rpc_impl.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/glide-im/glide/im_service/proto" 7 | "github.com/glide-im/glide/pkg/rpc" 8 | "github.com/glide-im/glide/pkg/subscription" 9 | ) 10 | 11 | type SubscriptionRpcImpl struct { 12 | rpcCli *subscriptionRpcClient 13 | } 14 | 15 | func NewSubscriptionRpcImplWithClient(cli *rpc.BaseClient) *SubscriptionRpcImpl { 16 | return &SubscriptionRpcImpl{ 17 | rpcCli: &subscriptionRpcClient{cli: cli}, 18 | } 19 | } 20 | 21 | func NewSubscriptionRpcImpl(opts *rpc.ClientOptions) (*SubscriptionRpcImpl, error) { 22 | c, err := rpc.NewBaseClient(opts) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return NewSubscriptionRpcImplWithClient(c), nil 27 | } 28 | 29 | func (s *SubscriptionRpcImpl) Close() error { 30 | return s.rpcCli.Close() 31 | } 32 | 33 | func (s *SubscriptionRpcImpl) Subscribe(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error { 34 | 35 | marshal, err := json.Marshal(extra) 36 | if err != nil { 37 | return err 38 | } 39 | request := &proto.SubscribeRequest{ 40 | ChannelID: string(ch), 41 | SubscriberID: string(id), 42 | Extra: marshal, 43 | } 44 | reply := &proto.Response{} 45 | err = s.rpcCli.Subscribe(context.Background(), request, reply) 46 | if err != nil { 47 | return err 48 | } 49 | return getResponseError(reply) 50 | } 51 | 52 | func (s *SubscriptionRpcImpl) UnSubscribe(ch subscription.ChanID, id subscription.SubscriberID) error { 53 | request := &proto.UnsubscribeRequest{ 54 | ChannelID: string(ch), 55 | SubscriberID: string(id), 56 | } 57 | reply := &proto.Response{} 58 | err := s.rpcCli.UnSubscribe(context.Background(), request, reply) 59 | if err != nil { 60 | return err 61 | } 62 | return getResponseError(reply) 63 | } 64 | 65 | func (s *SubscriptionRpcImpl) UpdateSubscriber(ch subscription.ChanID, id subscription.SubscriberID, extra interface{}) error { 66 | marshal, err := json.Marshal(extra) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | request := &proto.UpdateSubscriberRequest{ 72 | ChannelID: string(ch), 73 | SubscriberID: string(id), 74 | Extra: marshal, 75 | } 76 | reply := &proto.Response{} 77 | err = s.rpcCli.UpdateSubscriber(context.Background(), request, reply) 78 | if err != nil { 79 | return err 80 | } 81 | return getResponseError(reply) 82 | } 83 | 84 | func (s *SubscriptionRpcImpl) RemoveChannel(ch subscription.ChanID) error { 85 | request := &proto.RemoveChannelRequest{ 86 | ChannelID: string(ch), 87 | } 88 | reply := &proto.Response{} 89 | err := s.rpcCli.RemoveChannel(context.Background(), request, reply) 90 | if err != nil { 91 | return err 92 | } 93 | return getResponseError(reply) 94 | } 95 | 96 | func (s *SubscriptionRpcImpl) CreateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error { 97 | 98 | var children []string 99 | for _, child := range update.Child { 100 | children = append(children, string(child)) 101 | } 102 | parent := "" 103 | if update.Parent != nil { 104 | parent = string(*update.Parent) 105 | } 106 | channelInfo := &proto.ChannelInfo{ 107 | ID: string(update.ID), 108 | Type: int32(update.Type), 109 | Muted: update.Muted, 110 | Blocked: update.Blocked, 111 | Closed: update.Closed, 112 | Parent: parent, 113 | Children: children, 114 | } 115 | 116 | request := &proto.CreateChannelRequest{ 117 | ChannelID: string(ch), 118 | ChannelInfo: channelInfo, 119 | } 120 | reply := &proto.Response{} 121 | err := s.rpcCli.CreateChannel(context.Background(), request, reply) 122 | if err != nil { 123 | return err 124 | } 125 | return getResponseError(reply) 126 | } 127 | 128 | func (s *SubscriptionRpcImpl) UpdateChannel(ch subscription.ChanID, update *subscription.ChanInfo) error { 129 | var children []string 130 | for _, child := range update.Child { 131 | children = append(children, string(child)) 132 | } 133 | parent := "" 134 | if update.Parent != nil { 135 | parent = string(*update.Parent) 136 | } 137 | channelInfo := &proto.ChannelInfo{ 138 | ID: string(update.ID), 139 | Type: int32(update.Type), 140 | Muted: update.Muted, 141 | Blocked: update.Blocked, 142 | Closed: update.Closed, 143 | Parent: parent, 144 | Children: children, 145 | } 146 | 147 | request := &proto.UpdateChannelRequest{ 148 | ChannelID: string(ch), 149 | ChannelInfo: channelInfo, 150 | } 151 | reply := &proto.Response{} 152 | err := s.rpcCli.UpdateChannel(context.Background(), request, reply) 153 | if err != nil { 154 | return err 155 | } 156 | return getResponseError(reply) 157 | } 158 | 159 | func (s *SubscriptionRpcImpl) Publish(ch subscription.ChanID, msg subscription.Message) error { 160 | 161 | marshal, err := json.Marshal(msg) 162 | if err != nil { 163 | return err 164 | } 165 | request := &proto.PublishRequest{ 166 | ChannelID: string(ch), 167 | Message: marshal, 168 | } 169 | reply := &proto.Response{} 170 | err = s.rpcCli.Publish(context.Background(), request, reply) 171 | if err != nil { 172 | return err 173 | } 174 | return getResponseError(reply) 175 | } 176 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/subscription.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import ( 4 | "errors" 5 | "github.com/glide-im/glide/pkg/gate" 6 | "github.com/glide-im/glide/pkg/store" 7 | "github.com/glide-im/glide/pkg/subscription" 8 | "sync" 9 | ) 10 | 11 | var _ subscription.Subscribe = (*subscriptionImpl)(nil) 12 | 13 | type subscriptionImpl struct { 14 | unwrap *realSubscription 15 | } 16 | 17 | func NewSubscription(store store.SubscriptionStore, seqStore ChannelSequenceStore) subscription.Subscribe { 18 | return &subscriptionImpl{ 19 | unwrap: newRealSubscription(store, seqStore), 20 | } 21 | } 22 | 23 | func (s *subscriptionImpl) UpdateSubscriber(id subscription.ChanID, updates []subscription.Update) error { 24 | 25 | var errs []error 26 | 27 | for _, update := range updates { 28 | var err error 29 | switch update.Flag { 30 | case subscription.SubscriberSubscribe: 31 | err = s.unwrap.Subscribe(id, update.ID, update.Extra) 32 | case subscription.SubscriberUnsubscribe: 33 | err = s.unwrap.UnSubscribe(id, update.ID) 34 | case subscription.SubscriberUpdate: 35 | err = s.unwrap.UpdateSubscriber(id, update.ID, update.Extra) 36 | default: 37 | return subscription.ErrUnknownFlag 38 | } 39 | if err != nil { 40 | errs = append(errs, err) 41 | } 42 | } 43 | if len(errs) == 0 { 44 | return nil 45 | } 46 | 47 | errMsg := "" 48 | for _, err := range errs { 49 | errMsg += err.Error() + "\n" 50 | } 51 | 52 | return errors.New(errMsg) 53 | } 54 | 55 | func (s *subscriptionImpl) UpdateChannel(id subscription.ChanID, update subscription.ChannelUpdate) error { 56 | 57 | switch update.Flag { 58 | case subscription.ChanCreate: 59 | info, ok := update.Extra.(*subscription.ChanInfo) 60 | if !ok { 61 | return errors.New("invalid channel info") 62 | } 63 | return s.unwrap.CreateChannel(id, info) 64 | case subscription.ChanUpdate: 65 | info, ok := update.Extra.(*subscription.ChanInfo) 66 | if !ok { 67 | return errors.New("invalid channel info") 68 | } 69 | return s.unwrap.UpdateChannel(id, info) 70 | case subscription.ChanDelete: 71 | return s.unwrap.RemoveChannel(id) 72 | default: 73 | return subscription.ErrUnknownFlag 74 | } 75 | } 76 | 77 | func (s *subscriptionImpl) PublishMessage(id subscription.ChanID, message subscription.Message) error { 78 | return s.unwrap.Publish(id, message) 79 | } 80 | 81 | func (s *subscriptionImpl) SetGateInterface(g gate.DefaultGateway) { 82 | s.unwrap.gate = g 83 | } 84 | 85 | var _ SubscribeWrap = (*realSubscription)(nil) 86 | 87 | type realSubscription struct { 88 | mu sync.RWMutex 89 | channels map[subscription.ChanID]subscription.Channel 90 | store store.SubscriptionStore 91 | seqStore ChannelSequenceStore 92 | gate gate.DefaultGateway 93 | } 94 | 95 | func newRealSubscription(msgStore store.SubscriptionStore, seqStore ChannelSequenceStore) *realSubscription { 96 | return &realSubscription{ 97 | mu: sync.RWMutex{}, 98 | channels: make(map[subscription.ChanID]subscription.Channel), 99 | store: msgStore, 100 | seqStore: seqStore, 101 | } 102 | } 103 | 104 | func (u *realSubscription) Subscribe(chID subscription.ChanID, sbID subscription.SubscriberID, extra interface{}) error { 105 | u.mu.RLock() 106 | defer u.mu.RUnlock() 107 | 108 | ch, ok := u.channels[chID] 109 | if !ok { 110 | return errors.New(subscription.ErrChanNotExist) 111 | } 112 | return ch.Subscribe(sbID, extra) 113 | } 114 | 115 | func (u *realSubscription) UnSubscribe(chID subscription.ChanID, id subscription.SubscriberID) error { 116 | u.mu.RLock() 117 | defer u.mu.RUnlock() 118 | 119 | ch, ok := u.channels[chID] 120 | if !ok { 121 | return errors.New(subscription.ErrChanNotExist) 122 | } 123 | 124 | return ch.Unsubscribe(id) 125 | } 126 | 127 | func (u *realSubscription) UpdateSubscriber(chID subscription.ChanID, id subscription.SubscriberID, update interface{}) error { 128 | u.mu.RLock() 129 | defer u.mu.RUnlock() 130 | 131 | ch, ok := u.channels[chID] 132 | if !ok { 133 | return errors.New(subscription.ErrChanNotExist) 134 | } 135 | return ch.Subscribe(id, update) 136 | } 137 | 138 | func (u *realSubscription) RemoveChannel(chID subscription.ChanID) error { 139 | u.mu.Lock() 140 | defer u.mu.Unlock() 141 | 142 | _, ok := u.channels[chID] 143 | if !ok { 144 | return errors.New(subscription.ErrChanNotExist) 145 | } 146 | delete(u.channels, chID) 147 | return nil 148 | } 149 | 150 | func (u *realSubscription) CreateChannel(chID subscription.ChanID, update *subscription.ChanInfo) error { 151 | u.mu.Lock() 152 | defer u.mu.Unlock() 153 | 154 | if _, ok := u.channels[chID]; ok { 155 | return errors.New(subscription.ErrChanAlreadyExists) 156 | } 157 | 158 | channel, err := NewChannel(chID, u.gate, u.store, u.seqStore) 159 | if err != nil { 160 | return err 161 | } 162 | err = channel.Update(update) 163 | if err != nil { 164 | return err 165 | } 166 | u.channels[chID] = channel 167 | return nil 168 | } 169 | 170 | func (u *realSubscription) UpdateChannel(chID subscription.ChanID, update *subscription.ChanInfo) error { 171 | u.mu.RLock() 172 | defer u.mu.RUnlock() 173 | 174 | ch, ok := u.channels[chID] 175 | if !ok { 176 | return errors.New(subscription.ErrChanNotExist) 177 | } 178 | 179 | return ch.Update(update) 180 | } 181 | 182 | func (u *realSubscription) Publish(chID subscription.ChanID, msg subscription.Message) error { 183 | u.mu.RLock() 184 | defer u.mu.RUnlock() 185 | 186 | ch, ok := u.channels[chID] 187 | if !ok { 188 | return errors.New(subscription.ErrChanNotExist) 189 | } 190 | return ch.Publish(msg) 191 | } 192 | -------------------------------------------------------------------------------- /pkg/gate/client_impl_test.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/conn" 5 | "github.com/glide-im/glide/pkg/messages" 6 | "github.com/stretchr/testify/assert" 7 | "log" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func mockMsgHandler(cliInfo *Info, message *messages.GlideMessage) { 13 | } 14 | 15 | func TestClient_RunReadHeartbeatTimeout(t *testing.T) { 16 | fn, ch := mockReadFn() 17 | go func() { 18 | time.Sleep(time.Millisecond * 100) 19 | ch <- messages.NewMessage(1, messages.ActionHeartbeat, nil) 20 | time.Sleep(time.Millisecond * 100) 21 | ch <- messages.NewMessage(1, messages.ActionHeartbeat, nil) 22 | time.Sleep(time.Millisecond * 100) 23 | ch <- messages.NewMessage(1, messages.ActionHeartbeat, nil) 24 | }() 25 | 26 | client := NewClient(&mockConnection{ 27 | writeDelayMilliSec: 200, 28 | mockRead: fn, 29 | }, mockGateway{}, mockMsgHandler).(*UserClient) 30 | 31 | client.config.ClientHeartbeatDuration = time.Millisecond * 200 32 | client.Run() 33 | 34 | time.Sleep(time.Second * 1) 35 | client.Exit() 36 | } 37 | 38 | func TestClient_RunServerHeartbeat(t *testing.T) { 39 | fn, _ := mockReadFn() 40 | client := NewClientWithConfig(&mockConnection{ 41 | writeDelayMilliSec: 100, 42 | mockRead: fn, 43 | }, mockGateway{}, mockMsgHandler, &ClientConfig{ 44 | ServerHeartbeatDuration: time.Millisecond * 400, 45 | CloseImmediately: true, 46 | }) 47 | client.Run() 48 | 49 | time.Sleep(time.Second * 1) 50 | client.Exit() 51 | } 52 | 53 | func TestClient_RunServerHeartbeatTimeout(t *testing.T) { 54 | fn, _ := mockReadFn() 55 | client := NewClientWithConfig(&mockConnection{ 56 | writeDelayMilliSec: 100, 57 | mockRead: fn, 58 | }, mockGateway{}, mockMsgHandler, &ClientConfig{ 59 | ClientHeartbeatDuration: defaultHeartbeatDuration, 60 | ServerHeartbeatDuration: time.Millisecond * 100, 61 | HeartbeatLostLimit: 3, 62 | CloseImmediately: true, 63 | }) 64 | client.Run() 65 | 66 | time.Sleep(time.Second * 1) 67 | } 68 | 69 | func TestClient_ExitImmediately(t *testing.T) { 70 | 71 | fn, ch := mockReadFn() 72 | go func() { 73 | time.Sleep(time.Second * 1) 74 | ch <- messages.NewMessage(1, messages.ActionHeartbeat, nil) 75 | }() 76 | 77 | client := NewClient(&mockConnection{ 78 | writeDelayMilliSec: 200, 79 | mockRead: fn, 80 | }, mockGateway{}, mockMsgHandler).(*UserClient) 81 | client.config.CloseImmediately = true 82 | client.Run() 83 | 84 | for i := 0; i < 10; i++ { 85 | err := client.EnqueueMessage(messages.NewMessage(1, messages.ActionHeartbeat, nil)) 86 | if err != nil { 87 | t.Error(err) 88 | } 89 | } 90 | time.Sleep(time.Millisecond * 450) 91 | client.Exit() 92 | 93 | assert.Equal(t, client.queuedMessage, int64(8)) 94 | } 95 | 96 | func TestClient_Exit(t *testing.T) { 97 | fn, _ := mockReadFn() 98 | 99 | client := NewClient(&mockConnection{ 100 | writeDelayMilliSec: 10, 101 | mockRead: fn, 102 | }, mockGateway{}, mockMsgHandler).(*UserClient) 103 | client.config.CloseImmediately = false 104 | client.Run() 105 | 106 | for i := 0; i < 20; i++ { 107 | err := client.EnqueueMessage(messages.NewMessage(1, messages.ActionHeartbeat, nil)) 108 | if err != nil { 109 | t.Error(err) 110 | } 111 | } 112 | client.Exit() 113 | assert.False(t, client.IsRunning()) 114 | assert.Error(t, client.EnqueueMessage(messages.NewMessage(1, messages.ActionHeartbeat, nil))) 115 | assert.Equal(t, client.state, stateClosed) 116 | 117 | time.Sleep(time.Millisecond * 300) 118 | 119 | assert.Equal(t, client.queuedMessage, int64(0)) 120 | } 121 | 122 | func mockReadFn() (func() ([]byte, error), chan<- *messages.GlideMessage) { 123 | ch := make(chan *messages.GlideMessage) 124 | return func() ([]byte, error) { 125 | m := <-ch 126 | encode, err := messages.JsonCodec.Encode(m) 127 | return encode, err 128 | }, ch 129 | } 130 | 131 | type mockConnection struct { 132 | writeDelayMilliSec time.Duration 133 | mockRead func() ([]byte, error) 134 | } 135 | 136 | func (m *mockConnection) Write(data []byte) error { 137 | time.Sleep(time.Millisecond * m.writeDelayMilliSec) 138 | log.Println("runWrite:", string(data)) 139 | return nil 140 | } 141 | 142 | func (m *mockConnection) Read() ([]byte, error) { 143 | for { 144 | return m.mockRead() 145 | } 146 | } 147 | 148 | func (m *mockConnection) Close() error { 149 | log.Println("close connection") 150 | return nil 151 | } 152 | 153 | func (m *mockConnection) GetConnInfo() *conn.ConnectionInfo { 154 | return &conn.ConnectionInfo{ 155 | Ip: "127.0.0.1", 156 | Port: 9999, 157 | Addr: "[::1]:9999", 158 | } 159 | } 160 | 161 | type mockGateway struct { 162 | } 163 | 164 | func (m mockGateway) UpdateClient(id ID, info *ClientSecrets) error { 165 | //TODO implement me 166 | panic("implement me") 167 | } 168 | 169 | func (m mockGateway) GetClient(id ID) Client { 170 | //TODO implement me 171 | panic("implement me") 172 | } 173 | 174 | func (m mockGateway) GetAll() map[ID]Info { 175 | //TODO implement me 176 | panic("implement me") 177 | } 178 | 179 | func (m mockGateway) SetMessageHandler(h MessageHandler) { 180 | //TODO implement me 181 | panic("implement me") 182 | } 183 | 184 | func (m mockGateway) AddClient(cs Client) { 185 | //TODO implement me 186 | panic("implement me") 187 | } 188 | 189 | func (m mockGateway) SetClientID(old ID, new_ ID) error { 190 | return nil 191 | } 192 | 193 | func (m mockGateway) ExitClient(id ID) error { 194 | log.Println("exit client:", id) 195 | return nil 196 | } 197 | 198 | func (m mockGateway) EnqueueMessage(id ID, message *messages.GlideMessage) error { 199 | return nil 200 | } 201 | -------------------------------------------------------------------------------- /pkg/subscription/subscription_impl/chan_test.go: -------------------------------------------------------------------------------- 1 | package subscription_impl 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "github.com/glide-im/glide/pkg/gate" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "github.com/glide-im/glide/pkg/subscription" 9 | "github.com/stretchr/testify/assert" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | var normalOpts = &SubscriberOptions{ 15 | Perm: PermWrite | PermRead, 16 | } 17 | 18 | func mockNewChannel(id subscription.ChanID) *Channel { 19 | channel, _ := NewChannel(id, &mockGate{}, &mockStore{}, &mockSeqStore{}) 20 | return channel 21 | } 22 | 23 | type mockGate struct { 24 | } 25 | 26 | func (m mockGate) SetClientID(old gate.ID, new_ gate.ID) error { 27 | //TODO implement me 28 | panic("implement me") 29 | } 30 | 31 | func (m mockGate) UpdateClient(id gate.ID, info *gate.ClientSecrets) error { 32 | //TODO implement me 33 | panic("implement me") 34 | } 35 | 36 | func (m mockGate) ExitClient(id gate.ID) error { 37 | //TODO implement me 38 | panic("implement me") 39 | } 40 | 41 | func (m mockGate) GetClient(id gate.ID) gate.Client { 42 | //TODO implement me 43 | panic("implement me") 44 | } 45 | 46 | func (m mockGate) GetAll() map[gate.ID]gate.Info { 47 | //TODO implement me 48 | panic("implement me") 49 | } 50 | 51 | func (m mockGate) SetMessageHandler(h gate.MessageHandler) { 52 | //TODO implement me 53 | panic("implement me") 54 | } 55 | 56 | func (m mockGate) AddClient(cs gate.Client) { 57 | //TODO implement me 58 | panic("implement me") 59 | } 60 | 61 | func (m mockGate) EnqueueMessage(gate.ID, *messages.GlideMessage) error { 62 | return nil 63 | } 64 | 65 | type message struct{} 66 | 67 | func (*message) GetFrom() subscription.SubscriberID { 68 | return "" 69 | } 70 | 71 | type mockSeqStore struct { 72 | segmentLen int64 73 | nextSeq int64 74 | } 75 | 76 | func (m *mockSeqStore) NextSegmentSequence(subscription.ChanID, subscription.ChanInfo) (int64, int64, error) { 77 | seq := m.nextSeq 78 | m.nextSeq = seq + m.segmentLen 79 | return seq, m.segmentLen, nil 80 | } 81 | 82 | func TestGroup_Publish(t *testing.T) { 83 | channel := mockNewChannel("test") 84 | err2 := channel.Subscribe("test", normalOpts) 85 | assert.NoError(t, err2) 86 | msg := &PublishMessage{ 87 | From: "test", 88 | Type: TypeNotify, 89 | Message: &messages.GlideMessage{}, 90 | } 91 | err := channel.Publish(msg) 92 | assert.NoError(t, err) 93 | err = channel.Publish(msg) 94 | assert.NoError(t, err) 95 | err = channel.Publish(msg) 96 | assert.NoError(t, err) 97 | time.Sleep(time.Millisecond * 50) 98 | } 99 | 100 | func TestChannel_Sleep(t *testing.T) { 101 | channel := mockNewChannel("test") 102 | err2 := channel.Subscribe("test", normalOpts) 103 | assert.NoError(t, err2) 104 | msg := &PublishMessage{ 105 | From: "test", 106 | Type: TypeNotify, 107 | Message: &messages.GlideMessage{}, 108 | } 109 | go func() { 110 | for i := 0; i < 100; i++ { 111 | time.Sleep(time.Millisecond * 50) 112 | _ = channel.Publish(msg) 113 | } 114 | }() 115 | 116 | time.Sleep(time.Millisecond * 50) 117 | } 118 | 119 | func TestChannel_PublishErr(t *testing.T) { 120 | channel := mockNewChannel("test") 121 | 122 | // invalid type 123 | err := channel.Publish(&PublishMessage{}) 124 | assert.Error(t, err) 125 | 126 | // permission denied 127 | err = channel.Subscribe("t", &SubscriberOptions{Perm: PermRead}) 128 | assert.NoError(t, err) 129 | err = channel.Publish(&PublishMessage{From: "t"}) 130 | assert.Error(t, err) 131 | 132 | // muted 133 | channel.info.Muted = true 134 | err = channel.Publish(&PublishMessage{From: "t"}) 135 | assert.Error(t, err) 136 | } 137 | 138 | func TestGroup_PublishUnknownType(t *testing.T) { 139 | group := mockNewChannel("test") 140 | err := group.Publish(&PublishMessage{}) 141 | assert.EqualError(t, err, errUnknownMessageType) 142 | } 143 | 144 | func TestGroup_PublishUnexpectedMessageType(t *testing.T) { 145 | group := mockNewChannel("test") 146 | err := group.Publish(&PublishMessage{}) 147 | assert.Error(t, err) 148 | } 149 | 150 | func TestChannel_nextSeq(t *testing.T) { 151 | m := &mockSeqStore{ 152 | segmentLen: 4, 153 | nextSeq: 0, 154 | } 155 | channel, err := NewChannel("test", nil, nil, m) 156 | assert.NoError(t, err) 157 | 158 | for i := 1; i < 20; i++ { 159 | seq, err := channel.nextSeq() 160 | assert.NoError(t, err) 161 | assert.Equal(t, int64(i), seq) 162 | } 163 | } 164 | 165 | func TestChannel_Subscribe(t *testing.T) { 166 | channel := mockNewChannel("test") 167 | err := channel.Subscribe("sb_test", normalOpts) 168 | assert.NoError(t, err) 169 | } 170 | 171 | func TestChannel_SubscribeUpdate(t *testing.T) { 172 | channel := mockNewChannel("test") 173 | err := channel.Subscribe("sb_test", normalOpts) 174 | assert.NoError(t, err) 175 | err = channel.Subscribe("sb_test", &SubscriberOptions{Perm: PermNone}) 176 | assert.NoError(t, err) 177 | assert.Equal(t, channel.subscribers["sb_test"].Perm, PermNone) 178 | } 179 | 180 | func TestChannel_Unsubscribe(t *testing.T) { 181 | channel := mockNewChannel("test") 182 | err := channel.Subscribe("sb_test", normalOpts) 183 | assert.NoError(t, err) 184 | err = channel.Unsubscribe("sb_test") 185 | assert.NoError(t, err) 186 | err = channel.Unsubscribe("sb_test") 187 | assert.EqualError(t, err, subscription.ErrNotSubscribed) 188 | } 189 | 190 | func TestChannel_Update(t *testing.T) { 191 | channel := mockNewChannel("test") 192 | err := channel.Update(&subscription.ChanInfo{Blocked: false, Muted: true}) 193 | assert.NoError(t, err) 194 | assert.Equal(t, channel.info.Blocked, false) 195 | assert.Equal(t, channel.info.Muted, true) 196 | } 197 | 198 | func TestChannel_Close(t *testing.T) { 199 | channel := mockNewChannel("test") 200 | err := channel.Subscribe("t", normalOpts) 201 | assert.NoError(t, err) 202 | err = channel.Close() 203 | assert.NoError(t, err) 204 | err = channel.Publish(&PublishMessage{From: "t", Type: TypeMessage}) 205 | assert.Error(t, err) 206 | } 207 | 208 | func TestWrap_Subscribe(t *testing.T) { 209 | channel := mockNewChannel("test") 210 | err := channel.Update(&subscription.ChanInfo{ 211 | Secret: "ABC", 212 | }) 213 | assert.NoError(t, err) 214 | c := fmt.Sprintf("%d_%s_%s", normalOpts.Perm, "sb_test", "ABC") 215 | ticket := fmt.Sprintf("%x", md5.Sum([]byte(c))) 216 | normalOpts.Ticket = ticket 217 | err = channel.Subscribe("sb_test", normalOpts) 218 | assert.NoError(t, err) 219 | } 220 | -------------------------------------------------------------------------------- /pkg/timingwheel/timingwheel.go: -------------------------------------------------------------------------------- 1 | package timingwheel 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | var Executor = func(f func()) { 11 | go f() 12 | } 13 | 14 | type Task struct { 15 | offset int 16 | s *slot 17 | at time.Time 18 | 19 | fn func() 20 | C chan struct{} 21 | } 22 | 23 | func (s *Task) TTL() int64 { 24 | now := float64(time.Now().UnixNano()) 25 | at := float64(s.at.UnixNano()) 26 | return int64(math.Floor((at-now)/float64(time.Millisecond) + 1.0/2.0)) 27 | } 28 | 29 | func (s *Task) call() { 30 | if s.s == nil { 31 | return 32 | } 33 | Executor(func() { 34 | s.Cancel() 35 | if s.fn != nil { 36 | s.fn() 37 | } 38 | select { 39 | case s.C <- struct{}{}: 40 | default: 41 | } 42 | }) 43 | } 44 | 45 | func (s *Task) Callback(f func()) { 46 | s.fn = f 47 | } 48 | 49 | func (s *Task) Cancel() { 50 | if s.s != nil { 51 | s.s.remove(s) 52 | s.s = nil 53 | } 54 | } 55 | 56 | type slot struct { 57 | index int 58 | next *slot 59 | len int 60 | values map[*Task]interface{} 61 | 62 | m sync.Mutex 63 | circulate bool 64 | } 65 | 66 | func newSlot(circulate bool, len int) *slot { 67 | var head *slot 68 | var s *slot 69 | for i := 0; i < len; i++ { 70 | n := &slot{ 71 | index: i, 72 | len: len, 73 | values: map[*Task]interface{}{}, 74 | circulate: circulate, 75 | m: sync.Mutex{}, 76 | } 77 | if i == 0 { 78 | head = n 79 | } else { 80 | s.next = n 81 | } 82 | s = n 83 | } 84 | s.next = head 85 | return s 86 | } 87 | 88 | func (s *slot) put(offset int, v *Task) int { 89 | if offset < 0 { 90 | panic("offset less the zero") 91 | } 92 | if !s.circulate && s.index == s.len && offset > 0 { 93 | return offset 94 | } 95 | if offset == 0 { 96 | s.m.Lock() 97 | s.values[v] = nil 98 | v.s = s 99 | s.m.Unlock() 100 | return 0 101 | } 102 | if offset >= s.len { 103 | return offset - s.len 104 | } 105 | return s.next.put(offset-1, v) 106 | } 107 | 108 | func (s *slot) isEmpty() bool { 109 | s.m.Lock() 110 | defer s.m.Unlock() 111 | return len(s.values) == 0 112 | } 113 | 114 | func (s *slot) callAndRm() { 115 | if s.isEmpty() { 116 | return 117 | } 118 | s.m.Lock() 119 | for k := range s.values { 120 | k.call() 121 | } 122 | s.m.Unlock() 123 | } 124 | 125 | func (s *slot) remove(v *Task) { 126 | s.m.Lock() 127 | delete(s.values, v) 128 | s.m.Unlock() 129 | } 130 | 131 | func (s *slot) valueArray() []*Task { 132 | var r []*Task 133 | s.m.Lock() 134 | for k := range s.values { 135 | r = append(r, k) 136 | } 137 | s.m.Unlock() 138 | return r 139 | } 140 | 141 | type wheel struct { 142 | slotCap int 143 | remain int 144 | 145 | slot *slot 146 | 147 | parent *wheel 148 | child *wheel 149 | } 150 | 151 | func newWheel(buckets int, dep int, wheels int, child *wheel) *wheel { 152 | wh := &wheel{ 153 | slot: newSlot(dep == wheels, buckets), 154 | slotCap: int(math.Pow(float64(buckets), float64(dep))) / buckets, 155 | child: child, 156 | } 157 | if dep == wheels { 158 | wh.remain = wh.slotCap * buckets 159 | } 160 | if child != nil { 161 | child.parent = wh 162 | } 163 | return wh 164 | } 165 | 166 | func (w *wheel) tick() { 167 | if w.parent != nil { 168 | w.remain-- 169 | if w.remain <= 0 { 170 | w.remain = w.slotCap * w.slot.len 171 | } 172 | w.parent.tick() 173 | } 174 | } 175 | 176 | func (w *wheel) move() bool { 177 | if w.child != nil { 178 | for _, v := range w.slot.valueArray() { 179 | w.slot.remove(v) 180 | w.child.put(v) 181 | } 182 | if w.child.move() { 183 | w.slot = w.slot.next 184 | for _, v := range w.slot.valueArray() { 185 | w.slot.remove(v) 186 | w.child.put(v) 187 | } 188 | return w.slot.index == 0 189 | } else { 190 | return false 191 | } 192 | } else { 193 | w.tick() 194 | w.slot = w.slot.next 195 | w.slot.callAndRm() 196 | return w.slot.index == 0 197 | } 198 | } 199 | 200 | func (w *wheel) put(v *Task) { 201 | 202 | s := int(math.Floor(float64(v.offset) / float64(w.slotCap))) 203 | if s == 0 { 204 | if w.child == nil { 205 | v.call() 206 | } else { 207 | w.child.put(v) 208 | } 209 | } else { 210 | if w.child != nil { 211 | v.offset = v.offset - ((s-1)*w.slotCap + w.child.remain - 1) - 1 212 | } 213 | w.slot.put(s, v) 214 | } 215 | } 216 | 217 | func (w *wheel) put2(v *Task) { 218 | 219 | s := int(math.Floor(float64(v.offset) / float64(w.slotCap))) 220 | sl := w.slot 221 | if s == 0 { 222 | if w.child != nil { 223 | if w.child.remain > v.offset { 224 | w.child.put2(v) 225 | } else { 226 | v.offset = v.offset - w.child.remain 227 | sl.put(1, v) 228 | } 229 | } else { 230 | sl.put(s, v) 231 | v.call() 232 | } 233 | } else { 234 | if w.child != nil { 235 | v.offset = v.offset - ((s-1)*w.slotCap + w.child.remain - 1) - 1 236 | if v.offset >= w.slotCap { 237 | s++ 238 | v.offset = v.offset - w.slotCap 239 | } 240 | } 241 | sl.put(s, v) 242 | } 243 | } 244 | 245 | // TimingWheel the timing wheel ticker implementation 246 | type TimingWheel struct { 247 | interval time.Duration 248 | ticker *time.Ticker 249 | quit chan struct{} 250 | maxTimeout time.Duration 251 | 252 | wheel *wheel 253 | } 254 | 255 | func NewTimingWheel(interval time.Duration, wheels int, slots int) *TimingWheel { 256 | tw := new(TimingWheel) 257 | 258 | tw.interval = interval 259 | tw.quit = make(chan struct{}) 260 | s := int64(math.Pow(float64(wheels), float64(slots))) 261 | 262 | tw.maxTimeout = interval * time.Duration(s) 263 | tw.ticker = time.NewTicker(interval) 264 | 265 | var w *wheel 266 | for i := 1; i <= wheels; i++ { 267 | wh := newWheel(slots, i, wheels, nil) 268 | if w != nil { 269 | wh.child = w 270 | w.parent = wh 271 | } 272 | w = wh 273 | } 274 | tw.wheel = w 275 | 276 | go tw.run() 277 | 278 | return tw 279 | } 280 | 281 | func (w *TimingWheel) Stop() { 282 | close(w.quit) 283 | } 284 | 285 | func (w *TimingWheel) After(timeout time.Duration) *Task { 286 | if timeout >= w.maxTimeout { 287 | panic(fmt.Sprintf("maxTimeout=%d, current=%d", w.maxTimeout, timeout)) 288 | } 289 | //offset := int(float64(TTL) / float64(w.interval)) 290 | offset := int(math.Floor(float64(timeout.Milliseconds())/float64(w.interval.Milliseconds()) + 1.0/2.0)) 291 | 292 | ch := make(chan struct{}) 293 | 294 | t := &Task{ 295 | offset: offset, 296 | C: ch, 297 | at: time.Now().Add(timeout), 298 | } 299 | w.wheel.put2(t) 300 | return t 301 | } 302 | 303 | func (w *TimingWheel) run() { 304 | for { 305 | select { 306 | case <-w.ticker.C: 307 | w.onTicker() 308 | case <-w.quit: 309 | w.ticker.Stop() 310 | return 311 | } 312 | } 313 | } 314 | 315 | func (w *TimingWheel) onTicker() { 316 | w.wheel.move() 317 | } 318 | -------------------------------------------------------------------------------- /pkg/gate/client.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "github.com/glide-im/glide/pkg/messages" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | ClientTypeRobot = 1 10 | ClientTypeUser = 2 11 | ) 12 | 13 | // tempIdPrefix is the prefix for temporary IDs in the second part of the ID. 14 | const tempIdPrefix = "tmp@" 15 | 16 | // idSeparator is the separator used to separate the part of the ID. 17 | const idSeparator = "_" 18 | 19 | // ID is used to identify the client, the ID is consist of multiple parts, some of them are optional. 20 | // The ID is constructed by concatenating the parts with a '_' separator, and the parts are: 21 | // - The gateway id (optional): the string id of the gateway that the client is connected to. 22 | // - The client id (required): the string id of the client, it is unique for user. 23 | // - if the client is temporary, this id is a string generated by the gateway and start with `tmp`. 24 | // - The client type (optional): the int type of the client, like 'web', 'mobile', 'desktop', etc. 25 | type ID string 26 | 27 | // NewID2 creates a new ID from the given user id, use the empty gateway id and the empty client type. 28 | func NewID2(uid string) ID { 29 | return ID(strings.Join([]string{"", uid, ""}, idSeparator)) 30 | } 31 | 32 | // NewID creates a new ID from the given user id, gateway id and client type. 33 | func NewID(gate string, uid string, device string) ID { 34 | return ID(strings.Join([]string{gate, uid, device}, idSeparator)) 35 | } 36 | 37 | // Device returns the device type of the client, if the client device type is not set, it returns "". 38 | func (i *ID) Device() string { 39 | return i.getPart(2) 40 | } 41 | 42 | // UID returns the user id of the client, if the client is temporary, it returns "". 43 | func (i *ID) UID() string { 44 | return i.getPart(1) 45 | } 46 | 47 | // Gateway returns the gateway id of the client, if not set, it returns an empty string. 48 | func (i *ID) Gateway() string { 49 | return i.getPart(0) 50 | } 51 | 52 | // SetGateway sets the gateway part of the ID. 53 | func (i *ID) SetGateway(gateway string) bool { 54 | if strings.HasPrefix(string(*i), gateway) { 55 | return false 56 | } 57 | s := strings.Split(string(*i), idSeparator) 58 | if len(s) != 3 { 59 | return false 60 | } 61 | s[0] = gateway 62 | *i = ID(strings.Join(s, idSeparator)) 63 | return true 64 | } 65 | 66 | // SetDevice sets the device type of the client. 67 | func (i *ID) SetDevice(device string) bool { 68 | if strings.HasSuffix(string(*i), device) { 69 | return false 70 | } 71 | s := strings.Split(string(*i), idSeparator) 72 | if len(s) != 3 { 73 | return false 74 | } 75 | s[2] = device 76 | *i = ID(strings.Join(s, idSeparator)) 77 | return true 78 | } 79 | 80 | // IsTemp returns true if the ID is a temporary. 81 | func (i *ID) IsTemp() bool { 82 | return strings.HasPrefix(i.getPart(1), tempIdPrefix) 83 | } 84 | 85 | func (i *ID) Equals(other ID) bool { 86 | return i.UID()+i.Device() == other.UID()+other.Device() 87 | } 88 | 89 | func (i *ID) getPart(index int) string { 90 | s := strings.Split(string(*i), idSeparator) 91 | if index >= len(s) { 92 | return "" 93 | } 94 | return s[index] 95 | } 96 | 97 | // Info represents a client's information. 98 | type Info struct { 99 | 100 | // ID is the unique identifier for the client. 101 | ID ID 102 | 103 | // ConnectionId generated by client, used to identify the client connection. 104 | ConnectionId string 105 | 106 | // Version is the version of the client. 107 | Version string 108 | 109 | // AliveAt is the time the client was last seen. 110 | AliveAt int64 111 | 112 | // ConnectionAt is the time the client was connected. 113 | ConnectionAt int64 114 | 115 | // Gateway is the name of the gateway the client is connected to. 116 | Gateway string 117 | 118 | // CliAddr is the address of the client. 119 | CliAddr string 120 | } 121 | 122 | // Client is a client connection abstraction. 123 | type Client interface { 124 | 125 | // SetID sets the ID of the client. 126 | SetID(id ID) 127 | 128 | // IsRunning returns true if the client is running/alive. 129 | IsRunning() bool 130 | 131 | // EnqueueMessage enqueues a message to be sent to the client. 132 | EnqueueMessage(message *messages.GlideMessage) error 133 | 134 | // Exit the client and close the connection. 135 | Exit() 136 | 137 | // Run starts the client message handling loop and blocks until the client. 138 | Run() 139 | 140 | // GetInfo returns the client's information. 141 | GetInfo() Info 142 | } 143 | 144 | // ClientSecrets used to control client permission. 145 | type ClientSecrets struct { 146 | // MessageDeliverSecret is the secret of the client, used to authenticate the client message. 147 | // The secret is generated by the business service, saved in business service, client should not know it. 148 | // When client send a message to someone else, it should get the sign of the message target, and send it 149 | // with the message. If business service want to control which one the client can send message to, 150 | // business service can generate different secret for client, and notify the gateway update the secret, to make 151 | // client old sign invalid. 152 | MessageDeliverSecret string `json:"message_deliver_secret"` 153 | OnlineStateSecret string `json:"online_state_secret"` 154 | } 155 | 156 | // EncryptedCredential represents the encrypted credential. 157 | type EncryptedCredential struct { 158 | // Version is the version of the credential. 159 | Version int `json:"version"` 160 | 161 | // Credential is the encrypted credential string. 162 | Credential string `json:"credential"` 163 | } 164 | 165 | // ConnectionConfig _ 166 | type ConnectionConfig struct { 167 | AllowMaxHeartbeatLost int 168 | HeartbeatDuration int 169 | CloseImmediately bool 170 | } 171 | 172 | type RiskControl struct { 173 | MaxMessagesPeerSecond int 174 | } 175 | 176 | // ClientAuthCredentials represents the client authentication credentials. 177 | // Used to client authentication when connecting to the gateway, credentials are generated by business service, 178 | // encrypted use the gateway's secret key, and sent to the client. 179 | type ClientAuthCredentials struct { 180 | 181 | // Type is the type of the client. 182 | Type int `json:"type"` 183 | 184 | // UserID uniquely identifies the client. 185 | UserID string `json:"user_id"` 186 | 187 | // DeviceID is the id of the client device, it is unique for same client. 188 | DeviceID string `json:"device_id"` 189 | 190 | DeviceName string `json:"device_name"` 191 | 192 | Secrets *ClientSecrets `json:"secrets"` 193 | 194 | RiskControl *RiskControl `json:"risk_control"` 195 | 196 | ConnectionConfig *ConnectionConfig `json:"connection_config"` 197 | 198 | // ConnectionID is the temporary connection id of the client, generated by the client. 199 | ConnectionID string `json:"connection_id"` 200 | 201 | // Timestamp of credentials creation. 202 | Timestamp int64 `json:"timestamp"` 203 | } 204 | 205 | func (a *ClientAuthCredentials) validate() error { 206 | 207 | return nil 208 | } 209 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/glide-im/glide 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Shopify/sarama v1.38.1 7 | github.com/go-redis/redis v6.15.9+incompatible 8 | github.com/go-sql-driver/mysql v1.6.0 9 | github.com/golang-jwt/jwt v3.2.2+incompatible 10 | github.com/gorilla/websocket v1.5.0 11 | github.com/panjf2000/ants/v2 v2.5.0 12 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 13 | github.com/rpcxio/rpcx-etcd v0.2.0 14 | github.com/smallnest/rpcx v1.7.4 15 | github.com/spf13/viper v1.11.0 16 | github.com/stretchr/testify v1.8.1 17 | go.uber.org/zap v1.21.0 18 | google.golang.org/protobuf v1.28.0 19 | gorm.io/driver/mysql v1.3.3 20 | gorm.io/gorm v1.23.5 21 | ) 22 | 23 | require ( 24 | github.com/akutz/memconn v0.1.0 // indirect 25 | github.com/apache/thrift v0.16.0 // indirect 26 | github.com/armon/go-metrics v0.3.10 // indirect 27 | github.com/cenk/backoff v2.2.1+incompatible // indirect 28 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 29 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 30 | github.com/cheekybits/genny v1.0.0 // indirect 31 | github.com/coreos/go-semver v0.3.0 // indirect 32 | github.com/coreos/go-systemd/v22 v22.3.2 // indirect 33 | github.com/davecgh/go-spew v1.1.1 // indirect 34 | github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 // indirect 35 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 36 | github.com/eapache/go-resiliency v1.3.0 // indirect 37 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect 38 | github.com/eapache/queue v1.1.0 // indirect 39 | github.com/edwingeng/doublejump v0.0.0-20210724020454-c82f1bcb3280 // indirect 40 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect 41 | github.com/fatih/color v1.13.0 // indirect 42 | github.com/forgoer/openssl v1.6.0 // indirect 43 | github.com/fsnotify/fsnotify v1.5.1 // indirect 44 | github.com/go-logr/logr v1.2.3 // indirect 45 | github.com/go-logr/stdr v1.2.2 // indirect 46 | github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 // indirect 47 | github.com/go-redis/redis/v8 v8.11.5 // indirect 48 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect 49 | github.com/gogo/protobuf v1.3.2 // indirect 50 | github.com/golang/protobuf v1.5.3 // indirect 51 | github.com/golang/snappy v0.0.4 // indirect 52 | github.com/google/uuid v1.3.0 // indirect 53 | github.com/grandcat/zeroconf v1.0.0 // indirect 54 | github.com/hashicorp/consul/api v1.12.0 // indirect 55 | github.com/hashicorp/errwrap v1.1.0 // indirect 56 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 57 | github.com/hashicorp/go-hclog v1.2.0 // indirect 58 | github.com/hashicorp/go-immutable-radix v1.3.1 // indirect 59 | github.com/hashicorp/go-multierror v1.1.1 // indirect 60 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 61 | github.com/hashicorp/go-uuid v1.0.3 // indirect 62 | github.com/hashicorp/golang-lru v0.5.4 // indirect 63 | github.com/hashicorp/hcl v1.0.0 // indirect 64 | github.com/hashicorp/serf v0.9.7 // indirect 65 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 66 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 67 | github.com/jcmturner/gofork v1.7.6 // indirect 68 | github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect 69 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 70 | github.com/jinzhu/inflection v1.0.0 // indirect 71 | github.com/jinzhu/now v1.1.4 // indirect 72 | github.com/json-iterator/go v1.1.12 // indirect 73 | github.com/juju/ratelimit v1.0.1 // indirect 74 | github.com/julienschmidt/httprouter v1.3.0 // indirect 75 | github.com/kavu/go_reuseport v1.5.0 // indirect 76 | github.com/klauspost/compress v1.15.14 // indirect 77 | github.com/klauspost/cpuid/v2 v2.0.12 // indirect 78 | github.com/klauspost/reedsolomon v1.9.16 // indirect 79 | github.com/lucas-clemente/quic-go v0.27.0 // indirect 80 | github.com/magiconair/properties v1.8.6 // indirect 81 | github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect 82 | github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect 83 | github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect 84 | github.com/mattn/go-colorable v0.1.12 // indirect 85 | github.com/mattn/go-isatty v0.0.14 // indirect 86 | github.com/miekg/dns v1.1.48 // indirect 87 | github.com/mitchellh/go-homedir v1.1.0 // indirect 88 | github.com/mitchellh/mapstructure v1.4.3 // indirect 89 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 90 | github.com/modern-go/reflect2 v1.0.2 // indirect 91 | github.com/nxadm/tail v1.4.8 // indirect 92 | github.com/onsi/ginkgo v1.16.5 // indirect 93 | github.com/onsi/gomega v1.19.0 // indirect 94 | github.com/pelletier/go-toml v1.9.4 // indirect 95 | github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect 96 | github.com/philhofer/fwd v1.1.1 // indirect 97 | github.com/pierrec/lz4/v4 v4.1.17 // indirect 98 | github.com/pkg/errors v0.9.1 // indirect 99 | github.com/pmezard/go-difflib v1.0.0 // indirect 100 | github.com/rogpeppe/go-internal v1.10.0 // indirect 101 | github.com/rpcxio/libkv v0.5.1-0.20210420120011-1fceaedca8a5 // indirect 102 | github.com/rs/cors v1.8.2 // indirect 103 | github.com/rubyist/circuitbreaker v2.2.1+incompatible // indirect 104 | github.com/samuel/go-zookeeper v0.0.0-20201211165307-7117e9ea2414 // indirect 105 | github.com/smallnest/quick v0.0.0-20220103065406-780def6371e6 // indirect 106 | github.com/soheilhy/cmux v0.1.5 // indirect 107 | github.com/spf13/afero v1.8.2 // indirect 108 | github.com/spf13/cast v1.4.1 // indirect 109 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 110 | github.com/spf13/pflag v1.0.5 // indirect 111 | github.com/subosito/gotenv v1.2.0 // indirect 112 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 113 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 114 | github.com/tinylib/msgp v1.1.6 // indirect 115 | github.com/tjfoc/gmsm v1.4.1 // indirect 116 | github.com/valyala/bytebufferpool v1.0.0 // indirect 117 | github.com/valyala/fastrand v1.1.0 // indirect 118 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 119 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 120 | github.com/xtaci/kcp-go v5.4.20+incompatible // indirect 121 | go.etcd.io/etcd/api/v3 v3.5.2 // indirect 122 | go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect 123 | go.etcd.io/etcd/client/v2 v2.305.2 // indirect 124 | go.etcd.io/etcd/client/v3 v3.5.1 // indirect 125 | go.opentelemetry.io/otel v1.6.3 // indirect 126 | go.opentelemetry.io/otel/trace v1.6.3 // indirect 127 | go.uber.org/atomic v1.7.0 // indirect 128 | go.uber.org/multierr v1.6.0 // indirect 129 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect 130 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect 131 | golang.org/x/net v0.5.0 // indirect 132 | golang.org/x/sync v0.1.0 // indirect 133 | golang.org/x/sys v0.4.0 // indirect 134 | golang.org/x/text v0.6.0 // indirect 135 | golang.org/x/tools v0.1.12 // indirect 136 | google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect 137 | google.golang.org/grpc v1.45.0 // indirect 138 | gopkg.in/ini.v1 v1.66.4 // indirect 139 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 140 | gopkg.in/yaml.v2 v2.4.0 // indirect 141 | gopkg.in/yaml.v3 v3.0.1 // indirect 142 | ) 143 | -------------------------------------------------------------------------------- /im_service/server/rpc_server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "github.com/glide-im/glide/im_service/proto" 8 | "github.com/glide-im/glide/pkg/gate" 9 | "github.com/glide-im/glide/pkg/messages" 10 | "github.com/glide-im/glide/pkg/rpc" 11 | "github.com/glide-im/glide/pkg/subscription" 12 | "github.com/glide-im/glide/pkg/subscription/subscription_impl" 13 | ) 14 | 15 | type GatewayRpcServer interface { 16 | UpdateClient(ctx context.Context, request *proto.UpdateClient, response *proto.Response) error 17 | 18 | EnqueueMessage(ctx context.Context, request *proto.EnqueueMessageRequest, response *proto.Response) error 19 | } 20 | 21 | type SubscriptionRpcServer interface { 22 | Subscribe(ctx context.Context, request *proto.SubscribeRequest, response *proto.Response) error 23 | 24 | UnSubscribe(ctx context.Context, request *proto.UnsubscribeRequest, response *proto.Response) error 25 | 26 | UpdateSubscriber(ctx context.Context, request *proto.UpdateSubscriberRequest, response *proto.Response) error 27 | 28 | RemoveChannel(ctx context.Context, request *proto.RemoveChannelRequest, response *proto.Response) error 29 | 30 | CreateChannel(ctx context.Context, request *proto.CreateChannelRequest, response *proto.Response) error 31 | 32 | UpdateChannel(ctx context.Context, request *proto.UpdateChannelRequest, response *proto.Response) error 33 | 34 | Publish(ctx context.Context, request *proto.PublishRequest, response *proto.Response) error 35 | } 36 | 37 | type IMRpcService struct { 38 | gateway gate.Server 39 | sub subscription_impl.SubscribeWrap 40 | } 41 | 42 | func RunRpcService(options *rpc.ServerOptions, gate gate.Server, subscribe subscription.Subscribe) error { 43 | server := rpc.NewBaseServer(options) 44 | rpcServer := IMRpcService{ 45 | gateway: gate, 46 | sub: subscription_impl.NewSubscribeWrap(subscribe), 47 | } 48 | server.Register(options.Name, &rpcServer) 49 | return server.Run() 50 | } 51 | 52 | func (r *IMRpcService) UpdateClient(ctx context.Context, request *proto.UpdateClient, response *proto.Response) error { 53 | id := gate.ID(request.GetId()) 54 | 55 | var err error 56 | switch request.Type { 57 | case proto.UpdateClient_UpdateID: 58 | err = r.gateway.SetClientID(id, gate.ID(request.GetNewId())) 59 | break 60 | case proto.UpdateClient_Close: 61 | err = r.gateway.ExitClient(id) 62 | break 63 | case proto.UpdateClient_UpdateSecret: 64 | secrets := &gate.ClientSecrets{ 65 | MessageDeliverSecret: request.GetSecret(), 66 | } 67 | gt := r.gateway 68 | err2 := gt.UpdateClient(id, secrets) 69 | if err2 != nil { 70 | err = err2 71 | } 72 | break 73 | default: 74 | err = errors.New("unknown update type") 75 | } 76 | if err != nil { 77 | response.Code = int32(proto.Response_ERROR) 78 | response.Msg = err.Error() 79 | } else { 80 | response.Code = int32(proto.Response_OK) 81 | } 82 | return err 83 | } 84 | 85 | func (r *IMRpcService) EnqueueMessage(ctx context.Context, request *proto.EnqueueMessageRequest, response *proto.Response) error { 86 | 87 | msg := messages.GlideMessage{} 88 | err := json.Unmarshal(request.Msg, &msg) 89 | if err != nil { 90 | response.Code = int32(proto.Response_ERROR) 91 | response.Msg = err.Error() 92 | return nil 93 | } 94 | 95 | err = r.gateway.EnqueueMessage(gate.ID(request.Id), &msg) 96 | if err != nil { 97 | response.Code = int32(proto.Response_ERROR) 98 | response.Msg = err.Error() 99 | } 100 | return err 101 | } 102 | 103 | ////////////////////////////////////// Subscription ////////////////////////////////////////////// 104 | 105 | func (r *IMRpcService) Subscribe(ctx context.Context, request *proto.SubscribeRequest, response *proto.Response) error { 106 | 107 | subscriberID := subscription.SubscriberID(request.SubscriberID) 108 | channelID := subscription.ChanID(request.ChannelID) 109 | 110 | info := subscription_impl.SubscriberOptions{} 111 | err := json.Unmarshal(request.GetExtra(), &info) 112 | if err != nil { 113 | response.Code = int32(proto.Response_ERROR) 114 | response.Msg = err.Error() 115 | return nil 116 | } 117 | 118 | err = r.sub.Subscribe(channelID, subscriberID, &info) 119 | if err != nil { 120 | response.Code = int32(proto.Response_ERROR) 121 | response.Msg = err.Error() 122 | } 123 | return nil 124 | } 125 | 126 | func (r *IMRpcService) UnSubscribe(ctx context.Context, request *proto.UnsubscribeRequest, response *proto.Response) error { 127 | chanId := subscription.ChanID(request.ChannelID) 128 | subscriberId := subscription.SubscriberID(request.SubscriberID) 129 | err := r.sub.UnSubscribe(chanId, subscriberId) 130 | if err != nil { 131 | response.Code = int32(proto.Response_ERROR) 132 | response.Msg = err.Error() 133 | } 134 | return nil 135 | } 136 | 137 | func (r *IMRpcService) UpdateSubscriber(ctx context.Context, request *proto.UpdateSubscriberRequest, response *proto.Response) error { 138 | 139 | chanId := subscription.ChanID(request.ChannelID) 140 | subscriberID := subscription.SubscriberID(request.SubscriberID) 141 | info := subscription_impl.SubscriberOptions{} 142 | err := json.Unmarshal(request.GetExtra(), &info) 143 | if err != nil { 144 | response.Code = int32(proto.Response_ERROR) 145 | response.Msg = err.Error() 146 | return nil 147 | } 148 | err = r.sub.UpdateSubscriber(chanId, subscriberID, &info) 149 | if err != nil { 150 | response.Code = int32(proto.Response_ERROR) 151 | response.Msg = err.Error() 152 | } 153 | return nil 154 | } 155 | 156 | func (r *IMRpcService) RemoveChannel(ctx context.Context, request *proto.RemoveChannelRequest, response *proto.Response) error { 157 | chanId := subscription.ChanID(request.ChannelID) 158 | err := r.sub.RemoveChannel(chanId) 159 | if err != nil { 160 | response.Code = int32(proto.Response_ERROR) 161 | response.Msg = err.Error() 162 | } 163 | return nil 164 | } 165 | 166 | func (r *IMRpcService) CreateChannel(ctx context.Context, request *proto.CreateChannelRequest, response *proto.Response) error { 167 | chanId := subscription.ChanID(request.ChannelID) 168 | cInfo := request.GetChannelInfo() 169 | 170 | info := subscription.ChanInfo{ 171 | ID: chanId, 172 | Type: subscription.ChanType(cInfo.Type), 173 | Muted: cInfo.Muted, 174 | Blocked: cInfo.Blocked, 175 | Closed: cInfo.Closed, 176 | } 177 | err := r.sub.CreateChannel(chanId, &info) 178 | if err != nil { 179 | response.Code = int32(proto.Response_ERROR) 180 | response.Msg = err.Error() 181 | } 182 | return nil 183 | } 184 | 185 | func (r *IMRpcService) UpdateChannel(ctx context.Context, request *proto.UpdateChannelRequest, response *proto.Response) error { 186 | chanId := subscription.ChanID(request.ChannelID) 187 | cInfo := request.GetChannelInfo() 188 | info := subscription.ChanInfo{ 189 | ID: chanId, 190 | Type: subscription.ChanType(cInfo.Type), 191 | Muted: cInfo.Muted, 192 | Blocked: cInfo.Blocked, 193 | Closed: cInfo.Closed, 194 | } 195 | err := r.sub.UpdateChannel(chanId, &info) 196 | if err != nil { 197 | response.Code = int32(proto.Response_ERROR) 198 | response.Msg = err.Error() 199 | } 200 | return nil 201 | } 202 | 203 | func (r *IMRpcService) Publish(ctx context.Context, request *proto.PublishRequest, response *proto.Response) error { 204 | chanId := subscription.ChanID(request.ChannelID) 205 | msg := subscription_impl.PublishMessage{} 206 | err := json.Unmarshal(request.GetMessage(), &msg) 207 | if err != nil { 208 | response.Code = int32(proto.Response_ERROR) 209 | response.Msg = err.Error() 210 | return nil 211 | } 212 | err = r.sub.Publish(chanId, &msg) 213 | if err != nil { 214 | response.Code = int32(proto.Response_ERROR) 215 | response.Msg = err.Error() 216 | } 217 | return nil 218 | } 219 | -------------------------------------------------------------------------------- /pkg/messaging/messaging.go: -------------------------------------------------------------------------------- 1 | package messaging 2 | 3 | import ( 4 | "errors" 5 | "github.com/glide-im/glide/pkg/gate" 6 | "github.com/glide-im/glide/pkg/logger" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "github.com/glide-im/glide/pkg/subscription" 9 | "github.com/panjf2000/ants/v2" 10 | ) 11 | 12 | // MessageHandler is the interface for message offlineMessageHandler 13 | type MessageHandler interface { 14 | // Handle handles the message, returns true if the message is handled, 15 | // otherwise the message is delegated to next offlineMessageHandler. 16 | Handle(h *MessageInterfaceImpl, cliInfo *gate.Info, message *messages.GlideMessage) bool 17 | } 18 | 19 | type Messaging interface { 20 | 21 | // Handle handles message from gate, the entry point for the messaging. 22 | Handle(clientInfo *gate.Info, msg *messages.GlideMessage) error 23 | 24 | AddHandler(i MessageHandler) 25 | 26 | SetSubscription(g subscription.Interface) 27 | 28 | SetGate(g gate.Gateway) 29 | } 30 | 31 | // MessageValidator is used to validate message. 32 | // if error is not nil, this message will be handled by MessageValidationHandler 33 | // the second return value is the reply message, if not nil, the message will be sent to 34 | // the client, if nil, the MessageValidationHandler will return the error message 35 | type MessageValidator = func(msg *messages.GlideMessage) (error, *messages.GlideMessage) 36 | 37 | // MessageValidationHandler validates message before handling 38 | type MessageValidationHandler struct { 39 | validators []MessageValidator 40 | } 41 | 42 | func NewMessageValidationHandler(validators ...MessageValidator) *MessageValidationHandler { 43 | return &MessageValidationHandler{ 44 | validators: validators, 45 | } 46 | } 47 | 48 | func (m *MessageValidationHandler) Handle(h *MessageInterfaceImpl, cliInfo *gate.Info, message *messages.GlideMessage) bool { 49 | 50 | for _, v := range m.validators { 51 | err, reply := v(message) 52 | if err != nil { 53 | if reply == nil { 54 | reply = messages.NewMessage(message.GetSeq(), messages.ActionNotifyError, err.Error()) 55 | } 56 | _ = h.GetClientInterface().EnqueueMessage(cliInfo.ID, reply) 57 | return true 58 | } 59 | } 60 | 61 | return false 62 | } 63 | 64 | func DefaultMessageValidator(msg *messages.GlideMessage) (error, *messages.GlideMessage) { 65 | if msg.To == "" { 66 | return errors.New("message.To is empty"), nil 67 | } 68 | if msg.Action == "" { 69 | return errors.New("message.Action is empty"), nil 70 | } 71 | return nil, nil 72 | } 73 | 74 | // HandlerFunc is used to handle message with specified action in ActionHandler 75 | type HandlerFunc func(cliInfo *gate.Info, message *messages.GlideMessage) error 76 | 77 | // ActionHandler is a offlineMessageHandler for a specific message action. 78 | type ActionHandler struct { 79 | action messages.Action 80 | fn HandlerFunc 81 | } 82 | 83 | func NewActionHandler(action messages.Action, fn HandlerFunc) *ActionHandler { 84 | return &ActionHandler{ 85 | action: action, 86 | fn: fn, 87 | } 88 | } 89 | 90 | func (a *ActionHandler) Handle(h *MessageInterfaceImpl, cliInfo *gate.Info, message *messages.GlideMessage) bool { 91 | if message.GetAction() == a.action { 92 | err := a.fn(cliInfo, message) 93 | if err != nil { 94 | h.OnHandleMessageError(cliInfo, message, err) 95 | } 96 | return true 97 | } 98 | return false 99 | } 100 | 101 | type ReplyHandlerFunc func(cliInfo *gate.Info, message *messages.GlideMessage) (*messages.GlideMessage, error) 102 | 103 | // ActionWithReplyHandler is a offlineMessageHandler for a specific message action, this offlineMessageHandler will return a reply message. 104 | type ActionWithReplyHandler struct { 105 | action messages.Action 106 | fn ReplyHandlerFunc 107 | } 108 | 109 | func NewActionWithReplyHandler(action messages.Action, fn ReplyHandlerFunc) *ActionWithReplyHandler { 110 | return &ActionWithReplyHandler{ 111 | action: action, 112 | fn: fn, 113 | } 114 | } 115 | 116 | func (rh *ActionWithReplyHandler) Handle(h *MessageInterfaceImpl, cInfo *gate.Info, msg *messages.GlideMessage) bool { 117 | if msg.GetAction() == rh.action { 118 | r, err := rh.fn(cInfo, msg) 119 | if err != nil { 120 | h.OnHandleMessageError(cInfo, msg, err) 121 | } 122 | _ = h.GetClientInterface().EnqueueMessage(cInfo.ID, r) 123 | return true 124 | } 125 | return false 126 | } 127 | 128 | type Options struct { 129 | NotifyServerError bool 130 | MaxMessageConcurrency int 131 | } 132 | 133 | func onMessageHandlerPanic(i interface{}) { 134 | logger.E("MessageInterfaceImpl panic: %v", i) 135 | } 136 | 137 | // MessageInterfaceImpl default implementation of the messaging interface. 138 | type MessageInterfaceImpl struct { 139 | 140 | // execPool 100 capacity goroutine pool, 假设每个消息处理需要10ms, 一个协程则每秒能处理100条消息 141 | execPool *ants.Pool 142 | 143 | // hc message offlineMessageHandler chain 144 | hc *handlerChain 145 | 146 | subscription subscription.Interface 147 | gate gate.Gateway 148 | 149 | // notifyOnSrvErr notify client on server error 150 | notifyOnSrvErr bool 151 | } 152 | 153 | func NewDefaultImpl(options *Options) (*MessageInterfaceImpl, error) { 154 | 155 | ret := MessageInterfaceImpl{ 156 | notifyOnSrvErr: options.NotifyServerError, 157 | hc: &handlerChain{}, 158 | } 159 | 160 | var err error 161 | ret.execPool, err = ants.NewPool( 162 | options.MaxMessageConcurrency, 163 | ants.WithNonblocking(true), 164 | ants.WithPanicHandler(onMessageHandlerPanic), 165 | ants.WithPreAlloc(false), 166 | ) 167 | if err != nil { 168 | return nil, err 169 | } 170 | return &ret, nil 171 | } 172 | 173 | func (d *MessageInterfaceImpl) Handle(cInfo *gate.Info, msg *messages.GlideMessage) error { 174 | 175 | if !msg.GetAction().IsInternal() { 176 | msg.From = cInfo.ID.UID() 177 | } 178 | logger.D("handle message: %s", msg) 179 | err := d.execPool.Submit(func() { 180 | handled := d.hc.handle(d, cInfo, msg) 181 | if !handled { 182 | if !msg.GetAction().IsInternal() { 183 | r := messages.NewMessage(msg.GetSeq(), messages.ActionNotifyUnknownAction, msg.GetAction()) 184 | _ = d.gate.EnqueueMessage(cInfo.ID, r) 185 | } 186 | logger.W("action is not handled: %s", msg.GetAction()) 187 | } 188 | }) 189 | if err != nil { 190 | d.OnHandleMessageError(cInfo, msg, err) 191 | return err 192 | } 193 | return nil 194 | } 195 | 196 | func (d *MessageInterfaceImpl) AddHandler(i MessageHandler) { 197 | d.hc.add(i) 198 | } 199 | 200 | func (d *MessageInterfaceImpl) SetGate(g gate.Gateway) { 201 | d.gate = g 202 | } 203 | 204 | func (d *MessageInterfaceImpl) SetSubscription(g subscription.Interface) { 205 | d.subscription = g 206 | } 207 | 208 | func (d *MessageInterfaceImpl) SetNotifyErrorOnServer(enable bool) { 209 | d.notifyOnSrvErr = enable 210 | } 211 | 212 | func (d *MessageInterfaceImpl) GetClientInterface() gate.Gateway { 213 | return d.gate 214 | } 215 | 216 | func (d *MessageInterfaceImpl) GetGroupInterface() subscription.Interface { 217 | return d.subscription 218 | } 219 | 220 | func (d *MessageInterfaceImpl) OnHandleMessageError(cInfo *gate.Info, msg *messages.GlideMessage, err error) { 221 | if d.notifyOnSrvErr { 222 | _ = d.gate.EnqueueMessage(cInfo.ID, messages.NewMessage(-1, messages.ActionNotifyError, err.Error())) 223 | } 224 | } 225 | 226 | // handlerChain is a chain of MessageHandlers. 227 | type handlerChain struct { 228 | h MessageHandler 229 | next *handlerChain 230 | } 231 | 232 | func (hc *handlerChain) add(i MessageHandler) { 233 | if hc.next == nil { 234 | hc.next = &handlerChain{ 235 | h: i, 236 | } 237 | } else { 238 | hc.next.add(i) 239 | } 240 | } 241 | 242 | func (hc handlerChain) handle(h2 *MessageInterfaceImpl, cliInfo *gate.Info, message *messages.GlideMessage) bool { 243 | if hc.h != nil && hc.h.Handle(h2, cliInfo, message) { 244 | return true 245 | } 246 | if hc.next != nil { 247 | return hc.next.handle(h2, cliInfo, message) 248 | } 249 | return false 250 | } 251 | -------------------------------------------------------------------------------- /pkg/gate/authenticator.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/rand" 8 | "crypto/sha512" 9 | "encoding/base64" 10 | "encoding/json" 11 | "errors" 12 | "github.com/glide-im/glide/pkg/hash" 13 | "github.com/glide-im/glide/pkg/logger" 14 | "github.com/glide-im/glide/pkg/messages" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | type CredentialCrypto interface { 20 | EncryptCredentials(c *ClientAuthCredentials) ([]byte, error) 21 | 22 | DecryptCredentials(src []byte) (*ClientAuthCredentials, error) 23 | } 24 | 25 | // AesCBCCrypto cbc mode PKCS7 padding 26 | type AesCBCCrypto struct { 27 | Key []byte 28 | } 29 | 30 | func NewAesCBCCrypto(key []byte) *AesCBCCrypto { 31 | keyLen := len(key) 32 | count := 0 33 | switch true { 34 | case keyLen <= 16: 35 | count = 16 - keyLen 36 | case keyLen <= 24: 37 | count = 24 - keyLen 38 | case keyLen <= 32: 39 | count = 32 - keyLen 40 | default: 41 | key = key[:32] 42 | } 43 | if count != 0 { 44 | key = append(key, bytes.Repeat([]byte{0}, count)...) 45 | } 46 | return &AesCBCCrypto{Key: key} 47 | } 48 | 49 | func (a *AesCBCCrypto) EncryptCredentials(c *ClientAuthCredentials) ([]byte, error) { 50 | jsonBytes, err := json.Marshal(c) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | // generate random iv 56 | iv := make([]byte, aes.BlockSize) 57 | _, err = rand.Read(iv) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | encryptBody, err := a.Encrypt(jsonBytes, iv) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | // NOTE: append iv 68 | var encrypt []byte 69 | encrypt = append(encrypt, iv...) 70 | encrypt = append(encrypt, encryptBody...) 71 | 72 | // base64 encoding encrypted json credentials 73 | b64Bytes := make([]byte, base64.RawStdEncoding.EncodedLen(len(encrypt))) 74 | base64.RawStdEncoding.Encode(b64Bytes, encrypt) 75 | return b64Bytes, nil 76 | } 77 | 78 | func (a *AesCBCCrypto) DecryptCredentials(src []byte) (*ClientAuthCredentials, error) { 79 | 80 | encrypt := make([]byte, base64.RawStdEncoding.DecodedLen(len(src))) 81 | _, err := base64.RawStdEncoding.Decode(encrypt, src) 82 | if err != nil { 83 | return nil, err 84 | } 85 | var iv []byte 86 | iv = append(iv, encrypt[:aes.BlockSize]...) 87 | var encryptBody []byte 88 | encryptBody = append(encryptBody, encrypt[aes.BlockSize:]...) 89 | 90 | jsonBytes, err := a.Decrypt(encryptBody, iv) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | credentials := ClientAuthCredentials{} 96 | err = json.Unmarshal(jsonBytes, &credentials) 97 | if err != nil { 98 | return nil, err 99 | } 100 | return &credentials, nil 101 | } 102 | 103 | func (a *AesCBCCrypto) Encrypt(src, iv []byte) ([]byte, error) { 104 | 105 | block, err := aes.NewCipher(a.Key) 106 | if err != nil { 107 | return nil, err 108 | } 109 | // padding 110 | blockSize := block.BlockSize() 111 | padding := blockSize - len(src)%blockSize 112 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 113 | src = append(src, padtext...) 114 | 115 | encryptData := make([]byte, len(src)) 116 | 117 | if len(iv) != block.BlockSize() { 118 | iv = a.cbcIVPending(iv, blockSize) 119 | } 120 | 121 | mode := cipher.NewCBCEncrypter(block, iv) 122 | mode.CryptBlocks(encryptData, src) 123 | 124 | return encryptData, nil 125 | } 126 | 127 | func (a *AesCBCCrypto) Decrypt(src, iv []byte) ([]byte, error) { 128 | 129 | block, err := aes.NewCipher(a.Key) 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | dst := make([]byte, len(src)) 135 | blockSize := block.BlockSize() 136 | if len(iv) != blockSize { 137 | iv = a.cbcIVPending(iv, blockSize) 138 | } 139 | 140 | mode := cipher.NewCBCDecrypter(block, iv) 141 | mode.CryptBlocks(dst, src) 142 | 143 | length := len(dst) 144 | if length == 0 { 145 | return nil, errors.New("unpadding") 146 | } 147 | unpadding := int(dst[length-1]) 148 | if length < unpadding { 149 | return nil, errors.New("unpadding") 150 | } 151 | res := dst[:(length - unpadding)] 152 | 153 | return res, nil 154 | } 155 | 156 | func (a *AesCBCCrypto) cbcIVPending(iv []byte, blockSize int) []byte { 157 | k := len(iv) 158 | if k < blockSize { 159 | return append(iv, bytes.Repeat([]byte{0}, blockSize-k)...) 160 | } else if k > blockSize { 161 | return iv[0:blockSize] 162 | } 163 | return iv 164 | } 165 | 166 | // Authenticator handle client authentication message 167 | type Authenticator struct { 168 | credentialCrypto CredentialCrypto 169 | gateway DefaultGateway 170 | } 171 | 172 | func NewAuthenticator(gateway DefaultGateway, key string) *Authenticator { 173 | k := sha512.New().Sum([]byte(key)) 174 | return &Authenticator{ 175 | credentialCrypto: NewAesCBCCrypto(k), 176 | gateway: gateway, 177 | } 178 | } 179 | 180 | func (a *Authenticator) MessageInterceptor(dc DefaultClient, msg *messages.GlideMessage) bool { 181 | 182 | if dc.GetCredentials() == nil { 183 | return false 184 | } 185 | switch msg.Action { 186 | case messages.ActionGroupMessage, messages.ActionChatMessage, messages.ActionChatMessageResend: 187 | break 188 | default: 189 | return false 190 | } 191 | 192 | if dc.GetCredentials() == nil || dc.GetCredentials().Secrets == nil { 193 | _ = a.gateway.EnqueueMessage(dc.GetInfo().ID, messages.NewMessage(msg.GetSeq(), messages.ActionNotifyForbidden, "no credentials")) 194 | return true 195 | } 196 | 197 | secret := dc.GetCredentials().Secrets.MessageDeliverSecret 198 | if secret == "" { 199 | _ = a.gateway.EnqueueMessage(dc.GetInfo().ID, messages.NewMessage(msg.GetSeq(), messages.ActionNotifyForbidden, "no message deliver secret")) 200 | return true 201 | } 202 | 203 | var ticket = msg.Ticket 204 | // sha1 hash 205 | if len(ticket) != 40 { 206 | _ = a.gateway.EnqueueMessage(dc.GetInfo().ID, messages.NewMessage(msg.GetSeq(), messages.ActionNotifyForbidden, "invalid ticket")) 207 | return true 208 | } 209 | sum1 := hash.SHA1(secret + msg.To) 210 | id := dc.GetInfo().ID 211 | expectTicket := hash.SHA1(secret + id.UID() + sum1) 212 | 213 | if strings.ToUpper(ticket) != strings.ToUpper(expectTicket) { 214 | logger.I("invalid ticket, expected=%s, actually=%s, secret=%s, to=%s, from=%s", expectTicket, ticket, secret, msg.To, id.UID()) 215 | // invalid ticket 216 | _ = a.gateway.EnqueueMessage(dc.GetInfo().ID, messages.NewMessage(msg.GetSeq(), messages.ActionNotifyForbidden, "ticket expired")) 217 | return true 218 | } 219 | return false 220 | } 221 | 222 | func (a *Authenticator) ClientAuthMessageInterceptor(dc DefaultClient, msg *messages.GlideMessage) (intercept bool) { 223 | if msg.Action != messages.ActionAuthenticate { 224 | return false 225 | } 226 | 227 | intercept = true 228 | 229 | var err error 230 | var errMsg string 231 | var newId ID 232 | var span int64 233 | var authCredentials *ClientAuthCredentials 234 | 235 | credential := EncryptedCredential{} 236 | err = msg.Data.Deserialize(&credential) 237 | if err != nil { 238 | errMsg = "invalid authenticate message" 239 | goto DONE 240 | } 241 | 242 | if len(credential.Credential) < 5 { 243 | errMsg = "invalid authenticate message" 244 | goto DONE 245 | } 246 | 247 | authCredentials, err = a.credentialCrypto.DecryptCredentials([]byte(credential.Credential)) 248 | if err != nil { 249 | errMsg = "invalid authenticate message" 250 | goto DONE 251 | } 252 | 253 | span = time.Now().UnixMilli() - authCredentials.Timestamp 254 | if span > 1500*1000 { 255 | errMsg = "credential expired" 256 | goto DONE 257 | } 258 | 259 | newId, err = a.updateClient(dc, authCredentials) 260 | 261 | DONE: 262 | 263 | ac, _ := json.Marshal(authCredentials) 264 | logger.D("credential: %s", string(ac)) 265 | 266 | logger.D("client auth message intercepted %s, %v", dc.GetInfo().ID, err) 267 | 268 | if err != nil || errMsg != "" { 269 | _ = a.gateway.EnqueueMessage(dc.GetInfo().ID, messages.NewMessage(msg.GetSeq(), messages.ActionNotifyError, errMsg)) 270 | } else { 271 | _ = a.gateway.EnqueueMessage(newId, messages.NewMessage(msg.GetSeq(), messages.ActionNotifySuccess, nil)) 272 | } 273 | return 274 | } 275 | 276 | func (a *Authenticator) updateClient(dc DefaultClient, authCredentials *ClientAuthCredentials) (ID, error) { 277 | 278 | dc.SetCredentials(authCredentials) 279 | 280 | oldID := dc.GetInfo().ID 281 | newID := NewID2(authCredentials.UserID) 282 | err := a.gateway.SetClientID(oldID, newID) 283 | if IsIDAlreadyExist(err) { 284 | if newID.Equals(oldID) { 285 | // already authenticated 286 | return newID, nil 287 | } 288 | tempID, _ := GenTempID("") 289 | err = a.gateway.SetClientID(newID, tempID) 290 | if err != nil { 291 | return "", err 292 | } 293 | kickOut := messages.NewMessage(0, messages.ActionNotifyKickOut, &messages.KickOutNotify{ 294 | DeviceName: authCredentials.DeviceName, 295 | DeviceId: authCredentials.DeviceID, 296 | }) 297 | _ = a.gateway.EnqueueMessage(tempID, kickOut) 298 | err = a.gateway.SetClientID(oldID, newID) 299 | if err != nil { 300 | return "", err 301 | } 302 | } 303 | return newID, err 304 | } 305 | -------------------------------------------------------------------------------- /pkg/gate/gateway.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "errors" 5 | "github.com/glide-im/glide/pkg/conn" 6 | "github.com/glide-im/glide/pkg/logger" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "github.com/panjf2000/ants/v2" 9 | "log" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // Gateway is the basic and common interface for all gate implementations. 15 | // As the basic gate, it is used to provide a common gate interface for other modules to interact with the gate. 16 | type Gateway interface { 17 | 18 | // SetClientID sets the client id with the new id. 19 | SetClientID(old ID, new_ ID) error 20 | 21 | UpdateClient(id ID, info *ClientSecrets) error 22 | 23 | // ExitClient exits the client with the given id. 24 | ExitClient(id ID) error 25 | 26 | // EnqueueMessage enqueues the message to the client with the given id. 27 | EnqueueMessage(id ID, message *messages.GlideMessage) error 28 | } 29 | 30 | // Server is the interface for the gateway server, which is used to handle and manager client connections. 31 | type Server interface { 32 | Gateway 33 | 34 | // SetMessageHandler sets the client message handler. 35 | SetMessageHandler(h MessageHandler) 36 | 37 | // HandleConnection handles the new client connection and returns the random and temporary id set for the connection. 38 | HandleConnection(c conn.Connection) ID 39 | 40 | Run() error 41 | } 42 | 43 | // MessageHandler used to handle messages from the gate. 44 | type MessageHandler func(cliInfo *Info, message *messages.GlideMessage) 45 | 46 | // DefaultGateway is gateway default implements. 47 | type DefaultGateway interface { 48 | Gateway 49 | 50 | GetClient(id ID) Client 51 | 52 | GetAll() map[ID]Info 53 | 54 | SetMessageHandler(h MessageHandler) 55 | 56 | AddClient(cs Client) 57 | } 58 | 59 | type Options struct { 60 | // ID is the gateway id. 61 | ID string 62 | // SecretKey is the secret key used to encrypt and decrypt authentication token. 63 | SecretKey string 64 | // MaxMessageConcurrency is the max message concurrency. 65 | MaxMessageConcurrency int 66 | } 67 | 68 | var _ DefaultGateway = (*Impl)(nil) 69 | 70 | type Impl struct { 71 | id string 72 | 73 | // clients is a map of all connected clients 74 | clients map[ID]Client 75 | mu sync.RWMutex 76 | 77 | // msgHandler client message handler 78 | msgHandler MessageHandler 79 | 80 | authenticator *Authenticator 81 | 82 | // pool of ants, used to process messages concurrently. 83 | pool *ants.Pool 84 | } 85 | 86 | func NewServer(options *Options) (*Impl, error) { 87 | 88 | ret := new(Impl) 89 | ret.clients = map[ID]Client{} 90 | ret.mu = sync.RWMutex{} 91 | ret.id = options.ID 92 | 93 | if options.SecretKey != "" { 94 | ret.authenticator = NewAuthenticator(ret, options.SecretKey) 95 | } 96 | 97 | pool, err := ants.NewPool(options.MaxMessageConcurrency, 98 | ants.WithNonblocking(true), 99 | ants.WithPanicHandler(func(i interface{}) { 100 | log.Printf("panic: %v", i) 101 | }), 102 | ants.WithPreAlloc(false), 103 | ) 104 | if err != nil { 105 | return nil, err 106 | } 107 | ret.pool = pool 108 | return ret, nil 109 | } 110 | 111 | // GetClient returns the client with specified id 112 | func (c *Impl) GetClient(id ID) Client { 113 | c.mu.RLock() 114 | defer c.mu.RUnlock() 115 | return c.clients[id] 116 | } 117 | 118 | // GetAll returns all clients in the gateway. 119 | func (c *Impl) GetAll() map[ID]Info { 120 | c.mu.RLock() 121 | defer c.mu.RUnlock() 122 | 123 | result := map[ID]Info{} 124 | for id, client := range c.clients { 125 | result[id] = client.GetInfo() 126 | } 127 | return result 128 | } 129 | 130 | func (c *Impl) SetMessageHandler(h MessageHandler) { 131 | c.msgHandler = h 132 | } 133 | 134 | func (c *Impl) UpdateClient(id ID, info *ClientSecrets) error { 135 | c.mu.Lock() 136 | defer c.mu.Unlock() 137 | 138 | id.SetGateway(c.id) 139 | 140 | cli, ok := c.clients[id] 141 | if !ok || cli == nil { 142 | return errors.New(errClientNotExist) 143 | } 144 | 145 | dc, ok := cli.(DefaultClient) 146 | if ok { 147 | credentials := dc.GetCredentials() 148 | credentials.Secrets = info 149 | dc.SetCredentials(credentials) 150 | logger.D("gateway", "update client %s, %v", id, info.MessageDeliverSecret) 151 | } 152 | 153 | return nil 154 | } 155 | 156 | func (c *Impl) AddClient(cs Client) { 157 | c.mu.Lock() 158 | defer c.mu.Unlock() 159 | 160 | id := cs.GetInfo().ID 161 | id.SetGateway(c.id) 162 | 163 | dc, ok := cs.(DefaultClient) 164 | if ok { 165 | dc.AddMessageInterceptor(c.interceptClientMessage) 166 | } 167 | 168 | c.clients[id] = cs 169 | info := cs.GetInfo() 170 | c.msgHandler(&info, messages.NewMessage(0, messages.ActionInternalOnline, id)) 171 | } 172 | 173 | // SetClientID replace the oldID with newID of the client. 174 | // If the oldID is not exist, return errClientNotExist. 175 | // If the newID is existed, return errClientAlreadyExist. 176 | func (c *Impl) SetClientID(oldID, newID ID) error { 177 | c.mu.Lock() 178 | defer c.mu.Unlock() 179 | 180 | oldID.SetGateway(c.id) 181 | newID.SetGateway(c.id) 182 | 183 | cli, ok := c.clients[oldID] 184 | if !ok || cli == nil { 185 | return errors.New(errClientNotExist) 186 | } 187 | cliLogged, exist := c.clients[newID] 188 | if exist && cliLogged != nil { 189 | return errors.New(errClientAlreadyExist) 190 | } 191 | 192 | oldInfo := cli.GetInfo() 193 | cli.SetID(newID) 194 | newInfo := cli.GetInfo() 195 | delete(c.clients, oldID) 196 | c.msgHandler(&oldInfo, messages.NewMessage(0, messages.ActionInternalOffline, oldID)) 197 | c.msgHandler(&newInfo, messages.NewMessage(0, messages.ActionInternalOnline, newID)) 198 | 199 | c.clients[newID] = cli 200 | return nil 201 | } 202 | 203 | // ExitClient close the client with the specified id. 204 | // If the client is not exist, return errClientNotExist. 205 | func (c *Impl) ExitClient(id ID) error { 206 | c.mu.Lock() 207 | defer c.mu.Unlock() 208 | 209 | id.SetGateway(c.id) 210 | 211 | cli, ok := c.clients[id] 212 | if !ok || cli == nil { 213 | return errors.New(errClientNotExist) 214 | } 215 | 216 | info := cli.GetInfo() 217 | cli.SetID("") 218 | delete(c.clients, id) 219 | c.msgHandler(&info, messages.NewMessage(0, messages.ActionInternalOffline, id)) 220 | cli.Exit() 221 | 222 | return nil 223 | } 224 | 225 | // EnqueueMessage to the client with the specified id. 226 | func (c *Impl) EnqueueMessage(id ID, msg *messages.GlideMessage) error { 227 | 228 | c.mu.RLock() 229 | defer c.mu.RUnlock() 230 | 231 | id.SetGateway(c.id) 232 | cli, ok := c.clients[id] 233 | if !ok || cli == nil { 234 | return errors.New(errClientNotExist) 235 | } 236 | 237 | return c.enqueueMessage(cli, msg) 238 | } 239 | 240 | func (c *Impl) interceptClientMessage(dc DefaultClient, m *messages.GlideMessage) bool { 241 | 242 | if m.Action == messages.ActionAuthenticate { 243 | if c.authenticator != nil { 244 | return c.authenticator.ClientAuthMessageInterceptor(dc, m) 245 | } 246 | } 247 | 248 | return c.authenticator.MessageInterceptor(dc, m) 249 | } 250 | 251 | func (c *Impl) enqueueMessage(cli Client, msg *messages.GlideMessage) error { 252 | if !cli.IsRunning() { 253 | return errors.New(errClientClosed) 254 | } 255 | err := c.pool.Submit(func() { 256 | _ = cli.EnqueueMessage(msg) 257 | }) 258 | if err != nil { 259 | return errors.New("enqueue message to client failed") 260 | } 261 | return nil 262 | } 263 | 264 | type WebsocketGatewayServer struct { 265 | gateId string 266 | addr string 267 | port int 268 | server conn.Server 269 | decorator DefaultGateway 270 | h MessageHandler 271 | } 272 | 273 | func NewWebsocketServer(gateId string, addr string, port int, secretKey string) *WebsocketGatewayServer { 274 | srv := WebsocketGatewayServer{} 275 | srv.decorator, _ = NewServer( 276 | &Options{ 277 | ID: gateId, 278 | MaxMessageConcurrency: 30_0000, 279 | SecretKey: secretKey, 280 | }, 281 | ) 282 | srv.addr = addr 283 | srv.port = port 284 | srv.gateId = gateId 285 | options := &conn.WsServerOptions{ 286 | ReadTimeout: time.Minute * 3, 287 | WriteTimeout: time.Minute * 3, 288 | } 289 | srv.server = conn.NewWsServer(options) 290 | return &srv 291 | } 292 | 293 | func (w *WebsocketGatewayServer) SetMessageHandler(h MessageHandler) { 294 | w.h = h 295 | w.decorator.SetMessageHandler(h) 296 | } 297 | 298 | func (w *WebsocketGatewayServer) HandleConnection(c conn.Connection) ID { 299 | // 获取一个临时 uid 标识这个连接 300 | id, err := GenTempID(w.gateId) 301 | if err != nil { 302 | logger.E("[gateway] gen temp id error: %v", err) 303 | return "" 304 | } 305 | ret := NewClientWithConfig(c, w, w.h, &ClientConfig{ 306 | HeartbeatLostLimit: 3, 307 | ClientHeartbeatDuration: time.Second * 30, 308 | ServerHeartbeatDuration: time.Second * 30, 309 | CloseImmediately: false, 310 | }) 311 | ret.SetID(id) 312 | w.decorator.AddClient(ret) 313 | 314 | // 开始处理连接的消息 315 | ret.Run() 316 | 317 | hello := messages.ServerHello{ 318 | TempID: id.UID(), 319 | HeartbeatInterval: 30, 320 | } 321 | 322 | m := messages.NewMessage(0, messages.ActionHello, hello) 323 | _ = ret.EnqueueMessage(m) 324 | 325 | return id 326 | } 327 | 328 | func (w *WebsocketGatewayServer) Run() error { 329 | w.server.SetConnHandler(func(conn conn.Connection) { 330 | w.HandleConnection(conn) 331 | }) 332 | return w.server.Run(w.addr, w.port) 333 | } 334 | 335 | func (w *WebsocketGatewayServer) GetClient(id ID) Client { 336 | return w.decorator.GetClient(id) 337 | } 338 | 339 | func (w *WebsocketGatewayServer) GetAll() map[ID]Info { 340 | return w.decorator.GetAll() 341 | } 342 | 343 | func (w *WebsocketGatewayServer) AddClient(cs Client) { 344 | w.decorator.AddClient(cs) 345 | } 346 | 347 | func (w *WebsocketGatewayServer) SetClientID(old ID, new_ ID) error { 348 | return w.decorator.SetClientID(old, new_) 349 | } 350 | 351 | func (w *WebsocketGatewayServer) UpdateClient(id ID, info *ClientSecrets) error { 352 | return w.decorator.UpdateClient(id, info) 353 | } 354 | 355 | func (w *WebsocketGatewayServer) ExitClient(id ID) error { 356 | return w.decorator.ExitClient(id) 357 | } 358 | 359 | func (w *WebsocketGatewayServer) EnqueueMessage(id ID, message *messages.GlideMessage) error { 360 | return w.decorator.EnqueueMessage(id, message) 361 | } 362 | -------------------------------------------------------------------------------- /pkg/gate/client_impl.go: -------------------------------------------------------------------------------- 1 | package gate 2 | 3 | import ( 4 | "errors" 5 | "github.com/glide-im/glide/pkg/conn" 6 | "github.com/glide-im/glide/pkg/logger" 7 | "github.com/glide-im/glide/pkg/messages" 8 | "github.com/glide-im/glide/pkg/timingwheel" 9 | "sync" 10 | "sync/atomic" 11 | "time" 12 | ) 13 | 14 | // tw is a timer for heartbeat. 15 | var tw = timingwheel.NewTimingWheel(time.Millisecond*500, 3, 20) 16 | 17 | const ( 18 | defaultServerHeartbeatDuration = time.Second * 30 19 | defaultHeartbeatDuration = time.Second * 20 20 | defaultHeartbeatLostLimit = 3 21 | defaultCloseImmediately = false 22 | ) 23 | 24 | // client state 25 | const ( 26 | _ int32 = iota 27 | // stateRunning client is running, can runRead and runWrite message. 28 | stateRunning 29 | // stateClosed client is closed, cannot do anything. 30 | stateClosed 31 | ) 32 | 33 | // ClientConfig client config 34 | type ClientConfig struct { 35 | 36 | // ClientHeartbeatDuration is the duration of heartbeat. 37 | ClientHeartbeatDuration time.Duration 38 | 39 | // ServerHeartbeatDuration is the duration of server heartbeat. 40 | ServerHeartbeatDuration time.Duration 41 | 42 | // HeartbeatLostLimit is the max lost heartbeat count. 43 | HeartbeatLostLimit int 44 | 45 | // CloseImmediately true express when client exit, discard all message in queue, and close connection immediately, 46 | // otherwise client will close runRead, and mark as stateClosing, the client cannot receive and enqueue message, 47 | // after all message in queue is sent, client will close runWrite and connection. 48 | CloseImmediately bool 49 | } 50 | 51 | type MessageInterceptor = func(dc DefaultClient, msg *messages.GlideMessage) bool 52 | 53 | type DefaultClient interface { 54 | Client 55 | 56 | SetCredentials(credentials *ClientAuthCredentials) 57 | 58 | GetCredentials() *ClientAuthCredentials 59 | 60 | AddMessageInterceptor(interceptor MessageInterceptor) 61 | } 62 | 63 | var _ DefaultClient = (*UserClient)(nil) 64 | 65 | // UserClient represent a user conn client. 66 | type UserClient struct { 67 | 68 | // conn is the real connection 69 | conn conn.Connection 70 | 71 | // state is the client state 72 | state int32 73 | 74 | // queuedMessage message count in the messages channel 75 | queuedMessage int64 76 | // messages is the buffered channel for message to push to client. 77 | messages chan *messages.GlideMessage 78 | 79 | // closeReadCh is the channel for runRead goroutine to close 80 | closeReadCh chan struct{} 81 | // closeWriteCh is the channel for runWrite goroutine to close 82 | closeWriteCh chan struct{} 83 | 84 | // closeWriteOnce is the once for close runWrite goroutine 85 | closeWriteOnce sync.Once 86 | // closeReadOnce is the once for close runRead goroutine 87 | closeReadOnce sync.Once 88 | 89 | // hbC is the timer for client heartbeat 90 | hbC *timingwheel.Task 91 | // hbS is the timer for server heartbeat 92 | hbS *timingwheel.Task 93 | // hbLost is the count of heartbeat lost 94 | hbLost int 95 | 96 | // info is the client info 97 | info *Info 98 | 99 | credentials *ClientAuthCredentials 100 | 101 | // mgr the client manager which manage this client 102 | mgr Gateway 103 | // msgHandler client message handler 104 | msgHandler MessageHandler 105 | 106 | // config is the client config 107 | config *ClientConfig 108 | } 109 | 110 | func NewClientWithConfig(conn conn.Connection, mgr Gateway, handler MessageHandler, config *ClientConfig) DefaultClient { 111 | if config == nil { 112 | config = &ClientConfig{ 113 | ClientHeartbeatDuration: defaultHeartbeatDuration, 114 | ServerHeartbeatDuration: defaultServerHeartbeatDuration, 115 | HeartbeatLostLimit: defaultHeartbeatLostLimit, 116 | CloseImmediately: false, 117 | } 118 | } 119 | 120 | ret := UserClient{ 121 | conn: conn, 122 | messages: make(chan *messages.GlideMessage, 100), 123 | closeReadCh: make(chan struct{}), 124 | closeWriteCh: make(chan struct{}), 125 | hbC: tw.After(config.ClientHeartbeatDuration), 126 | hbS: tw.After(config.ServerHeartbeatDuration), 127 | info: &Info{ 128 | ConnectionAt: time.Now().UnixMilli(), 129 | CliAddr: conn.GetConnInfo().Addr, 130 | }, 131 | mgr: mgr, 132 | msgHandler: handler, 133 | config: config, 134 | } 135 | return &ret 136 | } 137 | 138 | func NewClient(conn conn.Connection, mgr Gateway, handler MessageHandler) DefaultClient { 139 | return NewClientWithConfig(conn, mgr, handler, nil) 140 | } 141 | 142 | func (c *UserClient) SetCredentials(credentials *ClientAuthCredentials) { 143 | c.credentials = credentials 144 | c.info.ConnectionId = credentials.ConnectionID 145 | if credentials.ConnectionConfig != nil { 146 | c.config.HeartbeatLostLimit = credentials.ConnectionConfig.AllowMaxHeartbeatLost 147 | c.config.CloseImmediately = credentials.ConnectionConfig.CloseImmediately 148 | c.config.ClientHeartbeatDuration = time.Duration(credentials.ConnectionConfig.HeartbeatDuration) * time.Second 149 | } 150 | } 151 | 152 | func (c *UserClient) GetCredentials() *ClientAuthCredentials { 153 | return c.credentials 154 | } 155 | 156 | func (c *UserClient) AddMessageInterceptor(interceptor MessageInterceptor) { 157 | h := c.msgHandler 158 | c.msgHandler = func(cliInfo *Info, msg *messages.GlideMessage) { 159 | if interceptor(c, msg) { 160 | return 161 | } 162 | h(cliInfo, msg) 163 | } 164 | } 165 | 166 | func (c *UserClient) GetInfo() Info { 167 | return *c.info 168 | } 169 | 170 | // SetID set client id. 171 | func (c *UserClient) SetID(id ID) { 172 | c.info.ID = id 173 | } 174 | 175 | // IsRunning return true if client is running 176 | func (c *UserClient) IsRunning() bool { 177 | return atomic.LoadInt32(&c.state) == stateRunning 178 | } 179 | 180 | // EnqueueMessage enqueue message to client message queue. 181 | func (c *UserClient) EnqueueMessage(msg *messages.GlideMessage) error { 182 | if atomic.LoadInt32(&c.state) == stateClosed { 183 | return errors.New("client has closed") 184 | } 185 | logger.I("EnqueueMessage ID=%s msg=%v", c.info.ID, msg) 186 | select { 187 | case c.messages <- msg: 188 | atomic.AddInt64(&c.queuedMessage, 1) 189 | default: 190 | logger.E("msg chan is full, id=%v", c.info.ID) 191 | } 192 | return nil 193 | } 194 | 195 | // runRead message from client. 196 | func (c *UserClient) runRead() { 197 | defer func() { 198 | err := recover() 199 | if err != nil { 200 | logger.E("read message panic: %v", err) 201 | c.Exit() 202 | } 203 | }() 204 | 205 | readChan, done := messageReader.ReadCh(c.conn) 206 | var closeReason string 207 | for { 208 | select { 209 | case <-c.closeReadCh: 210 | if closeReason == "" { 211 | closeReason = "closed initiative" 212 | } 213 | goto STOP 214 | case <-c.hbC.C: 215 | if !c.IsRunning() { 216 | goto STOP 217 | } 218 | c.hbLost++ 219 | if c.hbLost > c.config.HeartbeatLostLimit { 220 | closeReason = "heartbeat lost" 221 | c.Exit() 222 | } 223 | c.hbC.Cancel() 224 | c.hbC = tw.After(c.config.ClientHeartbeatDuration) 225 | _ = c.EnqueueMessage(messages.NewMessage(0, messages.ActionHeartbeat, nil)) 226 | case msg := <-readChan: 227 | if msg == nil { 228 | closeReason = "readCh closed" 229 | c.Exit() 230 | continue 231 | } 232 | if msg.err != nil { 233 | if messages.IsDecodeError(msg.err) { 234 | _ = c.EnqueueMessage(messages.NewMessage(0, messages.ActionNotifyError, msg.err.Error())) 235 | continue 236 | } 237 | closeReason = msg.err.Error() 238 | c.Exit() 239 | continue 240 | } 241 | if c.info.ID == "" { 242 | closeReason = "client not logged" 243 | c.Exit() 244 | break 245 | } 246 | c.hbLost = 0 247 | c.hbC.Cancel() 248 | c.hbC = tw.After(c.config.ClientHeartbeatDuration) 249 | 250 | if msg.m.GetAction() == messages.ActionHello { 251 | c.handleHello(msg.m) 252 | } else { 253 | c.msgHandler(c.info, msg.m) 254 | } 255 | msg.Recycle() 256 | } 257 | } 258 | STOP: 259 | close(done) 260 | c.hbC.Cancel() 261 | logger.I("read exit, reason=%s", closeReason) 262 | } 263 | 264 | // runWrite message to client. 265 | func (c *UserClient) runWrite() { 266 | defer func() { 267 | err := recover() 268 | if err != nil { 269 | logger.D("write message error, exit client: %v", err) 270 | c.Exit() 271 | } 272 | }() 273 | 274 | var closeReason string 275 | for { 276 | select { 277 | case <-c.closeWriteCh: 278 | if closeReason == "" { 279 | closeReason = "closed initiative" 280 | } 281 | goto STOP 282 | case <-c.hbS.C: 283 | if !c.IsRunning() { 284 | closeReason = "client not running" 285 | goto STOP 286 | } 287 | _ = c.EnqueueMessage(messages.NewMessage(0, messages.ActionHeartbeat, nil)) 288 | c.hbS.Cancel() 289 | c.hbS = tw.After(c.config.ServerHeartbeatDuration) 290 | case m := <-c.messages: 291 | if m == nil { 292 | closeReason = "message is nil, maybe client has closed" 293 | c.Exit() 294 | break 295 | } 296 | c.write2Conn(m) 297 | c.hbS.Cancel() 298 | c.hbS = tw.After(c.config.ServerHeartbeatDuration) 299 | } 300 | } 301 | STOP: 302 | c.hbS.Cancel() 303 | logger.D("write exit, addr=%s, reason:%s", c.info.CliAddr, closeReason) 304 | } 305 | 306 | // Exit client, note: exit client will not close conn right now, but will close when message chan is empty. 307 | // It's close read right now, and close write2Conn when all message in queue is sent. 308 | func (c *UserClient) Exit() { 309 | if atomic.LoadInt32(&c.state) == stateClosed { 310 | return 311 | } 312 | atomic.StoreInt32(&c.state, stateClosed) 313 | 314 | id := c.info.ID 315 | // exit by client self, remove client from manager 316 | if c.mgr != nil && id != "" { 317 | _ = c.mgr.ExitClient(id) 318 | } 319 | c.SetID("") 320 | c.mgr = nil 321 | c.stopReadWrite() 322 | 323 | if c.config.CloseImmediately { 324 | // dropping all message in queue and close connection immediately 325 | c.close() 326 | } else { 327 | // close connection when all message in queue is sent 328 | go func() { 329 | for { 330 | select { 331 | case m := <-c.messages: 332 | c.write2Conn(m) 333 | default: 334 | goto END 335 | } 336 | } 337 | END: 338 | c.close() 339 | }() 340 | } 341 | } 342 | 343 | func (c *UserClient) Run() { 344 | logger.I("new client running addr:%s id:%s", c.conn.GetConnInfo().Addr, c.info.ID) 345 | atomic.StoreInt32(&c.state, stateRunning) 346 | c.closeWriteOnce = sync.Once{} 347 | c.closeReadOnce = sync.Once{} 348 | 349 | go c.runRead() 350 | go c.runWrite() 351 | } 352 | 353 | func (c *UserClient) isClosed() bool { 354 | return atomic.LoadInt32(&c.state) == stateClosed 355 | } 356 | 357 | func (c *UserClient) close() { 358 | close(c.messages) 359 | _ = c.conn.Close() 360 | } 361 | 362 | func (c *UserClient) write2Conn(m *messages.GlideMessage) { 363 | b, err := codec.Encode(m) 364 | if err != nil { 365 | logger.E("serialize output message", err) 366 | return 367 | } 368 | err = c.conn.Write(b) 369 | atomic.AddInt64(&c.queuedMessage, -1) 370 | if err != nil { 371 | logger.D("runWrite error: %s", err.Error()) 372 | c.closeWriteOnce.Do(func() { 373 | close(c.closeWriteCh) 374 | }) 375 | } 376 | } 377 | 378 | func (c *UserClient) stopReadWrite() { 379 | c.closeWriteOnce.Do(func() { 380 | close(c.closeWriteCh) 381 | }) 382 | c.closeReadOnce.Do(func() { 383 | close(c.closeReadCh) 384 | }) 385 | } 386 | 387 | func (c *UserClient) handleHello(m *messages.GlideMessage) { 388 | hello := messages.Hello{} 389 | err := m.Data.Deserialize(&hello) 390 | if err != nil { 391 | _ = c.EnqueueMessage(messages.NewMessage(0, messages.ActionNotifyError, "invalid handleHello message")) 392 | } else { 393 | c.info.Version = hello.ClientVersion 394 | } 395 | } 396 | --------------------------------------------------------------------------------