In this example we are going to attach headers to client requests and server responses. The client will send request headers and the server will respond with headers. In both ends, we will extract headers.


Example


I am not going to show the proto file because it is irrelevant.


Client


package main

import (
"context"
"log"
"strings"
"time"

"pkg/proto/user"

"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

func main() {
log.Println("Client running ...")

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

conn, err := grpc.Dial(":50051", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()

client := user.NewDeleteServiceClient(conn)

request := &user.DeleteRequest{Uuid: "UUID-123"}

// Anything linked to this variable will transmit request headers.
md := metadata.New(map[string]string{"x-request-id": "req-123"})
ctx = metadata.NewOutgoingContext(ctx, md)

// Anything linked to this variable will fetch response headers.
var header metadata.MD

response, err := client.Delete(ctx, request, grpc.Header(&header))
if err != nil {
log.Fatalln(err)
}

xrid := header["x-response-id"]
if len(xrid) == 0 {
log.Fatalln("missing 'x-response-id' header")
}
if strings.Trim(xrid[0], " ") == "" {
log.Fatalln("empty 'x-response-id' header")
}

log.Println(response.GetCode())
log.Println(xrid[0])
}

Server


package main

import (
"context"
"log"
"net"
"strings"

"pkg/proto/user"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

type server struct {
user.UnimplementedDeleteServiceServer
}

func main() {
log.Println("Server running ...")

lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalln(err)
}

srv := grpc.NewServer()
user.RegisterDeleteServiceServer(srv, &server{})

log.Fatalln(srv.Serve(lis))
}

func (s *server) Delete(ctx context.Context, request *user.DeleteRequest) (*user.DeleteResponse, error) {
// Anything linked to this variable will fetch request headers.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
}
xrid := md["x-request-id"]
if len(xrid) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "missing 'x-request-id' header")
}
if strings.Trim(xrid[0], " ") == "" {
return nil, status.Errorf(codes.InvalidArgument, "empty 'x-request-id' header")
}

log.Println(request.GetUuid())
log.Println(xrid[0])

// Anything linked to this variable will transmit response headers.
header := metadata.New(map[string]string{"x-response-id": "res-123"})
if err := grpc.SendHeader(ctx, header); err != nil {
return nil, status.Errorf(codes.Internal, "unable to send 'x-response-id' header")
}

return &user.DeleteResponse{Code: 123}, nil
}

Test


$ go run client/main.go 

2020/04/08 21:50:17 Client running ...
2020/04/08 21:50:17 123
2020/04/08 21:50:17 res-123 # Test with full header
2020/04/08 21:51:32 missing 'x-response-id' header # Test without header
2020/04/08 21:51:32 empty 'x-response-id' header # Test with empty header

$ go run server/main.go 

2020/04/08 21:49:35 Server running ...
2020/04/08 21:49:39 UUID-123
2020/04/08 21:49:39 req-123