|
@@ -2,6 +2,7 @@
|
|
|
package wsrpc
|
|
|
|
|
|
import (
|
|
|
+ "context"
|
|
|
"errors"
|
|
|
"log"
|
|
|
"reflect"
|
|
@@ -12,6 +13,7 @@ import (
|
|
|
"golang.org/x/net/websocket"
|
|
|
)
|
|
|
|
|
|
+// Vars
|
|
|
var (
|
|
|
ErrNParam = errors.New("Invalid number of parameters")
|
|
|
ErrNReturn = errors.New("Number of outputs mismatch")
|
|
@@ -20,14 +22,17 @@ var (
|
|
|
errorInterface = reflect.TypeOf((*error)(nil)).Elem()
|
|
|
)
|
|
|
|
|
|
-// Call object
|
|
|
+// CallObj object
|
|
|
+// Message
|
|
|
type CallObj struct {
|
|
|
OP string `json:"op"` // operation
|
|
|
ID string `json:"id"` // Id of the call
|
|
|
Method string `json:"method"` // could be param 0 instead?
|
|
|
|
|
|
+ // Payload
|
|
|
Params []interface{} `json:"params"`
|
|
|
Response interface{} `json:"response"`
|
|
|
+ Error string `json:"error"`
|
|
|
}
|
|
|
|
|
|
// DataObj common structure for dymanic json object
|
|
@@ -40,61 +45,87 @@ type ListenerFunc func(...interface{}) (interface{}, error)
|
|
|
|
|
|
// ClientCtx main client struct
|
|
|
type ClientCtx struct {
|
|
|
- locker sync.Mutex
|
|
|
+ context.Context
|
|
|
+ Close func()
|
|
|
|
|
|
- WS *websocket.Conn
|
|
|
+ locker sync.Mutex
|
|
|
+ WS *websocket.Conn
|
|
|
// stuff
|
|
|
listeners map[string]ListenerFunc
|
|
|
- requests map[string]chan interface{}
|
|
|
+ requests map[string]chan CallObj
|
|
|
+ message chan CallObj
|
|
|
}
|
|
|
|
|
|
-//NewHandler creates a new WsRPC client handler
|
|
|
-func NewHandler(id string, ws *websocket.Conn) *ClientCtx {
|
|
|
- var c = ClientCtx{
|
|
|
+// NewClient new client (should have background context here)
|
|
|
+func NewClient(ws *websocket.Conn) *ClientCtx {
|
|
|
+ ctx, cancelFunc := context.WithCancel(context.Background())
|
|
|
+ return &ClientCtx{
|
|
|
+ Context: ctx,
|
|
|
+ Close: cancelFunc,
|
|
|
WS: ws,
|
|
|
listeners: map[string]ListenerFunc{},
|
|
|
- requests: map[string]chan interface{}{},
|
|
|
+ requests: map[string]chan CallObj{},
|
|
|
}
|
|
|
- return &c
|
|
|
+
|
|
|
}
|
|
|
|
|
|
-// Process messages
|
|
|
-func (c *ClientCtx) Process(data CallObj) {
|
|
|
- switch data.OP {
|
|
|
- case "call":
|
|
|
- params := data.Params
|
|
|
- var idn = data.Method
|
|
|
- var reqID = data.ID
|
|
|
- var fn, ok = c.listeners[idn]
|
|
|
- if !ok {
|
|
|
+// Process received messages
|
|
|
+func (c *ClientCtx) Process() {
|
|
|
+ for {
|
|
|
+ select {
|
|
|
+ case <-c.Done():
|
|
|
+ return // Break loop
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ var data = CallObj{}
|
|
|
+ err := websocket.JSON.Receive(c.WS, &data)
|
|
|
+ if err != nil { // Close receive
|
|
|
+ c.Close()
|
|
|
return
|
|
|
}
|
|
|
- go func() { // async send
|
|
|
- ret, err := fn(params...)
|
|
|
- if err != nil {
|
|
|
- log.Println("Create error response, panic?")
|
|
|
+
|
|
|
+ switch data.OP {
|
|
|
+ case "call":
|
|
|
+ params := data.Params
|
|
|
+ var idn = data.Method
|
|
|
+ var reqID = data.ID
|
|
|
+ var fn, ok = c.listeners[idn]
|
|
|
+ if !ok {
|
|
|
+ websocket.JSON.Send(c.WS, CallObj{
|
|
|
+ OP: "response",
|
|
|
+ ID: reqID,
|
|
|
+ Error: "Method not found",
|
|
|
+ })
|
|
|
+ break
|
|
|
}
|
|
|
- var responseObj = CallObj{
|
|
|
- OP: "response",
|
|
|
- ID: reqID,
|
|
|
- Response: ret,
|
|
|
+ go func() { // async send
|
|
|
+ errStr := ""
|
|
|
+ ret, err := fn(params...)
|
|
|
+ if err != nil {
|
|
|
+ errStr = err.Error()
|
|
|
+ }
|
|
|
+ var responseObj = CallObj{
|
|
|
+ OP: "response",
|
|
|
+ ID: reqID,
|
|
|
+ Response: ret,
|
|
|
+ Error: errStr,
|
|
|
+ }
|
|
|
+ websocket.JSON.Send(c.WS, responseObj)
|
|
|
+ }()
|
|
|
+ case "response":
|
|
|
+ c.locker.Lock()
|
|
|
+ mchan, ok := c.requests[data.ID]
|
|
|
+ delete(c.requests, data.ID)
|
|
|
+ c.locker.Unlock()
|
|
|
+ if ok {
|
|
|
+ mchan <- data
|
|
|
}
|
|
|
- //log.Println("Sending response")
|
|
|
- websocket.JSON.Send(c.WS, responseObj)
|
|
|
- }()
|
|
|
- case "response":
|
|
|
- c.locker.Lock()
|
|
|
- mchan, ok := c.requests[data.ID]
|
|
|
- delete(c.requests, data.ID)
|
|
|
- c.locker.Unlock()
|
|
|
- if ok {
|
|
|
- mchan <- data.Response
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Call a client method and estabilishes a request id
|
|
|
-func (c *ClientCtx) Call(method string, params ...interface{}) interface{} {
|
|
|
+func (c *ClientCtx) Call(method string, params ...interface{}) (interface{}, error) {
|
|
|
u := uuid.New()
|
|
|
uuidStr := u.String()
|
|
|
|
|
@@ -105,20 +136,33 @@ func (c *ClientCtx) Call(method string, params ...interface{}) interface{} {
|
|
|
Params: params,
|
|
|
}
|
|
|
|
|
|
- res := make(chan interface{}, 1)
|
|
|
+ resCh := make(chan CallObj, 1)
|
|
|
+ // Store the channel
|
|
|
c.locker.Lock()
|
|
|
- c.requests[uuidStr] = res
|
|
|
+ c.requests[uuidStr] = resCh
|
|
|
c.locker.Unlock()
|
|
|
+
|
|
|
+ // IO send
|
|
|
+ // Send to dispatcher instead?
|
|
|
websocket.JSON.Send(c.WS, &callObj)
|
|
|
|
|
|
- return <-res // Block until value
|
|
|
+ // Hang until response
|
|
|
+ res := <-resCh // Wait for response
|
|
|
+
|
|
|
+ // Got Response
|
|
|
+ var err error
|
|
|
+ if res.Error != "" {
|
|
|
+ err = errors.New(res.Error)
|
|
|
+ }
|
|
|
+ return res.Response, err
|
|
|
}
|
|
|
|
|
|
-//On add a event listener on browser
|
|
|
+//Define export a method to ws client
|
|
|
func (c *ClientCtx) Define(name string, listener ListenerFunc) {
|
|
|
c.listeners[name] = listener
|
|
|
}
|
|
|
|
|
|
+//Export a struct of funcs
|
|
|
func (c *ClientCtx) Export(obj interface{}) {
|
|
|
// Reflect here
|
|
|
typ := reflect.TypeOf(obj)
|