Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to identify a disconnected client in gRPC?

Tags:

go

grpc

grpc-go

I am building an API using gRPC and in server side, I want to receive a notification when a client disconnects, identify it and perform some tasks based on that.

So far, I was able to detect client disconnection using grpc.StatsHandler method HandleConn. I tried passing values using context, but they can't be accessed from server side.

Client side:

conn, err := grpc.DialContext(
    context.WithValue(context.Background(), "user_id", 1234),
    address,
    grpc.WithInsecure(),
)

Server side:

// Build stats handler
type serverStats struct {}

func (h *serverStats) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
    return ctx
}

func (h *serverStats) HandleRPC(ctx context.Context, s stats.RPCStats) {}

func (h *serverStats) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
    return context.TODO()
}

func (h *serverStats) HandleConn(ctx context.Context, s stats.ConnStats) {
    fmt.Println(ctx.Value("user_id")) // Returns nil, can't access the value

    switch s.(type) {
    case *stats.ConnEnd:
        fmt.Println("client disconnected")
        break
    }
}


// Build server
s := grpc.NewServer(grpc.StatsHandler(&serverStats{}))

I want to access the value passed from client side in server side. What is the right way to do it, or is there any other way to identify the client that has disconnected?

like image 427
Gustavs Rācenājs Avatar asked May 02 '19 09:05

Gustavs Rācenājs


1 Answers

I don't think you may pass that value when you are dialling, but you may tag the connection and than all next client requests will have the same value in its context:

In your serverStats implementation:

func (h *serverStats) TagConn(ctx context.Context, info *stats.ConnTagInfo) context.Context {
    return context.WithValue(ctx, "user_counter", userCounter++)
}

In your service implementation

func (s *service) SetUserId(ctx context.Context, v MyMessage) (r MyResponse, err Error) {

   s.userIdMap[ctx.Value("user_counter")] = v.userId
...

}

In the other methods:


func (s *service) AnotherMethod(ctx context.Context, v MyMessage) (r MyResponse, err Error) {

   userId := s.userIdMap[ctx.Value("user_counter")]
...

}
func (h *serverStats) HandleConn(ctx context.Context, s stats.ConnStats) {

    switch s.(type) {
    case *stats.ConnEnd:
        fmt.Printf("client %d disconnected", s.userIdMap[ctx.Value("user_counter")])
        break
    }
}

Please let me know if you find a way to pass values in the dialling step.

like image 58
rubens21 Avatar answered Sep 19 '22 16:09

rubens21