WIP mrpc/jstreamrpc make jstreamrpc simpler and implement mrpc.Debug
This commit is contained in:
parent
bdfd7500fb
commit
dfa32aa6d9
64
mrpc/debug.go
Normal file
64
mrpc/debug.go
Normal file
@ -0,0 +1,64 @@
|
||||
package mrpc
|
||||
|
||||
import "context"
|
||||
|
||||
// Debug data is arbitrary data embedded in a Call's request by the Client or in
|
||||
// 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
|
||||
// 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
|
||||
// accidentally removed from the Call's request/response.
|
||||
type Debug map[string]map[string]interface{}
|
||||
|
||||
// Copy returns an identical copy of the Debug being called upon
|
||||
func (d Debug) Copy() Debug {
|
||||
d2 := make(Debug, len(d))
|
||||
for ns, kv := range d {
|
||||
d2[ns] = make(map[string]interface{}, len(kv))
|
||||
for k, v := range kv {
|
||||
d2[ns][k] = v
|
||||
}
|
||||
}
|
||||
return d2
|
||||
}
|
||||
|
||||
// Set returns a copy of the Debug instance with the key set to the value within
|
||||
// the given namespace.
|
||||
func (d Debug) Set(ns, key string, val interface{}) Debug {
|
||||
d = d.Copy()
|
||||
if d[ns] == nil {
|
||||
d[ns] = map[string]interface{}{}
|
||||
}
|
||||
d[ns][key] = val
|
||||
return d
|
||||
}
|
||||
|
||||
// Get returns the value for the key within the namespace. Also returns whether
|
||||
// or not the key was set. This method will never panic.
|
||||
func (d Debug) Get(ns, key string) (interface{}, bool) {
|
||||
if d == nil || d[ns] == nil {
|
||||
return nil, false
|
||||
}
|
||||
val, ok := d[ns][key]
|
||||
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)
|
||||
}
|
@ -14,20 +14,30 @@ import (
|
||||
)
|
||||
|
||||
// TODO Debug
|
||||
// - ReqHead
|
||||
// - client encodes into context
|
||||
// - handler decodes from context
|
||||
// - ResTail
|
||||
// - handler ?
|
||||
// - client ?
|
||||
|
||||
// TODO Error?
|
||||
// TODO SizeHints
|
||||
// TODO it seems like request tail and response head aren't useful nor
|
||||
// convenient to use, might be better to leave them out
|
||||
|
||||
type headTail struct {
|
||||
type debug struct {
|
||||
Debug map[string]map[string]json.RawMessage `json:"debug,omitempty"`
|
||||
}
|
||||
|
||||
type reqHead struct {
|
||||
headTail
|
||||
debug
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
type resTail struct {
|
||||
debug
|
||||
Error interface{} `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
type ctxVal int
|
||||
|
||||
const (
|
||||
@ -105,9 +115,7 @@ func HandleCall(
|
||||
// marshalBody is called they will block forever. Probably need to cancel
|
||||
// the context to let them know?
|
||||
|
||||
if err := w.EncodeValue(headTail{}); err != nil {
|
||||
return err
|
||||
} else if err := marshalBody(w, ret); err != nil {
|
||||
if err := marshalBody(w, ret); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -119,12 +127,11 @@ func HandleCall(
|
||||
// Reading the tail (and maybe discarding the body) should only be done once
|
||||
// marshalBody has finished
|
||||
if !didReadBody {
|
||||
// TODO if this errors then presumably reading the tail will too?
|
||||
// TODO what if this errors?
|
||||
r.Next().Discard()
|
||||
}
|
||||
if err := r.Next().Discard(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO write response tail
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,6 +4,9 @@
|
||||
// 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 (
|
||||
|
@ -21,8 +21,7 @@ this spec:
|
||||
|
||||
* In all JSON object specs, a field which is not required can be omitted
|
||||
entirely, and its value is assumed to be the expected type's empty value (e.g.
|
||||
`""` for strings, `0` for numbers, `{}` for objects, `null` for any-JSON
|
||||
types).
|
||||
`""` for strings, `0` for numbers, `{}` for objects).
|
||||
|
||||
## Debug
|
||||
|
||||
@ -45,11 +44,11 @@ client.
|
||||
|
||||
## Call request
|
||||
|
||||
A call request is defined as being three jstream elements read off the pipe by
|
||||
the server. Once all three elements have been read the request is considered to
|
||||
be completely consumed and the pipe may be used for a new request.
|
||||
A call request is defined as being two jstream elements read off the pipe by the
|
||||
server. Once both elements have been read the request is considered to be
|
||||
completely consumed and the pipe may be used for a new request.
|
||||
|
||||
The three elements of the request stream are specified as follows:
|
||||
The two elements of the request stream are specified as follows:
|
||||
|
||||
* The first element, the head, is a JSON value with an object containing a
|
||||
`name` field, which identifies the call being made, and optionally a `debug`
|
||||
@ -60,32 +59,23 @@ The three elements of the request stream are specified as follows:
|
||||
the call. It's up to the client and server to coordinate beforehand what to
|
||||
expect here.
|
||||
|
||||
* The third element, the tail, is a JSON value with an object optionally
|
||||
containing a `debug` field.
|
||||
|
||||
## Call response
|
||||
|
||||
A call response almost the same as the call request. The only difference is the
|
||||
lack of `name` field in the head, and the addition of the `err` field in the
|
||||
tail.
|
||||
A call response is defined as being two jstream elements read off the pipe by
|
||||
the client. Once both elements have been read the response is considered to be
|
||||
completely consumed and the pipe may be used for a new request.
|
||||
|
||||
A call response is defined as being three jstream elements read off the pipe by
|
||||
the client. Once all three elements have been read the response is considered to
|
||||
be completely consumed and the pipe may be used for a new request.
|
||||
The two elements of the response stream are specified as follows:
|
||||
|
||||
The three elements of the response stream are specified as follows:
|
||||
|
||||
* The first element, the head, is a JSON value with an object containing
|
||||
optionally containing a `debug` field.
|
||||
|
||||
* The second element is the response from the call. This may be a JSON value, a
|
||||
* The first element is the response from the call. This may be a JSON value, a
|
||||
byte blob, or an embedded stream containing even more elements, depending on
|
||||
the call. It's up to the client and server to coordinate beforehand what to
|
||||
expect here.
|
||||
|
||||
* The third element, the tail, is a JSON value with an object optionally
|
||||
containing an `err` field, and optionally containing `debug` field. The value
|
||||
of `err` may be any JSON value which is meaningful to the client and server.
|
||||
* The second element, the tail, is a JSON value with an object optionally
|
||||
containing an `err` field, and optionally containing a `debug` field. The
|
||||
value of `err` may be any JSON value which is meaningful to the client and
|
||||
server. This element is required even if there's no `err` or `debug` data.
|
||||
|
||||
## Pipelining
|
||||
|
||||
@ -95,8 +85,9 @@ effect this means that the server can be sending back response data while the
|
||||
client is still sending request data.
|
||||
|
||||
Once the server has sent the response tail it can assume the call has completed
|
||||
successfully and ignore all subsequent request data (though it must still fully
|
||||
read the three request elements off the pipe in order to use it again).
|
||||
Likewise, once a client receives the response tail it can cancel whatever it's
|
||||
doing, finish sending the request argument and tail as soon as possible, and
|
||||
assume the call has been completed.
|
||||
successfully, and the server will ignore all subsequent request data (though it
|
||||
must still fully read the request body off the pipe in order to use the pipe
|
||||
again for a new call).
|
||||
|
||||
From the client's perspective once the the response tail has been received it
|
||||
can cancel whatever request body data it's in the process of sending.
|
||||
|
Loading…
Reference in New Issue
Block a user