add websocket support
parent
074c9020d7
commit
e9fc2a99ad
@ -0,0 +1,120 @@
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
log "github.com/fangdingjun/go-log"
|
||||
)
|
||||
|
||||
// HTTPTransport json rpc over http
|
||||
type HTTPTransport struct {
|
||||
Client *http.Client
|
||||
URL string
|
||||
nextid uint64
|
||||
Mu *sync.Mutex
|
||||
}
|
||||
|
||||
var _ Transport = &HTTPTransport{}
|
||||
|
||||
// NewHTTPTransport create a new http transport
|
||||
func NewHTTPTransport(uri string, client *http.Client) (Transport, error) {
|
||||
_, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := client
|
||||
if client == nil {
|
||||
c = http.DefaultClient
|
||||
}
|
||||
return &HTTPTransport{
|
||||
Client: c,
|
||||
URL: uri,
|
||||
Mu: new(sync.Mutex),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *HTTPTransport) nextID() uint64 {
|
||||
h.Mu.Lock()
|
||||
defer h.Mu.Unlock()
|
||||
h.nextid++
|
||||
return h.nextid
|
||||
}
|
||||
|
||||
// Call call a remote method
|
||||
func (h *HTTPTransport) Call(method string, args interface{}, reply interface{}) error {
|
||||
if args == nil {
|
||||
args = []string{}
|
||||
}
|
||||
|
||||
r := &request{
|
||||
Version: "2.0",
|
||||
Method: method,
|
||||
Params: args,
|
||||
ID: fmt.Sprintf("%d", h.nextID()),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("send %s", data)
|
||||
|
||||
body := bytes.NewBuffer(data)
|
||||
|
||||
req, err := http.NewRequest("POST", h.URL, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := h.Client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("recevied %s", data)
|
||||
|
||||
var res response
|
||||
|
||||
if err = json.Unmarshal(data, &res); err != nil {
|
||||
// non 200 response without valid json response
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http error: %s", resp.Status)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// non 200 response with valid json response
|
||||
if res.ID == r.ID && res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
|
||||
// non 200 response without valid json response
|
||||
if res.ID == r.ID && resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("http error: %s", resp.Status)
|
||||
}
|
||||
|
||||
return json.Unmarshal(res.Result, &reply)
|
||||
}
|
||||
|
||||
// Subscribe subscribe for change
|
||||
func (h *HTTPTransport) Subscribe(method string, notifyMethod string,
|
||||
args interface{}, reply interface{}) (chan json.RawMessage, chan *Error, error) {
|
||||
return nil, nil, errors.New("not supported")
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package jsonrpc
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Transport json rpc transport
|
||||
type Transport interface {
|
||||
Call(method string, args interface{}, reply interface{}) error
|
||||
Subscribe(method string, notifyMethod string,
|
||||
args interface{}, reply interface{}) (chan json.RawMessage, chan *Error, error)
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
log "github.com/fangdingjun/go-log"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebsocketTransport json rpc over websocket
|
||||
type WebsocketTransport struct {
|
||||
Conn *websocket.Conn
|
||||
URL string
|
||||
Mu *sync.Mutex
|
||||
inflight map[string]*inflightReq
|
||||
nextid uint64
|
||||
err error
|
||||
}
|
||||
|
||||
type inflightReq struct {
|
||||
id string
|
||||
ch chan json.RawMessage
|
||||
errch chan *Error
|
||||
}
|
||||
|
||||
var _ Transport = &WebsocketTransport{}
|
||||
|
||||
// NewWebsocketTransport create a new websocket transport
|
||||
func NewWebsocketTransport(uri string) (Transport, error) {
|
||||
dialer := &websocket.Dialer{}
|
||||
|
||||
conn, res, err := dialer.Dial(uri, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusSwitchingProtocols {
|
||||
return nil, fmt.Errorf("http error %d", res.StatusCode)
|
||||
}
|
||||
|
||||
w := &WebsocketTransport{
|
||||
Conn: conn,
|
||||
URL: uri,
|
||||
inflight: make(map[string]*inflightReq),
|
||||
Mu: new(sync.Mutex),
|
||||
}
|
||||
go w.readloop()
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func (h *WebsocketTransport) readloop() {
|
||||
for {
|
||||
var res response
|
||||
|
||||
_, data, err := h.Conn.ReadMessage()
|
||||
if err != nil {
|
||||
h.err = err
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("received: %s", data)
|
||||
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
h.err = err
|
||||
return
|
||||
}
|
||||
|
||||
if res.ID != "" {
|
||||
h.Mu.Lock()
|
||||
req, ok := h.inflight[res.ID]
|
||||
h.Mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if res.Error != nil {
|
||||
req.errch <- res.Error
|
||||
|
||||
h.Mu.Lock()
|
||||
delete(h.inflight, res.ID)
|
||||
h.Mu.Unlock()
|
||||
|
||||
continue
|
||||
}
|
||||
req.ch <- res.Result
|
||||
|
||||
h.Mu.Lock()
|
||||
delete(h.inflight, res.ID)
|
||||
h.Mu.Unlock()
|
||||
|
||||
continue
|
||||
}
|
||||
if res.Method != "" {
|
||||
h.Mu.Lock()
|
||||
req, ok := h.inflight[res.Method]
|
||||
h.Mu.Unlock()
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if res.Error != nil {
|
||||
req.errch <- res.Error
|
||||
continue
|
||||
}
|
||||
req.ch <- res.Params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WebsocketTransport) nextID() uint64 {
|
||||
h.Mu.Lock()
|
||||
defer h.Mu.Unlock()
|
||||
|
||||
h.nextid++
|
||||
|
||||
return h.nextid
|
||||
}
|
||||
|
||||
// Call call a remote method
|
||||
func (h *WebsocketTransport) Call(method string, args interface{}, reply interface{}) error {
|
||||
if h.err != nil {
|
||||
return h.err
|
||||
}
|
||||
|
||||
id := fmt.Sprintf("%d", h.nextID())
|
||||
req := request{
|
||||
ID: id,
|
||||
Version: "2.0",
|
||||
Method: method,
|
||||
Params: args,
|
||||
}
|
||||
|
||||
d, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("write %s", d)
|
||||
|
||||
if err := h.Conn.WriteMessage(websocket.TextMessage, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch := make(chan json.RawMessage, 1)
|
||||
errch := make(chan *Error, 1)
|
||||
|
||||
h.Mu.Lock()
|
||||
h.inflight[id] = &inflightReq{
|
||||
id: id,
|
||||
ch: ch,
|
||||
errch: errch,
|
||||
}
|
||||
h.Mu.Unlock()
|
||||
|
||||
select {
|
||||
case data := <-ch:
|
||||
return json.Unmarshal(data, reply)
|
||||
case err := <-errch:
|
||||
return err
|
||||
}
|
||||
//return nil
|
||||
}
|
||||
|
||||
// Subscribe subscribe for change
|
||||
func (h *WebsocketTransport) Subscribe(method string, notifyMethod string,
|
||||
args interface{}, reply interface{}) (chan json.RawMessage, chan *Error, error) {
|
||||
|
||||
err := h.Call(method, args, reply)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ch := make(chan json.RawMessage)
|
||||
errch := make(chan *Error)
|
||||
|
||||
h.Mu.Lock()
|
||||
h.inflight[notifyMethod] = &inflightReq{
|
||||
ch: ch,
|
||||
errch: errch,
|
||||
id: notifyMethod,
|
||||
}
|
||||
h.Mu.Unlock()
|
||||
|
||||
return ch, errch, nil
|
||||
}
|
Loading…
Reference in New Issue