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 }