implement basic mrpc functionality
This commit is contained in:
parent
50db101620
commit
56dbb1827e
138
mrpc/mrpc.go
Normal file
138
mrpc/mrpc.go
Normal file
@ -0,0 +1,138 @@
|
||||
// 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 an example of an implementation of these interfaces can be found in the
|
||||
// m package
|
||||
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 interface {
|
||||
Context() context.Context
|
||||
|
||||
// Method returns 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 types.
|
||||
UnmarshalArgs(interface{}) error
|
||||
}
|
||||
|
||||
type call struct {
|
||||
ctx context.Context
|
||||
method string
|
||||
unmarshalArgs func(interface{}) error
|
||||
}
|
||||
|
||||
func (c call) Context() context.Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
func (c call) Method() string {
|
||||
return c.method
|
||||
}
|
||||
|
||||
func (c call) UnmarshalArgs(i interface{}) error {
|
||||
return c.unmarshalArgs(i)
|
||||
}
|
||||
|
||||
// WithContext returns the same Call it's given, but the new Call will return
|
||||
// the given context when Context() is called
|
||||
func WithContext(c Call, ctx context.Context) Call {
|
||||
return call{ctx: ctx, method: c.Method(), unmarshalArgs: c.UnmarshalArgs}
|
||||
}
|
||||
|
||||
// WithMethod returns the same Call it's given, but the new Call will return the
|
||||
// given method name when Method() is called
|
||||
func WithMethod(c Call, method string) Call {
|
||||
return call{ctx: c.Context(), method: method, unmarshalArgs: c.UnmarshalArgs}
|
||||
}
|
||||
|
||||
// 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{
|
||||
ctx: 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)
|
||||
})
|
||||
}
|
67
mrpc/mrpc_test.go
Normal file
67
mrpc/mrpc_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
package mrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
. "testing"
|
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/mtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReflectClient(t *T) {
|
||||
type argT struct {
|
||||
In string
|
||||
}
|
||||
|
||||
type resT struct {
|
||||
Out string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
{ // test with handler returning non-pointer
|
||||
client := ReflectClient(HandlerFunc(func(c Call) (interface{}, error) {
|
||||
var args argT
|
||||
assert.NoError(t, c.UnmarshalArgs(&args))
|
||||
assert.Equal(t, "foo", c.Method())
|
||||
return resT{Out: args.In}, nil
|
||||
}))
|
||||
|
||||
{ // test with arg being non-pointer
|
||||
in := mtest.RandHex(8)
|
||||
var res resT
|
||||
assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in}))
|
||||
assert.Equal(t, in, res.Out)
|
||||
}
|
||||
|
||||
{ // test with arg being pointer
|
||||
in := mtest.RandHex(8)
|
||||
var res resT
|
||||
assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in}))
|
||||
assert.Equal(t, in, res.Out)
|
||||
}
|
||||
}
|
||||
|
||||
{ // test with handler returning pointer
|
||||
client := ReflectClient(HandlerFunc(func(c Call) (interface{}, error) {
|
||||
var args argT
|
||||
assert.NoError(t, c.UnmarshalArgs(&args))
|
||||
assert.Equal(t, "foo", c.Method())
|
||||
return &resT{Out: args.In}, nil
|
||||
}))
|
||||
|
||||
{ // test with arg being non-pointer
|
||||
in := mtest.RandHex(8)
|
||||
var res resT
|
||||
assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in}))
|
||||
assert.Equal(t, in, res.Out)
|
||||
}
|
||||
|
||||
{ // test with arg being pointer
|
||||
in := mtest.RandHex(8)
|
||||
var res resT
|
||||
assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in}))
|
||||
assert.Equal(t, in, res.Out)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user