| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // Copyright 2019 The go-ethereum Authors
- // This file is part of the go-ethereum library.
- //
- // The go-ethereum library is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Lesser General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // The go-ethereum library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public License
- // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
- package rpc
- import (
- "context"
- "errors"
- "fmt"
- "reflect"
- "runtime"
- "strings"
- "sync"
- "unicode"
- "github.com/ethereum/go-ethereum/log"
- )
- var (
- contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
- errorType = reflect.TypeOf((*error)(nil)).Elem()
- subscriptionType = reflect.TypeOf(Subscription{})
- stringType = reflect.TypeOf("")
- )
- type serviceRegistry struct {
- mu sync.Mutex
- services map[string]service
- }
- // service represents a registered object.
- type service struct {
- name string // name for service
- callbacks map[string]*callback // registered handlers
- subscriptions map[string]*callback // available subscriptions/notifications
- }
- // callback is a method callback which was registered in the server
- type callback struct {
- fn reflect.Value // the function
- rcvr reflect.Value // receiver object of method, set if fn is method
- argTypes []reflect.Type // input argument types
- hasCtx bool // method's first argument is a context (not included in argTypes)
- errPos int // err return idx, of -1 when method cannot return error
- isSubscribe bool // true if this is a subscription callback
- }
- func (r *serviceRegistry) registerName(name string, rcvr interface{}) error {
- rcvrVal := reflect.ValueOf(rcvr)
- if name == "" {
- return fmt.Errorf("no service name for type %s", rcvrVal.Type().String())
- }
- callbacks := suitableCallbacks(rcvrVal)
- if len(callbacks) == 0 {
- return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
- }
- r.mu.Lock()
- defer r.mu.Unlock()
- if r.services == nil {
- r.services = make(map[string]service)
- }
- svc, ok := r.services[name]
- if !ok {
- svc = service{
- name: name,
- callbacks: make(map[string]*callback),
- subscriptions: make(map[string]*callback),
- }
- r.services[name] = svc
- }
- for name, cb := range callbacks {
- if cb.isSubscribe {
- svc.subscriptions[name] = cb
- } else {
- svc.callbacks[name] = cb
- }
- }
- return nil
- }
- // callback returns the callback corresponding to the given RPC method name.
- func (r *serviceRegistry) callback(method string) *callback {
- elem := strings.SplitN(method, serviceMethodSeparator, 2)
- if len(elem) != 2 {
- return nil
- }
- r.mu.Lock()
- defer r.mu.Unlock()
- return r.services[elem[0]].callbacks[elem[1]]
- }
- // subscription returns a subscription callback in the given service.
- func (r *serviceRegistry) subscription(service, name string) *callback {
- r.mu.Lock()
- defer r.mu.Unlock()
- return r.services[service].subscriptions[name]
- }
- // suitableCallbacks iterates over the methods of the given type. It determines if a method
- // satisfies the criteria for a RPC callback or a subscription callback and adds it to the
- // collection of callbacks. See server documentation for a summary of these criteria.
- func suitableCallbacks(receiver reflect.Value) map[string]*callback {
- typ := receiver.Type()
- callbacks := make(map[string]*callback)
- for m := 0; m < typ.NumMethod(); m++ {
- method := typ.Method(m)
- if method.PkgPath != "" {
- continue // method not exported
- }
- cb := newCallback(receiver, method.Func)
- if cb == nil {
- continue // function invalid
- }
- name := formatName(method.Name)
- callbacks[name] = cb
- }
- return callbacks
- }
- // newCallback turns fn (a function) into a callback object. It returns nil if the function
- // is unsuitable as an RPC callback.
- func newCallback(receiver, fn reflect.Value) *callback {
- fntype := fn.Type()
- c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)}
- // Determine parameter types. They must all be exported or builtin types.
- c.makeArgTypes()
- // Verify return types. The function must return at most one error
- // and/or one other non-error value.
- outs := make([]reflect.Type, fntype.NumOut())
- for i := 0; i < fntype.NumOut(); i++ {
- outs[i] = fntype.Out(i)
- }
- if len(outs) > 2 {
- return nil
- }
- // If an error is returned, it must be the last returned value.
- switch {
- case len(outs) == 1 && isErrorType(outs[0]):
- c.errPos = 0
- case len(outs) == 2:
- if isErrorType(outs[0]) || !isErrorType(outs[1]) {
- return nil
- }
- c.errPos = 1
- }
- return c
- }
- // makeArgTypes composes the argTypes list.
- func (c *callback) makeArgTypes() {
- fntype := c.fn.Type()
- // Skip receiver and context.Context parameter (if present).
- firstArg := 0
- if c.rcvr.IsValid() {
- firstArg++
- }
- if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType {
- c.hasCtx = true
- firstArg++
- }
- // Add all remaining parameters.
- c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg)
- for i := firstArg; i < fntype.NumIn(); i++ {
- c.argTypes[i-firstArg] = fntype.In(i)
- }
- }
- // call invokes the callback.
- func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) {
- // Create the argument slice.
- fullargs := make([]reflect.Value, 0, 2+len(args))
- if c.rcvr.IsValid() {
- fullargs = append(fullargs, c.rcvr)
- }
- if c.hasCtx {
- fullargs = append(fullargs, reflect.ValueOf(ctx))
- }
- fullargs = append(fullargs, args...)
- // Catch panic while running the callback.
- defer func() {
- if err := recover(); err != nil {
- const size = 64 << 10
- buf := make([]byte, size)
- buf = buf[:runtime.Stack(buf, false)]
- log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf))
- errRes = errors.New("method handler crashed")
- }
- }()
- // Run the callback.
- results := c.fn.Call(fullargs)
- if len(results) == 0 {
- return nil, nil
- }
- if c.errPos >= 0 && !results[c.errPos].IsNil() {
- // Method has returned non-nil error value.
- err := results[c.errPos].Interface().(error)
- return reflect.Value{}, err
- }
- return results[0].Interface(), nil
- }
- // Is t context.Context or *context.Context?
- func isContextType(t reflect.Type) bool {
- for t.Kind() == reflect.Ptr {
- t = t.Elem()
- }
- return t == contextType
- }
- // Does t satisfy the error interface?
- func isErrorType(t reflect.Type) bool {
- for t.Kind() == reflect.Ptr {
- t = t.Elem()
- }
- return t.Implements(errorType)
- }
- // Is t Subscription or *Subscription?
- func isSubscriptionType(t reflect.Type) bool {
- for t.Kind() == reflect.Ptr {
- t = t.Elem()
- }
- return t == subscriptionType
- }
- // isPubSub tests whether the given method has as as first argument a context.Context and
- // returns the pair (Subscription, error).
- func isPubSub(methodType reflect.Type) bool {
- // numIn(0) is the receiver type
- if methodType.NumIn() < 2 || methodType.NumOut() != 2 {
- return false
- }
- return isContextType(methodType.In(1)) &&
- isSubscriptionType(methodType.Out(0)) &&
- isErrorType(methodType.Out(1))
- }
- // formatName converts to first character of name to lowercase.
- func formatName(name string) string {
- ret := []rune(name)
- if len(ret) > 0 {
- ret[0] = unicode.ToLower(ret[0])
- }
- return string(ret)
- }
|