mirror of
				https://github.com/smallnest/rpcx.git
				synced 2025-10-31 11:26:56 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			326 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package server
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"sync"
 | |
| 	"unicode"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"github.com/smallnest/rpcx/log"
 | |
| )
 | |
| 
 | |
| // Precompute the reflect type for error. Can't use error directly
 | |
| // because Typeof takes an empty interface value. This is annoying.
 | |
| var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
 | |
| 
 | |
| // Precompute the reflect type for context.
 | |
| var typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem()
 | |
| 
 | |
| var emptyService = new(service)
 | |
| 
 | |
| type methodType struct {
 | |
| 	sync.Mutex // protects counters
 | |
| 	method     reflect.Method
 | |
| 	ArgType    reflect.Type
 | |
| 	ReplyType  reflect.Type
 | |
| 	numCalls   uint
 | |
| }
 | |
| 
 | |
| type functionType struct {
 | |
| 	sync.Mutex // protects counters
 | |
| 	fn         reflect.Value
 | |
| 	ArgType    reflect.Type
 | |
| 	ReplyType  reflect.Type
 | |
| 	numCalls   uint
 | |
| }
 | |
| 
 | |
| type service struct {
 | |
| 	name     string                   // name of service
 | |
| 	rcvr     reflect.Value            // receiver of methods for the service
 | |
| 	typ      reflect.Type             // type of the receiver
 | |
| 	method   map[string]*methodType   // registered methods
 | |
| 	function map[string]*functionType // registered functions
 | |
| }
 | |
| 
 | |
| func isExported(name string) bool {
 | |
| 	rune, _ := utf8.DecodeRuneInString(name)
 | |
| 	return unicode.IsUpper(rune)
 | |
| }
 | |
| 
 | |
| func isExportedOrBuiltinType(t reflect.Type) bool {
 | |
| 	for t.Kind() == reflect.Ptr {
 | |
| 		t = t.Elem()
 | |
| 	}
 | |
| 	// PkgPath will be non-empty even for an exported type,
 | |
| 	// so we need to check the type name as well.
 | |
| 	return isExported(t.Name()) || t.PkgPath() == ""
 | |
| }
 | |
| 
 | |
| // Register publishes in the server the set of methods of the
 | |
| // receiver value that satisfy the following conditions:
 | |
| //	- exported method of exported type
 | |
| //	- three arguments, the first is of context.Context, both of exported type for three arguments
 | |
| //	- the third argument is a pointer
 | |
| //	- one return value, of type error
 | |
| // It returns an error if the receiver is not an exported type or has
 | |
| // no suitable methods. It also logs the error.
 | |
| // The client accesses each method using a string of the form "Type.Method",
 | |
| // where Type is the receiver's concrete type.
 | |
| func (s *Server) Register(rcvr interface{}, metadata string) error {
 | |
| 	s.Plugins.DoRegister("", rcvr, metadata)
 | |
| 	return s.register(rcvr, "", false)
 | |
| }
 | |
| 
 | |
| // RegisterName is like Register but uses the provided name for the type
 | |
| // instead of the receiver's concrete type.
 | |
| func (s *Server) RegisterName(name string, rcvr interface{}, metadata string) error {
 | |
| 	if s.Plugins == nil {
 | |
| 		s.Plugins = &pluginContainer{}
 | |
| 	}
 | |
| 
 | |
| 	s.Plugins.DoRegister(name, rcvr, metadata)
 | |
| 	return s.register(rcvr, name, true)
 | |
| }
 | |
| 
 | |
| // RegisterFunction publishes a function that satisfy the following conditions:
 | |
| //	- three arguments, the first is of context.Context, both of exported type for three arguments
 | |
| //	- the third argument is a pointer
 | |
| //	- one return value, of type error
 | |
| // The client accesses function using a string of the form ".Method",
 | |
| // where service path is empty.
 | |
| func (s *Server) RegisterFunction(fn interface{}, metadata string) error {
 | |
| 	s.Plugins.DoRegisterFunction("", fn, metadata)
 | |
| 	return s.registerFunction(fn, "", false)
 | |
| }
 | |
| 
 | |
| // RegisterFunctionName is like RegisterFunction but uses the provided name for the function
 | |
| // instead of the function's concrete type.
 | |
| func (s *Server) RegisterFunctionName(name string, fn interface{}, metadata string) error {
 | |
| 	if s.Plugins == nil {
 | |
| 		s.Plugins = &pluginContainer{}
 | |
| 	}
 | |
| 
 | |
| 	s.Plugins.DoRegisterFunction(name, fn, metadata)
 | |
| 	return s.registerFunction(fn, name, true)
 | |
| }
 | |
| 
 | |
| func (s *Server) register(rcvr interface{}, name string, useName bool) error {
 | |
| 	s.serviceMapMu.Lock()
 | |
| 	defer s.serviceMapMu.Unlock()
 | |
| 	if s.serviceMap == nil {
 | |
| 		s.serviceMap = make(map[string]*service)
 | |
| 	}
 | |
| 
 | |
| 	service := new(service)
 | |
| 	service.typ = reflect.TypeOf(rcvr)
 | |
| 	service.rcvr = reflect.ValueOf(rcvr)
 | |
| 	sname := reflect.Indirect(service.rcvr).Type().Name() // Type
 | |
| 	if useName {
 | |
| 		sname = name
 | |
| 	}
 | |
| 	if sname == "" {
 | |
| 		errorStr := "rpcx.Register: no service name for type " + service.typ.String()
 | |
| 		log.Error(errorStr)
 | |
| 		return errors.New(errorStr)
 | |
| 	}
 | |
| 	if !useName && !isExported(sname) {
 | |
| 		errorStr := "rpcx.Register: type " + sname + " is not exported"
 | |
| 		log.Error(errorStr)
 | |
| 		return errors.New(errorStr)
 | |
| 	}
 | |
| 	service.name = sname
 | |
| 
 | |
| 	// Install the methods
 | |
| 	service.method = suitableMethods(service.typ, true)
 | |
| 
 | |
| 	if len(service.method) == 0 {
 | |
| 		var errorStr string
 | |
| 
 | |
| 		// To help the user, see if a pointer receiver would work.
 | |
| 		method := suitableMethods(reflect.PtrTo(service.typ), false)
 | |
| 		if len(method) != 0 {
 | |
| 			errorStr = "rpcx.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
 | |
| 		} else {
 | |
| 			errorStr = "rpcx.Register: type " + sname + " has no exported methods of suitable type"
 | |
| 		}
 | |
| 		log.Error(errorStr)
 | |
| 		return errors.New(errorStr)
 | |
| 	}
 | |
| 	s.serviceMap[service.name] = service
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *Server) registerFunction(fn interface{}, name string, useName bool) error {
 | |
| 	s.serviceMapMu.Lock()
 | |
| 	defer s.serviceMapMu.Unlock()
 | |
| 	if s.serviceMap == nil {
 | |
| 		s.serviceMap = make(map[string]*service)
 | |
| 	}
 | |
| 
 | |
| 	f, ok := fn.(reflect.Value)
 | |
| 	if !ok {
 | |
| 		f = reflect.ValueOf(fn)
 | |
| 	}
 | |
| 	if f.Kind() != reflect.Func {
 | |
| 		return errors.New("function must be func or bound method")
 | |
| 	}
 | |
| 
 | |
| 	fname := reflect.Indirect(f).Type().Name()
 | |
| 	if useName {
 | |
| 		fname = name
 | |
| 	}
 | |
| 	if fname == "" {
 | |
| 		errorStr := "rpcx.registerFunction: no func name for type " + f.Type().String()
 | |
| 		log.Error(errorStr)
 | |
| 		return errors.New(errorStr)
 | |
| 	}
 | |
| 
 | |
| 	t := f.Type()
 | |
| 	if t.NumIn() != 3 {
 | |
| 		return fmt.Errorf("rpcx.registerFunction: has wrong number of ins: %s", f.Type().String())
 | |
| 	}
 | |
| 	if t.NumOut() != 1 {
 | |
| 		return fmt.Errorf("rpcx.registerFunction: has wrong number of outs: %s", f.Type().String())
 | |
| 	}
 | |
| 
 | |
| 	// First arg must be context.Context
 | |
| 	ctxType := t.In(0)
 | |
| 	if !ctxType.Implements(typeOfContext) {
 | |
| 		return fmt.Errorf("function %s must use context as  the first parameter", f.Type().String())
 | |
| 	}
 | |
| 
 | |
| 	argType := t.In(1)
 | |
| 	if !isExportedOrBuiltinType(argType) {
 | |
| 		return fmt.Errorf("function %s parameter type not exported: %v", f.Type().String(), argType)
 | |
| 	}
 | |
| 
 | |
| 	replyType := t.In(2)
 | |
| 	if replyType.Kind() != reflect.Ptr {
 | |
| 		return fmt.Errorf("function %s reply type not a pointer: %s", f.Type().String(), replyType)
 | |
| 	}
 | |
| 	if !isExportedOrBuiltinType(replyType) {
 | |
| 		return fmt.Errorf("function %s reply type not exported: %v", f.Type().String(), replyType)
 | |
| 	}
 | |
| 
 | |
| 	// The return type of the method must be error.
 | |
| 	if returnType := t.Out(0); returnType != typeOfError {
 | |
| 		return fmt.Errorf("function %s returns %s, not error", f.Type().String(), returnType.String())
 | |
| 	}
 | |
| 
 | |
| 	// Install the methods
 | |
| 	emptyService.function[fname] = &functionType{fn: f, ArgType: argType, ReplyType: replyType}
 | |
| 
 | |
| 	s.serviceMap[""] = emptyService
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // suitableMethods returns suitable Rpc methods of typ, it will report
 | |
| // error using log if reportErr is true.
 | |
| func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {
 | |
| 	methods := make(map[string]*methodType)
 | |
| 	for m := 0; m < typ.NumMethod(); m++ {
 | |
| 		method := typ.Method(m)
 | |
| 		mtype := method.Type
 | |
| 		mname := method.Name
 | |
| 		// Method must be exported.
 | |
| 		if method.PkgPath != "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		// Method needs four ins: receiver, context.Context, *args, *reply.
 | |
| 		if mtype.NumIn() != 4 {
 | |
| 			if reportErr {
 | |
| 				log.Info("method", mname, "has wrong number of ins:", mtype.NumIn())
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		// First arg must be context.Context
 | |
| 		ctxType := mtype.In(1)
 | |
| 		if !ctxType.Implements(typeOfContext) {
 | |
| 			if reportErr {
 | |
| 				log.Info("method", mname, " must use context.Context as the first parameter")
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// Second arg need not be a pointer.
 | |
| 		argType := mtype.In(2)
 | |
| 		if !isExportedOrBuiltinType(argType) {
 | |
| 			if reportErr {
 | |
| 				log.Info(mname, "parameter type not exported:", argType)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		// Third arg must be a pointer.
 | |
| 		replyType := mtype.In(3)
 | |
| 		if replyType.Kind() != reflect.Ptr {
 | |
| 			if reportErr {
 | |
| 				log.Info("method", mname, "reply type not a pointer:", replyType)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		// Reply type must be exported.
 | |
| 		if !isExportedOrBuiltinType(replyType) {
 | |
| 			if reportErr {
 | |
| 				log.Info("method", mname, "reply type not exported:", replyType)
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		// Method needs one out.
 | |
| 		if mtype.NumOut() != 1 {
 | |
| 			if reportErr {
 | |
| 				log.Info("method", mname, "has wrong number of outs:", mtype.NumOut())
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		// The return type of the method must be error.
 | |
| 		if returnType := mtype.Out(0); returnType != typeOfError {
 | |
| 			if reportErr {
 | |
| 				log.Info("method", mname, "returns", returnType.String(), "not error")
 | |
| 			}
 | |
| 			continue
 | |
| 		}
 | |
| 		methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
 | |
| 	}
 | |
| 	return methods
 | |
| }
 | |
| 
 | |
| func (s *service) call(ctx context.Context, mtype *methodType, argv, replyv reflect.Value) (err error) {
 | |
| 	defer func() {
 | |
| 		if r := recover(); r != nil {
 | |
| 			err = fmt.Errorf("internal error: %v", r)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	function := mtype.method.Func
 | |
| 	// Invoke the method, providing a new value for the reply.
 | |
| 	returnValues := function.Call([]reflect.Value{s.rcvr, reflect.ValueOf(ctx), argv, replyv})
 | |
| 	// The return value for the method is an error.
 | |
| 	errInter := returnValues[0].Interface()
 | |
| 	if errInter != nil {
 | |
| 		return errInter.(error)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *service) callForFunction(ctx context.Context, ft *functionType, argv, replyv reflect.Value) (err error) {
 | |
| 	defer func() {
 | |
| 		if r := recover(); r != nil {
 | |
| 			err = fmt.Errorf("internal error: %v", r)
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// Invoke the function, providing a new value for the reply.
 | |
| 	returnValues := ft.fn.Call([]reflect.Value{reflect.ValueOf(ctx), argv, replyv})
 | |
| 	// The return value for the method is an error.
 | |
| 	errInter := returnValues[0].Interface()
 | |
| 	if errInter != nil {
 | |
| 		return errInter.(error)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | 
