ing
This commit is contained in:
commit
267d8ac0e1
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
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
}
|
83
adapter/http/handler.go
Normal file
83
adapter/http/handler.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPAdapter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP
|
||||||
|
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
WriteError(w, 405, "rpc: POST method required, received "+r.Method)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
idx := strings.Index(contentType, ";")
|
||||||
|
if idx != -1 {
|
||||||
|
contentType = contentType[:idx]
|
||||||
|
}
|
||||||
|
var codec Codec
|
||||||
|
if contentType == "" && len(s.codecs) == 1 {
|
||||||
|
// If Content-Type is not set and only one codec has been registered,
|
||||||
|
// then default to that codec.
|
||||||
|
for _, c := range s.codecs {
|
||||||
|
codec = c
|
||||||
|
}
|
||||||
|
} else if codec = s.codecs[strings.ToLower(contentType)]; codec == nil {
|
||||||
|
WriteError(w, 415, "rpc: unrecognized Content-Type: "+contentType)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Create a new codec request.
|
||||||
|
codecReq := codec.NewRequest(r)
|
||||||
|
// Get service method to be called.
|
||||||
|
method, errMethod := codecReq.Method()
|
||||||
|
if errMethod != nil {
|
||||||
|
codecReq.WriteError(w, 400, errMethod)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serviceSpec, methodSpec, errGet := s.services.get(method)
|
||||||
|
if errGet != nil {
|
||||||
|
codecReq.WriteError(w, 400, errGet)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Decode the args.
|
||||||
|
args := reflect.New(methodSpec.argsType)
|
||||||
|
if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil {
|
||||||
|
codecReq.WriteError(w, 400, errRead)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Call the service method.
|
||||||
|
reply := reflect.New(methodSpec.replyType)
|
||||||
|
errValue := methodSpec.method.Func.Call([]reflect.Value{
|
||||||
|
serviceSpec.rcvr,
|
||||||
|
reflect.ValueOf(r),
|
||||||
|
args,
|
||||||
|
reply,
|
||||||
|
})
|
||||||
|
// Cast the result to error if needed.
|
||||||
|
var errResult error
|
||||||
|
errInter := errValue[0].Interface()
|
||||||
|
if errInter != nil {
|
||||||
|
errResult = errInter.(error)
|
||||||
|
}
|
||||||
|
// Prevents Internet Explorer from MIME-sniffing a response away
|
||||||
|
// from the declared content-type
|
||||||
|
w.Header().Set("x-content-type-options", "nosniff")
|
||||||
|
// Encode the response.
|
||||||
|
if errResult == nil {
|
||||||
|
codecReq.WriteResponse(w, reply.Interface())
|
||||||
|
} else {
|
||||||
|
codecReq.WriteError(w, 400, errResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteError(w http.ResponseWriter, status int, msg string) {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
fmt.Fprint(w, msg)
|
||||||
|
}
|
27
codec/codec.go
Normal file
27
codec/codec.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package codec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Codec
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Codec creates a CodecRequest to process each request.
|
||||||
|
type Codec interface {
|
||||||
|
NewRequest(r io.Reader) (CodecRequest, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodecRequest decodes a request and encodes a response using a specific
|
||||||
|
// serialization scheme.
|
||||||
|
type CodecRequest interface {
|
||||||
|
// Reads the request and returns the RPC method name.
|
||||||
|
Method() (string, error)
|
||||||
|
// Reads the request filling the RPC method args.
|
||||||
|
ReadRequest(interface{}) error
|
||||||
|
// Writes the response using the RPC method reply.
|
||||||
|
WriteResponse(io.Writer, interface{})
|
||||||
|
// Writes an error produced by the server.
|
||||||
|
WriteError(w io.Writer, status int, err error)
|
||||||
|
}
|
34
codec/json/error.go
Normal file
34
codec/json/error.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 e.Message
|
||||||
|
}
|
195
codec/json/server.go
Normal file
195
codec/json/server.go
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var null = json.RawMessage([]byte("null"))
|
||||||
|
var Version = "2.0"
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Request and Response
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 *Error `json:"error,omitempty"`
|
||||||
|
|
||||||
|
// This must be the same id as the request it is responding to.
|
||||||
|
ID *json.RawMessage `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Codec
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NewcustomCodec returns a new JSON Codec based on passed encoder selector.
|
||||||
|
func NewCustomCodec(encSel rpc.EncoderSelector) *Codec {
|
||||||
|
return &Codec{encSel: encSel}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCodec returns a new JSON Codec.
|
||||||
|
func NewCodec() *Codec {
|
||||||
|
return NewCustomCodec(rpc.DefaultEncoderSelector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codec creates a CodecRequest to process each request.
|
||||||
|
type Codec struct {
|
||||||
|
encSel rpc.EncoderSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest returns a CodecRequest.
|
||||||
|
func (c *Codec) NewRequest(r *http.Request) rpc.CodecRequest {
|
||||||
|
return newCodecRequest(r, c.encSel.Select(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// CodecRequest
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// newCodecRequest returns a new CodecRequest.
|
||||||
|
func newCodecRequest(r *http.Request, encoder rpc.Encoder) rpc.CodecRequest {
|
||||||
|
// Decode the request body and check if RPC method is valid.
|
||||||
|
req := new(serverRequest)
|
||||||
|
err := json.NewDecoder(r.Body).Decode(req)
|
||||||
|
if err != nil {
|
||||||
|
err = &Error{
|
||||||
|
Code: E_PARSE,
|
||||||
|
Message: err.Error(),
|
||||||
|
Data: req,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req.Version != Version {
|
||||||
|
err = &Error{
|
||||||
|
Code: E_INVALID_REQ,
|
||||||
|
Message: "jsonrpc must be " + Version,
|
||||||
|
Data: req,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.Body.Close()
|
||||||
|
return &CodecRequest{request: req, err: err, encoder: encoder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodecRequest decodes and encodes a single request.
|
||||||
|
type CodecRequest struct {
|
||||||
|
request *serverRequest
|
||||||
|
err error
|
||||||
|
encoder rpc.Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns the RPC method for the current request.
|
||||||
|
//
|
||||||
|
// The method uses a dotted notation as in "Service.Method".
|
||||||
|
func (c *CodecRequest) Method() (string, error) {
|
||||||
|
if c.err == nil {
|
||||||
|
return c.request.Method, nil
|
||||||
|
}
|
||||||
|
return "", c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (c *CodecRequest) ReadRequest(args interface{}) error {
|
||||||
|
if c.err == nil && c.request.Params != nil {
|
||||||
|
// Note: if c.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 := json.Unmarshal(*c.request.Params, args); err != nil {
|
||||||
|
// Clearly JSON params is not a structured object,
|
||||||
|
// fallback and attempt an unmarshal with JSON params as
|
||||||
|
// array value and RPC params is struct. Unmarshal into
|
||||||
|
// array containing the request struct.
|
||||||
|
params := [1]interface{}{args}
|
||||||
|
if err = json.Unmarshal(*c.request.Params, ¶ms); err != nil {
|
||||||
|
c.err = &Error{
|
||||||
|
Code: E_INVALID_REQ,
|
||||||
|
Message: err.Error(),
|
||||||
|
Data: c.request.Params,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteResponse encodes the response and writes it to the ResponseWriter.
|
||||||
|
func (c *CodecRequest) WriteResponse(w http.ResponseWriter, reply interface{}) {
|
||||||
|
res := &serverResponse{
|
||||||
|
Version: Version,
|
||||||
|
Result: reply,
|
||||||
|
Id: c.request.Id,
|
||||||
|
}
|
||||||
|
c.writeServerResponse(w, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CodecRequest) WriteError(w http.ResponseWriter, status int, err error) {
|
||||||
|
jsonErr, ok := err.(*Error)
|
||||||
|
if !ok {
|
||||||
|
jsonErr = &Error{
|
||||||
|
Code: E_SERVER,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := &serverResponse{
|
||||||
|
Version: Version,
|
||||||
|
Error: jsonErr,
|
||||||
|
Id: c.request.Id,
|
||||||
|
}
|
||||||
|
c.writeServerResponse(w, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CodecRequest) writeServerResponse(w http.ResponseWriter, res *serverResponse) {
|
||||||
|
// Id is null for notifications and they don't have a response.
|
||||||
|
if c.request.Id != nil {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
encoder := json.NewEncoder(c.encoder.Encode(w))
|
||||||
|
err := encoder.Encode(res)
|
||||||
|
|
||||||
|
// Not sure in which case will this happen. But seems harmless.
|
||||||
|
if err != nil {
|
||||||
|
rpc.WriteError(w, 400, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyResponse struct {
|
||||||
|
}
|
38
encoder_selector.go
Normal file
38
encoder_selector.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder interface contains the encoder for http response.
|
||||||
|
// Eg. gzip, flate compressions.
|
||||||
|
type Encoder interface {
|
||||||
|
Encode(w http.ResponseWriter) io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
type encoder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *encoder) Encode(w http.ResponseWriter) io.Writer {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultEncoder = &encoder{}
|
||||||
|
|
||||||
|
// EncoderSelector interface provides a way to select encoder using the http
|
||||||
|
// request. Typically people can use this to check HEADER of the request and
|
||||||
|
// figure out client capabilities.
|
||||||
|
// Eg. "Accept-Encoding" tells about supported compressions.
|
||||||
|
type EncoderSelector interface {
|
||||||
|
Select(r *http.Request) Encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type encoderSelector struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ *encoderSelector) Select(_ *http.Request) Encoder {
|
||||||
|
return DefaultEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultEncoderSelector = &encoderSelector{}
|
142
registry.go
Normal file
142
registry.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.loafle.net/commons_go/rpc/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
Network connection
|
||||||
|
Handshake(1..n)
|
||||||
|
Send request
|
||||||
|
HTTP
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// NewRPCRegistry returns a new RPC registry.
|
||||||
|
func NewRegistry() Registry {
|
||||||
|
return &rpcRegistry{
|
||||||
|
codecs: make(map[string]codec.Codec),
|
||||||
|
services: new(serviceMap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registry interface {
|
||||||
|
RegisterCodec(codec codec.Codec, contentType string)
|
||||||
|
RegisterService(receiver interface{}, name string) error
|
||||||
|
HasMethod(method string) bool
|
||||||
|
Invoke(contentType string, reader io.Reader, writer io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCRegistry serves registered RPC services using registered codecs.
|
||||||
|
type rpcRegistry struct {
|
||||||
|
codecs map[string]codec.Codec
|
||||||
|
services *serviceMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCodec adds a new codec to the server.
|
||||||
|
//
|
||||||
|
// 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 (r *rpcRegistry) RegisterCodec(codec codec.Codec, contentType string) {
|
||||||
|
r.codecs[strings.ToLower(contentType)] = codec
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 three arguments: *http.Request, *args, *reply.
|
||||||
|
// - All three arguments are pointers.
|
||||||
|
// - The second and third arguments are exported or local.
|
||||||
|
// - The method has return type error.
|
||||||
|
//
|
||||||
|
// All other methods are ignored.
|
||||||
|
func (r *rpcRegistry) RegisterService(receiver interface{}, name string) error {
|
||||||
|
return r.services.register(receiver, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasMethod returns true if the given method is registered.
|
||||||
|
//
|
||||||
|
// The method uses a dotted notation as in "Service.Method".
|
||||||
|
func (r *rpcRegistry) HasMethod(method string) bool {
|
||||||
|
if _, _, err := r.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 (r *rpcRegistry) Invoke(contentType string, reader io.Reader, writer io.Writer) error {
|
||||||
|
var codec codec.Codec
|
||||||
|
if contentType == "" && len(r.codecs) == 1 {
|
||||||
|
// If Content-Type is not set and only one codec has been registered,
|
||||||
|
// then default to that codec.
|
||||||
|
for _, c := range r.codecs {
|
||||||
|
codec = c
|
||||||
|
}
|
||||||
|
} else if codec = r.codecs[strings.ToLower(contentType)]; codec == nil {
|
||||||
|
return fmt.Errorf("Unrecognized Content-Type: %s", contentType)
|
||||||
|
}
|
||||||
|
// Create a new codec request.
|
||||||
|
codecReq, hasResponse := codec.NewRequest(reader)
|
||||||
|
// Get service method to be called.
|
||||||
|
method, errMethod := codecReq.Method()
|
||||||
|
if errMethod != nil {
|
||||||
|
return errMethod
|
||||||
|
}
|
||||||
|
serviceSpec, methodSpec, errGet := r.services.get(method)
|
||||||
|
if errGet != nil {
|
||||||
|
return errGet
|
||||||
|
}
|
||||||
|
// Decode the args.
|
||||||
|
args := reflect.New(methodSpec.argsType)
|
||||||
|
if errRead := codecReq.ReadRequest(args.Interface()); errRead != nil {
|
||||||
|
return errRead
|
||||||
|
}
|
||||||
|
// Call the service method.
|
||||||
|
reply := reflect.New(methodSpec.replyType)
|
||||||
|
errValue := methodSpec.method.Func.Call([]reflect.Value{
|
||||||
|
serviceSpec.rcvr,
|
||||||
|
reflect.ValueOf(r),
|
||||||
|
args,
|
||||||
|
reply,
|
||||||
|
})
|
||||||
|
|
||||||
|
if !hasResponse {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cast the result to error if needed.
|
||||||
|
var errResult error
|
||||||
|
errInter := errValue[0].Interface()
|
||||||
|
if errInter != nil {
|
||||||
|
errResult = errInter.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the response.
|
||||||
|
if errResult == nil {
|
||||||
|
codecReq.WriteResponse(writer, reply.Interface())
|
||||||
|
} else {
|
||||||
|
codecReq.WriteError(writer, 400, errResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
159
service_map.go
Normal file
159
service_map.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Precompute the reflect.Type of error and http.Request
|
||||||
|
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
|
typeOfRequest = reflect.TypeOf((*http.Request)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// service
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
name string // name of service
|
||||||
|
rcvr reflect.Value // receiver of methods for the service
|
||||||
|
rcvrType reflect.Type // type of the receiver
|
||||||
|
methods map[string]*serviceMethod // registered methods
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceMethod struct {
|
||||||
|
method reflect.Method // receiver method
|
||||||
|
argsType reflect.Type // type of the request argument
|
||||||
|
replyType reflect.Type // type of the response argument
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// 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,
|
||||||
|
rcvr: reflect.ValueOf(rcvr),
|
||||||
|
rcvrType: reflect.TypeOf(rcvr),
|
||||||
|
methods: make(map[string]*serviceMethod),
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
s.name = reflect.Indirect(s.rcvr).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.rcvrType.String())
|
||||||
|
}
|
||||||
|
// Setup methods.
|
||||||
|
for i := 0; i < s.rcvrType.NumMethod(); i++ {
|
||||||
|
method := s.rcvrType.Method(i)
|
||||||
|
mtype := method.Type
|
||||||
|
// Method must be exported.
|
||||||
|
if method.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Method needs four ins: receiver, *http.Request, *args, *reply.
|
||||||
|
if mtype.NumIn() != 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// First argument must be a pointer and must be http.Request.
|
||||||
|
reqType := mtype.In(1)
|
||||||
|
if reqType.Kind() != reflect.Ptr || reqType.Elem() != typeOfRequest {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Second argument must be a pointer and must be exported.
|
||||||
|
args := mtype.In(2)
|
||||||
|
if args.Kind() != reflect.Ptr || !isExportedOrBuiltin(args) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Third argument must be a pointer and must be exported.
|
||||||
|
reply := mtype.In(3)
|
||||||
|
if reply.Kind() != reflect.Ptr || !isExportedOrBuiltin(reply) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Method needs one out: error.
|
||||||
|
if mtype.NumOut() != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if returnType := mtype.Out(0); returnType != typeOfError {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.methods[method.Name] = &serviceMethod{
|
||||||
|
method: method,
|
||||||
|
argsType: args.Elem(),
|
||||||
|
replyType: reply.Elem(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() == ""
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user