storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/rpc/map.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Copyright 2012 The Gorilla Authors. All rights reserved.
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  // Copyright 2020 MinIO, Inc. All rights reserved.
     7  // forked from https://github.com/gorilla/rpc/v2
     8  // modified to be used with MinIO under Apache
     9  // 2.0 license that can be found in the LICENSE file.
    10  
    11  package rpc
    12  
    13  import (
    14  	"fmt"
    15  	"net/http"
    16  	"reflect"
    17  	"strings"
    18  	"sync"
    19  	"unicode"
    20  	"unicode/utf8"
    21  )
    22  
    23  var (
    24  	// Precompute the reflect.Type of error and http.Request
    25  	typeOfError   = reflect.TypeOf((*error)(nil)).Elem()
    26  	typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
    27  )
    28  
    29  // ----------------------------------------------------------------------------
    30  // service
    31  // ----------------------------------------------------------------------------
    32  
    33  type service struct {
    34  	name     string                    // name of service
    35  	rcvr     reflect.Value             // receiver of methods for the service
    36  	rcvrType reflect.Type              // type of the receiver
    37  	methods  map[string]*serviceMethod // registered methods
    38  }
    39  
    40  type serviceMethod struct {
    41  	method    reflect.Method // receiver method
    42  	argsType  reflect.Type   // type of the request argument
    43  	replyType reflect.Type   // type of the response argument
    44  }
    45  
    46  // ----------------------------------------------------------------------------
    47  // serviceMap
    48  // ----------------------------------------------------------------------------
    49  
    50  // serviceMap is a registry for services.
    51  type serviceMap struct {
    52  	mutex    sync.Mutex
    53  	services map[string]*service
    54  }
    55  
    56  // register adds a new service using reflection to extract its methods.
    57  func (m *serviceMap) register(rcvr interface{}, name string) error {
    58  	// Setup service.
    59  	s := &service{
    60  		name:     name,
    61  		rcvr:     reflect.ValueOf(rcvr),
    62  		rcvrType: reflect.TypeOf(rcvr),
    63  		methods:  make(map[string]*serviceMethod),
    64  	}
    65  	if name == "" {
    66  		s.name = reflect.Indirect(s.rcvr).Type().Name()
    67  		if !isExported(s.name) {
    68  			return fmt.Errorf("rpc: type %q is not exported", s.name)
    69  		}
    70  	}
    71  	if s.name == "" {
    72  		return fmt.Errorf("rpc: no service name for type %q",
    73  			s.rcvrType.String())
    74  	}
    75  	// Setup methods.
    76  	for i := 0; i < s.rcvrType.NumMethod(); i++ {
    77  		method := s.rcvrType.Method(i)
    78  		mtype := method.Type
    79  		// Method must be exported.
    80  		if method.PkgPath != "" {
    81  			continue
    82  		}
    83  		// Method needs four ins: receiver, *http.Request, *args, *reply.
    84  		if mtype.NumIn() != 4 {
    85  			continue
    86  		}
    87  		// First argument must be a pointer and must be http.Request.
    88  		reqType := mtype.In(1)
    89  		if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
    90  			continue
    91  		}
    92  		// Second argument must be a pointer and must be exported.
    93  		args := mtype.In(2)
    94  		if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
    95  			continue
    96  		}
    97  		// Third argument must be a pointer and must be exported.
    98  		reply := mtype.In(3)
    99  		if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
   100  			continue
   101  		}
   102  		// Method needs one out: error.
   103  		if mtype.NumOut() != 1 {
   104  			continue
   105  		}
   106  		if returnType := mtype.Out(0); returnType != typeOfError {
   107  			continue
   108  		}
   109  		s.methods[method.Name] = &serviceMethod{
   110  			method:    method,
   111  			argsType:  args.Elem(),
   112  			replyType: reply.Elem(),
   113  		}
   114  	}
   115  	if len(s.methods) == 0 {
   116  		return fmt.Errorf("rpc: %q has no exported methods of suitable type",
   117  			s.name)
   118  	}
   119  	// Add to the map.
   120  	m.mutex.Lock()
   121  	defer m.mutex.Unlock()
   122  	if m.services == nil {
   123  		m.services = make(map[string]*service)
   124  	} else if _, ok := m.services[s.name]; ok {
   125  		return fmt.Errorf("rpc: service already defined: %q", s.name)
   126  	}
   127  	m.services[s.name] = s
   128  	return nil
   129  }
   130  
   131  // get returns a registered service given a method name.
   132  //
   133  // The method name uses a dotted notation as in "Service.Method".
   134  func (m *serviceMap) get(method string) (*service, *serviceMethod, error) {
   135  	parts := strings.Split(method, ".")
   136  	if len(parts) != 2 {
   137  		err := fmt.Errorf("rpc: service/method request ill-formed: %q", method)
   138  		return nil, nil, err
   139  	}
   140  	m.mutex.Lock()
   141  	service := m.services[parts[0]]
   142  	m.mutex.Unlock()
   143  	if service == nil {
   144  		err := fmt.Errorf("rpc: can't find service %q", method)
   145  		return nil, nil, err
   146  	}
   147  	serviceMethod := service.methods[parts[1]]
   148  	if serviceMethod == nil {
   149  		err := fmt.Errorf("rpc: can't find method %q", method)
   150  		return nil, nil, err
   151  	}
   152  	return service, serviceMethod, nil
   153  }
   154  
   155  // isExported returns true of a string is an exported (upper case) name.
   156  func isExported(name string) bool {
   157  	rune, _ := utf8.DecodeRuneInString(name)
   158  	return unicode.IsUpper(rune)
   159  }
   160  
   161  // isExportedOrBuiltin returns true if a type is exported or a builtin.
   162  func isExportedOrBuiltin(t reflect.Type) bool {
   163  	for t.Kind() == reflect.Ptr {
   164  		t = t.Elem()
   165  	}
   166  	// PkgPath will be non-empty even for an exported type,
   167  	// so we need to check the type name as well.
   168  	return isExported(t.Name()) || t.PkgPath() == ""
   169  }