github.com/core-coin/go-core/v2@v2.1.9/rpc/service.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core 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-core 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-core 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/core-coin/go-core/v2/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  	services map[string]service
    42  }
    43  
    44  // service represents a registered object.
    45  type service struct {
    46  	name          string               // name for service
    47  	callbacks     map[string]*callback // registered handlers
    48  	subscriptions map[string]*callback // available subscriptions/notifications
    49  }
    50  
    51  // callback is a method callback which was registered in the server
    52  type callback struct {
    53  	fn          reflect.Value  // the function
    54  	rcvr        reflect.Value  // receiver object of method, set if fn is method
    55  	argTypes    []reflect.Type // input argument types
    56  	hasCtx      bool           // method's first argument is a context (not included in argTypes)
    57  	errPos      int            // err return idx, of -1 when method cannot return error
    58  	isSubscribe bool           // true if this is a subscription callback
    59  }
    60  
    61  func (r *serviceRegistry) registerName(name string, rcvr interface{}) error {
    62  	rcvrVal := reflect.ValueOf(rcvr)
    63  	if name == "" {
    64  		return fmt.Errorf("no service name for type %s", rcvrVal.Type().String())
    65  	}
    66  	callbacks := suitableCallbacks(rcvrVal)
    67  	if len(callbacks) == 0 {
    68  		return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr)
    69  	}
    70  
    71  	r.mu.Lock()
    72  	defer r.mu.Unlock()
    73  	if r.services == nil {
    74  		r.services = make(map[string]service)
    75  	}
    76  	svc, ok := r.services[name]
    77  	if !ok {
    78  		svc = service{
    79  			name:          name,
    80  			callbacks:     make(map[string]*callback),
    81  			subscriptions: make(map[string]*callback),
    82  		}
    83  		r.services[name] = svc
    84  	}
    85  	for name, cb := range callbacks {
    86  		if cb.isSubscribe {
    87  			svc.subscriptions[name] = cb
    88  		} else {
    89  			svc.callbacks[name] = cb
    90  		}
    91  	}
    92  	return nil
    93  }
    94  
    95  // callback returns the callback corresponding to the given RPC method name.
    96  func (r *serviceRegistry) callback(method string) *callback {
    97  	elem := strings.SplitN(method, serviceMethodSeparator, 2)
    98  	if len(elem) != 2 {
    99  		return nil
   100  	}
   101  	r.mu.Lock()
   102  	defer r.mu.Unlock()
   103  	return r.services[elem[0]].callbacks[elem[1]]
   104  }
   105  
   106  // subscription returns a subscription callback in the given service.
   107  func (r *serviceRegistry) subscription(service, name string) *callback {
   108  	r.mu.Lock()
   109  	defer r.mu.Unlock()
   110  	return r.services[service].subscriptions[name]
   111  }
   112  
   113  // suitableCallbacks iterates over the methods of the given type. It determines if a method
   114  // satisfies the criteria for a RPC callback or a subscription callback and adds it to the
   115  // collection of callbacks. See server documentation for a summary of these criteria.
   116  func suitableCallbacks(receiver reflect.Value) map[string]*callback {
   117  	typ := receiver.Type()
   118  	callbacks := make(map[string]*callback)
   119  	for m := 0; m < typ.NumMethod(); m++ {
   120  		method := typ.Method(m)
   121  		if method.PkgPath != "" {
   122  			continue // method not exported
   123  		}
   124  		cb := newCallback(receiver, method.Func)
   125  		if cb == nil {
   126  			continue // function invalid
   127  		}
   128  		name := formatName(method.Name)
   129  		callbacks[name] = cb
   130  	}
   131  	return callbacks
   132  }
   133  
   134  // newCallback turns fn (a function) into a callback object. It returns nil if the function
   135  // is unsuitable as an RPC callback.
   136  func newCallback(receiver, fn reflect.Value) *callback {
   137  	fntype := fn.Type()
   138  	c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)}
   139  	// Determine parameter types. They must all be exported or builtin types.
   140  	c.makeArgTypes()
   141  
   142  	// Verify return types. The function must return at most one error
   143  	// and/or one other non-error value.
   144  	outs := make([]reflect.Type, fntype.NumOut())
   145  	for i := 0; i < fntype.NumOut(); i++ {
   146  		outs[i] = fntype.Out(i)
   147  	}
   148  	if len(outs) > 2 {
   149  		return nil
   150  	}
   151  	// If an error is returned, it must be the last returned value.
   152  	switch {
   153  	case len(outs) == 1 && isErrorType(outs[0]):
   154  		c.errPos = 0
   155  	case len(outs) == 2:
   156  		if isErrorType(outs[0]) || !isErrorType(outs[1]) {
   157  			return nil
   158  		}
   159  		c.errPos = 1
   160  	}
   161  	return c
   162  }
   163  
   164  // makeArgTypes composes the argTypes list.
   165  func (c *callback) makeArgTypes() {
   166  	fntype := c.fn.Type()
   167  	// Skip receiver and context.Context parameter (if present).
   168  	firstArg := 0
   169  	if c.rcvr.IsValid() {
   170  		firstArg++
   171  	}
   172  	if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType {
   173  		c.hasCtx = true
   174  		firstArg++
   175  	}
   176  	// Add all remaining parameters.
   177  	c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg)
   178  	for i := firstArg; i < fntype.NumIn(); i++ {
   179  		c.argTypes[i-firstArg] = fntype.In(i)
   180  	}
   181  }
   182  
   183  // call invokes the callback.
   184  func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) {
   185  	// Create the argument slice.
   186  	fullargs := make([]reflect.Value, 0, 2+len(args))
   187  	if c.rcvr.IsValid() {
   188  		fullargs = append(fullargs, c.rcvr)
   189  	}
   190  	if c.hasCtx {
   191  		fullargs = append(fullargs, reflect.ValueOf(ctx))
   192  	}
   193  	fullargs = append(fullargs, args...)
   194  
   195  	// Catch panic while running the callback.
   196  	defer func() {
   197  		if err := recover(); err != nil {
   198  			const size = 64 << 10
   199  			buf := make([]byte, size)
   200  			buf = buf[:runtime.Stack(buf, false)]
   201  			log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf))
   202  			errRes = errors.New("method handler crashed")
   203  		}
   204  	}()
   205  	// Run the callback.
   206  	results := c.fn.Call(fullargs)
   207  	if len(results) == 0 {
   208  		return nil, nil
   209  	}
   210  	if c.errPos >= 0 && !results[c.errPos].IsNil() {
   211  		// Method has returned non-nil error value.
   212  		err := results[c.errPos].Interface().(error)
   213  		return reflect.Value{}, err
   214  	}
   215  	return results[0].Interface(), nil
   216  }
   217  
   218  // Is t context.Context or *context.Context?
   219  func isContextType(t reflect.Type) bool {
   220  	for t.Kind() == reflect.Ptr {
   221  		t = t.Elem()
   222  	}
   223  	return t == contextType
   224  }
   225  
   226  // Does t satisfy the error interface?
   227  func isErrorType(t reflect.Type) bool {
   228  	for t.Kind() == reflect.Ptr {
   229  		t = t.Elem()
   230  	}
   231  	return t.Implements(errorType)
   232  }
   233  
   234  // Is t Subscription or *Subscription?
   235  func isSubscriptionType(t reflect.Type) bool {
   236  	for t.Kind() == reflect.Ptr {
   237  		t = t.Elem()
   238  	}
   239  	return t == subscriptionType
   240  }
   241  
   242  // isPubSub tests whether the given method has as as first argument a context.Context and
   243  // returns the pair (Subscription, error).
   244  func isPubSub(methodType reflect.Type) bool {
   245  	// numIn(0) is the receiver type
   246  	if methodType.NumIn() < 2 || methodType.NumOut() != 2 {
   247  		return false
   248  	}
   249  	return isContextType(methodType.In(1)) &&
   250  		isSubscriptionType(methodType.Out(0)) &&
   251  		isErrorType(methodType.Out(1))
   252  }
   253  
   254  // formatName converts to first character of name to lowercase.
   255  func formatName(name string) string {
   256  	ret := []rune(name)
   257  	if len(ret) > 0 {
   258  		ret[0] = unicode.ToLower(ret[0])
   259  	}
   260  	return string(ret)
   261  }