112 lines
3.6 KiB
Go
112 lines
3.6 KiB
Go
// Package mrpc contains types and functionality to facilitate creating RPC
|
|
// interfaces and for making calls against those same interfaces.
|
|
//
|
|
// This package contains a few fundamental types: Handler, Call, and
|
|
// Client. Together these form the components needed to implement nearly any RPC
|
|
// system.
|
|
//
|
|
// TODO document examples
|
|
// TODO document Debug?
|
|
package mrpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
// Handler is a type which serves RPC calls. For each incoming Call the ServeRPC
|
|
// method is called, and the return from the method is used as the response. If
|
|
// an error is returned the response return is ignored.
|
|
type Handler interface {
|
|
ServeRPC(Call) (interface{}, error)
|
|
}
|
|
|
|
// HandlerFunc can be used to wrap an individual function which fits the
|
|
// ServeRPC signature, and use that function as a Handler
|
|
type HandlerFunc func(Call) (interface{}, error)
|
|
|
|
// ServeRPC implements the method for the Handler interface by calling the
|
|
// underlying function
|
|
func (hf HandlerFunc) ServeRPC(c Call) (interface{}, error) {
|
|
return hf(c)
|
|
}
|
|
|
|
// Call is passed into the ServeRPC method and contains all information about
|
|
// the incoming RPC call which is being made
|
|
type Call struct {
|
|
// Context relating to the call. May contain extra metadata/debug
|
|
// information, be used for further interaction with the underlying
|
|
// protocol, or be used for timeout/disconnect cancelation.
|
|
Context context.Context
|
|
|
|
// The name of the RPC method being called
|
|
Method string
|
|
|
|
// UnmarshalArgs takes in a pointer and unmarshals the RPC call's arguments
|
|
// into it. The properties of the unmarshaling are dependent on the
|
|
// underlying implementation of the codec.
|
|
UnmarshalArgs func(interface{}) error
|
|
}
|
|
|
|
// Client is an entity which can perform RPC calls against a remote endpoint.
|
|
//
|
|
// res should be a pointer into which the result of the RPC call will be
|
|
// unmarshaled according to Client's implementation. args will be marshaled and
|
|
// sent to the remote endpoint according to Client's implementation.
|
|
type Client interface {
|
|
CallRPC(ctx context.Context, res interface{}, method string, args interface{}) error
|
|
}
|
|
|
|
// ClientFunc can be used to wrap an individual function which fits the CallRPC
|
|
// signature, and use that function as a Client
|
|
type ClientFunc func(context.Context, interface{}, string, interface{}) error
|
|
|
|
// CallRPC implements the method for the Client interface by calling the
|
|
// underlying function
|
|
func (cf ClientFunc) CallRPC(
|
|
ctx context.Context,
|
|
res interface{},
|
|
method string,
|
|
args interface{},
|
|
) error {
|
|
return cf(ctx, res, method, args)
|
|
}
|
|
|
|
// ReflectClient returns a Client whose CallRPC method will use reflection to
|
|
// call the given Handler's ServeRPC method directly, using reflect.Value's Set
|
|
// method to copy CallRPC's args parameter into UnmarshalArgs' receiver
|
|
// parameter, and similarly to copy the result from ServeRPC into CallRPC's
|
|
// receiver parameter.
|
|
func ReflectClient(h Handler) Client {
|
|
into := func(dst, src interface{}) error {
|
|
dstV, srcV := reflect.ValueOf(dst), reflect.ValueOf(src)
|
|
dstVi, srcVi := reflect.Indirect(dstV), reflect.Indirect(srcV)
|
|
if !dstVi.CanSet() || dstVi.Type() != srcVi.Type() {
|
|
return fmt.Errorf("can't set value of type %v into type %v", srcV.Type(), dstV.Type())
|
|
}
|
|
dstVi.Set(srcVi)
|
|
return nil
|
|
}
|
|
|
|
return ClientFunc(func(
|
|
ctx context.Context,
|
|
resInto interface{},
|
|
method string,
|
|
args interface{},
|
|
) error {
|
|
c := Call{
|
|
Context: ctx,
|
|
Method: method,
|
|
UnmarshalArgs: func(i interface{}) error { return into(i, args) },
|
|
}
|
|
|
|
res, err := h.ServeRPC(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return into(resInto, res)
|
|
})
|
|
}
|