commit 8587e2e369a0aa0ff5fc3d0ccb1d3b293d8817cd Author: crusader Date: Tue Apr 3 17:58:26 2018 +0900 ing diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3733e36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ +.idea/ +*.iml + +vendor/ +glide.lock +.DS_Store +dist/ +debug diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ca2b1d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}/main.go", + "env": {}, + "args": [], + "showLog": true + }, + { + "name": "File Debug", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [], + "showLog": true + } + + ] +} \ No newline at end of file diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..bd908b7 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,6 @@ +package: git.loafle.net/commons/rpc-go +import: +- package: git.loafle.net/commons/util-go + subpackages: + - encoding/json + - reflect diff --git a/protocol/client_codec.go b/protocol/client_codec.go new file mode 100644 index 0000000..92d9fd1 --- /dev/null +++ b/protocol/client_codec.go @@ -0,0 +1,18 @@ +package protocol + +// ClientCodec creates a ClientCodecRequest to process each request. +type ClientCodec interface { + NewRequest(method string, args []interface{}, id interface{}) ([]byte, error) + NewResponse(buf []byte) (ClientResponseCodec, error) +} + +type ClientResponseCodec interface { + Notification() (ClientNotificationCodec, error) + Result(result interface{}) error + Error() *Error + ID() interface{} +} + +type ClientNotificationCodec interface { + RegistryCodec +} diff --git a/protocol/error.go b/protocol/error.go new file mode 100644 index 0000000..8ece1da --- /dev/null +++ b/protocol/error.go @@ -0,0 +1,35 @@ +package protocol + +import ( + "errors" + "fmt" +) + +type ErrorCode int + +const ( + E_PARSE ErrorCode = -32700 + E_INVALID_REQ ErrorCode = -32600 + E_NO_METHOD ErrorCode = -32601 + E_BAD_PARAMS ErrorCode = -32602 + E_INTERNAL ErrorCode = -32603 + E_SERVER ErrorCode = -32000 +) + +var ErrNullResult = errors.New("result is null") + +type Error struct { + // A Number that indicates the error type that occurred. + Code ErrorCode `json:"code"` /* required */ + + // A String providing a short description of the error. + // The message SHOULD be limited to a concise single sentence. + Message string `json:"message"` /* required */ + + // A Primitive or Structured value that contains additional information about the error. + Data interface{} `json:"data"` /* optional */ +} + +func (e Error) Error() string { + return fmt.Sprintf("Code[%d] Message[%s] Data[%v]", e.Code, e.Message, e.Data) +} diff --git a/protocol/json/client.go b/protocol/json/client.go new file mode 100644 index 0000000..8a2d4c8 --- /dev/null +++ b/protocol/json/client.go @@ -0,0 +1,39 @@ +package json + +import ( + "encoding/json" + + "git.loafle.net/commons/rpc-go/protocol" +) + +// ---------------------------------------------------------------------------- +// Codec +// ---------------------------------------------------------------------------- + +// NewClientCodec returns a new JSON Codec. +func NewClientCodec() protocol.ClientCodec { + return &ClientCodec{} +} + +// ClientCodec creates a ClientCodecRequest to process each request. +type ClientCodec struct { +} + +func (cc *ClientCodec) NewRequest(method string, args []interface{}, id interface{}) ([]byte, error) { + params, err := convertParamsToStringArray(args) + if nil != err { + return nil, err + } + + req := &clientRequest{ + Version: Version, + Method: method, + Params: params, + ID: id, + } + + return json.Marshal(req) +} +func (cc *ClientCodec) NewResponse(buf []byte) (protocol.ClientResponseCodec, error) { + return newClientResponseCodec(buf) +} diff --git a/protocol/json/client_notification.go b/protocol/json/client_notification.go new file mode 100644 index 0000000..44c6679 --- /dev/null +++ b/protocol/json/client_notification.go @@ -0,0 +1,68 @@ +package json + +import ( + "encoding/json" + + crp "git.loafle.net/commons/rpc-go/protocol" + cuej "git.loafle.net/commons/util-go/encoding/json" +) + +// ---------------------------------------------------------------------------- +// ClientNotificationCodec +// ---------------------------------------------------------------------------- +// clientRequest represents a JSON-RPC notification sent to a client. +type clientNotification struct { + // A String containing the name of the method to be invoked. + Method string `json:"method"` + // Object to pass as request parameter to the method. + Params *json.RawMessage `json:"params,omitempty"` +} + +// ClientNotificationCodec decodes and encodes a single notification. +type ClientNotificationCodec struct { + noti *clientNotification + err error +} + +func (cnc *ClientNotificationCodec) HasResponse() bool { + return false +} + +func (cnc *ClientNotificationCodec) Method() string { + return cnc.noti.Method +} + +func (cnc *ClientNotificationCodec) ReadParams(args []interface{}) error { + if cnc.err == nil && cnc.noti.Params != nil { + // Note: if scr.request.Params is nil it's not an error, it's an optional member. + // JSON params structured object. Unmarshal to the args object. + if err := cuej.SetValueWithJSONStringArray(*cnc.noti.Params, args); nil != err { + cnc.err = &crp.Error{ + Code: crp.E_INVALID_REQ, + Message: err.Error(), + Data: cnc.noti.Params, + } + return cnc.err + } + return nil + } + return cnc.err +} + +func (cnc *ClientNotificationCodec) Params() ([]string, error) { + if cnc.err == nil && cnc.noti.Params != nil { + var values []string + + if err := json.Unmarshal(*cnc.noti.Params, &values); err != nil { + cnc.err = &crp.Error{ + Code: crp.E_INVALID_REQ, + Message: err.Error(), + Data: cnc.noti.Params, + } + return nil, cnc.err + } + + return values, nil + } + return nil, cnc.err +} diff --git a/protocol/json/client_request.go b/protocol/json/client_request.go new file mode 100644 index 0000000..52c3828 --- /dev/null +++ b/protocol/json/client_request.go @@ -0,0 +1,21 @@ +package json + +// ---------------------------------------------------------------------------- +// Request and Response +// ---------------------------------------------------------------------------- + +// clientRequest represents a JSON-RPC request sent by a client. +type clientRequest struct { + // JSON-RPC protocol. + Version string `json:"jsonrpc"` + + // A String containing the name of the method to be invoked. + Method string `json:"method"` + + // Object to pass as request parameter to the method. + Params interface{} `json:"params"` + + // The request id. This can be of any type. It is used to match the + // response with the request that it is replying to. + ID interface{} `json:"id"` +} diff --git a/protocol/json/client_response.go b/protocol/json/client_response.go new file mode 100644 index 0000000..359f850 --- /dev/null +++ b/protocol/json/client_response.go @@ -0,0 +1,97 @@ +package json + +import ( + "encoding/json" + "fmt" + + "git.loafle.net/commons/rpc-go/protocol" +) + +// ---------------------------------------------------------------------------- +// ClientResponseCodec +// ---------------------------------------------------------------------------- +// clientResponse represents a JSON-RPC response returned to a client. +type clientResponse struct { + Version string `json:"jsonrpc"` + Result *json.RawMessage `json:"result,omitempty"` + Error *json.RawMessage `json:"error,omitempty"` + ID interface{} `json:"id,omitempty"` +} + +// ClientResponseCodec decodes and encodes a single request. +type ClientResponseCodec struct { + res *clientResponse + err error +} + +func (crc *ClientResponseCodec) ID() interface{} { + return crc.res.ID +} + +func (crc *ClientResponseCodec) Result(result interface{}) error { + if nil == crc.err && nil != crc.res.Result { + if err := json.Unmarshal(*crc.res.Result, result); nil != err { + crc.err = &protocol.Error{ + Code: protocol.E_PARSE, + Message: err.Error(), + Data: crc.res.Result, + } + } + } + + return crc.err +} + +func (crc *ClientResponseCodec) Error() *protocol.Error { + if nil == crc.res.Error { + return nil + } + protocolError := &protocol.Error{} + err := json.Unmarshal(*crc.res.Error, protocolError) + if nil != err { + return &protocol.Error{ + Code: protocol.E_PARSE, + Message: err.Error(), + Data: crc.res.Error, + } + } + return protocolError +} + +func (crc *ClientResponseCodec) Notification() (protocol.ClientNotificationCodec, error) { + if nil != crc.res.ID || nil == crc.res.Result { + return nil, fmt.Errorf("RPC[JSON]: This is not notification") + } + + noti := &clientNotification{} + err := json.Unmarshal(*crc.res.Result, noti) + if nil != err { + return nil, err + } + + return &ClientNotificationCodec{noti: noti, err: err}, nil +} + +// newClientMessageCodec returns a new ClientMessageCodec. +func newClientResponseCodec(buf []byte) (protocol.ClientResponseCodec, error) { + + res := &clientResponse{} + err := json.Unmarshal(buf, res) + + if err != nil { + err = &protocol.Error{ + Code: protocol.E_PARSE, + Message: err.Error(), + Data: res, + } + } + if res.Version != Version { + err = &protocol.Error{ + Code: protocol.E_INVALID_REQ, + Message: "jsonrpc must be " + Version, + Data: res, + } + } + + return &ClientResponseCodec{res: res, err: err}, nil +} diff --git a/protocol/json/constants.go b/protocol/json/constants.go new file mode 100644 index 0000000..b5e4742 --- /dev/null +++ b/protocol/json/constants.go @@ -0,0 +1,6 @@ +package json + +const ( + Name = "jsonrpc" + Version = "2.0" +) diff --git a/protocol/json/server.go b/protocol/json/server.go new file mode 100644 index 0000000..0c044e1 --- /dev/null +++ b/protocol/json/server.go @@ -0,0 +1,38 @@ +package json + +import ( + "encoding/json" + + "git.loafle.net/commons/rpc-go/protocol" +) + +var null = json.RawMessage([]byte("null")) + +// ---------------------------------------------------------------------------- +// Codec +// ---------------------------------------------------------------------------- + +// NewServerCodec returns a new JSON Codec. +func NewServerCodec() protocol.ServerCodec { + return &ServerCodec{} +} + +// ServerCodec creates a ServerRequestCodec to process each request. +type ServerCodec struct { +} + +func (sc *ServerCodec) NewRequest(buf []byte) (protocol.ServerRequestCodec, error) { + return newServerRequestCodec(buf) +} + +func (sc *ServerCodec) NewNotification(method string, args []interface{}) ([]byte, error) { + params, err := convertParamsToStringArray(args) + if nil != err { + return nil, err + } + + noti := &serverNotification{Method: method, Params: params} + res := &serverResponse{Version: Version, Result: noti} + + return json.Marshal(res) +} diff --git a/protocol/json/server_notification.go b/protocol/json/server_notification.go new file mode 100644 index 0000000..124670a --- /dev/null +++ b/protocol/json/server_notification.go @@ -0,0 +1,10 @@ +package json + +// clientRequest represents a JSON-RPC request sent by a client. +type serverNotification struct { + // A String containing the name of the method to be invoked. + Method string `json:"method"` + + // Object to pass as request parameter to the method. + Params interface{} `json:"params"` +} diff --git a/protocol/json/server_request.go b/protocol/json/server_request.go new file mode 100644 index 0000000..2b9d214 --- /dev/null +++ b/protocol/json/server_request.go @@ -0,0 +1,143 @@ +package json + +import ( + "encoding/json" + + "git.loafle.net/commons/rpc-go/protocol" + cuej "git.loafle.net/commons/util-go/encoding/json" +) + +// ---------------------------------------------------------------------------- +// Request +// ---------------------------------------------------------------------------- + +// serverRequest represents a JSON-RPC request received by the server. +type serverRequest struct { + // JSON-RPC protocol. + Version string `json:"jsonrpc"` + + // A String containing the name of the method to be invoked. + Method string `json:"method"` + + // A Structured value to pass as arguments to the method. + Params *json.RawMessage `json:"params,omitempty"` + + // The request id. MUST be a string, number or null. + // Our implementation will not do type checking for id. + // It will be copied as it is. + ID *json.RawMessage `json:"id,omitempty"` +} + +// ---------------------------------------------------------------------------- +// ServerRequestCodec +// ---------------------------------------------------------------------------- + +// newRequestCodec returns a new ServerRequestCodec. +func newServerRequestCodec(buf []byte) (protocol.ServerRequestCodec, error) { + + req := &serverRequest{} + err := json.Unmarshal(buf, req) + + if err != nil { + err = &protocol.Error{ + Code: protocol.E_PARSE, + Message: err.Error(), + Data: req, + } + } + + if req.Version != Version { + err = &protocol.Error{ + Code: protocol.E_INVALID_REQ, + Message: "jsonrpc must be " + Version, + Data: req, + } + } + + return &ServerRequestCodec{req: req, err: err}, nil +} + +// ServerRequestCodec decodes and encodes a single request. +type ServerRequestCodec struct { + req *serverRequest + err error +} + +func (src *ServerRequestCodec) HasResponse() bool { + return src.req.ID != nil +} + +// Method returns the RPC method for the current request. +// +// The method uses a dotted notation as in "Service.Method". +func (src *ServerRequestCodec) Method() string { + return src.req.Method +} + +// ReadRequest fills the request object for the RPC method. +// +// ReadRequest parses request parameters in two supported forms in +// accordance with http://www.jsonrpc.org/specification#parameter_structures +// +// by-position: params MUST be an Array, containing the +// values in the Server expected order. +// +// by-name: params MUST be an Object, with member names +// that match the Server expected parameter names. The +// absence of expected names MAY result in an error being +// generated. The names MUST match exactly, including +// case, to the method's expected parameters. +func (src *ServerRequestCodec) ReadParams(args []interface{}) error { + if src.err == nil && src.req.Params != nil { + // Note: if scr.request.Params is nil it's not an error, it's an optional member. + // JSON params structured object. Unmarshal to the args object. + if err := cuej.SetValueWithJSONStringArray(*src.req.Params, args); nil != err { + src.err = &protocol.Error{ + Code: protocol.E_INVALID_REQ, + Message: err.Error(), + Data: src.req.Params, + } + return src.err + } + return nil + } + return src.err +} + +func (src *ServerRequestCodec) Params() ([]string, error) { + if src.err == nil && src.req.Params != nil { + var values []string + + if err := json.Unmarshal(*src.req.Params, &values); err != nil { + src.err = &protocol.Error{ + Code: protocol.E_INVALID_REQ, + Message: err.Error(), + Data: src.req.Params, + } + return nil, src.err + } + + return values, nil + } + return nil, src.err +} + +func (src *ServerRequestCodec) NewResponse(reply interface{}) ([]byte, error) { + res := &serverResponse{Version: Version, Result: reply, ID: src.req.ID} + return src.newServerResponse(res) +} +func (src *ServerRequestCodec) NewError(status int, err error) ([]byte, error) { + jsonErr, ok := err.(*protocol.Error) + if !ok { + jsonErr = &protocol.Error{ + Code: protocol.E_SERVER, + Message: err.Error(), + } + } + res := &serverResponse{Version: Version, Error: jsonErr, ID: src.req.ID} + return src.newServerResponse(res) +} +func (src *ServerRequestCodec) newServerResponse(res *serverResponse) ([]byte, error) { + + return json.Marshal(res) +} diff --git a/protocol/json/server_response.go b/protocol/json/server_response.go new file mode 100644 index 0000000..373785a --- /dev/null +++ b/protocol/json/server_response.go @@ -0,0 +1,29 @@ +package json + +import ( + "encoding/json" + + "git.loafle.net/commons/rpc-go/protocol" +) + +// ---------------------------------------------------------------------------- +// Response +// ---------------------------------------------------------------------------- + +// serverResponse represents a JSON-RPC response returned by the server. +type serverResponse struct { + // JSON-RPC protocol. + Version string `json:"jsonrpc"` + // The Object that was returned by the invoked method. This must be null + // in case there was an error invoking the method. + // As per spec the member will be omitted if there was an error. + Result interface{} `json:"result,omitempty"` + + // An Error object if there was an error invoking the method. It must be + // null if there was no error. + // As per spec the member will be omitted if there was no error. + Error *protocol.Error `json:"error,omitempty"` + + // This must be the same id as the request it is responding to. + ID *json.RawMessage `json:"id,omitempty"` +} diff --git a/protocol/json/util.go b/protocol/json/util.go new file mode 100644 index 0000000..0bbd605 --- /dev/null +++ b/protocol/json/util.go @@ -0,0 +1,46 @@ +package json + +import ( + "encoding/json" + "fmt" + "reflect" + + cur "git.loafle.net/commons/util-go/reflect" +) + +func convertParamsToStringArray(params []interface{}) ([]string, error) { + var values []string + if nil == params || 0 == len(params) { + return values, nil + } + + for i, param := range params { + t := reflect.TypeOf(param) + switch t.Kind() { + case reflect.String: + values = append(values, param.(string)) + case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: + b, err := json.Marshal(param) + if nil != err { + return nil, err + } + values = append(values, string(b)) + case reflect.Ptr: + if t.Elem().Kind() != reflect.Struct { + return nil, fmt.Errorf("Pointer of param[%d] is permitted only Struct type", i) + } + b, err := json.Marshal(param) + if nil != err { + return nil, err + } + values = append(values, string(b)) + default: + s, err := cur.ConvertToString(param) + if nil != err { + return nil, fmt.Errorf("String conversion of param[%d] has been failed [%v]", i, err) + } + values = append(values, s) + } + } + return values, nil +} diff --git a/protocol/registry_codec.go b/protocol/registry_codec.go new file mode 100644 index 0000000..88adc7e --- /dev/null +++ b/protocol/registry_codec.go @@ -0,0 +1,15 @@ +package protocol + +// ---------------------------------------------------------------------------- +// Codec +// ---------------------------------------------------------------------------- +// RegistryCodec creates a RegistryCodecRequest to process each request. +type RegistryCodec interface { + // Reads the request and returns the RPC method name. + Method() string + // Reads the request filling the RPC method args. + ReadParams(args []interface{}) error + Params() ([]string, error) + + HasResponse() bool +} diff --git a/protocol/server_codec.go b/protocol/server_codec.go new file mode 100644 index 0000000..2181669 --- /dev/null +++ b/protocol/server_codec.go @@ -0,0 +1,16 @@ +package protocol + +// ServerCodec creates a ServerRequestCodec to process each request. +type ServerCodec interface { + NewRequest(buf []byte) (ServerRequestCodec, error) + NewNotification(method string, args []interface{}) ([]byte, error) +} + +// ServerRequestCodec decodes a request and encodes a response using a specific +// serialization scheme. +type ServerRequestCodec interface { + RegistryCodec + + NewResponse(reply interface{}) ([]byte, error) + NewError(status int, err error) ([]byte, error) +} diff --git a/registry/registry.go b/registry/registry.go new file mode 100644 index 0000000..f41936c --- /dev/null +++ b/registry/registry.go @@ -0,0 +1,10 @@ +package registry + +import ( + "git.loafle.net/commons/rpc-go/protocol" +) + +type RPCInvoker interface { + HasMethod(method string) bool + Invoke(codec protocol.RegistryCodec) (result interface{}, err error) +} diff --git a/registry/rpc_registry.go b/registry/rpc_registry.go new file mode 100644 index 0000000..8a158c9 --- /dev/null +++ b/registry/rpc_registry.go @@ -0,0 +1,148 @@ +package registry + +import ( + "reflect" + + "git.loafle.net/commons/rpc-go/protocol" +) + +// NewRPCRegistry returns a new RPC registry. +func NewRPCRegistry() RPCRegistry { + return &rpcRegistry{ + services: new(serviceMap), + } +} + +type RPCRegistry interface { + RPCInvoker + GetService(name string) interface{} + RegisterService(receiver interface{}, name string) error +} + +// RPCRegistry serves registered RPC services using registered codecs. +type rpcRegistry struct { + services *serviceMap +} + +// RegisterService adds a new service to the server. +// +// The name parameter is optional: if empty it will be inferred from +// the receiver type name. +// +// Methods from the receiver will be extracted if these rules are satisfied: +// +// - The receiver is exported (begins with an upper case letter) or local +// (defined in the package registering the service). +// - The method name is exported. +// - The method has two arguments: *args, *reply. +// - All two arguments are pointers. +// - The first and second arguments are exported or local. +// - The method has return type error. +// +// All other methods are ignored. +func (rr *rpcRegistry) RegisterService(receiver interface{}, name string) error { + return rr.services.register(receiver, name) +} + +func (rr *rpcRegistry) GetService(name string) interface{} { + return rr.services.getService(name) +} + +// HasMethod returns true if the given method is registered. +// +// The method uses a dotted notation as in "Service.Method". +func (rr *rpcRegistry) HasMethod(method string) bool { + if _, _, err := rr.services.get(method); err == nil { + return true + } + return false +} + +// Invoke execute a method. +// +// Codecs are defined to process a given serialization scheme, e.g., JSON or +// XML. A codec is chosen based on the "Content-Type" header from the request, +// excluding the charset definition. +func (rr *rpcRegistry) Invoke(codec protocol.RegistryCodec) (result interface{}, err error) { + serviceSpec, methodSpec, errGet := rr.services.get(codec.Method()) + if errGet != nil { + return nil, errGet + } + // Decode the args. + + var in []reflect.Value + pValues, pInstances := methodSpec.getParamValues() + + if nil != pInstances && 0 < len(pInstances) { + if errRead := codec.ReadParams(pInstances); errRead != nil { + return nil, errRead + } + pCount := len(pInstances) + in = make([]reflect.Value, pCount+1) + for indexI := 0; indexI < pCount; indexI++ { + if pValues[indexI].Type().Kind() == reflect.Ptr && pValues[indexI].Type().Elem().Kind() != reflect.Struct { + in[indexI+1] = reflect.Indirect(pValues[indexI]) + } else { + in[indexI+1] = pValues[indexI] + } + } + } else { + in = make([]reflect.Value, 1) + } + in[0] = serviceSpec.rcvrV + + // Call the service method. + returnValues := methodSpec.method.Func.Call(in) + + if nil != methodSpec.returnType { + result = returnValues[0].Interface() + if nil != returnValues[1].Interface() { + err = returnValues[1].Interface().(error) + } + } else { + if nil != returnValues[0].Interface() { + err = returnValues[0].Interface().(error) + } + } + + return +} + +// func convertValues(values []interface{}, types []reflect.Type) []reflect.Value { +// c := len(values) +// vs := make([]reflect.Value, c) +// for indexI := 0; indexI < c; indexI++ { +// vs[indexI] = convertValue(&values[indexI], types[indexI]) +// } +// return vs +// } + +// func convertValue(v *interface{}, t reflect.Type) reflect.Value { +// switch t.Kind() { +// case reflect.Bool: +// return reflect.ValueOf(*v).Convert(t) +// case reflect.Float32, reflect.Float64: +// return reflect.ValueOf(*v).Convert(t) +// case reflect.Int, reflect.Int32, reflect.Int64: +// return reflect.ValueOf(*v).Convert(t) +// case reflect.Interface: +// // When we see an interface, we make our own thing +// return reflect.ValueOf(*v).Convert(t) +// case reflect.Map: +// return reflect.ValueOf(*v).M +// return d.decodeMap(name, node, result) +// case reflect.Ptr: +// return d.decodePtr(name, node, result) +// case reflect.Slice: +// return d.decodeSlice(name, node, result) +// case reflect.String: +// return d.decodeString(name, node, result) +// case reflect.Struct: +// return d.decodeStruct(name, node, result) +// default: +// return &parser.PosError{ +// Pos: node.Pos(), +// Err: fmt.Errorf("%s: unknown kind to decode into: %s", name, k.Kind()), +// } +// } +// } diff --git a/registry/service_map.go b/registry/service_map.go new file mode 100644 index 0000000..10635d1 --- /dev/null +++ b/registry/service_map.go @@ -0,0 +1,229 @@ +package registry + +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 + returnType reflect.Type // type of the response argument +} + +func (sm *serviceMethod) getParamValues() (values []reflect.Value, instances []interface{}) { + if nil == sm.paramTypes || 0 == len(sm.paramTypes) { + return nil, nil + } + + pCount := len(sm.paramTypes) + values = make([]reflect.Value, pCount) + instances = make([]interface{}, pCount) + + for indexI := 0; indexI < pCount; indexI++ { + values[indexI] = getValue(sm.paramTypes[indexI]) + instances[indexI] = values[indexI].Interface() + } + + return +} + +func getValue(t reflect.Type) reflect.Value { + rt := t + if rt.Kind() == reflect.Ptr { + rt = rt.Elem() + } + + // rv := reflect.New(rt) + // if rt.Kind() != reflect.Struct { + // rv = reflect.Indirect(rv) + // } + + // var rv reflect.Value + + // switch rt.Kind() { + // case reflect.Slice: + // rv = reflect.New(reflect.SliceOf(rt.Elem())) + // default: + // rv = reflect.New(rt) + // } + + return reflect.New(rt) +} + +// ---------------------------------------------------------------------------- +// serviceMap +// ---------------------------------------------------------------------------- + +// serviceMap is a registry for services. +type serviceMap struct { + mutex sync.RWMutex + services map[string]*service +} + +func (sm *serviceMap) getService(name string) interface{} { + sm.mutex.RLock() + defer sm.mutex.RUnlock() + if sm.services == nil { + return nil + } + return sm.services[name] +} + +// register adds a new service using reflection to extract its methods. +func (sm *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 returnType reflect.Type + + pCount := mt.NumIn() - 1 + + if 0 < pCount { + paramTypes = make([]reflect.Type, pCount) + + for indexI := 0; indexI < pCount; indexI++ { + pt := mt.In(indexI + 1) + if err = validateType(mt.In(indexI + 1)); nil != err { + return err + } + paramTypes[indexI] = pt + } + } + + 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 + } + rt := mt.Out(0) + if err = validateType(rt); nil != err { + return err + } + returnType = rt + default: + continue + } + + s.methods[m.Name] = &serviceMethod{ + method: m, + paramTypes: paramTypes, + returnType: returnType, + } + } + if len(s.methods) == 0 { + return fmt.Errorf("rpc: %q has no exported methods of suitable type", s.name) + } + // Add to the map. + sm.mutex.Lock() + defer sm.mutex.Unlock() + if sm.services == nil { + sm.services = make(map[string]*service) + } else if _, ok := sm.services[s.name]; ok { + return fmt.Errorf("rpc: service already defined: %q", s.name) + } + sm.services[s.name] = s + return nil +} + +func validateType(t reflect.Type) error { + if t.Kind() == reflect.Struct { + return fmt.Errorf("Type is Struct. Pass by reference, i.e. *%s", t) + } + return nil +} + +// get returns a registered service given a method name. +// +// The method name uses a dotted notation as in "Service.Method". +func (sm *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 + } + sm.mutex.Lock() + service := sm.services[parts[0]] + sm.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() == "" +}