/* * * Copyright 2016 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* Package reflection implements server reflection service. The service implemented is defined in: https://github.com/grpc/grpc/blob/master/src/proto/grpc/reflection/v1alpha/reflection.proto. To register server reflection on a gRPC server: import "google.golang.org/grpc/reflection" s := grpc.NewServer() pb.RegisterYourOwnServer(s, &server{}) // Register reflection service on gRPC server. reflection.Register(s) s.Serve(lis) */ package reflection // import "google.golang.org/grpc/reflection" import ( "io" "sort" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" v1reflectiongrpc "google.golang.org/grpc/reflection/grpc_reflection_v1" v1reflectionpb "google.golang.org/grpc/reflection/grpc_reflection_v1" v1alphareflectiongrpc "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" ) // GRPCServer is the interface provided by a gRPC server. It is implemented by // *grpc.Server, but could also be implemented by other concrete types. It acts // as a registry, for accumulating the services exposed by the server. type GRPCServer interface { grpc.ServiceRegistrar ServiceInfoProvider } var _ GRPCServer = (*grpc.Server)(nil) // Register registers the server reflection service on the given gRPC server. // Both the v1 and v1alpha versions are registered. func Register(s GRPCServer) { svr := NewServerV1(ServerOptions{Services: s}) v1alphareflectiongrpc.RegisterServerReflectionServer(s, asV1Alpha(svr)) v1reflectiongrpc.RegisterServerReflectionServer(s, svr) } // RegisterV1 registers only the v1 version of the server reflection service // on the given gRPC server. Many clients may only support v1alpha so most // users should use Register instead, at least until clients have upgraded. func RegisterV1(s GRPCServer) { svr := NewServerV1(ServerOptions{Services: s}) v1reflectiongrpc.RegisterServerReflectionServer(s, svr) } // ServiceInfoProvider is an interface used to retrieve metadata about the // services to expose. // // The reflection service is only interested in the service names, but the // signature is this way so that *grpc.Server implements it. So it is okay // for a custom implementation to return zero values for the // grpc.ServiceInfo values in the map. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type ServiceInfoProvider interface { GetServiceInfo() map[string]grpc.ServiceInfo } // ExtensionResolver is the interface used to query details about extensions. // This interface is satisfied by protoregistry.GlobalTypes. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type ExtensionResolver interface { protoregistry.ExtensionTypeResolver RangeExtensionsByMessage(message protoreflect.FullName, f func(protoreflect.ExtensionType) bool) } // ServerOptions represents the options used to construct a reflection server. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type ServerOptions struct { // The source of advertised RPC services. If not specified, the reflection // server will report an empty list when asked to list services. // // This value will typically be a *grpc.Server. But the set of advertised // services can be customized by wrapping a *grpc.Server or using an // alternate implementation that returns a custom set of service names. Services ServiceInfoProvider // Optional resolver used to load descriptors. If not specified, // protoregistry.GlobalFiles will be used. DescriptorResolver protodesc.Resolver // Optional resolver used to query for known extensions. If not specified, // protoregistry.GlobalTypes will be used. ExtensionResolver ExtensionResolver } // NewServer returns a reflection server implementation using the given options. // This can be used to customize behavior of the reflection service. Most usages // should prefer to use Register instead. For backwards compatibility reasons, // this returns the v1alpha version of the reflection server. For a v1 version // of the reflection server, see NewServerV1. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func NewServer(opts ServerOptions) v1alphareflectiongrpc.ServerReflectionServer { return asV1Alpha(NewServerV1(opts)) } // NewServerV1 returns a reflection server implementation using the given options. // This can be used to customize behavior of the reflection service. Most usages // should prefer to use Register instead. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func NewServerV1(opts ServerOptions) v1reflectiongrpc.ServerReflectionServer { if opts.DescriptorResolver == nil { opts.DescriptorResolver = protoregistry.GlobalFiles } if opts.ExtensionResolver == nil { opts.ExtensionResolver = protoregistry.GlobalTypes } return &serverReflectionServer{ s: opts.Services, descResolver: opts.DescriptorResolver, extResolver: opts.ExtensionResolver, } } type serverReflectionServer struct { v1alphareflectiongrpc.UnimplementedServerReflectionServer s ServiceInfoProvider descResolver protodesc.Resolver extResolver ExtensionResolver } // fileDescWithDependencies returns a slice of serialized fileDescriptors in // wire format ([]byte). The fileDescriptors will include fd and all the // transitive dependencies of fd with names not in sentFileDescriptors. func (s *serverReflectionServer) fileDescWithDependencies(fd protoreflect.FileDescriptor, sentFileDescriptors map[string]bool) ([][]byte, error) { var r [][]byte queue := []protoreflect.FileDescriptor{fd} for len(queue) > 0 { currentfd := queue[0] queue = queue[1:] if sent := sentFileDescriptors[currentfd.Path()]; len(r) == 0 || !sent { sentFileDescriptors[currentfd.Path()] = true fdProto := protodesc.ToFileDescriptorProto(currentfd) currentfdEncoded, err := proto.Marshal(fdProto) if err != nil { return nil, err } r = append(r, currentfdEncoded) } for i := 0; i < currentfd.Imports().Len(); i++ { queue = append(queue, currentfd.Imports().Get(i)) } } return r, nil } // fileDescEncodingContainingSymbol finds the file descriptor containing the // given symbol, finds all of its previously unsent transitive dependencies, // does marshalling on them, and returns the marshalled result. The given symbol // can be a type, a service or a method. func (s *serverReflectionServer) fileDescEncodingContainingSymbol(name string, sentFileDescriptors map[string]bool) ([][]byte, error) { d, err := s.descResolver.FindDescriptorByName(protoreflect.FullName(name)) if err != nil { return nil, err } return s.fileDescWithDependencies(d.ParentFile(), sentFileDescriptors) } // fileDescEncodingContainingExtension finds the file descriptor containing // given extension, finds all of its previously unsent transitive dependencies, // does marshalling on them, and returns the marshalled result. func (s *serverReflectionServer) fileDescEncodingContainingExtension(typeName string, extNum int32, sentFileDescriptors map[string]bool) ([][]byte, error) { xt, err := s.extResolver.FindExtensionByNumber(protoreflect.FullName(typeName), protoreflect.FieldNumber(extNum)) if err != nil { return nil, err } return s.fileDescWithDependencies(xt.TypeDescriptor().ParentFile(), sentFileDescriptors) } // allExtensionNumbersForTypeName returns all extension numbers for the given type. func (s *serverReflectionServer) allExtensionNumbersForTypeName(name string) ([]int32, error) { var numbers []int32 s.extResolver.RangeExtensionsByMessage(protoreflect.FullName(name), func(xt protoreflect.ExtensionType) bool { numbers = append(numbers, int32(xt.TypeDescriptor().Number())) return true }) sort.Slice(numbers, func(i, j int) bool { return numbers[i] < numbers[j] }) if len(numbers) == 0 { // maybe return an error if given type name is not known if _, err := s.descResolver.FindDescriptorByName(protoreflect.FullName(name)); err != nil { return nil, err } } return numbers, nil } // listServices returns the names of services this server exposes. func (s *serverReflectionServer) listServices() []*v1reflectionpb.ServiceResponse { serviceInfo := s.s.GetServiceInfo() resp := make([]*v1reflectionpb.ServiceResponse, 0, len(serviceInfo)) for svc := range serviceInfo { resp = append(resp, &v1reflectionpb.ServiceResponse{Name: svc}) } sort.Slice(resp, func(i, j int) bool { return resp[i].Name < resp[j].Name }) return resp } // ServerReflectionInfo is the reflection service handler. func (s *serverReflectionServer) ServerReflectionInfo(stream v1reflectiongrpc.ServerReflection_ServerReflectionInfoServer) error { sentFileDescriptors := make(map[string]bool) for { in, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return err } out := &v1reflectionpb.ServerReflectionResponse{ ValidHost: in.Host, OriginalRequest: in, } switch req := in.MessageRequest.(type) { case *v1reflectionpb.ServerReflectionRequest_FileByFilename: var b [][]byte fd, err := s.descResolver.FindFileByPath(req.FileByFilename) if err == nil { b, err = s.fileDescWithDependencies(fd, sentFileDescriptors) } if err != nil { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_ErrorResponse{ ErrorResponse: &v1reflectionpb.ErrorResponse{ ErrorCode: int32(codes.NotFound), ErrorMessage: err.Error(), }, } } else { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_FileDescriptorResponse{ FileDescriptorResponse: &v1reflectionpb.FileDescriptorResponse{FileDescriptorProto: b}, } } case *v1reflectionpb.ServerReflectionRequest_FileContainingSymbol: b, err := s.fileDescEncodingContainingSymbol(req.FileContainingSymbol, sentFileDescriptors) if err != nil { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_ErrorResponse{ ErrorResponse: &v1reflectionpb.ErrorResponse{ ErrorCode: int32(codes.NotFound), ErrorMessage: err.Error(), }, } } else { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_FileDescriptorResponse{ FileDescriptorResponse: &v1reflectionpb.FileDescriptorResponse{FileDescriptorProto: b}, } } case *v1reflectionpb.ServerReflectionRequest_FileContainingExtension: typeName := req.FileContainingExtension.ContainingType extNum := req.FileContainingExtension.ExtensionNumber b, err := s.fileDescEncodingContainingExtension(typeName, extNum, sentFileDescriptors) if err != nil { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_ErrorResponse{ ErrorResponse: &v1reflectionpb.ErrorResponse{ ErrorCode: int32(codes.NotFound), ErrorMessage: err.Error(), }, } } else { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_FileDescriptorResponse{ FileDescriptorResponse: &v1reflectionpb.FileDescriptorResponse{FileDescriptorProto: b}, } } case *v1reflectionpb.ServerReflectionRequest_AllExtensionNumbersOfType: extNums, err := s.allExtensionNumbersForTypeName(req.AllExtensionNumbersOfType) if err != nil { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_ErrorResponse{ ErrorResponse: &v1reflectionpb.ErrorResponse{ ErrorCode: int32(codes.NotFound), ErrorMessage: err.Error(), }, } } else { out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_AllExtensionNumbersResponse{ AllExtensionNumbersResponse: &v1reflectionpb.ExtensionNumberResponse{ BaseTypeName: req.AllExtensionNumbersOfType, ExtensionNumber: extNums, }, } } case *v1reflectionpb.ServerReflectionRequest_ListServices: out.MessageResponse = &v1reflectionpb.ServerReflectionResponse_ListServicesResponse{ ListServicesResponse: &v1reflectionpb.ListServiceResponse{ Service: s.listServices(), }, } default: return status.Errorf(codes.InvalidArgument, "invalid MessageRequest: %v", in.MessageRequest) } if err := stream.Send(out); err != nil { return err } } }