ing
This commit is contained in:
commit
8587e2e369
68
.gitignore
vendored
Normal file
68
.gitignore
vendored
Normal file
|
@ -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
|
32
.vscode/launch.json
vendored
Normal file
32
.vscode/launch.json
vendored
Normal file
|
@ -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
|
||||
}
|
||||
|
||||
]
|
||||
}
|
6
glide.yaml
Normal file
6
glide.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
package: git.loafle.net/commons/rpc-go
|
||||
import:
|
||||
- package: git.loafle.net/commons/util-go
|
||||
subpackages:
|
||||
- encoding/json
|
||||
- reflect
|
18
protocol/client_codec.go
Normal file
18
protocol/client_codec.go
Normal file
|
@ -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
|
||||
}
|
35
protocol/error.go
Normal file
35
protocol/error.go
Normal file
|
@ -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)
|
||||
}
|
39
protocol/json/client.go
Normal file
39
protocol/json/client.go
Normal file
|
@ -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)
|
||||
}
|
68
protocol/json/client_notification.go
Normal file
68
protocol/json/client_notification.go
Normal file
|
@ -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
|
||||
}
|
21
protocol/json/client_request.go
Normal file
21
protocol/json/client_request.go
Normal file
|
@ -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"`
|
||||
}
|
97
protocol/json/client_response.go
Normal file
97
protocol/json/client_response.go
Normal file
|
@ -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
|
||||
}
|
6
protocol/json/constants.go
Normal file
6
protocol/json/constants.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package json
|
||||
|
||||
const (
|
||||
Name = "jsonrpc"
|
||||
Version = "2.0"
|
||||
)
|
38
protocol/json/server.go
Normal file
38
protocol/json/server.go
Normal file
|
@ -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)
|
||||
}
|
10
protocol/json/server_notification.go
Normal file
10
protocol/json/server_notification.go
Normal file
|
@ -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"`
|
||||
}
|
143
protocol/json/server_request.go
Normal file
143
protocol/json/server_request.go
Normal file
|
@ -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)
|
||||
}
|
29
protocol/json/server_response.go
Normal file
29
protocol/json/server_response.go
Normal file
|
@ -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"`
|
||||
}
|
46
protocol/json/util.go
Normal file
46
protocol/json/util.go
Normal file
|
@ -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
|
||||
}
|
15
protocol/registry_codec.go
Normal file
15
protocol/registry_codec.go
Normal file
|
@ -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
|
||||
}
|
16
protocol/server_codec.go
Normal file
16
protocol/server_codec.go
Normal file
|
@ -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)
|
||||
}
|
10
registry/registry.go
Normal file
10
registry/registry.go
Normal file
|
@ -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)
|
||||
}
|
148
registry/rpc_registry.go
Normal file
148
registry/rpc_registry.go
Normal file
|
@ -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()),
|
||||
// }
|
||||
// }
|
||||
// }
|
229
registry/service_map.go
Normal file
229
registry/service_map.go
Normal file
|
@ -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() == ""
|
||||
}
|
Loading…
Reference in New Issue
Block a user