| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- package swagger_api
- import (
- "bytes"
- "compress/gzip"
- "context"
- "errors"
- "fmt"
- "io"
- "sort"
- "sync"
- "github.com/go-kratos/kratos/v2/api/metadata"
- "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"
- dpb "google.golang.org/protobuf/types/descriptorpb"
- "github.com/go-kratos/kratos/v2/log"
- )
- // Server is api meta server
- type Server struct {
- metadata.UnimplementedMetadataServer
- srv *grpc.Server
- lock sync.Mutex
- services map[string]*dpb.FileDescriptorSet
- methods map[string][]string
- SkipError bool
- }
- // NewServer create server instance
- func NewServer(srv *grpc.Server, skipError bool) *Server {
- return &Server{
- srv: srv,
- SkipError: skipError,
- services: make(map[string]*dpb.FileDescriptorSet),
- methods: make(map[string][]string),
- }
- }
- func (s *Server) load() error {
- if len(s.services) > 0 {
- return nil
- }
- if s.srv != nil {
- for name, info := range s.srv.GetServiceInfo() {
- fd, err := s.parseMetadata(info.Metadata)
- if err != nil {
- return fmt.Errorf("invalid service %s metadata err:%v", name, err)
- }
- protoSet, err := s.allDependency(fd)
- if err != nil {
- return err
- }
- s.services[name] = &dpb.FileDescriptorSet{File: protoSet}
- for _, method := range info.Methods {
- s.methods[name] = append(s.methods[name], method.Name)
- }
- }
- return nil
- }
- var err error
- protoregistry.GlobalFiles.RangeFiles(func(fd protoreflect.FileDescriptor) bool {
- if fd.Services() == nil {
- return true
- }
- for i := 0; i < fd.Services().Len(); i++ {
- svc := fd.Services().Get(i)
- fdp, e := fileDescriptorProto(fd.Path())
- if e != nil {
- if s.SkipError {
- continue
- }
- err = e
- return false
- }
- fdps, e := s.allDependency(fdp)
- if e != nil {
- if errors.Is(e, protoregistry.NotFound) {
- // Skip this service if one of its dependencies is not found.
- continue
- }
- err = e
- return false
- }
- s.services[string(svc.FullName())] = &dpb.FileDescriptorSet{File: fdps}
- if svc.Methods() == nil {
- continue
- }
- for j := 0; j < svc.Methods().Len(); j++ {
- method := svc.Methods().Get(j)
- s.methods[string(svc.FullName())] = append(s.methods[string(svc.FullName())], string(method.Name()))
- }
- }
- return true
- })
- return err
- }
- // ListServices return all services
- func (s *Server) ListServices(_ context.Context, _ *metadata.ListServicesRequest) (*metadata.ListServicesReply, error) {
- s.lock.Lock()
- defer s.lock.Unlock()
- if err := s.load(); err != nil {
- return nil, err
- }
- reply := &metadata.ListServicesReply{
- Services: make([]string, 0, len(s.services)),
- Methods: make([]string, 0, len(s.methods)),
- }
- for name := range s.services {
- reply.Services = append(reply.Services, name)
- }
- for name, methods := range s.methods {
- for _, method := range methods {
- reply.Methods = append(reply.Methods, fmt.Sprintf("/%s/%s", name, method))
- }
- }
- sort.Strings(reply.Services)
- sort.Strings(reply.Methods)
- return reply, nil
- }
- // GetServiceDesc return service meta by name
- func (s *Server) GetServiceDesc(_ context.Context, in *metadata.GetServiceDescRequest) (*metadata.GetServiceDescReply, error) {
- s.lock.Lock()
- defer s.lock.Unlock()
- if err := s.load(); err != nil {
- return nil, err
- }
- fds, ok := s.services[in.Name]
- if !ok {
- return nil, status.Errorf(codes.NotFound, "service %s not found", in.Name)
- }
- return &metadata.GetServiceDescReply{FileDescSet: fds}, nil
- }
- // parseMetadata finds the file descriptor bytes specified meta.
- // For SupportPackageIsVersion4, m is the name of the proto file, we
- // call proto.FileDescriptor to get the byte slice.
- // For SupportPackageIsVersion3, m is a byte slice itself.
- func (s *Server) parseMetadata(meta interface{}) (*dpb.FileDescriptorProto, error) {
- // Check if meta is the file name.
- if fileNameForMeta, ok := meta.(string); ok {
- return fileDescriptorProto(fileNameForMeta)
- }
- // Check if meta is the byte slice.
- if enc, ok := meta.([]byte); ok {
- return s.decodeFileDesc(enc)
- }
- return nil, fmt.Errorf("proto not sumpport metadata: %v", meta)
- }
- // decodeFileDesc does decompression and unmarshalling on the given
- // file descriptor byte slice.
- func (s *Server) decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) {
- raw, err := s.decompress(enc)
- if err != nil {
- return nil, fmt.Errorf("failed to decompress enc: %v", err)
- }
- fd := new(dpb.FileDescriptorProto)
- if err := proto.Unmarshal(raw, fd); err != nil {
- return nil, fmt.Errorf("bad descriptor: %v", err)
- }
- return fd, nil
- }
- func (s *Server) allDependency(fd *dpb.FileDescriptorProto) ([]*dpb.FileDescriptorProto, error) {
- var files []*dpb.FileDescriptorProto
- for _, dep := range fd.Dependency {
- fdDep, err := fileDescriptorProto(dep)
- if err != nil {
- if !s.SkipError {
- log.Warnf("%s", err)
- }
- continue
- }
- temp, err := s.allDependency(fdDep)
- if err != nil {
- return nil, err
- }
- files = append(files, temp...)
- }
- files = append(files, fd)
- return files, nil
- }
- // decompress does gzip decompression.
- func (s *Server) decompress(b []byte) ([]byte, error) {
- r, err := gzip.NewReader(bytes.NewReader(b))
- if err != nil {
- return nil, fmt.Errorf("bad gzipped descriptor: %v", err)
- }
- out, err := io.ReadAll(r)
- if err != nil {
- return nil, fmt.Errorf("bad gzipped descriptor: %v", err)
- }
- return out, nil
- }
- func fileDescriptorProto(path string) (*dpb.FileDescriptorProto, error) {
- fd, err := protoregistry.GlobalFiles.FindFileByPath(path)
- if err != nil {
- return nil, fmt.Errorf("find proto by path failed, path: %s, err: %s", path, err)
- }
- fdpb := protodesc.ToFileDescriptorProto(fd)
- return fdpb, nil
- }
|