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  }