// Client handler package wsrpc import ( "errors" "log" "reflect" "strings" "sync" "github.com/google/uuid" "golang.org/x/net/websocket" ) var ( ErrNParam = errors.New("Invalid number of parameters") ErrNReturn = errors.New("Number of outputs mismatch") ErrBadImplementation = errors.New("Bad implementation") errorInterface = reflect.TypeOf((*error)(nil)).Elem() ) // Call object 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? Params []interface{} `json:"params"` Response interface{} `json:"response"` } // DataObj common structure for dymanic json object //type DataObj map[string]interface{} // ListenerFunc function type to handle browser events type ListenerFunc func(...interface{}) (interface{}, error) // Request request type for handling requests channels // ClientCtx main client struct type ClientCtx struct { locker sync.Mutex WS *websocket.Conn // stuff listeners map[string]ListenerFunc requests map[string]chan interface{} } //NewHandler creates a new WsRPC client handler func NewHandler(id string, ws *websocket.Conn) *ClientCtx { var c = ClientCtx{ WS: ws, listeners: map[string]ListenerFunc{}, requests: map[string]chan interface{}{}, } 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 { return } go func() { // async send ret, err := fn(params...) if err != nil { log.Println("Create error response, panic?") } var responseObj = CallObj{ OP: "response", ID: reqID, Response: ret, } //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{} { u := uuid.New() uuidStr := u.String() var callObj = CallObj{ OP: "call", ID: uuidStr, Method: method, Params: params, } res := make(chan interface{}, 1) c.locker.Lock() c.requests[uuidStr] = res c.locker.Unlock() websocket.JSON.Send(c.WS, &callObj) return <-res // Block until value } //On add a event listener on browser func (c *ClientCtx) Define(name string, listener ListenerFunc) { c.listeners[name] = listener } func (c *ClientCtx) Export(obj interface{}) { // Reflect here typ := reflect.TypeOf(obj) val := reflect.ValueOf(obj) for i := 0; i < typ.NumField(); i++ { fTyp := typ.Field(i) if fTyp.Type.Kind() != reflect.Func { log.Println("Ignore non func value") continue } tag, ok := fTyp.Tag.Lookup("wsexport") if !ok { continue } tagParts := strings.Split(tag, ",") exportName := tagParts[0] fnVal := val.Field(i) // Slow? c.listeners[exportName] = func(params ...interface{}) (interface{}, error) { if len(params) != fnVal.Type().NumIn() { return nil, ErrNParam } if fnVal.Type().NumOut() != 2 { return nil, ErrNReturn } if !fnVal.Type().Out(1).Implements(errorInterface) { return nil, ErrBadImplementation } vparam := []reflect.Value{} for _, p := range params { vparam = append(vparam, reflect.ValueOf(p)) } r := fnVal.Call(vparam) return r[0].Interface(), r[1].Interface().(error) } // It is a func, check for tag } }