Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
26 Jun 2025 · 6 min read ·Article 18 / 110
Go

18 Reading Metadata on a gRPC Server

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

gRPC has become one of the most popular service-to-service communication frameworks in the cloud-native era. Thanks to its high performance and support for many programming languages, gRPC is widely adopted across large-scale projects. One of gRPC’s powerful yet often underrated features is metadata. Metadata is a set of key-value pairs that can be sent alongside every request or response in an RPC, similar to HTTP headers but more tightly integrated at the gRPC protocol level.

In this article, we’ll take a thorough look at how to read metadata on the gRPC server side, using an implementation example in Go (Golang). We’ll also walk through hands-on examples and simulations, tables, and flow diagrams to make the concepts easier to grasp.


Getting to Know Metadata in gRPC

Before diving into the implementation, let’s first understand the concept of metadata in gRPC. Metadata lets us carry additional information such as authentication tokens, request IDs, custom context, and so on—information that isn’t rigidly tied to the main data protocol (the protobuf message).

On HTTP/2 (which is the primary transport for gRPC), metadata is typically translated into headers. In gRPC, however, both the client and the server have dedicated APIs for interacting with metadata.

In general, metadata consists of key-value pairs like the following:

KeyValueDescription
authorizationBearer xxxxxAuthorization token
client-id12345Unique client ID
custom-datasome-informationCustom data as needed


Flow Diagram for Reading Metadata on a gRPC Server

Processing metadata on the gRPC server side can be illustrated as follows:

MERMAID
flowchart TD
    A[Client] -- Kirim RPC + Metadata --> B[gRPC Server]
    B -- Interceptor/Handler Baca Metadata --> C[Business Logic]
    C -- Response + Metadata Opsional --> A

In the flow above, when the client sends a request, the metadata is sent along with it. The server can read that metadata either in a regular handler or through an interceptor for cross-cutting concerns such as authentication or logging.


Metadata Structure in Go

In Go, the main package for working with metadata is google.golang.org/grpc/metadata.

Here are its most important functions:

  • md, ok := metadata.FromIncomingContext(ctx)
    Retrieves the metadata sent by the client from the context.
  • metadata.Pairs(“key”, “value”)
    Creates new metadata for outbound use.

Reading Metadata in the Server Handler

Let’s build a simple service that can read the metadata sent by the client.

Service Definition (Protobuf)

protobuf
 1syntax = "proto3";
 2
 3package hello;
 4
 5service Greeter {
 6   rpc SayHello (HelloRequest) returns (HelloResponse);
 7}
 8
 9message HelloRequest {
10    string name = 1;
11}
12
13message HelloResponse {
14    string message = 1;
15}

Server Implementation in Go

The core of reading metadata can be done inside each individual RPC handler.

go
 1import (
 2    "context"
 3    "fmt"
 4    "google.golang.org/grpc"
 5    "google.golang.org/grpc/metadata"
 6    pb "path/to/your/proto"
 7)
 8
 9type server struct {
10    pb.UnimplementedGreeterServer
11}
12
13func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
14    // Read metadata from the context
15    md, ok := metadata.FromIncomingContext(ctx)
16    if !ok {
17        fmt.Println("No metadata was sent")
18    } else {
19        // Print all received metadata
20        for k, v := range md {
21            fmt.Printf("Metadata %s: %v\n", k, v)
22        }
23    }
24
25    // Get a specific value
26    clientIds := md.Get("client-id")
27    if len(clientIds) > 0 {
28        fmt.Println("Client ID:", clientIds[0])
29    }
30
31    return &pb.HelloResponse{
32        Message: fmt.Sprintf("Hello, %s!", req.Name),
33    }, nil
34}

In the example above, the SayHello handler reads all the metadata sent by the client along with its values.


Simulation: Running the Server and Sending Metadata

Let’s simulate a client that sends metadata.

Example Client

go
 1import (
 2    "context"
 3    "log"
 4    "google.golang.org/grpc"
 5    "google.golang.org/grpc/metadata"
 6    pb "path/to/your/proto"
 7    "time"
 8)
 9
10func main() {
11    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
12    if err != nil {
13        log.Fatalf("failed to connect: %v", err)
14    }
15    defer conn.Close()
16
17    c := pb.NewGreeterClient(conn)
18    md := metadata.New(map[string]string{
19        "client-id":   "abc-123",
20        "custom-data": "example-info",
21    })
22    ctx := metadata.NewOutgoingContext(context.Background(), md)
23
24    resp, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Budi"})
25    if err != nil {
26        log.Fatalf("Error during request: %v", err)
27    }
28    log.Printf("Server reply: %s", resp.Message)
29}

Output on the Server

bash
1Metadata client-id: [abc-123]
2Metadata custom-data: [example-info]
3Client ID: abc-123

The server successfully captured the metadata sent by the client.


Using an Interceptor for Metadata

At larger scale, reading metadata is usually done in an interceptor—middleware that can run logic outside the business core, such as authentication or audit logging.

Example Unary Interceptor in Go

go
 1func metadataInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
 2    md, ok := metadata.FromIncomingContext(ctx)
 3    if ok {
 4        if tokens := md.Get("authorization"); len(tokens) > 0 {
 5            fmt.Println("Auth token:", tokens[0])
 6        }
 7    }
 8    // Continue to the main handler
 9    return handler(ctx, req)
10}
11
12// When setting up the server
13grpc.NewServer(
14    grpc.UnaryInterceptor(metadataInterceptor),
15)

When to use an interceptor:

  • Validating session tokens across every gRPC method
  • Audit logging of metadata
  • Monitoring and tracing

Table: Comparing Approaches to Reading Metadata

ApproachAdvantageDisadvantageUse Case
In the HandlerLives together with business logicRedundant across multiple methodsUse cases specific to a single RPC
In the InterceptorDRY, implemented once globallyNo per-RPC customizationAuthentication, logging for all RPCs
CombinationFlexible, choose as neededMay require coordinationCross-cutting + per-RPC custom data parsing

Best Practices

  1. Prefix custom metadata with a unique identifier to avoid collisions (e.g., x-myapp-userid).
  2. Avoid metadata for large payloads—use it for small data like tokens, trace IDs, and so on.
  3. Case insensitive – Metadata keys are not case sensitive, but a lower-case convention is preferable.
  4. Use interceptors for cross-cutting concerns, and handlers for per-method needs.

Conclusion

Reading metadata on a gRPC server isn’t just about pulling key-value pairs from a context—it’s the foundation for building services that are secure, maintainable, and scalable. By understanding how metadata works and the best practices for using it, we can build applications that are more modular, easier to extend, and more secure.

Don’t hesitate to explore the metadata feature more deeply, for example to implement rate limiting, distributed tracing, or an integrated monitoring system within your microservices ecosystem!


References


Danger
Happy experimenting with gRPC metadata, for the sake of better system security and observability!

Related Articles

💬 Comments