lotus/lib/jsonrpc/websocket.go

342 lines
6.9 KiB
Go
Raw Normal View History

2019-07-12 17:12:38 +00:00
package jsonrpc
import (
"context"
"encoding/json"
"errors"
"io"
2019-07-15 16:21:48 +00:00
"io/ioutil"
2019-07-22 18:13:41 +00:00
"reflect"
2019-07-15 16:21:48 +00:00
"sync"
2019-07-22 18:13:41 +00:00
"sync/atomic"
2019-07-12 17:12:38 +00:00
"github.com/gorilla/websocket"
)
2019-07-15 16:21:48 +00:00
const wsCancel = "xrpc.cancel"
2019-07-22 18:13:41 +00:00
const chValue = "xrpc.ch.val"
const chClose = "xrpc.ch.close"
2019-07-15 16:21:48 +00:00
2019-07-12 17:12:38 +00:00
type frame struct {
// common
2019-07-15 16:32:43 +00:00
Jsonrpc string `json:"jsonrpc"`
ID *int64 `json:"id,omitempty"`
2019-07-12 17:12:38 +00:00
// request
2019-07-15 16:32:43 +00:00
Method string `json:"method,omitempty"`
Params []param `json:"params,omitempty"`
2019-07-12 17:12:38 +00:00
// response
2019-07-22 20:28:40 +00:00
Result json.RawMessage `json:"result,omitempty"`
Error *respError `json:"error,omitempty"`
2019-07-12 17:12:38 +00:00
}
func handleWsConn(ctx context.Context, conn *websocket.Conn, handler handlers, requests <-chan clientRequest, stop <-chan struct{}) {
incoming := make(chan io.Reader)
2019-07-13 12:44:20 +00:00
var incErr error
2019-07-12 17:12:38 +00:00
2019-07-22 18:13:41 +00:00
// nextMessage wait for one message and puts it to the incoming channel
2019-07-12 17:12:38 +00:00
nextMessage := func() {
mtype, r, err := conn.NextReader()
if err != nil {
2019-07-13 12:44:20 +00:00
incErr = err
close(incoming)
2019-07-12 17:12:38 +00:00
return
}
if mtype != websocket.BinaryMessage && mtype != websocket.TextMessage {
2019-07-13 12:44:20 +00:00
incErr = errors.New("unsupported message type")
close(incoming)
2019-07-12 17:12:38 +00:00
return
}
incoming <- r
}
2019-07-15 16:21:48 +00:00
var writeLk sync.Mutex
2019-07-22 18:13:41 +00:00
// nextWriter waits for writeLk and invokes the cb callback with WS message
// writer when the lock is acquired
2019-07-15 16:21:48 +00:00
nextWriter := func(cb func(io.Writer)) {
writeLk.Lock()
defer writeLk.Unlock()
wcl, err := conn.NextWriter(websocket.TextMessage)
if err != nil {
log.Error("handle me:", err)
return
}
cb(wcl)
if err := wcl.Close(); err != nil {
log.Error("handle me:", err)
return
}
}
2019-07-22 18:13:41 +00:00
sendReq := func(req request) {
writeLk.Lock()
if err := conn.WriteJSON(req); err != nil {
log.Error("handle me:", err)
writeLk.Unlock()
return
}
writeLk.Unlock()
}
// wait for the first message
2019-07-12 17:12:38 +00:00
go nextMessage()
2019-07-22 18:13:41 +00:00
// inflight are requests we sent to the remote
2019-07-12 17:12:38 +00:00
inflight := map[int64]clientRequest{}
2019-07-22 18:13:41 +00:00
// handling are the calls we handle
2019-07-15 16:21:48 +00:00
handling := map[int64]context.CancelFunc{}
var handlingLk sync.Mutex
2019-07-22 18:13:41 +00:00
// ////
// Subscriptions (func() <-chan Typ - like methods)
var chOnce sync.Once
var outId uint64
2019-07-22 20:28:40 +00:00
type chReg struct {
id uint64
ch reflect.Value
}
2019-07-22 18:13:41 +00:00
registerCh := make(chan chReg)
defer close(registerCh)
handleOutChans := func() {
regV := reflect.ValueOf(registerCh)
cases := []reflect.SelectCase{
{ // registration chan always 0
2019-07-22 20:28:40 +00:00
Dir: reflect.SelectRecv,
2019-07-22 18:13:41 +00:00
Chan: regV,
},
}
var caseToId []uint64
for {
chosen, val, ok := reflect.Select(cases)
if chosen == 0 { // control channel
if !ok {
// not closing any channels as we're on receiving end.
// Also, context cancellation below should take care of any running
// requests
return
}
registration := val.Interface().(chReg)
caseToId = append(caseToId, registration.id)
cases = append(cases, reflect.SelectCase{
2019-07-22 20:28:40 +00:00
Dir: reflect.SelectRecv,
2019-07-22 18:13:41 +00:00
Chan: registration.ch,
})
continue
}
if !ok {
n := len(caseToId)
if n > 0 {
cases[chosen] = cases[n]
2019-07-22 20:28:40 +00:00
caseToId[chosen-1] = caseToId[n-1]
2019-07-22 18:13:41 +00:00
}
cases = cases[:n]
caseToId = caseToId[:n-1]
continue
}
sendReq(request{
Jsonrpc: "2.0",
2019-07-22 20:28:40 +00:00
ID: nil, // notification
Method: chValue,
Params: []param{{v: reflect.ValueOf(caseToId[chosen-1])}, {v: val}},
2019-07-22 18:13:41 +00:00
})
}
}
handleChanOut := func(ch reflect.Value) interface{} {
chOnce.Do(func() {
go handleOutChans()
})
id := atomic.AddUint64(&outId, 1)
registerCh <- chReg{
id: id,
ch: ch,
}
return id
}
// client side subs
chanHandlers := map[uint64]func(m []byte, ok bool){}
// ////
// on close, make sure to return from all pending calls, and cancel context
// on all calls we handle
defer func() {
for id, req := range inflight {
req.ready <- clientResponse{
Jsonrpc: "2.0",
2019-07-22 20:28:40 +00:00
ID: id,
Error: &respError{
Message: "handler: websocket connection closed",
},
}
handlingLk.Lock()
for _, cancel := range handling {
cancel()
}
handlingLk.Unlock()
}
}()
2019-07-22 18:13:41 +00:00
// cancelCtx is a built-in rpc which handles context cancellation over rpc
2019-07-15 16:21:48 +00:00
cancelCtx := func(req frame) {
if req.ID != nil {
log.Warnf("%s call with ID set, won't respond", wsCancel)
}
var id int64
if err := json.Unmarshal(req.Params[0].data, &id); err != nil {
log.Error("handle me:", err)
return
}
handlingLk.Lock()
defer handlingLk.Unlock()
cf, ok := handling[id]
if ok {
cf()
}
}
2019-07-12 17:12:38 +00:00
for {
select {
2019-07-13 12:44:20 +00:00
case r, ok := <-incoming:
if !ok {
if incErr != nil {
log.Debugf("websocket error", "error", incErr)
}
return // remote closed
}
2019-07-22 22:47:02 +00:00
// debug util - dump all messages to stderr
// r = io.TeeReader(r, os.Stderr)
2019-07-12 17:12:38 +00:00
var frame frame
if err := json.NewDecoder(r).Decode(&frame); err != nil {
log.Error("handle me:", err)
return
}
2019-07-22 18:13:41 +00:00
// Get message type by method name:
// "" - response
// "xrpc.*" - builtin
// anything else - incoming remote call
2019-07-15 16:21:48 +00:00
switch frame.Method {
case "": // Response to our call
2019-07-12 17:12:38 +00:00
req, ok := inflight[*frame.ID]
if !ok {
log.Error("client got unknown ID in response")
continue
}
2019-07-22 22:47:02 +00:00
if req.retCh != nil && frame.Result != nil {
2019-07-22 18:13:41 +00:00
// output is channel
var chid uint64
if err := json.Unmarshal(frame.Result, &chid); err != nil {
2019-07-22 22:47:02 +00:00
log.Errorf("failed to unmarshal channel id response: %s, data '%s'", err, string(frame.Result))
2019-07-22 18:13:41 +00:00
continue
}
chanHandlers[chid] = req.retCh()
}
2019-07-12 17:12:38 +00:00
req.ready <- clientResponse{
Jsonrpc: frame.Jsonrpc,
Result: frame.Result,
ID: *frame.ID,
Error: frame.Error,
}
delete(inflight, *frame.ID)
2019-07-15 16:21:48 +00:00
case wsCancel:
cancelCtx(frame)
2019-07-22 18:13:41 +00:00
case chValue:
var chid uint64
if err := json.Unmarshal(frame.Params[0].data, &chid); err != nil {
log.Error("failed to unmarshal channel id in xrpc.ch.val: %s", err)
continue
}
hnd, ok := chanHandlers[chid]
if !ok {
2019-07-22 22:47:02 +00:00
log.Errorf("xrpc.ch.val: handler %d not found", chid)
2019-07-22 18:13:41 +00:00
continue
}
hnd(frame.Params[1].data, true)
2019-07-15 16:21:48 +00:00
default: // Remote call
req := request{
Jsonrpc: frame.Jsonrpc,
ID: frame.ID,
Method: frame.Method,
Params: frame.Params,
}
ctx, cf := context.WithCancel(ctx)
nw := func(cb func(io.Writer)) {
cb(ioutil.Discard)
}
2019-07-22 18:13:41 +00:00
done := func(keepctx bool) {
if !keepctx {
cf()
}
}
2019-07-15 16:21:48 +00:00
if frame.ID != nil {
nw = nextWriter
handlingLk.Lock()
handling[*frame.ID] = cf
handlingLk.Unlock()
2019-07-22 18:13:41 +00:00
done = func(keepctx bool) {
2019-07-15 16:21:48 +00:00
handlingLk.Lock()
defer handlingLk.Unlock()
2019-07-22 18:13:41 +00:00
if !keepctx {
cf()
}
2019-07-15 16:21:48 +00:00
delete(handling, *frame.ID)
}
}
2019-07-22 18:13:41 +00:00
go handler.handle(ctx, req, nw, rpcError, done, handleChanOut)
2019-07-12 17:12:38 +00:00
}
2019-07-22 18:13:41 +00:00
go nextMessage() // TODO: fix on errors
2019-07-12 17:12:38 +00:00
case req := <-requests:
2019-07-15 16:21:48 +00:00
if req.req.ID != nil {
inflight[*req.req.ID] = req
}
2019-07-22 18:13:41 +00:00
sendReq(req.req)
2019-07-12 17:12:38 +00:00
case <-stop:
2019-07-13 12:44:20 +00:00
if err := conn.Close(); err != nil {
log.Debugf("websocket close error", "error", err)
}
2019-07-12 17:12:38 +00:00
return
}
}
}