How to identify a disconnected client in 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?

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.

I tried passing values using context, but they can't be accessed from server side.
You need to set metadata fields to the client context explicitly:
ctx := context.Background()
ctx = metadata.AppendToOutgoingContext(ctx, "user_id", "1234")
conn, err := grpc.DialContext(cxt, address)
On server side you can retrieve them like this:
md, ok := metadata.FromIncomingContext(ctx)
What is the right way to do it, or is there any other way to identify the client that has disconnected?
This really depends on your use case. I think that GRPC Stats API is good for some simple tasks (for example, to compute latency or aggregate network stats), but it's not very useful when some business logic should work when client leaves. I would suggest using defer calls in GRPC handlers directly for that. One more option is to implement custom GRPC interceptor.

Related

opentelemetry golang always give all 0 for traceID and spanID

I am totally new to the distributed world and trying to use open telemetry for distributed tracing. For phase 1, just trying to have a request id (traceID / spanID) going, so we can co-relate the request logs. Have not yet decided on collector/ exporter.
My span/ trace id is always zero, in below middleware code
Is it because I have not initialized the tracer with all the exporter/ collector etc?
func AddSpanId(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
values := []interface{}{}
spanID, traceID := getRequestIDs(r)
values = append(values, "spanID", spanID)
values = append(values, "traceID", traceID)
//code to wrap values into context
}
func getRequestIDs(r *http.Request) (string, string) {
_, span := otel.Tracer("somename").Start(r.Context(), "handler")
fmt.Print(span.SpanContext().SpanID().String())
return span.SpanContext().SpanID().String(), span.SpanContext().TraceID().String()
}
You need to instrument your http handler:
import (
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
// Package-level tracer.
// This should be configured in your code setup instead of here.
var tracer = otel.Tracer("github.com/full/path/to/mypkg")
func main() {
// Wrap your httpHandler function.
handler := http.HandlerFunc(httpHandler)
wrappedHandler := otelhttp.NewHandler(handler, "hello-instrumented")
http.Handle("/hello-instrumented", wrappedHandler)
// And start the HTTP serve.
log.Fatal(http.ListenAndServe(":3030", nil))
}
https://opentelemetry.io/docs/instrumentation/go/libraries/
You also rarely need to work directly with traceID and spanID. Starting Span API is typical building block to instrument the code.
_, span := otel.Tracer(name).Start(ctx, "Poll")
defer span.End()

Access information about the request and response payloads in grpc-go's stat/HandleRPC

I am using stats/HandleRPC() to emit some metrics about the RPC duration, when I receive the stats/End data, and I want to tag the metrics with some information that can be extracted from the incoming and outgoing payloads. What would be the best way to achieve this?
func (h *myStatsHandler) HandleRPC(ctx context.Context, rpcStats stats.RPCStats) {
switch stat := rpcStats.(type) {
case *stats.End:
durationMs := stat.EndTime.Sub(stat.BeginTime).Seconds() * 1000.0
// Now before sending this value, I need to know, for example the value of a specific key in the request payload, or whether the response is nil or not
}
}
In your implementation of TagRPC, you can create a struct and add a pointer to it to the context. Then add information in it over the successive calls to HandleRPC. So if you need something from the Payload that's only available in the *stats.InPayload invocation, you can pull it out and store it in the struct you added to the context, and then access it later when HandleRPC is called again with *stats.End
type recorderCtxKey struct{}
type recorder struct {
size int64
}
func (sl *statsHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo) context.Context {
return context.WithValue(ctx, rpcStatCtxKey{}, &recorder{})
}
func (h *statsHandler) HandleRPC(ctx context.Context, rpcStats stats.RPCStats) {
switch stat := rpcStats.(type) {
case *stats.InPayload:
r, _ := ctx.Value(recorderContextKey{}).(*Recorder)
r.size += stat.WireLength
case *stats.End:
durationMs := stat.EndTime.Sub(stat.BeginTime).Seconds() * 1000.0
r, _ := ctx.Value(recorderContextKey{}).(*Recorder)
# use r.size #
}
}

Record and Persist API call details in KrakenD for API monetization

We are trying out KrakenD as a primary API gateway for our backend services. The plugins available are only for response manipulation, we want to go a step ahead and start recording all the API calls and persist them in a database. I was checking the example of having a custom HTTPStatusHandler, but that mainly caters to handling the status codes and as a best case we can only return the status codes from our backends as it is. Is there any working example of this scenario that we are trying? We are already using a custom middleware to grant API access based on our role based access rules.
Other commercial solutions offer API monetization out of the box, but we want to stick with KrakenD for performance reasons.
KrakenD plugins are not only for response manipulation, but a much more powerful solution. There are 4 types of plugins in krakend, what you want to do I think that fits best as an HTTP SERVER plugin.
This type of plugin intercepts all incoming traffic and you can record it in a Redis database.
I have improvised a main.go of the idea, but you should check the plugin guide in the documentation for more details.
Hope this helps illustrating the idea
package main
import (
"context"
"errors"
"fmt"
"html"
"net/http"
)
// HandlerRegisterer is the symbol the plugin loader will try to load. It must implement the Registerer interface
var HandlerRegisterer = registerer("krakend-api-monetization")
type registerer string
var logger Logger = nil
func (registerer) RegisterLogger(v interface{}) {
l, ok := v.(Logger)
if !ok {
return
}
logger = l
logger.Debug(fmt.Sprintf("[PLUGIN: %s] Logger loaded", HandlerRegisterer))
}
func (r registerer) RegisterHandlers(f func(
name string,
handler func(context.Context, map[string]interface{}, http.Handler) (http.Handler, error),
)) {
f(string(r), r.registerHandlers)
}
func (r registerer) registerHandlers(_ context.Context, extra map[string]interface{}, h http.Handler) (http.Handler, error) {
// check the passed configuration and initialize the plugin
name, ok := extra["name"].([]interface{})
if !ok {
return nil, errors.New("wrong config")
}
if v, ok := name[0].(string); !ok || v != string(r) {
return nil, fmt.Errorf("unknown register %s", name)
}
// check the cfg. If the modifier requires some configuration,
// it should be under the name of the plugin. E.g.:
/*
"extra_config":{
"plugin/http-server":{
"name":["krakend-api-monetization"],
"krakend-api-monetization":{
"redis_host": "localhost:6379"
}
}
}
*/
// The config variable contains all the keys you hace defined in the configuration:
config, _ := extra["krakend-api-monetization"].(map[string]interface{})
// The plugin will save activity in this host:
redis_host, _ := config["redis_host"].(string)
logger.Debug(fmt.Sprintf("The plugin is now storing on %s", redis_host))
// return the actual handler wrapping or your custom logic so it can be used as a replacement for the default http handler
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// TODO: Save in Redis the request
rdb := redis.NewClient(&redis.Options{
Addr: redis_host,
Password: "", // no password set
DB: 0, // use default DB
})
// etc...
logger.Debug("Request saved:", html.EscapeString(req.URL.Path))
}), nil
}
func main() {}
type Logger interface {
Debug(v ...interface{})
Info(v ...interface{})
Warning(v ...interface{})
Error(v ...interface{})
Critical(v ...interface{})
Fatal(v ...interface{})
}

Tracing golang http request

I want to trace complete execution of an HTTP request in golang. For any non trivial operation the request will eventually call many different functions. I would like to have logs from the entire execution stack tagged with a unique request id. e.g.
http.Handle("/my-request", myrequesthandler)
func myrequestHandler(w http.ResponseWriter, r *http.Request) {
//debug print1
log.Printf("....")
myfunc()
}
func myfunc() {
//debug print2
log.Printf("....")
}
Here I need a way to identify print1 and print2 as part of same request. It looks like zerolog does have something like this, as described here. Like so:
....
c = c.Append(hlog.RequestIDHandler("req_id", "Request-Id"))
h := c.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hlog.FromRequest(r).Info().
Msg("Something happened")
}))
http.Handle("/", h)
But if I understand it right, it will involve passing request object to each and every function. Is this the idiomatic way to solve this problem? What are the alternatives?
Set a unique id on a context.Context as soon as the request is received, and pass that context down the call stack. This is what contexts are for.
[Context] carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.
Example:
// You could place this in a separate helper package to improve encapsulation
type ctxKey struct{}
func myRequestHandler(w http.ResponseWriter, r *http.Request) {
uniqueID := // generate a unique identifier
// create a context with the http.Request context as parent
ctx := context.WithValue(r.Context(), ctxKey{}, uniqueID)
foo(ctx, ...)
bar(ctx, ...)
}
func foo(ctx context.Context, ...) {
uniqueID := ctx.Value(ctxKey{})
// do something with the unique id
baz(ctx, ...)
}
In particular:
Create the context with *http.Request.Context() as parent. This way if the request is canceled, e.g. due to client disconnection, cancellation will propagate to your sub-context
Consider setting the context value using an unexported struct as key. Unexported structs defined in your package will never conflict with other keys. If you use strings as keys instead, any package could in theory use the same key and overwrite your value (or you could overwrite others' values). YMMV.
Pass your context as the first argument of any function in your call stack, as the package documentation recommends.
For tracing and logging across applications, you might want to look into opentracing. Propagation of tracers is still done with Contexts as outlined above.
you can use context.Context and set request id on it via middleware function.
example:
type requestIDKey struct{}
func requestIDSetter(next http.HandlerFunc) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
// use provided request id from incoming request if any
reqID := r.Header.Get("X-Request-ID")
if reqID == "" {
// or use some generated string
reqID = uuid.New().String()
}
ctx := context.WithValue(r.Context(), requestIDKey{}, reqID)
next(rw, r.WithContext(ctx))
}
}
then you need to modify your logger to accept context.Context
example:
func printfWithContext(ctx context.Context, format string, v ...interface{}) {
reqID := ctx.Value(requestIDKey{}).(string)
log.Printf(reqID+": "+format, v...)
}
and finally apply to your code
http.HandleFunc("/my-request", requestIDSetter(myrequestHandler))
func myrequestHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
//debug print1
printfWithContext(ctx, "....1")
myfunc(ctx)
}
func myfunc(ctx context.Context) {
//debug print2
printfWithContext(ctx, "....2")
}

How to create a service type layer that will hold my db and redis connections

I am trying to prototype this little golang app and hoping to get some advice on how to go about managing my database and redis connection objects.
I want to create a a 'service layer' that will have all the product related logic, so maybe a ProductService.
I want ProductService to have a reference to both redis and my database client.
What would this ProductService look like roughly, and if I need to create a single instance of this and use it throughout the app do I define it in a var?
func main() {
db, err := gorm.Open("postgres", "host=localhost user=blankman dbname=blank_development sslmode=disable password=")
if err != nil {
log.Fatalf("Got error when connect database, the error is '%v'", err)
}
defer db.Close()
redis := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
pong, err := redis.Ping().Result()
fmt.Println(pong, err)
router := mux.NewRouter()
router.HandleFunc("/products", GetProducts).Methods("GET")
log.Fatal(http.ListenAndServe(":3001", router))
}
My GetProducts handler has your regular signature:
func GetProducts(w http.ResponseWriter, r *http.Request)
How am I suppose to pass in the ProductsService into this handler? It looks like the request/response are somehow automatically passed to this handler by MUX, so unsure how it can get a reference to the ProductService?
Create the product service with the fields you need:
type ProductService struct {
db *gorm.DB
redis *redis.Client
}
Make GetProducts a method of ProductService:
func (s *ProductService) GetProducts(w http.ResponseWriter, r *http.Request) {
// s.db is the database, s.redis is the redis client
}
Initialize ProductService in main. Use method values as handler functions:
s := &ProductService{db: db, redis: redis}
router.HandleFunc("/products", s.GetProducts).Methods("GET")
An alternative to the method value is to use a closure to adapt a function to a handler function:
func (s *ProductService) Handler(fn func(*ProductService, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fn(s, w, r)
}
}
Use
router.HandleFunc("/products", s.Handler(PostPoduct)).Methods("POST")
to register a function like this:
func PostProduct(s *ProductService, w http.ResponseWriter, r *http.Request) {
}
With this second approach, the ProductService layer can be kept separate from the individual handlers.
Another option is to avoid a struct for the service and build your handlers using a closure (with a function that closes over your dependencies) like this:
func MakeGetProductsHander(db *gorm.DB, client *redis.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Use db and client in here...
}
}
Then apply the handler like this:
router.HandleFunc("/products", MakeGetProductsHander(db, client)).Methods("GET")
This injects your dependencies into your handlers in a more explicit and obvious way.
Side note: rename your Redis client variable to client or something else since you are shadowing the package name by calling it redis.

Resources