or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin.mdadvanced.mdclient-server.mdcredentials-security.mderrors-status.mdhealth.mdindex.mdinterceptors.mdload-balancing.mdmetadata-context.mdname-resolution.mdobservability.mdreflection.mdstreaming.mdtesting.mdxds.md
tile.json

reflection.mddocs/

Server Reflection

This document covers server reflection in gRPC-Go, which allows clients to query a server for information about its RPC services and their protocol buffer definitions.

Overview

The reflection package implements the gRPC server reflection protocol, enabling clients to discover services and message types at runtime without accessing .proto files.

import "google.golang.org/grpc/reflection"

Specification: gRPC Server Reflection Protocol

Basic Usage

Register Reflection

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

// Create server
server := grpc.NewServer()

// Register your services
pb.RegisterMyServiceServer(server, &myServiceImpl{})

// Register reflection service (registers both v1 and v1alpha)
reflection.Register(server)

// Start serving
lis, _ := net.Listen("tcp", ":50051")
server.Serve(lis)

Registration Functions

// Register registers both v1 and v1alpha versions of server reflection
func Register(s GRPCServer)

// RegisterV1 registers only v1 version (clients may not support v1)
func RegisterV1(s GRPCServer)

Custom Reflection Server

type ServerOptions struct {
    // Services is source of advertised RPC services
    // Typically a *grpc.Server
    Services ServiceInfoProvider

    // DescriptorResolver loads descriptors
    // Defaults to protoregistry.GlobalFiles
    DescriptorResolver protodesc.Resolver

    // ExtensionResolver queries for known extensions
    // Defaults to protoregistry.GlobalTypes
    ExtensionResolver ExtensionResolver
}

// NewServer returns v1alpha reflection server with custom options
// Experimental
func NewServer(opts ServerOptions) v1alphareflectiongrpc.ServerReflectionServer

// NewServerV1 returns v1 reflection server with custom options
// Experimental
func NewServerV1(opts ServerOptions) v1reflectiongrpc.ServerReflectionServer

Example with custom options:

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    "google.golang.org/protobuf/reflect/protoregistry"
)

server := grpc.NewServer()
pb.RegisterMyServiceServer(server, &myServiceImpl{})

// Create custom reflection server
reflectionServer := reflection.NewServerV1(reflection.ServerOptions{
    Services:          server,
    DescriptorResolver: protoregistry.GlobalFiles,
    ExtensionResolver: protoregistry.GlobalTypes,
})

// Register manually
v1reflectiongrpc.RegisterServerReflectionServer(server, reflectionServer)

lis, _ := net.Listen("tcp", ":50051")
server.Serve(lis)

Interfaces

GRPCServer

type GRPCServer interface {
    grpc.ServiceRegistrar
    ServiceInfoProvider
}

Implemented by *grpc.Server but can be implemented by custom types.

ServiceInfoProvider

type ServiceInfoProvider interface {
    // GetServiceInfo returns metadata about services
    GetServiceInfo() map[string]grpc.ServiceInfo
}

ExtensionResolver

type ExtensionResolver interface {
    protoregistry.ExtensionTypeResolver
    RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
}

Satisfied by protoregistry.GlobalTypes.

Client Usage

Using grpcurl

# List services
grpcurl -plaintext localhost:50051 list

# List methods of a service
grpcurl -plaintext localhost:50051 list mypackage.MyService

# Describe a service
grpcurl -plaintext localhost:50051 describe mypackage.MyService

# Describe a method
grpcurl -plaintext localhost:50051 describe mypackage.MyService.MyMethod

# Call a method
grpcurl -plaintext -d '{"name": "world"}' localhost:50051 mypackage.MyService.SayHello

Using grpc_cli

# List services
grpc_cli ls localhost:50051

# List methods
grpc_cli ls localhost:50051 mypackage.MyService

# Get type information
grpc_cli type localhost:50051 mypackage.MyRequest

# Call method
grpc_cli call localhost:50051 mypackage.MyService.SayHello "name: 'world'"

Programmatic Client

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    reflectionpb "google.golang.org/grpc/reflection/grpc_reflection_v1"
)

conn, err := grpc.NewClient("localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

client := reflectionpb.NewServerReflectionClient(conn)

stream, err := client.ServerReflectionInfo(context.Background())
if err != nil {
    log.Fatal(err)
}

// List services
err = stream.Send(&reflectionpb.ServerReflectionRequest{
    MessageRequest: &reflectionpb.ServerReflectionRequest_ListServices{
        ListServices: "",
    },
})
if err != nil {
    log.Fatal(err)
}

resp, err := stream.Recv()
if err != nil {
    log.Fatal(err)
}

listResp := resp.GetListServicesResponse()
for _, svc := range listResp.Service {
    fmt.Printf("Service: %s\n", svc.Name)
}

Best Practices

Security

  1. Disable in production: Consider disabling reflection in production for security
  2. Authentication: Use interceptors to restrict reflection access
  3. Internal only: Expose reflection only on internal networks

Usage

  1. Enable during development: Useful for development and debugging
  2. Register after services: Register reflection after all services are registered
  3. Use v1 and v1alpha: Register both versions for compatibility

Example with authentication

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/reflection"
    "google.golang.org/grpc/status"
)

// Interceptor to protect reflection
func reflectionAuthInterceptor(
    srv any,
    ss grpc.ServerStream,
    info *grpc.StreamServerInfo,
    handler grpc.StreamHandler,
) error {
    // Check if this is a reflection call
    if info.FullMethod == "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo" ||
       info.FullMethod == "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo" {
        // Validate auth token
        md, ok := metadata.FromIncomingContext(ss.Context())
        if !ok {
            return status.Error(codes.Unauthenticated, "missing metadata")
        }

        token := md.Get("auth-token")
        if len(token) == 0 || token[0] != "secret" {
            return status.Error(codes.PermissionDenied, "invalid token")
        }
    }

    return handler(srv, ss)
}

// Use interceptor
server := grpc.NewServer(
    grpc.ChainStreamInterceptor(reflectionAuthInterceptor))

pb.RegisterMyServiceServer(server, &myServiceImpl{})
reflection.Register(server)

Conditional registration

import (
    "os"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

server := grpc.NewServer()
pb.RegisterMyServiceServer(server, &myServiceImpl{})

// Only enable reflection in non-production
if os.Getenv("ENVIRONMENT") != "production" {
    reflection.Register(server)
}

lis, _ := net.Listen("tcp", ":50051")
server.Serve(lis)

Testing

import (
    "context"
    "testing"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    "google.golang.org/grpc/reflection"
    reflectionpb "google.golang.org/grpc/reflection/grpc_reflection_v1"
    "google.golang.org/grpc/test/bufconn"
)

func TestReflection(t *testing.T) {
    lis := bufconn.Listen(1024 * 1024)
    server := grpc.NewServer()
    defer server.Stop()

    // Register service and reflection
    pb.RegisterMyServiceServer(server, &myServiceImpl{})
    reflection.Register(server)

    go server.Serve(lis)

    // Create client
    conn, err := grpc.NewClient("bufnet",
        grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
            return lis.DialContext(ctx)
        }),
        grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        t.Fatal(err)
    }
    defer conn.Close()

    // Test reflection
    client := reflectionpb.NewServerReflectionClient(conn)
    stream, err := client.ServerReflectionInfo(context.Background())
    if err != nil {
        t.Fatal(err)
    }

    // List services
    stream.Send(&reflectionpb.ServerReflectionRequest{
        MessageRequest: &reflectionpb.ServerReflectionRequest_ListServices{},
    })

    resp, err := stream.Recv()
    if err != nil {
        t.Fatal(err)
    }

    services := resp.GetListServicesResponse().Service
    if len(services) == 0 {
        t.Error("No services found")
    }
}