package rpc import ( "fmt" "reflect" "strings" "sync" "unicode" "unicode/utf8" ) var ( // Precompute the reflect.Type of error and http.Request typeOfError = reflect.TypeOf((*error)(nil)).Elem() ) // ---------------------------------------------------------------------------- // service // ---------------------------------------------------------------------------- type service struct { name string // name of service rcvrV reflect.Value // receiver of methods for the service rcvrT reflect.Type // type of the receiver methods map[string]*serviceMethod // registered methods } type serviceMethod struct { method reflect.Method // receiver method paramTypes []reflect.Type // type of the request argument paramValues []reflect.Value returnType reflect.Type // type of the response argument returnValue reflect.Value } func (sm *serviceMethod) getInterfaces() (instances []interface{}) { if nil == sm.paramValues || 0 == len(sm.paramValues) { return nil } pCount := len(sm.paramValues) instances = make([]interface{}, pCount) for indexI := 0; indexI < pCount; indexI++ { instances[indexI] = sm.paramValues[indexI].Interface() } return } // ---------------------------------------------------------------------------- // serviceMap // ---------------------------------------------------------------------------- // serviceMap is a registry for services. type serviceMap struct { mutex sync.Mutex services map[string]*service } // register adds a new service using reflection to extract its methods. func (m *serviceMap) register(rcvr interface{}, name string) error { // Setup service. s := &service{ name: name, rcvrV: reflect.ValueOf(rcvr), rcvrT: reflect.TypeOf(rcvr), methods: make(map[string]*serviceMethod), } if name == "" { s.name = reflect.Indirect(s.rcvrV).Type().Name() if !isExported(s.name) { return fmt.Errorf("rpc: type %q is not exported", s.name) } } if s.name == "" { return fmt.Errorf("rpc: no service name for type %q", s.rcvrT.String()) } var err error // Setup methods. Loop: for i := 0; i < s.rcvrT.NumMethod(); i++ { m := s.rcvrT.Method(i) mt := m.Type // Method must be exported. if m.PkgPath != "" { continue } var paramTypes []reflect.Type var paramValues []reflect.Value var returnType reflect.Type var returnValue reflect.Value pCount := mt.NumIn() - 1 if 0 < pCount { paramTypes = make([]reflect.Type, pCount) paramValues = make([]reflect.Value, pCount) for indexI := 0; indexI < pCount; indexI++ { if paramTypes[indexI], paramValues[indexI], err = validateType(mt.In(indexI + 1)); nil != err { return err } } } switch mt.NumOut() { case 1: if t := mt.Out(0); t != typeOfError { continue Loop } case 2: if t := mt.Out(0); !isExportedOrBuiltin(t) { continue Loop } if t := mt.Out(1); t != typeOfError { continue Loop } if returnType, returnValue, err = validateType(mt.Out(0)); nil != err { return err } default: continue } s.methods[m.Name] = &serviceMethod{ method: m, paramTypes: paramTypes, paramValues: paramValues, returnType: returnType, returnValue: returnValue, } } if len(s.methods) == 0 { return fmt.Errorf("rpc: %q has no exported methods of suitable type", s.name) } // Add to the map. m.mutex.Lock() defer m.mutex.Unlock() if m.services == nil { m.services = make(map[string]*service) } else if _, ok := m.services[s.name]; ok { return fmt.Errorf("rpc: service already defined: %q", s.name) } m.services[s.name] = s return nil } func validateType(t reflect.Type) (rt reflect.Type, rv reflect.Value, err error) { if t.Kind() == reflect.Struct { err = fmt.Errorf("Type is Struct. Pass by reference, i.e. %s", t) return } if t.Kind() == reflect.Ptr { rt = t.Elem() } rv = reflect.New(rt) if rt.Kind() != reflect.Struct { rv = reflect.Indirect(rv) } return } // get returns a registered service given a method name. // // The method name uses a dotted notation as in "Service.Method". func (m *serviceMap) get(method string) (*service, *serviceMethod, error) { parts := strings.Split(method, ".") if len(parts) != 2 { err := fmt.Errorf("rpc: service/method request ill-formed: %q", method) return nil, nil, err } m.mutex.Lock() service := m.services[parts[0]] m.mutex.Unlock() if service == nil { err := fmt.Errorf("rpc: can't find service %q", method) return nil, nil, err } serviceMethod := service.methods[parts[1]] if serviceMethod == nil { err := fmt.Errorf("rpc: can't find method %q", method) return nil, nil, err } return service, serviceMethod, nil } // isExported returns true of a string is an exported (upper case) name. func isExported(name string) bool { rune, _ := utf8.DecodeRuneInString(name) return unicode.IsUpper(rune) } // isExportedOrBuiltin returns true if a type is exported or a builtin. func isExportedOrBuiltin(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() == "" }