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.
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
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)// 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)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.ServerReflectionServerExample 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)type GRPCServer interface {
grpc.ServiceRegistrar
ServiceInfoProvider
}Implemented by *grpc.Server but can be implemented by custom types.
type ServiceInfoProvider interface {
// GetServiceInfo returns metadata about services
GetServiceInfo() map[string]grpc.ServiceInfo
}type ExtensionResolver interface {
protoregistry.ExtensionTypeResolver
RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool)
}Satisfied by protoregistry.GlobalTypes.
# 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# 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'"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)
}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)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)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")
}
}