|
@@ -0,0 +1,215 @@
|
|
|
|
|
+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
|
|
|
|
|
+}
|