github.com/oarkflow/sio@v0.0.6/adapters/redis.go (about) 1 package adapters 2 3 /* 4 5 import ( 6 "context" 7 "encoding/hex" 8 "log/slog" 9 10 "github.com/redis/go-redis/v9" 11 12 "github.com/oarkflow/sio" 13 ) 14 15 var rng *sio.RNG 16 17 func init() { 18 rng = sio.NewRNG() 19 } 20 21 const ( 22 // DefServerGroup the default redis.PubSub channel that will be subscribed to 23 DefServerGroup = "ss-rmhb-group-default" 24 ) 25 26 // RedisAdapter implements the sio.Adapter interface and uses 27 // Redis to syncronize between multiple machines running sio.Server 28 type RedisAdapter struct { 29 r *redis.Client 30 o *Options 31 rps *redis.PubSub 32 bps *redis.PubSub 33 ctx context.Context 34 roomPSName string 35 bcastPSName string 36 } 37 38 type Options struct { 39 40 //ServerName is a unique name for the sio.Adapter instance. 41 //This name must be unique per backend instance or the backend 42 //will not broadcast and roomcast properly. 43 // 44 //Leave this name blank to auto generate a unique name. 45 ServerName string 46 47 //ServerGroup is the server pool name that this instance's broadcasts 48 //and roomcasts will be published to. This can be used to break up 49 //sio.Adapter instances into separate domains. 50 // 51 //Leave this empty to use the default group "ss-rmhb-group-default" 52 ServerGroup string 53 } 54 55 // NewRedisAdapter creates a new *RedisAdapter specified by redis Options and ssredis Options 56 func NewRedisAdapter(ctx context.Context, rOpts *redis.Options, ssrOpts *Options) (*RedisAdapter, error) { 57 rClient := redis.NewClient(rOpts) 58 _, err := rClient.Ping(context.Background()).Result() 59 if err != nil { 60 return nil, err 61 } 62 63 if ssrOpts == nil { 64 ssrOpts = &Options{} 65 } 66 67 if ssrOpts.ServerGroup == "" { 68 ssrOpts.ServerGroup = DefServerGroup 69 } 70 71 if ssrOpts.ServerName == "" { 72 uid := make([]byte, 16) 73 _, err := rng.Read(uid) 74 if err != nil { 75 return nil, err 76 } 77 ssrOpts.ServerName = hex.EncodeToString(uid) 78 } 79 80 roomPSName := ssrOpts.ServerGroup + ":_ss_roomcasts" 81 bcastPSName := ssrOpts.ServerGroup + ":_ss_broadcasts" 82 83 rmhb := &RedisAdapter{ 84 r: rClient, 85 rps: rClient.Subscribe(ctx, roomPSName), 86 bps: rClient.Subscribe(ctx, bcastPSName), 87 roomPSName: roomPSName, 88 bcastPSName: bcastPSName, 89 o: ssrOpts, 90 ctx: ctx, 91 } 92 93 return rmhb, nil 94 } 95 96 // Init is just here to satisfy the sio.Adapter interface. 97 func (r *RedisAdapter) Init() { 98 99 } 100 101 // Shutdown closes the subscribed redis channel, then the redis connection. 102 func (r *RedisAdapter) Shutdown() error { 103 err := r.rps.Close() 104 if err != nil { 105 return err 106 } 107 err = r.bps.Close() 108 if err != nil { 109 return err 110 } 111 return r.r.Close() 112 } 113 114 // BroadcastToBackend will publish a broadcast message to the redis backend 115 func (r *RedisAdapter) BroadcastToBackend(b *sio.BroadcastMsg) { 116 t := &transmission{ 117 ServerName: r.o.ServerName, 118 EventName: b.EventName, 119 Data: b.Data, 120 } 121 122 data, err := t.toJSON() 123 if err != nil { 124 slog.Error(err.Error()) 125 return 126 } 127 128 err = r.r.Publish(context.Background(), r.bcastPSName, string(data)).Err() 129 if err != nil { 130 slog.Error(err.Error()) 131 } 132 } 133 134 // RoomcastToBackend will publish a roomcast message to the redis backend 135 func (r *RedisAdapter) RoomcastToBackend(rm *sio.RoomMsg) { 136 t := &transmission{ 137 ServerName: r.o.ServerName, 138 EventName: rm.EventName, 139 RoomName: rm.RoomName, 140 Data: rm.Data, 141 } 142 143 data, err := t.toJSON() 144 if err != nil { 145 slog.Error(err.Error()) 146 return 147 } 148 149 err = r.r.Publish(context.Background(), r.roomPSName, string(data)).Err() 150 if err != nil { 151 slog.Error(err.Error()) 152 } 153 } 154 155 // BroadcastFromBackend will receive broadcast messages from redis and propogate them to the neccessary sockets 156 func (r *RedisAdapter) BroadcastFromBackend(bc chan<- *sio.BroadcastMsg) { 157 bChan := r.bps.Channel() 158 159 for d := range bChan { 160 var t transmission 161 162 err := t.fromJSON([]byte(d.Payload)) 163 if err != nil { 164 slog.Error(err.Error()) 165 continue 166 } 167 168 if t.ServerName == r.o.ServerName { 169 continue 170 } 171 172 bc <- &sio.BroadcastMsg{ 173 EventName: t.EventName, 174 Data: t.Data, 175 } 176 } 177 } 178 179 // RoomcastFromBackend will receive roomcast messages from redis and propogate them to the neccessary sockets 180 func (r *RedisAdapter) RoomcastFromBackend(rc chan<- *sio.RoomMsg) { 181 rChan := r.rps.Channel() 182 183 for d := range rChan { 184 var t transmission 185 186 err := t.fromJSON([]byte(d.Payload)) 187 if err != nil { 188 slog.Error(err.Error()) 189 continue 190 } 191 192 if t.ServerName == r.o.ServerName { 193 continue 194 } 195 196 rc <- &sio.RoomMsg{ 197 EventName: t.EventName, 198 RoomName: t.RoomName, 199 Data: t.Data, 200 } 201 } 202 } 203 */