github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/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  
    29  	"github.com/unicornultrafoundation/go-u2u/log"
    30  )
    31  
    32  var (
    33  	contextType      = reflect.TypeOf((*context.Context)(nil)).Elem()
    34  	errorType        = reflect.TypeOf((*error)(nil)).Elem()
    35  	subscriptionType = reflect.TypeOf(Subscription{})
    36  	stringType       = reflect.TypeOf("")
    37  )
    38  
    39  type serviceRegistry struct {
    40  	mu       sync.Mutex
    41  	callWG   sync.WaitGroup
    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  
   143  	// Verify return types. The function must return at most one error
   144  	// and/or one other non-error value.
   145  	outs := make([]reflect.Type, fntype.NumOut())
   146  	for i := 0; i < fntype.NumOut(); i++ {
   147  		outs[i] = fntype.Out(i)
   148  	}
   149  	if len(outs) > 2 {
   150  		return nil
   151  	}
   152  	// If an error is returned, it must be the last returned value.
   153  	switch {
   154  	case len(outs) == 1 && isErrorType(outs[0]):
   155  		c.errPos = 0
   156  	case len(outs) == 2:
   157  		if isErrorType(outs[0]) || !isErrorType(outs[1]) {
   158  			return nil
   159  		}
   160  		c.errPos = 1
   161  	}
   162  	return c
   163  }
   164  
   165  // makeArgTypes composes the argTypes list.
   166  func (c *callback) makeArgTypes() {
   167  	fntype := c.fn.Type()
   168  	// Skip receiver and context.Context parameter (if present).
   169  	firstArg := 0
   170  	if c.rcvr.IsValid() {
   171  		firstArg++
   172  	}
   173  	if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType {
   174  		c.hasCtx = true
   175  		firstArg++
   176  	}
   177  	// Add all remaining parameters.
   178  	c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg)
   179  	for i := firstArg; i < fntype.NumIn(); i++ {
   180  		c.argTypes[i-firstArg] = fntype.In(i)
   181  	}
   182  }
   183  
   184  // call invokes the callback.
   185  func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) {
   186  	// Create the argument slice.
   187  	fullargs := make([]reflect.Value, 0, 2+len(args))
   188  	if c.rcvr.IsValid() {
   189  		fullargs = append(fullargs, c.rcvr)
   190  	}
   191  	if c.hasCtx {
   192  		fullargs = append(fullargs, reflect.ValueOf(ctx))
   193  	}
   194  	fullargs = append(fullargs, args...)
   195  
   196  	// Catch panic while running the callback.
   197  	defer func() {
   198  		if err := recover(); err != nil {
   199  			const size = 64 << 10
   200  			buf := make([]byte, size)
   201  			buf = buf[:runtime.Stack(buf, false)]
   202  			log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf))
   203  			errRes = errors.New("method handler crashed")
   204  		}
   205  	}()
   206  	// Run the callback.
   207  	results := c.fn.Call(fullargs)
   208  	if len(results) == 0 {
   209  		return nil, nil
   210  	}
   211  	if c.errPos >= 0 && !results[c.errPos].IsNil() {
   212  		// Method has returned non-nil error value.
   213  		err := results[c.errPos].Interface().(error)
   214  		return reflect.Value{}, err
   215  	}
   216  	return results[0].Interface(), nil
   217  }
   218  
   219  // Is t context.Context or *context.Context?
   220  func isContextType(t reflect.Type) bool {
   221  	for t.Kind() == reflect.Ptr {
   222  		t = t.Elem()
   223  	}
   224  	return t == contextType
   225  }
   226  
   227  // Does t satisfy the error interface?
   228  func isErrorType(t reflect.Type) bool {
   229  	for t.Kind() == reflect.Ptr {
   230  		t = t.Elem()
   231  	}
   232  	return t.Implements(errorType)
   233  }
   234  
   235  // Is t Subscription or *Subscription?
   236  func isSubscriptionType(t reflect.Type) bool {
   237  	for t.Kind() == reflect.Ptr {
   238  		t = t.Elem()
   239  	}
   240  	return t == subscriptionType
   241  }
   242  
   243  // isPubSub tests whether the given method has as as first argument a context.Context and
   244  // returns the pair (Subscription, error).
   245  func isPubSub(methodType reflect.Type) bool {
   246  	// numIn(0) is the receiver type
   247  	if methodType.NumIn() < 2 || methodType.NumOut() != 2 {
   248  		return false
   249  	}
   250  	return isContextType(methodType.In(1)) &&
   251  		isSubscriptionType(methodType.Out(0)) &&
   252  		isErrorType(methodType.Out(1))
   253  }
   254  
   255  // formatName converts to first character of name to lowercase.
   256  func formatName(name string) string {
   257  	ret := []rune(name)
   258  	if len(ret) > 0 {
   259  		ret[0] = unicode.ToLower(ret[0])
   260  	}
   261  	return string(ret)
   262  }