2019-07-03 17:39:07 +00:00
|
|
|
package jsonrpc
|
2019-06-28 13:49:34 +00:00
|
|
|
|
|
|
|
import (
|
2019-06-28 14:53:01 +00:00
|
|
|
"context"
|
2019-06-28 13:49:34 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2019-06-28 14:05:10 +00:00
|
|
|
"fmt"
|
2019-07-12 15:29:41 +00:00
|
|
|
"io"
|
2019-06-28 13:49:34 +00:00
|
|
|
"reflect"
|
2019-07-03 14:32:31 +00:00
|
|
|
"sync/atomic"
|
2019-07-08 19:07:16 +00:00
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
"github.com/gorilla/websocket"
|
2019-07-08 19:07:16 +00:00
|
|
|
logging "github.com/ipfs/go-log"
|
2019-06-28 13:49:34 +00:00
|
|
|
)
|
|
|
|
|
2019-07-08 19:07:16 +00:00
|
|
|
var log = logging.Logger("rpc")
|
|
|
|
|
2019-06-28 14:53:01 +00:00
|
|
|
var (
|
2019-07-01 20:00:22 +00:00
|
|
|
errorType = reflect.TypeOf(new(error)).Elem()
|
2019-06-28 14:53:01 +00:00
|
|
|
contextType = reflect.TypeOf(new(context.Context)).Elem()
|
|
|
|
)
|
|
|
|
|
2019-07-02 19:08:30 +00:00
|
|
|
// ErrClient is an error which occurred on the client side the library
|
2019-06-28 14:05:10 +00:00
|
|
|
type ErrClient struct {
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ErrClient) Error() string {
|
|
|
|
return fmt.Sprintf("RPC client error: %s", e.err)
|
|
|
|
}
|
|
|
|
|
2019-07-02 17:45:03 +00:00
|
|
|
// Unwrap unwraps the actual error
|
2019-06-28 14:05:10 +00:00
|
|
|
func (e *ErrClient) Unwrap(err error) error {
|
|
|
|
return e.err
|
|
|
|
}
|
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
type result []byte
|
2019-06-28 13:49:34 +00:00
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
func (p *result) UnmarshalJSON(raw []byte) error {
|
|
|
|
*p = make([]byte, len(raw))
|
|
|
|
copy(*p, raw)
|
|
|
|
return nil
|
2019-06-28 13:49:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type clientResponse struct {
|
2019-07-08 12:46:30 +00:00
|
|
|
Jsonrpc string `json:"jsonrpc"`
|
|
|
|
Result result `json:"result"`
|
|
|
|
ID int64 `json:"id"`
|
|
|
|
Error *respError `json:"error,omitempty"`
|
2019-06-28 13:49:34 +00:00
|
|
|
}
|
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
type clientRequest struct {
|
|
|
|
req request
|
|
|
|
ready chan clientResponse
|
|
|
|
}
|
|
|
|
|
2019-07-03 12:30:21 +00:00
|
|
|
// ClientCloser is used to close Client from further use
|
|
|
|
type ClientCloser func()
|
|
|
|
|
2019-07-02 17:45:03 +00:00
|
|
|
// NewClient creates new josnrpc 2.0 client
|
|
|
|
//
|
|
|
|
// handler must be pointer to a struct with function fields
|
2019-07-03 14:32:31 +00:00
|
|
|
// Returned value closes the client connection
|
2019-07-02 17:45:03 +00:00
|
|
|
// TODO: Example
|
2019-07-12 15:29:41 +00:00
|
|
|
func NewClient(addr string, namespace string, handler interface{}) (ClientCloser, error) {
|
2019-06-28 13:49:34 +00:00
|
|
|
htyp := reflect.TypeOf(handler)
|
|
|
|
if htyp.Kind() != reflect.Ptr {
|
|
|
|
panic("expected handler to be a pointer")
|
|
|
|
}
|
|
|
|
typ := htyp.Elem()
|
|
|
|
if typ.Kind() != reflect.Struct {
|
|
|
|
panic("handler should be a struct")
|
|
|
|
}
|
|
|
|
|
|
|
|
val := reflect.ValueOf(handler)
|
|
|
|
|
2019-07-03 14:32:31 +00:00
|
|
|
var idCtr int64
|
2019-06-28 13:49:34 +00:00
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
conn, _, err := websocket.DefaultDialer.Dial(addr, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
stop := make(chan struct{})
|
|
|
|
errs := make(chan error, 1)
|
|
|
|
requests := make(chan clientRequest)
|
|
|
|
responses := make(chan io.Reader)
|
|
|
|
|
|
|
|
nextMessage := func() {
|
|
|
|
mtype, r, err := conn.NextReader()
|
|
|
|
if err != nil {
|
|
|
|
r, _ := io.Pipe()
|
|
|
|
r.CloseWithError(err) // nolint
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
if mtype != websocket.BinaryMessage && mtype != websocket.TextMessage {
|
|
|
|
r, _ := io.Pipe()
|
|
|
|
r.CloseWithError(errors.New("unsupported message type")) // nolint
|
|
|
|
return
|
|
|
|
}
|
|
|
|
responses <- r
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
defer func() {
|
|
|
|
close(requests)
|
|
|
|
cerr := conn.Close()
|
|
|
|
if err == nil {
|
|
|
|
err = cerr
|
|
|
|
}
|
|
|
|
errs <- cerr
|
|
|
|
|
|
|
|
// close requests somehow
|
|
|
|
}()
|
|
|
|
|
|
|
|
inflight := map[int64]clientRequest{}
|
|
|
|
|
|
|
|
go nextMessage()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case req := <-requests:
|
|
|
|
inflight[*req.req.ID] = req
|
|
|
|
if err = conn.WriteJSON(req.req); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case r := <- responses:
|
|
|
|
var resp clientResponse
|
|
|
|
if err = json.NewDecoder(r).Decode(&resp); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req, ok := inflight[resp.ID]
|
|
|
|
if !ok {
|
|
|
|
log.Error("client got unknown ID in response")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
req.ready <- resp
|
|
|
|
delete(inflight, resp.ID)
|
|
|
|
|
|
|
|
go nextMessage()
|
|
|
|
case <-stop:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2019-06-28 13:49:34 +00:00
|
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
|
|
f := typ.Field(i)
|
|
|
|
ftyp := f.Type
|
|
|
|
if ftyp.Kind() != reflect.Func {
|
|
|
|
panic("handler field not a func")
|
|
|
|
}
|
|
|
|
|
2019-07-08 12:46:30 +00:00
|
|
|
valOut, errOut, nout := processFuncOut(ftyp)
|
2019-06-28 13:49:34 +00:00
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
processResponse := func(resp clientResponse, rval reflect.Value) []reflect.Value {
|
2019-06-28 13:49:34 +00:00
|
|
|
out := make([]reflect.Value, nout)
|
|
|
|
|
|
|
|
if valOut != -1 {
|
2019-07-12 15:29:41 +00:00
|
|
|
out[valOut] = rval.Elem()
|
2019-06-28 13:49:34 +00:00
|
|
|
}
|
|
|
|
if errOut != -1 {
|
2019-06-28 14:53:01 +00:00
|
|
|
out[errOut] = reflect.New(errorType).Elem()
|
2019-06-28 13:49:34 +00:00
|
|
|
if resp.Error != nil {
|
2019-07-02 13:49:10 +00:00
|
|
|
out[errOut].Set(reflect.ValueOf(resp.Error))
|
2019-06-28 13:49:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-07-01 20:00:22 +00:00
|
|
|
processError := func(err error) []reflect.Value {
|
2019-06-28 14:05:10 +00:00
|
|
|
out := make([]reflect.Value, nout)
|
|
|
|
|
|
|
|
if valOut != -1 {
|
|
|
|
out[valOut] = reflect.New(ftyp.Out(valOut)).Elem()
|
|
|
|
}
|
|
|
|
if errOut != -1 {
|
2019-06-28 14:53:01 +00:00
|
|
|
out[errOut] = reflect.New(errorType).Elem()
|
2019-06-28 14:05:10 +00:00
|
|
|
out[errOut].Set(reflect.ValueOf(&ErrClient{err}))
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-06-28 14:53:01 +00:00
|
|
|
hasCtx := 0
|
|
|
|
if ftyp.NumIn() > 0 && ftyp.In(0) == contextType {
|
|
|
|
hasCtx = 1
|
|
|
|
}
|
|
|
|
|
2019-06-28 13:49:34 +00:00
|
|
|
fn := reflect.MakeFunc(ftyp, func(args []reflect.Value) (results []reflect.Value) {
|
2019-07-03 14:32:31 +00:00
|
|
|
id := atomic.AddInt64(&idCtr, 1)
|
2019-07-08 12:46:30 +00:00
|
|
|
params := make([]param, len(args)-hasCtx)
|
2019-06-28 14:53:01 +00:00
|
|
|
for i, arg := range args[hasCtx:] {
|
2019-07-08 12:46:30 +00:00
|
|
|
params[i] = param{
|
2019-06-28 13:49:34 +00:00
|
|
|
v: arg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-08 12:46:30 +00:00
|
|
|
req := request{
|
2019-06-28 13:49:34 +00:00
|
|
|
Jsonrpc: "2.0",
|
2019-07-02 19:08:30 +00:00
|
|
|
ID: &id,
|
2019-06-28 13:49:34 +00:00
|
|
|
Method: namespace + "." + f.Name,
|
|
|
|
Params: params,
|
|
|
|
}
|
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
rchan := make(chan clientResponse, 1)
|
|
|
|
requests <- clientRequest{
|
|
|
|
req: req,
|
|
|
|
ready: rchan,
|
2019-07-08 19:07:16 +00:00
|
|
|
}
|
2019-07-12 15:29:41 +00:00
|
|
|
resp := <- rchan
|
|
|
|
var rval reflect.Value
|
2019-07-08 19:07:16 +00:00
|
|
|
|
2019-06-28 13:49:34 +00:00
|
|
|
if valOut != -1 {
|
2019-07-09 13:16:15 +00:00
|
|
|
log.Debugw("rpc result", "type", ftyp.Out(valOut))
|
2019-07-12 15:29:41 +00:00
|
|
|
rval = reflect.New(ftyp.Out(valOut))
|
|
|
|
if err := json.Unmarshal(resp.Result, rval.Interface()); err != nil {
|
|
|
|
return processError(err)
|
|
|
|
}
|
2019-07-02 19:08:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if resp.ID != *req.ID {
|
2019-06-28 14:05:10 +00:00
|
|
|
return processError(errors.New("request and response id didn't match"))
|
2019-06-28 13:49:34 +00:00
|
|
|
}
|
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
return processResponse(resp, rval)
|
2019-06-28 13:49:34 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
val.Elem().Field(i).Set(fn)
|
|
|
|
}
|
2019-07-03 14:32:31 +00:00
|
|
|
|
2019-07-12 15:29:41 +00:00
|
|
|
return func() {
|
|
|
|
close(stop)
|
|
|
|
<-errs // TODO: return
|
|
|
|
}, nil
|
2019-06-28 13:49:34 +00:00
|
|
|
}
|