286 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			286 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | // Copyright 2015 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" | ||
|  | 	"unicode/utf8" | ||
|  | 
 | ||
|  | 	"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() | ||
|  | 	if !allExportedOrBuiltin(c.argTypes) { | ||
|  | 		return nil | ||
|  | 	} | ||
|  | 	// 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 || !allExportedOrBuiltin(outs) { | ||
|  | 		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 this an exported - upper case - name? | ||
|  | func isExported(name string) bool { | ||
|  | 	rune, _ := utf8.DecodeRuneInString(name) | ||
|  | 	return unicode.IsUpper(rune) | ||
|  | } | ||
|  | 
 | ||
|  | // Are all those types exported or built-in? | ||
|  | func allExportedOrBuiltin(types []reflect.Type) bool { | ||
|  | 	for _, typ := range types { | ||
|  | 		for typ.Kind() == reflect.Ptr { | ||
|  | 			typ = typ.Elem() | ||
|  | 		} | ||
|  | 		// PkgPath will be non-empty even for an exported type, | ||
|  | 		// so we need to check the type name as well. | ||
|  | 		if !isExported(typ.Name()) && typ.PkgPath() != "" { | ||
|  | 			return false | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | // 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) | ||
|  | } |