2018-04-14 10:45:58 +00:00
|
|
|
// Package jstreamrpc implements the mrpc interfaces using the jstream protocol.
|
|
|
|
// This implementation makes a few design decisions which are not specified in
|
|
|
|
// the protocol, but which are good best practices none-the-less.
|
|
|
|
package jstreamrpc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/mediocregopher/mediocre-go-lib/jstream"
|
|
|
|
"github.com/mediocregopher/mediocre-go-lib/mrpc"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO Error?
|
|
|
|
// TODO SizeHints
|
2018-05-16 12:56:06 +00:00
|
|
|
// TODO it'd be nice if the types here played nice with mrpc.ReflectClient
|
2018-04-14 10:45:58 +00:00
|
|
|
|
2018-04-27 08:03:39 +00:00
|
|
|
type debug struct {
|
2018-05-16 12:56:06 +00:00
|
|
|
Debug mrpc.Debug `json:"debug,omitempty"`
|
2018-04-14 10:45:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type reqHead struct {
|
2018-04-27 08:03:39 +00:00
|
|
|
debug
|
2018-04-14 10:45:58 +00:00
|
|
|
Method string `json:"method"`
|
|
|
|
}
|
|
|
|
|
2018-04-27 08:03:39 +00:00
|
|
|
type resTail struct {
|
|
|
|
debug
|
2018-05-16 12:56:06 +00:00
|
|
|
Error error `json:"err,omitempty"`
|
2018-04-27 08:03:39 +00:00
|
|
|
}
|
|
|
|
|
2018-04-14 10:45:58 +00:00
|
|
|
type ctxVal int
|
|
|
|
|
|
|
|
const (
|
|
|
|
ctxValR ctxVal = iota
|
|
|
|
ctxValW
|
|
|
|
)
|
|
|
|
|
|
|
|
func unmarshalBody(i interface{}, el jstream.Element) error {
|
|
|
|
switch iT := i.(type) {
|
2018-05-17 10:29:42 +00:00
|
|
|
case *jstream.Element:
|
|
|
|
*iT = el
|
2018-04-14 10:45:58 +00:00
|
|
|
return nil
|
|
|
|
default:
|
2018-05-17 09:13:30 +00:00
|
|
|
return el.DecodeValue(i)
|
2018-04-14 10:45:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalBody(w *jstream.StreamWriter, i interface{}) error {
|
|
|
|
switch iT := i.(type) {
|
|
|
|
case func(*jstream.StreamWriter) error:
|
2018-05-17 10:29:42 +00:00
|
|
|
return iT(w)
|
2018-04-14 10:45:58 +00:00
|
|
|
default:
|
|
|
|
return w.EncodeValue(iT)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// HandleCall TODO
|
|
|
|
//
|
|
|
|
// If this returns an error then both r and w should be discarded and no longer
|
|
|
|
// used.
|
|
|
|
func HandleCall(
|
|
|
|
ctx context.Context,
|
|
|
|
r *jstream.StreamReader,
|
|
|
|
w *jstream.StreamWriter,
|
|
|
|
h mrpc.Handler,
|
|
|
|
) error {
|
2018-05-16 12:56:06 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
defer cancel()
|
|
|
|
|
2018-04-14 10:45:58 +00:00
|
|
|
var head reqHead
|
2018-05-17 09:13:30 +00:00
|
|
|
if err := r.Next().DecodeValue(&head); err != nil {
|
2018-04-14 10:45:58 +00:00
|
|
|
return err
|
|
|
|
} else if head.Method == "" {
|
|
|
|
return errors.New("request head missing 'method' field")
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx = context.WithValue(ctx, ctxValR, r)
|
|
|
|
ctx = context.WithValue(ctx, ctxValW, w)
|
2018-05-16 12:56:06 +00:00
|
|
|
|
2018-05-17 10:29:42 +00:00
|
|
|
body := r.Next()
|
|
|
|
if body.Err != nil {
|
|
|
|
return body.Err
|
|
|
|
}
|
|
|
|
|
2018-05-16 12:56:06 +00:00
|
|
|
rw := new(mrpc.ResponseWriter)
|
|
|
|
h.ServeRPC(mrpc.Request{
|
2018-04-14 10:45:58 +00:00
|
|
|
Context: ctx,
|
|
|
|
Method: head.Method,
|
2018-05-16 12:56:06 +00:00
|
|
|
Unmarshal: func(i interface{}) error {
|
2018-05-17 10:29:42 +00:00
|
|
|
return unmarshalBody(i, body)
|
2018-04-14 10:45:58 +00:00
|
|
|
},
|
2018-05-16 12:56:06 +00:00
|
|
|
Debug: head.debug.Debug,
|
|
|
|
}, rw)
|
2018-04-14 10:45:58 +00:00
|
|
|
|
2018-05-16 12:56:06 +00:00
|
|
|
// TODO unmarshaling request and marshaling response should be in
|
|
|
|
// their own go-routines, just in case they are streams/bytes which depend
|
|
|
|
// on each other
|
2018-04-14 10:45:58 +00:00
|
|
|
|
2018-05-16 12:56:06 +00:00
|
|
|
resErr, resErrOk := rw.Response.(error)
|
|
|
|
if resErrOk {
|
|
|
|
if err := w.EncodeValue(nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := marshalBody(w, rw.Response); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-04-14 10:45:58 +00:00
|
|
|
}
|
|
|
|
|
2018-05-17 10:29:42 +00:00
|
|
|
// make sure the body has been consumed
|
|
|
|
if err := body.Discard(); err != nil {
|
|
|
|
return err
|
2018-04-14 10:45:58 +00:00
|
|
|
}
|
2018-04-27 08:03:39 +00:00
|
|
|
|
2018-05-16 12:56:06 +00:00
|
|
|
if err := w.EncodeValue(resTail{
|
|
|
|
debug: debug{Debug: rw.Debug},
|
|
|
|
Error: resErr,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-04-14 10:45:58 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-16 12:56:06 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
func sqr(r mrpc.Request, rw *mrpc.ResponseWriter) {
|
2018-05-17 10:29:42 +00:00
|
|
|
var el jstream.Element
|
|
|
|
if err := r.Unmarshal(&el); err != nil {
|
|
|
|
rw.Response = err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sr, err := el.DecodeStream()
|
|
|
|
if err != nil {
|
|
|
|
rw.Response = err
|
|
|
|
return
|
2018-05-16 12:56:06 +00:00
|
|
|
}
|
|
|
|
|
2018-05-17 10:29:42 +00:00
|
|
|
ch := make(chan int)
|
2018-05-16 12:56:06 +00:00
|
|
|
go func() {
|
|
|
|
defer close(ch)
|
2018-05-17 10:29:42 +00:00
|
|
|
for {
|
|
|
|
var i int
|
|
|
|
if err := sr.Next().Value(&i); err == jstream.ErrStreamEnded {
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
panic("TODO")
|
2018-05-16 12:56:06 +00:00
|
|
|
}
|
2018-05-17 10:29:42 +00:00
|
|
|
ch <- i
|
2018-05-16 12:56:06 +00:00
|
|
|
}
|
|
|
|
}()
|
2018-05-17 10:29:42 +00:00
|
|
|
|
|
|
|
rw.Response = func(sw *jstream.StreamWriter) error {
|
|
|
|
sw = sw.EncodeStream()
|
|
|
|
for i := range ch {
|
|
|
|
if err := sw.EncodeValue(i * i); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2018-05-16 12:56:06 +00:00
|
|
|
}
|
|
|
|
*/
|