Forwarding grpc stream messages over websocket - go

I want to continuously feed the websocket via grpc stream(server stream).
I need to terminate the stream when the websocket connection down. But I couldn’t find any way out, unfortunately. I tried to use chan, but it didn't go the way I wanted.
if anyone has any ideas, I’d love to hear them. Thank you.
SERVER:
done := make(chan bool)
go func() {
for {
response, err := req.RequestToServiceWithTrace()
if err != nil {
fmt.Println("Error getting")
}
if err := srv.Send(response); err != nil {
log.Printf("send error %v", err)
done <- true
}
log.Printf("finishing request number : %d", 1)
time.Sleep(1 * time.Second)
}
}()
<-done
CLIENT:
wsupgrader.CheckOrigin = func(r *http.Request) bool { return true }
connWebSocket, err := wsupgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Printf("Failed to set websocket upgrade: %s", err.Error())
return
}
var stream trace.StreamService_GetResponseTimeClient
for {
done := make(chan bool)
t, msg, errWebsocket := connWebSocket.ReadMessage()
if errWebsocket != nil {
fmt.Println(errWebsocket.Error(), "ssssssssssss")
done <- true
}
stream = sada(msg)
fmt.Println("vvvvvvvvv")
go func() {
for {
select {
case _, ok := <-done:
if ok {
fmt.Println("ssscccsssccscscscscscss")
return
} else {
fmt.Println("Channel closed!")
}
default:
resp, err := stream.Recv()
if err != nil {
return
}
log.Printf("Resp received: %s", resp)
jsonBytes, _ := protojson.Marshal(resp)
connWebSocket.WriteMessage(t, jsonBytes)
time.Sleep(1 * time.Second)
}
}
}()
}

Related

Google Cloud Speech infinite streaming recognize in Go does not receive results after restarting the stream

I am trying to implement the equivalent of the InfiniteStreamingRecognize example in Go. This exercise has been implemented in Java, Python and Node.js according to the documentation.
During the first run transcription results are being received fine (with interim results option enabled). Unfortunately, after restarting the stream, no results are received. The new stream instance is configured the same way as the first, I also tried creating a new client from scratch without luck.
Specifically I make sure I wait for the stream to close after calling CloseSend() on it. After that, using the newly created stream I send a new initial message with configuration. If there is any unfinished audio data I send that too before sending any new audio data from my buffer. But my receiver routine stays blocks at stream.Recv() waiting for a response.
Am I missing something? Any help would be appreciated, thanks.
Here is my code (implemented as an echo (web framework) handler listening to web socket connections:
package handlers
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
echo "github.com/labstack/echo/v4"
speech "cloud.google.com/go/speech/apiv1"
speechpb "google.golang.org/genproto/googleapis/cloud/speech/v1"
)
const MAX_STREAM_DURATION = 60000 // seconds
func SpeechToText(c echo.Context) error {
var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
if err != nil {
return err
}
defer ws.Close()
var speechSamplesBuf bytes.Buffer
startTime := time.Now()
restart := make(chan bool)
go recognizeManager(restart, &speechSamplesBuf)
for {
duration := time.Since(startTime)
if duration.Milliseconds() > MAX_STREAM_DURATION {
fmt.Println("M - Duration reached")
startTime = time.Now()
restart <- true
fmt.Println("M - Notified restart channel")
}
_, msg, err := ws.ReadMessage()
if err != nil {
c.Logger().Error(err)
}
n, _ := speechSamplesBuf.Write(msg)
fmt.Printf("M - Read: %d Total: %d\n", n, speechSamplesBuf.Len())
}
}
func createNewClients(cfg speechpb.RecognitionConfig) (*speech.Client, *speechpb.Speech_StreamingRecognizeClient, error) {
ctx := context.Background()
return createNewClientsWithContext(ctx, cfg)
}
func createNewClientsWithContext(ctx context.Context, cfg speechpb.RecognitionConfig) (*speech.Client, *speechpb.Speech_StreamingRecognizeClient, error) {
fmt.Println("RM - Creating new clients")
fmt.Printf("RM - Sleeping for 1 second.. ")
time.Sleep(1 * time.Second)
fmt.Printf("OK\n")
client, err := speech.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
stream, err := client.StreamingRecognize(ctx)
if err != nil {
log.Fatal(err)
}
if err := stream.Send(&speechpb.StreamingRecognizeRequest{
StreamingRequest: &speechpb.StreamingRecognizeRequest_StreamingConfig{
StreamingConfig: &speechpb.StreamingRecognitionConfig{
Config: &cfg,
InterimResults: true,
},
},
}); err != nil {
log.Fatal(err)
}
return client, &stream, nil
}
func createNewStream(cfg speechpb.RecognitionConfig, c *speech.Client) (*speechpb.Speech_StreamingRecognizeClient, error) {
ctx := context.Background()
return createNewStreamWithContext(ctx, cfg, c)
}
func createNewStreamWithContext(ctx context.Context, cfg speechpb.RecognitionConfig, c *speech.Client) (*speechpb.Speech_StreamingRecognizeClient, error) {
fmt.Println("RM - Creating new stream")
fmt.Printf("RM - Sleeping for 1 second.. ")
time.Sleep(1 * time.Second)
fmt.Printf("OK\n")
stream, err := c.StreamingRecognize(ctx)
if err != nil {
log.Fatal(err)
}
if err := stream.Send(&speechpb.StreamingRecognizeRequest{
StreamingRequest: &speechpb.StreamingRecognizeRequest_StreamingConfig{
StreamingConfig: &speechpb.StreamingRecognitionConfig{
Config: &cfg,
InterimResults: true,
},
},
}); err != nil {
log.Fatal(err)
}
return &stream, nil
}
func recognizeManager(restart <-chan bool, buf *bytes.Buffer) {
chs := 2
//restart channels for go routines
var rcs []chan bool
// for i := 0; i < chs; i++ {
// rcs[i] = make(chan bool)
// }
fmt.Println("RM - Declared restart channels for go routines")
cfg := speechpb.RecognitionConfig{
Encoding: speechpb.RecognitionConfig_WEBM_OPUS,
SampleRateHertz: 48000,
LanguageCode: "el-GR"}
// ctx := context.Background()
var client *speech.Client
var stream *speechpb.Speech_StreamingRecognizeClient
var err error
ctx := context.Background()
client, err = speech.NewClient(ctx)
if err != nil {
log.Fatal(err)
}
lastAudioSentBuf := new(bytes.Buffer)
audioSentBuf := new(bytes.Buffer)
finalRequestEndTime := new(int64) // in ms
//var bridgingOffset int64 // in ms
var bytesFromMs int
wg := new(sync.WaitGroup)
started := false
for {
// fmt.Printf("B%d ", buf.Len())
select {
case <-restart:
fmt.Println("RM - Restart message received")
for i := 0; i < len(rcs); i++ {
rcs[i] <- true
}
wg.Wait()
fmt.Println("RM - Go routines have finished")
fmt.Println("RM - finalRequestEndTime:", finalRequestEndTime)
fmt.Println("RM - *finalRequestEndTime:", *finalRequestEndTime)
// err := stream.CloseSend()
// if err != nil {
// log.Fatal(err)
// }
rate := audioSentBuf.Len() / MAX_STREAM_DURATION
lastAudioSentBuf.Write(audioSentBuf.Bytes())
audioSentBuf.Reset()
finalOffsetInWindow := *finalRequestEndTime % MAX_STREAM_DURATION
fmt.Printf("finalOffsetInWindow = %v MOD %v = %v\n", *finalRequestEndTime, MAX_STREAM_DURATION, finalOffsetInWindow)
bytesFromMs = rate * int(finalOffsetInWindow)
fmt.Println("bytesFromMs:", bytesFromMs)
// fmt.Println("RM - Stream was closed")
finalRequestEndTime = new(int64)
started = false
default:
if started {
// fmt.Println("RM - Already started, sleeping 2 secs")
continue
}
fmt.Println("RM - Not started, need to start right now")
// if client != nil {
// client.Close()
// }
if stream != nil {
(*stream).CloseSend()
}
stream, err = createNewStream(cfg, client)
if err != nil {
log.Fatal(err)
}
//restart channels for go routines
rcs = make([]chan bool, chs)
for i := 0; i < chs; i++ {
rcs[i] = make(chan bool)
}
wg = new(sync.WaitGroup)
// sender
wg.Add(1)
go startSend(rcs[0], buf, audioSentBuf, lastAudioSentBuf, bytesFromMs, stream, wg)
// receiver
wg.Add(1)
go startReceive(rcs[1], finalRequestEndTime, stream, wg)
fmt.Println("RM - Started bootstrap go routine (start)")
started = true
}
}
}
func startSend(rc chan bool, messages *bytes.Buffer, audioSentBuf *bytes.Buffer, lastAudioSentBuf *bytes.Buffer, bytesFromMs int, stream *speechpb.Speech_StreamingRecognizeClient, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("S - Start")
fmt.Println("S - Using stream:", stream)
// fmt.Printf("%s [send] go() \n", mytime())
// begin := false
buf := make([]byte, 1024)
if lastAudioSentBuf.Len() > 0 {
var b bytes.Buffer
b.Write(lastAudioSentBuf.Bytes()[bytesFromMs:])
lastAudioSentBuf.Reset()
fmt.Printf("S - Resending %db \n", b.Len())
loop:
for {
n, err := b.Read(buf)
if n > 0 {
fmt.Printf("S - Resending %db (partial)\n", len(buf[:n]))
if err := (*stream).Send(&speechpb.StreamingRecognizeRequest{
StreamingRequest: &speechpb.StreamingRecognizeRequest_AudioContent{
AudioContent: buf[:n],
},
}); err != nil {
log.Printf("Could not resend audio: %v", err)
} else {
}
}
if err == io.EOF {
// Nothing else to pipe, move on to new audio.
break loop
// Nothing else to pipe
}
if err != nil {
log.Printf("Could not read from messages buffer: %v", err)
break loop
}
}
}
for {
select {
case <-rc:
// fmt.Printf("%s [send] go() - return \n", mytime())
if err := (*stream).CloseSend(); err != nil {
log.Fatalf("Could not close stream: %v", err)
}
fmt.Println("S - Stopping")
return
default:
// fmt.Println("S - Continue")
// // fmt.Printf("%s [send] continue \n", mytime())
}
if messages.Len() == 0 {
// fmt.Printf("0")
time.Sleep(1 * time.Second)
continue
}
n, err := messages.Read(buf)
// // fmt.Printf("%s [send] (reading %db / %db) \n", mytime(), n, messages.Len())
// fmt.Println("sending stream:", &stream)
if n > 0 {
// t = &newT
// fmt.Printf("%s [send] sending %db \n", mytime(), len(buf[:n]))
fmt.Printf("S - Sending %db \n", len(buf[:n]))
if err := (*stream).Send(&speechpb.StreamingRecognizeRequest{
StreamingRequest: &speechpb.StreamingRecognizeRequest_AudioContent{
AudioContent: buf[:n],
},
}); err != nil {
log.Printf("Could not send audio: %v", err)
} else {
fmt.Printf("S - Sent %db (to %v)\n", len(buf[:n]), stream)
audioSentBuf.Write(buf)
fmt.Printf("S - Total Audio Sent %db \n", audioSentBuf.Len())
// fmt.Printf("S - Sent %db \n", len(buf[:n]))
// if !begin {
// begin = true
// wg.Add(1)
// // fmt.Printf("%s [send] starting receiver\n", mytime())
// go startReceive(rc[1], stream, wg)
// }
// fmt.Printf("%s [send] sent %db \n", mytime(), len(buf[:n]))
}
}
if err == io.EOF {
// Nothing else to pipe, close the stream.
if err := (*stream).CloseSend(); err != nil {
log.Fatalf("Could not close stream: %v", err)
}
return
// Nothing else to pipe
// fmt.Printf("%s [send] nothing else to pipe \n", mytime())
// continue
}
if err != nil {
log.Printf("Could not read from messages buffer: %v", err)
continue
}
}
}
func startReceive(rc <-chan bool, fret *int64, stream *speechpb.Speech_StreamingRecognizeClient, wg *sync.WaitGroup) {
var finalRequestEndTime int64
defer wg.Done()
fmt.Println("R - Start")
fmt.Println("R - Using stream:", stream)
// fmt.Printf("%s [receive] go() \n", mytime())
for {
select {
case <-rc:
fmt.Println("R - Stopping")
*fret = finalRequestEndTime
fmt.Println("R - &fret:", fret)
// fmt.Printf("%s [receive] go() - return \n", mytime())
return
default:
// fmt.Println("R - Continue")
// fmt.Printf("%s [receive] continue \n", mytime())
}
// fmt.Printf("%s [receive] receiving..\n", mytime())
// fmt.Println("receiving stream:", &stream)
fmt.Println("R - Waiting to receive...")
resp, err := (*stream).Recv()
fmt.Printf("R - Received (from %v)\n", stream)
if err == io.EOF {
fmt.Printf("e\n")
// // fmt.Printf("%s [receive]: breaking loop\n", mytime())
// break
continue
}
if err != nil {
log.Fatalf("Cannot stream results: %v", err)
}
if err := resp.Error; err != nil {
log.Fatalf("Could not recognize: %v", err)
}
fmt.Printf("R - Received %d results\n", len(resp.Results))
for _, result := range resp.Results {
if result.IsFinal {
finalRequestEndTime = result.ResultEndTime.AsDuration().Milliseconds()
fmt.Println("R - finalRequestEndtime:", finalRequestEndTime)
fmt.Println("R - fret:", fret)
}
fmt.Printf("Result: %+v\n", result)
// textBuf.Read([]byte(result.Alternatives[0].Transcript))
// textBuf.Read([]byte(" "))
}
}
}
func mytime() string {
return time.Now().Format("15:04:05")
}
Thanks again in advance

correct websocket connection closure

I wrote a connection closure function. It sends a closing frame and expects the same in response.
func TryCloseNormally(wsConn *websocket.Conn) error {
closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
defer wsConn.Close()
if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
return err
}
if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
return err
}
_, _, err := wsConn.ReadMessage()
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
return nil
} else {
return errors.New("Websocket doesn't send a close frame in response")
}
}
I wrote a test for this function.
func TestTryCloseNormally(t *testing.T) {
done := make(chan struct{})
exit := make(chan struct{})
ctx := context.Background()
ln, err := net.Listen("tcp", "localhost:")
require.Nil(t, err)
handler := HandlerFunc(func(conn *websocket.Conn) {
for {
_, _, err := conn.ReadMessage()
if err != nil {
require.True(t, websocket.IsCloseError(err, websocket.CloseNormalClosure), err.Error())
return
}
}
})
s, err := makeServer(ctx, handler)
require.Nil(t, err)
go func() {
require.Nil(t, s.Run(ctx, exit, ln))
close(done)
}()
wsConn, _, err := websocket.DefaultDialer.Dial(addr+strconv.Itoa(ln.Addr().(*net.TCPAddr).Port), nil) //nolint:bodyclose
require.Nil(t, err)
require.Nil(t, wsConn.WriteMessage(websocket.BinaryMessage, []byte{'o', 'k'}))
require.Nil(t, TryCloseNormally(wsConn))
close(exit)
<-done
}
To my surprise, it works correctly. Readmessage() reads the closing frame. But in the test, I don't write anything.
Is this happening at the gorilla/websocket level?
Did I write the function correctly? Maybe reading the response frame also happens at the gorilla level.
The function is mostly correct.
Websocket endpoints echo close messages unless the endpoint has already send a close message on its own. See Closing Handshake in the Websocket RFC for more details.
In the normal close scenario, an application should expect to receive a close message after sending a close message.
To handle the case where the peer sent a data message before the sending the close message, read and discard data messages until an error is returned.
func TryCloseNormally(wsConn *websocket.Conn) error {
defer wsConn.Close()
closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
return err
}
if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
return err
}
for {
_, _, err := wsConn.ReadMessage()
if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
return nil
}
if err != nil {
return err
}
}
}

redigo error log: write: connection reset by peer

Almost the same amount of time (point in time as redigo error log: write: connection reset by peer?), redis error log:
Client id=45183 addr=127.0.0.1:40420 fd=39 name= age=39706 idle=46 flags=N db=0 sub=8 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=16114 oll=528 omem=8545237 events=rw cmd=ping scheduled to be closed ASAP for overcoming of output buffer limits.
go error log
write tcp 127.0.0.1:40806->127.0.0.1:6379: write: connection reset by peer
Before that, the Go program didn't receive the subscription message for about 7 minutes. I presume it was a cache overflow caused by messages not being consumed.
The Redis client-output-buffer-limit is the default configuration.
The linux fd and connection count are normal, and I can't find of a reason for the unconsumable.
Here is my code:
server.go
func WaitFroMsg(ctx context.Context, pool *redis.Pool, onMessage func(channel string, data []byte) error, channel ...string) (err error) {
conn := pool.Get()
psc := redis.PubSubConn{Conn: conn}
if err := psc.Subscribe(redis.Args{}.AddFlat(channel)...); err != nil {
return err
}
done := make(chan error, 1)
go func() {
for {
switch n := psc.Receive().(type) {
case error:
done <- fmt.Errorf("redis pubsub receive err: %v", n)
return
case redis.Message:
if err = onMessage(n.Channel, n.Data); err != nil {
done <- err
return
}
case redis.Subscription:
if n.Count == 0 {
fmt.Println("all channels are unsubscribed", channel)
done <- nil
return
}
}
}
}()
const healthCheck = time.Minute
ticker := time.NewTicker(healthCheck)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err = psc.Ping(""); err != nil {
fmt.Println("healthCheck ", err, channel)
return err
}
case err := <-done:
return err
case <-ctx.Done():
if err := psc.Unsubscribe(); err != nil {
return fmt.Errorf("redis unsubscribe failed: %v", err)
}
return nil
}
}
}
pool.go
func NewPool(addr string, db int) *redis.Pool {
return &redis.Pool{
MaxIdle: 3,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", addr)
if err != nil {
return nil, err
}
if _, err = c.Do("SELECT", db); err != nil {
c.Close()
return nil, err
}
return c, nil
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
if time.Since(t) < time.Minute {
return nil
}
_, err := c.Do("PING")
fmt.Println("PING error", err)
return err
},
}
}

How I can close the connection by timeout if the client doesn't respond in 10 seconds?

I have code (I use https://github.com/fiorix/go-smpp):
// -----------------------------------------------
// handleConnection new clients.
// -----------------------------------------------
func (_srv *ServerSmpp) handleConnection(_cfg *ConfigSmpp, c *conn) {
defer c.Close()
if err := _srv.auth(_cfg, c); err != nil {
if err != io.EOF {
log.Printf("smpp_server: server auth failed: %s\n", err)
}
return
}
notify := make(chan error)
go func() {
for {
pb, err := c.Read()
if err != nil {
notify <- err
return
}
err = _srv.Handler(_srv.RemoteProvider, c, pb)
if err != nil {
fmt.Printf("%s\n", err)
notify <- err
return
}
}
}()
for {
select {
case err:= <-notify:
if io.EOF == err {
fmt.Printf("Smpp server (read): %s\n", err)
return
}
case <-time.After(time.Second * 10):
fmt.Printf("Client disconnected by timeout.\n")
return
}
}
}
Code for invoked handleConnection:
func (_srv *ServerSmpp) Serve(_cfg *ConfigSmpp) {
for {
client, err := _srv.NetListener.Accept()
if err != nil {
break
}
c := newConn(client)
go _srv.handleConnection(_cfg, c)
}
}
When this code work, the server disconnects all clients by timeout 10 sec, but how I can disconnect the client when it's doesn't work 10 sec?
Your client object seems to be a net.Conn,
choose a way to call client.SetReadDeadline() with the appropriate time.Time value before blocking on client.Read() :
c.client.SetDeadline( time.Now().Add(10 * time.Second )
pb, err := c.Read() { ...

Closing a redis subscription and ending the go routine when websocket connection closes

I'm pushing events from a redis subscription to a client who is connected via websocket. I'm having trouble unsubscribing and exiting the redis go routine when the client disconnects the websocket.
Inspired by this post, here's what I have thus far. I'm able to receive subscription events and send messages to the client via websocket, but when the client closes the websocket and the defer close(done) code fires, my case b, ok := <-done: doesn't fire. It seems to be overloaded by the default case???
package api
import (
...
"github.com/garyburd/redigo/redis"
"github.com/gorilla/websocket"
)
func wsHandler(w http.ResponseWriter, r *http.Request) {
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
HandleError(w, err)
return
}
defer conn.Close()
done := make(chan bool)
defer close(done)
for {
var req WSRequest
err := conn.ReadJSON(&req)
if err != nil {
HandleWSError(conn, err)
return
}
defer conn.Close()
go func(done chan bool, req *WSRequest, conn *websocket.Conn) {
rc := redisPool.Get()
defer rc.Close()
psc := redis.PubSubConn{Conn: rc}
if err := psc.PSubscribe(req.chanName); err != nil {
HandleWSError(conn, err)
return
}
defer psc.PUnsubscribe()
for {
select {
case b, ok := <-done:
if !ok || b == true {
return
}
default:
switch v := psc.Receive().(type) {
case redis.PMessage:
err := handler(conn, req, v)
if err != nil {
HandleWSError(conn, err)
}
case redis.Subscription:
log.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
case error:
log.Printf("error in redis subscription; err:\n%v\n", v)
HandleWSError(conn, v)
default:
// do nothing...
log.Printf("unknown redis subscription event type; %s\n", reflect.TypeOf(v))
}
}
}
}(done, &req, conn)
}
}
Make these changes to break out of the read loop when done serving the websocket connection:
Maintain a slice of the Redis connections created for this websocket connection.
Unsubscribe all connections when done.
Modify the read loop to return when the subscription count is zero.
Here's the code:
func wsHandler(w http.ResponseWriter, r *http.Request) {
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
HandleError(w, err)
return
}
defer conn.Close()
// Keep slice of all connections. Unsubscribe all connections on exit.
var pscs []redis.PubSubConn
defer func() {
for _, psc := range rcs {
psc.Unsubscribe() // unsubscribe with no args unsubs all channels
}
}()
for {
var req WSRequest
err := conn.ReadJSON(&req)
if err != nil {
HandleWSError(conn, err)
return
}
rc := redisPool.Get()
psc := redis.PubSubConn{Conn: rc}
pscs = append(pscs, psc)
if err := psc.PSubscribe(req.chanName); err != nil {
HandleWSError(conn, err)
return
}
go func(req *WSRequest, conn *websocket.Conn) {
defer rc.Close()
for {
switch v := psc.Receive().(type) {
case redis.PMessage:
err := handler(conn, req, v)
if err != nil {
HandleWSError(conn, err)
}
case redis.Subscription:
log.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
if v.Count == 0 {
return
}
case error:
log.Printf("error in redis subscription; err:\n%v\n", v)
HandleWSError(conn, v)
default:
// do nothing...
log.Printf("unknown redis subscription event type; %s\n", reflect.TypeOf(v))
}
}
}(&req, conn)
}
}
The code in the question and this answer dial multiple Redis connections for each websocket client. A more typical and scalable approach is to share a single Redis pubsub connection across multiple clients. The typical approach may be appropriate for your application given the high-level description, but I am still unsure of what you are trying to do given the code in the question.

Resources