master
dingjun 5 years ago
parent 1ac79fbc3c
commit 1a1e7d7404

@ -1,120 +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.NewReader(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")
}
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.NewReader(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")
}

@ -1,10 +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)
}
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)
}

@ -1,230 +1,230 @@
package jsonrpc
import (
"context"
"encoding/json"
"errors"
"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
ctx context.Context
cancelFunc context.CancelFunc
}
type inflightReq struct {
id string
ch chan json.RawMessage
errch chan *Error
}
// ErrConnClosed error for connection closed
var ErrConnClosed = errors.New("connection closed")
var _ Transport = &WebsocketTransport{}
// NewWebsocketTransport create a new websocket transport
func NewWebsocketTransport(ctx context.Context, uri string) (Transport, error) {
var dialer = &websocket.Dialer{}
conn, res, err := dialer.DialContext(ctx, uri, nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
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),
}
w.ctx, w.cancelFunc = context.WithCancel(ctx)
go w.readloop()
return w, nil
}
// Context return the context transport used
func (h *WebsocketTransport) Context() context.Context {
return h.ctx
}
func (h *WebsocketTransport) readloop() {
defer func() {
//log.Debugf("close websocket connection")
h.Conn.Close()
//log.Debugf("cancel context")
h.cancelFunc()
}()
for {
select {
case <-h.ctx.Done():
return
default:
}
var res response
_, data, err := h.Conn.ReadMessage()
if err != nil {
log.Errorln(err)
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 {
log.Warnf("handler for id %s not exists", res.ID)
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 {
log.Warnf("handler for method %s not exists", res.Method)
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 {
select {
case <-h.ctx.Done():
return ErrConnClosed
default:
}
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
}
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()
log.Debugf("write %s", d)
if err := h.Conn.WriteMessage(websocket.TextMessage, d); err != nil {
h.Mu.Lock()
delete(h.inflight, id)
h.Mu.Unlock()
return err
}
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) {
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()
err := h.Call(method, args, reply)
if err != nil {
h.Mu.Lock()
delete(h.inflight, notifyMethod)
h.Mu.Unlock()
return nil, nil, err
}
return ch, errch, nil
}
package jsonrpc
import (
"context"
"encoding/json"
"errors"
"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
ctx context.Context
cancelFunc context.CancelFunc
}
type inflightReq struct {
id string
ch chan json.RawMessage
errch chan *Error
}
// ErrConnClosed error for connection closed
var ErrConnClosed = errors.New("connection closed")
var _ Transport = &WebsocketTransport{}
// NewWebsocketTransport create a new websocket transport
func NewWebsocketTransport(ctx context.Context, uri string) (Transport, error) {
var dialer = &websocket.Dialer{}
conn, res, err := dialer.DialContext(ctx, uri, nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
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),
}
w.ctx, w.cancelFunc = context.WithCancel(ctx)
go w.readloop()
return w, nil
}
// Context return the context transport used
func (h *WebsocketTransport) Context() context.Context {
return h.ctx
}
func (h *WebsocketTransport) readloop() {
defer func() {
//log.Debugf("close websocket connection")
h.Conn.Close()
//log.Debugf("cancel context")
h.cancelFunc()
}()
for {
select {
case <-h.ctx.Done():
return
default:
}
var res response
_, data, err := h.Conn.ReadMessage()
if err != nil {
log.Errorln(err)
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 {
log.Warnf("handler for id %s not exists", res.ID)
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 {
log.Warnf("handler for method %s not exists", res.Method)
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 {
select {
case <-h.ctx.Done():
return ErrConnClosed
default:
}
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
}
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()
log.Debugf("write %s", d)
if err := h.Conn.WriteMessage(websocket.TextMessage, d); err != nil {
h.Mu.Lock()
delete(h.inflight, id)
h.Mu.Unlock()
return err
}
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) {
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()
err := h.Call(method, args, reply)
if err != nil {
h.Mu.Lock()
delete(h.inflight, notifyMethod)
h.Mu.Unlock()
return nil, nil, err
}
return ch, errch, nil
}

Loading…
Cancel
Save