github.com/julesgoullee/go-ethereum@v1.9.7/rpc/service.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package rpc 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "reflect" 24 "runtime" 25 "strings" 26 "sync" 27 "unicode" 28 "unicode/utf8" 29 30 "github.com/ethereum/go-ethereum/log" 31 ) 32 33 var ( 34 contextType = reflect.TypeOf((*context.Context)(nil)).Elem() 35 errorType = reflect.TypeOf((*error)(nil)).Elem() 36 subscriptionType = reflect.TypeOf(Subscription{}) 37 stringType = reflect.TypeOf("") 38 ) 39 40 type serviceRegistry struct { 41 mu sync.Mutex 42 services map[string]service 43 } 44 45 // service represents a registered object. 46 type service struct { 47 name string // name for service 48 callbacks map[string]*callback // registered handlers 49 subscriptions map[string]*callback // available subscriptions/notifications 50 } 51 52 // callback is a method callback which was registered in the server 53 type callback struct { 54 fn reflect.Value // the function 55 rcvr reflect.Value // receiver object of method, set if fn is method 56 argTypes []reflect.Type // input argument types 57 hasCtx bool // method's first argument is a context (not included in argTypes) 58 errPos int // err return idx, of -1 when method cannot return error 59 isSubscribe bool // true if this is a subscription callback 60 } 61 62 func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { 63 rcvrVal := reflect.ValueOf(rcvr) 64 if name == "" { 65 return fmt.Errorf("no service name for type %s", rcvrVal.Type().String()) 66 } 67 callbacks := suitableCallbacks(rcvrVal) 68 if len(callbacks) == 0 { 69 return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr) 70 } 71 72 r.mu.Lock() 73 defer r.mu.Unlock() 74 if r.services == nil { 75 r.services = make(map[string]service) 76 } 77 svc, ok := r.services[name] 78 if !ok { 79 svc = service{ 80 name: name, 81 callbacks: make(map[string]*callback), 82 subscriptions: make(map[string]*callback), 83 } 84 r.services[name] = svc 85 } 86 for name, cb := range callbacks { 87 if cb.isSubscribe { 88 svc.subscriptions[name] = cb 89 } else { 90 svc.callbacks[name] = cb 91 } 92 } 93 return nil 94 } 95 96 // callback returns the callback corresponding to the given RPC method name. 97 func (r *serviceRegistry) callback(method string) *callback { 98 elem := strings.SplitN(method, serviceMethodSeparator, 2) 99 if len(elem) != 2 { 100 return nil 101 } 102 r.mu.Lock() 103 defer r.mu.Unlock() 104 return r.services[elem[0]].callbacks[elem[1]] 105 } 106 107 // subscription returns a subscription callback in the given service. 108 func (r *serviceRegistry) subscription(service, name string) *callback { 109 r.mu.Lock() 110 defer r.mu.Unlock() 111 return r.services[service].subscriptions[name] 112 } 113 114 // suitableCallbacks iterates over the methods of the given type. It determines if a method 115 // satisfies the criteria for a RPC callback or a subscription callback and adds it to the 116 // collection of callbacks. See server documentation for a summary of these criteria. 117 func suitableCallbacks(receiver reflect.Value) map[string]*callback { 118 typ := receiver.Type() 119 callbacks := make(map[string]*callback) 120 for m := 0; m < typ.NumMethod(); m++ { 121 method := typ.Method(m) 122 if method.PkgPath != "" { 123 continue // method not exported 124 } 125 cb := newCallback(receiver, method.Func) 126 if cb == nil { 127 continue // function invalid 128 } 129 name := formatName(method.Name) 130 callbacks[name] = cb 131 } 132 return callbacks 133 } 134 135 // newCallback turns fn (a function) into a callback object. It returns nil if the function 136 // is unsuitable as an RPC callback. 137 func newCallback(receiver, fn reflect.Value) *callback { 138 fntype := fn.Type() 139 c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)} 140 // Determine parameter types. They must all be exported or builtin types. 141 c.makeArgTypes() 142 if !allExportedOrBuiltin(c.argTypes) { 143 return nil 144 } 145 // Verify return types. The function must return at most one error 146 // and/or one other non-error value. 147 outs := make([]reflect.Type, fntype.NumOut()) 148 for i := 0; i < fntype.NumOut(); i++ { 149 outs[i] = fntype.Out(i) 150 } 151 if len(outs) > 2 || !allExportedOrBuiltin(outs) { 152 return nil 153 } 154 // If an error is returned, it must be the last returned value. 155 switch { 156 case len(outs) == 1 && isErrorType(outs[0]): 157 c.errPos = 0 158 case len(outs) == 2: 159 if isErrorType(outs[0]) || !isErrorType(outs[1]) { 160 return nil 161 } 162 c.errPos = 1 163 } 164 return c 165 } 166 167 // makeArgTypes composes the argTypes list. 168 func (c *callback) makeArgTypes() { 169 fntype := c.fn.Type() 170 // Skip receiver and context.Context parameter (if present). 171 firstArg := 0 172 if c.rcvr.IsValid() { 173 firstArg++ 174 } 175 if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { 176 c.hasCtx = true 177 firstArg++ 178 } 179 // Add all remaining parameters. 180 c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg) 181 for i := firstArg; i < fntype.NumIn(); i++ { 182 c.argTypes[i-firstArg] = fntype.In(i) 183 } 184 } 185 186 // call invokes the callback. 187 func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) { 188 // Create the argument slice. 189 fullargs := make([]reflect.Value, 0, 2+len(args)) 190 if c.rcvr.IsValid() { 191 fullargs = append(fullargs, c.rcvr) 192 } 193 if c.hasCtx { 194 fullargs = append(fullargs, reflect.ValueOf(ctx)) 195 } 196 fullargs = append(fullargs, args...) 197 198 // Catch panic while running the callback. 199 defer func() { 200 if err := recover(); err != nil { 201 const size = 64 << 10 202 buf := make([]byte, size) 203 buf = buf[:runtime.Stack(buf, false)] 204 log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) 205 errRes = errors.New("method handler crashed") 206 } 207 }() 208 // Run the callback. 209 results := c.fn.Call(fullargs) 210 if len(results) == 0 { 211 return nil, nil 212 } 213 if c.errPos >= 0 && !results[c.errPos].IsNil() { 214 // Method has returned non-nil error value. 215 err := results[c.errPos].Interface().(error) 216 return reflect.Value{}, err 217 } 218 return results[0].Interface(), nil 219 } 220 221 // Is this an exported - upper case - name? 222 func isExported(name string) bool { 223 rune, _ := utf8.DecodeRuneInString(name) 224 return unicode.IsUpper(rune) 225 } 226 227 // Are all those types exported or built-in? 228 func allExportedOrBuiltin(types []reflect.Type) bool { 229 for _, typ := range types { 230 for typ.Kind() == reflect.Ptr { 231 typ = typ.Elem() 232 } 233 // PkgPath will be non-empty even for an exported type, 234 // so we need to check the type name as well. 235 if !isExported(typ.Name()) && typ.PkgPath() != "" { 236 return false 237 } 238 } 239 return true 240 } 241 242 // Is t context.Context or *context.Context? 243 func isContextType(t reflect.Type) bool { 244 for t.Kind() == reflect.Ptr { 245 t = t.Elem() 246 } 247 return t == contextType 248 } 249 250 // Does t satisfy the error interface? 251 func isErrorType(t reflect.Type) bool { 252 for t.Kind() == reflect.Ptr { 253 t = t.Elem() 254 } 255 return t.Implements(errorType) 256 } 257 258 // Is t Subscription or *Subscription? 259 func isSubscriptionType(t reflect.Type) bool { 260 for t.Kind() == reflect.Ptr { 261 t = t.Elem() 262 } 263 return t == subscriptionType 264 } 265 266 // isPubSub tests whether the given method has as as first argument a context.Context and 267 // returns the pair (Subscription, error). 268 func isPubSub(methodType reflect.Type) bool { 269 // numIn(0) is the receiver type 270 if methodType.NumIn() < 2 || methodType.NumOut() != 2 { 271 return false 272 } 273 return isContextType(methodType.In(1)) && 274 isSubscriptionType(methodType.Out(0)) && 275 isErrorType(methodType.Out(1)) 276 } 277 278 // formatName converts to first character of name to lowercase. 279 func formatName(name string) string { 280 ret := []rune(name) 281 if len(ret) > 0 { 282 ret[0] = unicode.ToLower(ret[0]) 283 } 284 return string(ret) 285 }