WIP mrpc wrote some docs, tried to make some progress on request/response types

This commit is contained in:
Brian Picciano 2018-05-16 12:19:31 +00:00
parent 0460a7745c
commit bb0eeaff16
2 changed files with 45 additions and 65 deletions

View File

@ -1,15 +1,13 @@
package mrpc package mrpc
import "context" // Debug data is arbitrary data embedded in a Request by the Client or in its
// Response by the Server. Debug data is organized into namespaces to help avoid
// Debug data is arbitrary data embedded in a Call's request by the Client or in // conflicts while still preserving serializability.
// its Response by the Server. Debug data is organized into namespaces to help
// avoid conflicts while still preserving serializability.
// //
// Debug data is intended to be used for debugging purposes only, and should // Debug data is intended to be used for debugging purposes only, and should
// never be used to effect the path-of-action a Call takes. Put another way: // never be used to effect the path-of-action a call takes. Put another way:
// when implementing a Call always assume that the Debug info has been // when implementing a call always assume that the Debug info has been
// accidentally removed from the Call's request/response. // accidentally removed from the call's Request/Response.
type Debug map[string]map[string]interface{} type Debug map[string]map[string]interface{}
// Copy returns an identical copy of the Debug being called upon // Copy returns an identical copy of the Debug being called upon
@ -25,10 +23,11 @@ func (d Debug) Copy() Debug {
} }
// Set returns a copy of the Debug instance with the key set to the value within // Set returns a copy of the Debug instance with the key set to the value within
// the given namespace. // the given namespace. If Debug is nil a new instance is created and returned
// with the key set.
func (d Debug) Set(ns, key string, val interface{}) Debug { func (d Debug) Set(ns, key string, val interface{}) Debug {
if d == nil { if d == nil {
return Debug{ns: map[string]interface{}{key, val}} return Debug{ns: map[string]interface{}{key: val}}
} }
d = d.Copy() d = d.Copy()
if d[ns] == nil { if d[ns] == nil {
@ -47,21 +46,3 @@ func (d Debug) Get(ns, key string) (interface{}, bool) {
val, ok := d[ns][key] val, ok := d[ns][key]
return val, ok return val, ok
} }
type debugKey int
// CtxWithDebug returns a new Context with the given Debug embedded in it,
// overwriting any previously embedded Debug.
func CtxWithDebug(ctx context.Context, d Debug) context.Context {
return context.WithValue(ctx, debugKey(0), d)
}
// CtxDebug returns the Debug instance embedded in the Context, or nil if none
// has been embedded.
func CtxDebug(ctx context.Context) Debug {
d := ctx.Value(debugKey(0))
if d == nil {
return Debug(nil)
}
return d.(Debug)
}

View File

@ -15,8 +15,14 @@ import (
"reflect" "reflect"
) )
// Request TODO // TODO Request is making things difficult. It wants to be split into two types,
// like the Response is, but naming them is annoying. ClientRequest and
// HandlerRequest? RequestReader? Maybe 4 arguments to Client isn't that much...
// Request describes an RPC request being processed by a Handler
type Request struct { type Request struct {
// Depending on the implementation of Client, Context may be canceled to
// indicate the Client has canceled the request.
Context context.Context Context context.Context
// The name of the RPC method being called. // The name of the RPC method being called.
@ -25,24 +31,38 @@ type Request struct {
// Unmarshal takes in a pointer and unmarshals the RPC request's arguments // Unmarshal takes in a pointer and unmarshals the RPC request's arguments
// into it. The properties of the unmarshaling are dependent on the // into it. The properties of the unmarshaling are dependent on the
// underlying implementation of the protocol. // underlying implementation of the protocol.
//
// This should only be called within ServeRPC.
Unmarshal func(interface{}) error Unmarshal func(interface{}) error
// Debugging information being carried with the Request. See Debug's docs
// for more on how it is intended to be used
Debug Debug
} }
// ResponseWriter TODO // ResponseWriter is used to capture the response of an RPC request being
// processed by a Handler
type ResponseWriter struct { type ResponseWriter struct {
Context context.Context // Response should be overwritten with whatever response to the call should
// be. The exact nature and behavior of how the response value is treated is
// dependent on the RPC implementation.
Response interface{}
Respond func(interface{}) // Debug may be overwritten to provide debugging information back to the
Err func(error) // Client with the Response. See Debug's docs for more on how it is intended
// to be used.
Debug Debug
} }
// Reponse TODO // Reponse describes the response from the RPC call being returned to the
// Client.
type Response struct { type Response struct {
Context context.Context // Unmarshal takes in a pointer value into which the Client will unmarshal
// the response value. The exact nature and behavior of how the pointer
// value is treated is dependend on the RPC implementation.
Unmarshal func(interface{}) error Unmarshal func(interface{}) error
Err error
// Debug will be whatever debug information was set by the server when
// responding to the call.
Debug Debug
} }
// Handler is a type which serves RPC calls. For each incoming Requests the // Handler is a type which serves RPC calls. For each incoming Requests the
@ -63,27 +83,17 @@ func (hf HandlerFunc) ServeRPC(r Request, rw *ResponseWriter) {
} }
// Client is an entity which can perform RPC calls against a remote endpoint. // 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 { type Client interface {
CallRPC(ctx context.Context, method string, args interface{}) Response CallRPC(Request) Response
} }
// ClientFunc can be used to wrap an individual function which fits the CallRPC // ClientFunc can be used to wrap an individual function which fits the CallRPC
// signature, and use that function as a Client // signature, and use that function as a Client
type ClientFunc func(context.Context, string, interface{}) Response type ClientFunc func(Request) Response
// CallRPC implements the method for the Client interface by calling the // CallRPC implements the method for the Client interface by calling the
// underlying function // underlying function
func (cf ClientFunc) CallRPC( func (cf ClientFunc) CallRPC(r Request) { return cf(r) }
ctx context.Context,
method string,
args interface{},
) Response {
return cf(ctx, method, args)
}
// ReflectClient returns a Client whose CallRPC method will use reflection to // ReflectClient returns a Client whose CallRPC method will use reflection to
// call the given Handler's ServeRPC method directly, using reflect.Value's Set // call the given Handler's ServeRPC method directly, using reflect.Value's Set
@ -111,25 +121,14 @@ func ReflectClient(h Handler) Client {
Method: method, Method: method,
Unmarshal: func(i interface{}) error { return into(i, args) }, Unmarshal: func(i interface{}) error { return into(i, args) },
} }
var res interface{} rw := ResponseWriter{}
var resErr error
rw := ResponseWriter{
Context: context.Background(),
Respond: func(i interface{}) { res = i },
Err: func(err error) { resErr = err },
}
h.ServeRPC(req, &rw) h.ServeRPC(req, &rw)
return Response{ return Response{
Context: rw.Context,
Unmarshal: func(i interface{}) error { Unmarshal: func(i interface{}) error {
if resErr != nil { return into(i, rw.Response)
return resErr
}
return into(i, res)
}, },
Err: resErr, Debug: rw.Debug,
} }
}) })
} }