mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-28 21:51:22 +00:00
commit
2c8df0ee6b
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.sh]
|
||||||
|
indent_style = tab
|
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:latest
|
||||||
|
|
||||||
|
RUN mkdir -p /go/src/github.com/tendermint/go-rpc
|
||||||
|
WORKDIR /go/src/github.com/tendermint/go-rpc
|
||||||
|
|
||||||
|
COPY Makefile /go/src/github.com/tendermint/go-rpc/
|
||||||
|
# COPY glide.yaml /go/src/github.com/tendermint/go-rpc/
|
||||||
|
# COPY glide.lock /go/src/github.com/tendermint/go-rpc/
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/tendermint/go-rpc
|
||||||
|
|
||||||
|
RUN make get_deps
|
19
Makefile
19
Makefile
@ -1,9 +1,18 @@
|
|||||||
.PHONY: all test get_deps
|
PACKAGES=$(shell go list ./... | grep -v "test")
|
||||||
|
|
||||||
all: test
|
all: get_deps test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
bash ./test/test.sh
|
@echo "--> Running go test --race"
|
||||||
|
@go test --race $(PACKAGES)
|
||||||
|
@echo "--> Running integration tests"
|
||||||
|
@bash ./test/integration_test.sh
|
||||||
|
|
||||||
get_deps:
|
get_deps:
|
||||||
go get -t -u github.com/tendermint/go-rpc/...
|
@echo "--> Running go get"
|
||||||
|
@go get -v -d $(PACKAGES)
|
||||||
|
@go list -f '{{join .TestImports "\n"}}' ./... | \
|
||||||
|
grep -v /vendor/ | sort | uniq | \
|
||||||
|
xargs go get -v -d
|
||||||
|
|
||||||
|
.PHONY: all test get_deps
|
||||||
|
39
README.md
39
README.md
@ -32,16 +32,16 @@ As a POST request, we use JSONRPC. For instance, the same request would have thi
|
|||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc": "2.0",
|
||||||
"id":"anything",
|
"id": "anything",
|
||||||
"method":"hello_world",
|
"method": "hello_world",
|
||||||
"params":["my_world", 5]
|
"params": {
|
||||||
|
"name": "my_world",
|
||||||
|
"num": 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the `params` does not currently support key-value pairs (https://github.com/tendermint/go-rpc/issues/1), so order matters (you can get the order from making a
|
|
||||||
GET request to `/`)
|
|
||||||
|
|
||||||
With the above saved in file `data.json`, we can make the request with
|
With the above saved in file `data.json`, we can make the request with
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -50,8 +50,8 @@ curl --data @data.json http://localhost:8008
|
|||||||
|
|
||||||
## WebSocket (JSONRPC)
|
## WebSocket (JSONRPC)
|
||||||
|
|
||||||
All requests are exposed over websocket in the same form as the POST JSONRPC.
|
All requests are exposed over websocket in the same form as the POST JSONRPC.
|
||||||
Websocket connections are available at their own endpoint, typically `/websocket`,
|
Websocket connections are available at their own endpoint, typically `/websocket`,
|
||||||
though this is configurable when starting the server.
|
though this is configurable when starting the server.
|
||||||
|
|
||||||
# Server Definition
|
# Server Definition
|
||||||
@ -102,10 +102,27 @@ go func() {
|
|||||||
Note that unix sockets are supported as well (eg. `/path/to/socket` instead of `0.0.0.0:8008`)
|
Note that unix sockets are supported as well (eg. `/path/to/socket` instead of `0.0.0.0:8008`)
|
||||||
|
|
||||||
Now see all available endpoints by sending a GET request to `0.0.0.0:8008`.
|
Now see all available endpoints by sending a GET request to `0.0.0.0:8008`.
|
||||||
Each route is available as a GET request, as a JSONRPCv2 POST request, and via JSONRPCv2 over websockets
|
Each route is available as a GET request, as a JSONRPCv2 POST request, and via JSONRPCv2 over websockets.
|
||||||
|
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
* [Tendermint](https://github.com/tendermint/tendermint/blob/master/rpc/core/routes.go)
|
* [Tendermint](https://github.com/tendermint/tendermint/blob/master/rpc/core/routes.go)
|
||||||
* [Network Monitor](https://github.com/tendermint/netmon/blob/master/handlers/routes.go)
|
* [tm-monitor](https://github.com/tendermint/tools/blob/master/tm-monitor/rpc.go)
|
||||||
|
|
||||||
|
## CHANGELOG
|
||||||
|
|
||||||
|
### 0.7.0
|
||||||
|
|
||||||
|
BREAKING CHANGES:
|
||||||
|
|
||||||
|
- removed `Client` empty interface
|
||||||
|
- `ClientJSONRPC#Call` `params` argument became a map
|
||||||
|
- rename `ClientURI` -> `URIClient`, `ClientJSONRPC` -> `JSONRPCClient`
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
- added `HTTPClient` interface, which can be used for both `ClientURI`
|
||||||
|
and `ClientJSONRPC`
|
||||||
|
- all params are now optional (Golang's default will be used if some param is missing)
|
||||||
|
- added `Call` method to `WSClient` (see method's doc for details)
|
||||||
|
@ -11,12 +11,10 @@ checkout:
|
|||||||
- rm -rf $REPO
|
- rm -rf $REPO
|
||||||
- mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME
|
- mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME
|
||||||
- mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO
|
- mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO
|
||||||
# - git submodule sync
|
|
||||||
# - git submodule update --init # use submodules
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
override:
|
override:
|
||||||
- "cd $REPO"
|
- "cd $REPO && make get_deps"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
override:
|
override:
|
||||||
|
@ -3,7 +3,6 @@ package rpcclient
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -12,11 +11,16 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
. "github.com/tendermint/go-common"
|
"github.com/pkg/errors"
|
||||||
"github.com/tendermint/go-rpc/types"
|
types "github.com/tendermint/go-rpc/types"
|
||||||
"github.com/tendermint/go-wire"
|
wire "github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HTTPClient is a common interface for JSONRPCClient and URIClient.
|
||||||
|
type HTTPClient interface {
|
||||||
|
Call(method string, params map[string]interface{}, result interface{}) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Deprecate support for IP:PORT or /path/to/socket
|
// TODO: Deprecate support for IP:PORT or /path/to/socket
|
||||||
func makeHTTPDialer(remoteAddr string) (string, func(string, string) (net.Conn, error)) {
|
func makeHTTPDialer(remoteAddr string) (string, func(string, string) (net.Conn, error)) {
|
||||||
|
|
||||||
@ -24,7 +28,7 @@ func makeHTTPDialer(remoteAddr string) (string, func(string, string) (net.Conn,
|
|||||||
var protocol, address string
|
var protocol, address string
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
log.Warn("WARNING (go-rpc): Please use fully formed listening addresses, including the tcp:// or unix:// prefix")
|
log.Warn("WARNING (go-rpc): Please use fully formed listening addresses, including the tcp:// or unix:// prefix")
|
||||||
protocol = rpctypes.SocketType(remoteAddr)
|
protocol = types.SocketType(remoteAddr)
|
||||||
address = remoteAddr
|
address = remoteAddr
|
||||||
} else {
|
} else {
|
||||||
protocol, address = parts[0], parts[1]
|
protocol, address = parts[0], parts[1]
|
||||||
@ -49,38 +53,39 @@ func makeHTTPClient(remoteAddr string) (string, *http.Client) {
|
|||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Client interface {
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// JSON rpc takes params as a slice
|
// JSON rpc takes params as a slice
|
||||||
type ClientJSONRPC struct {
|
type JSONRPCClient struct {
|
||||||
address string
|
address string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientJSONRPC(remote string) *ClientJSONRPC {
|
func NewJSONRPCClient(remote string) *JSONRPCClient {
|
||||||
address, client := makeHTTPClient(remote)
|
address, client := makeHTTPClient(remote)
|
||||||
return &ClientJSONRPC{
|
return &JSONRPCClient{
|
||||||
address: address,
|
address: address,
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientJSONRPC) Call(method string, params []interface{}, result interface{}) (interface{}, error) {
|
func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
||||||
return c.call(method, params, result)
|
// we need this step because we attempt to decode values using `go-wire`
|
||||||
}
|
// (handlers.go:176) on the server side
|
||||||
|
encodedParams := make(map[string]interface{})
|
||||||
func (c *ClientJSONRPC) call(method string, params []interface{}, result interface{}) (interface{}, error) {
|
for k, v := range params {
|
||||||
// Make request and get responseBytes
|
bytes := json.RawMessage(wire.JSONBytes(v))
|
||||||
request := rpctypes.RPCRequest{
|
encodedParams[k] = &bytes
|
||||||
|
}
|
||||||
|
request := types.RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: params,
|
Params: encodedParams,
|
||||||
ID: "",
|
ID: "",
|
||||||
}
|
}
|
||||||
requestBytes := wire.JSONBytes(request)
|
requestBytes, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// log.Info(string(requestBytes))
|
||||||
requestBuf := bytes.NewBuffer(requestBytes)
|
requestBuf := bytes.NewBuffer(requestBytes)
|
||||||
// log.Info(Fmt("RPC request to %v (%v): %v", c.remote, method, string(requestBytes)))
|
// log.Info(Fmt("RPC request to %v (%v): %v", c.remote, method, string(requestBytes)))
|
||||||
httpResponse, err := c.client.Post(c.address, "text/json", requestBuf)
|
httpResponse, err := c.client.Post(c.address, "text/json", requestBuf)
|
||||||
@ -99,24 +104,20 @@ func (c *ClientJSONRPC) call(method string, params []interface{}, result interfa
|
|||||||
//-------------------------------------------------------------
|
//-------------------------------------------------------------
|
||||||
|
|
||||||
// URI takes params as a map
|
// URI takes params as a map
|
||||||
type ClientURI struct {
|
type URIClient struct {
|
||||||
address string
|
address string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientURI(remote string) *ClientURI {
|
func NewURIClient(remote string) *URIClient {
|
||||||
address, client := makeHTTPClient(remote)
|
address, client := makeHTTPClient(remote)
|
||||||
return &ClientURI{
|
return &URIClient{
|
||||||
address: address,
|
address: address,
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientURI) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
func (c *URIClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
||||||
return c.call(method, params, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientURI) call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
|
||||||
values, err := argsToURLValues(params)
|
values, err := argsToURLValues(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -142,19 +143,19 @@ func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface
|
|||||||
// into the correct type
|
// into the correct type
|
||||||
// log.Notice("response", "response", string(responseBytes))
|
// log.Notice("response", "response", string(responseBytes))
|
||||||
var err error
|
var err error
|
||||||
response := &rpctypes.RPCResponse{}
|
response := &types.RPCResponse{}
|
||||||
err = json.Unmarshal(responseBytes, response)
|
err = json.Unmarshal(responseBytes, response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(Fmt("Error unmarshalling rpc response: %v", err))
|
return nil, errors.Errorf("Error unmarshalling rpc response: %v", err)
|
||||||
}
|
}
|
||||||
errorStr := response.Error
|
errorStr := response.Error
|
||||||
if errorStr != "" {
|
if errorStr != "" {
|
||||||
return nil, errors.New(Fmt("Response error: %v", errorStr))
|
return nil, errors.Errorf("Response error: %v", errorStr)
|
||||||
}
|
}
|
||||||
// unmarshal the RawMessage into the result
|
// unmarshal the RawMessage into the result
|
||||||
result = wire.ReadJSONPtr(result, *response.Result, &err)
|
result = wire.ReadJSONPtr(result, *response.Result, &err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(Fmt("Error unmarshalling rpc response result: %v", err))
|
return nil, errors.Errorf("Error unmarshalling rpc response result: %v", err)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,15 @@ package rpcclient
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
. "github.com/tendermint/go-common"
|
"github.com/pkg/errors"
|
||||||
"github.com/tendermint/go-rpc/types"
|
cmn "github.com/tendermint/go-common"
|
||||||
|
types "github.com/tendermint/go-rpc/types"
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -19,7 +20,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type WSClient struct {
|
type WSClient struct {
|
||||||
BaseService
|
cmn.BaseService
|
||||||
Address string // IP:PORT or /path/to/socket
|
Address string // IP:PORT or /path/to/socket
|
||||||
Endpoint string // /websocket/url/endpoint
|
Endpoint string // /websocket/url/endpoint
|
||||||
Dialer func(string, string) (net.Conn, error)
|
Dialer func(string, string) (net.Conn, error)
|
||||||
@ -32,14 +33,12 @@ type WSClient struct {
|
|||||||
func NewWSClient(remoteAddr, endpoint string) *WSClient {
|
func NewWSClient(remoteAddr, endpoint string) *WSClient {
|
||||||
addr, dialer := makeHTTPDialer(remoteAddr)
|
addr, dialer := makeHTTPDialer(remoteAddr)
|
||||||
wsClient := &WSClient{
|
wsClient := &WSClient{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
Dialer: dialer,
|
Dialer: dialer,
|
||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
Conn: nil,
|
Conn: nil,
|
||||||
ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity),
|
|
||||||
ErrorsCh: make(chan error, wsErrorsChannelCapacity),
|
|
||||||
}
|
}
|
||||||
wsClient.BaseService = *NewBaseService(log, "WSClient", wsClient)
|
wsClient.BaseService = *cmn.NewBaseService(log, "WSClient", wsClient)
|
||||||
return wsClient
|
return wsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,16 +46,24 @@ func (wsc *WSClient) String() string {
|
|||||||
return wsc.Address + ", " + wsc.Endpoint
|
return wsc.Address + ", " + wsc.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnStart implements cmn.BaseService interface
|
||||||
func (wsc *WSClient) OnStart() error {
|
func (wsc *WSClient) OnStart() error {
|
||||||
wsc.BaseService.OnStart()
|
wsc.BaseService.OnStart()
|
||||||
err := wsc.dial()
|
err := wsc.dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
wsc.ResultsCh = make(chan json.RawMessage, wsResultsChannelCapacity)
|
||||||
|
wsc.ErrorsCh = make(chan error, wsErrorsChannelCapacity)
|
||||||
go wsc.receiveEventsRoutine()
|
go wsc.receiveEventsRoutine()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnReset implements cmn.BaseService interface
|
||||||
|
func (wsc *WSClient) OnReset() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (wsc *WSClient) dial() error {
|
func (wsc *WSClient) dial() error {
|
||||||
|
|
||||||
// Dial
|
// Dial
|
||||||
@ -83,8 +90,10 @@ func (wsc *WSClient) dial() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnStop implements cmn.BaseService interface
|
||||||
func (wsc *WSClient) OnStop() {
|
func (wsc *WSClient) OnStop() {
|
||||||
wsc.BaseService.OnStop()
|
wsc.BaseService.OnStop()
|
||||||
|
wsc.Conn.Close()
|
||||||
// ResultsCh/ErrorsCh is closed in receiveEventsRoutine.
|
// ResultsCh/ErrorsCh is closed in receiveEventsRoutine.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +105,7 @@ func (wsc *WSClient) receiveEventsRoutine() {
|
|||||||
wsc.Stop()
|
wsc.Stop()
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
var response rpctypes.RPCResponse
|
var response types.RPCResponse
|
||||||
err := json.Unmarshal(data, &response)
|
err := json.Unmarshal(data, &response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("WSClient failed to parse message", "error", err, "data", string(data))
|
log.Info("WSClient failed to parse message", "error", err, "data", string(data))
|
||||||
@ -104,36 +113,60 @@ func (wsc *WSClient) receiveEventsRoutine() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if response.Error != "" {
|
if response.Error != "" {
|
||||||
wsc.ErrorsCh <- fmt.Errorf(response.Error)
|
wsc.ErrorsCh <- errors.Errorf(response.Error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
wsc.ResultsCh <- *response.Result
|
wsc.ResultsCh <- *response.Result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// this must be modified in the same go-routine that reads from the
|
||||||
|
// connection to avoid race conditions
|
||||||
|
wsc.Conn = nil
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
close(wsc.ResultsCh)
|
close(wsc.ResultsCh)
|
||||||
close(wsc.ErrorsCh)
|
close(wsc.ErrorsCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// subscribe to an event
|
// Subscribe to an event. Note the server must have a "subscribe" route
|
||||||
|
// defined.
|
||||||
func (wsc *WSClient) Subscribe(eventid string) error {
|
func (wsc *WSClient) Subscribe(eventid string) error {
|
||||||
err := wsc.WriteJSON(rpctypes.RPCRequest{
|
err := wsc.WriteJSON(types.RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: "",
|
ID: "",
|
||||||
Method: "subscribe",
|
Method: "subscribe",
|
||||||
Params: []interface{}{eventid},
|
Params: map[string]interface{}{"event": eventid},
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsubscribe from an event
|
// Unsubscribe from an event. Note the server must have a "unsubscribe" route
|
||||||
|
// defined.
|
||||||
func (wsc *WSClient) Unsubscribe(eventid string) error {
|
func (wsc *WSClient) Unsubscribe(eventid string) error {
|
||||||
err := wsc.WriteJSON(rpctypes.RPCRequest{
|
err := wsc.WriteJSON(types.RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: "",
|
ID: "",
|
||||||
Method: "unsubscribe",
|
Method: "unsubscribe",
|
||||||
Params: []interface{}{eventid},
|
Params: map[string]interface{}{"event": eventid},
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call asynchronously calls a given method by sending an RPCRequest to the
|
||||||
|
// server. Results will be available on ResultsCh, errors, if any, on ErrorsCh.
|
||||||
|
func (wsc *WSClient) Call(method string, params map[string]interface{}) error {
|
||||||
|
// we need this step because we attempt to decode values using `go-wire`
|
||||||
|
// (handlers.go:470) on the server side
|
||||||
|
encodedParams := make(map[string]interface{})
|
||||||
|
for k, v := range params {
|
||||||
|
bytes := json.RawMessage(wire.JSONBytes(v))
|
||||||
|
encodedParams[k] = &bytes
|
||||||
|
}
|
||||||
|
err := wsc.WriteJSON(types.RPCRequest{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
Method: method,
|
||||||
|
Params: encodedParams,
|
||||||
|
ID: "",
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
318
rpc_test.go
318
rpc_test.go
@ -1,20 +1,29 @@
|
|||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/go-rpc/client"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/tendermint/go-rpc/server"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/tendermint/go-rpc/types"
|
client "github.com/tendermint/go-rpc/client"
|
||||||
"github.com/tendermint/go-wire"
|
server "github.com/tendermint/go-rpc/server"
|
||||||
|
types "github.com/tendermint/go-rpc/types"
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client and Server should work over tcp or unix sockets
|
// Client and Server should work over tcp or unix sockets
|
||||||
var (
|
const (
|
||||||
tcpAddr = "tcp://0.0.0.0:46657"
|
tcpAddr = "tcp://0.0.0.0:46657"
|
||||||
unixAddr = "unix:///tmp/go-rpc.sock" // NOTE: must remove file for test to run again
|
|
||||||
|
unixSocket = "/tmp/go-rpc.sock"
|
||||||
|
unixAddr = "unix:///tmp/go-rpc.sock"
|
||||||
|
|
||||||
websocketEndpoint = "/websocket/endpoint"
|
websocketEndpoint = "/websocket/endpoint"
|
||||||
)
|
)
|
||||||
@ -22,44 +31,67 @@ var (
|
|||||||
// Define a type for results and register concrete versions
|
// Define a type for results and register concrete versions
|
||||||
type Result interface{}
|
type Result interface{}
|
||||||
|
|
||||||
type ResultStatus struct {
|
type ResultEcho struct {
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResultEchoBytes struct {
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
var _ = wire.RegisterInterface(
|
var _ = wire.RegisterInterface(
|
||||||
struct{ Result }{},
|
struct{ Result }{},
|
||||||
wire.ConcreteType{&ResultStatus{}, 0x1},
|
wire.ConcreteType{&ResultEcho{}, 0x1},
|
||||||
|
wire.ConcreteType{&ResultEchoBytes{}, 0x2},
|
||||||
)
|
)
|
||||||
|
|
||||||
// Define some routes
|
// Define some routes
|
||||||
var Routes = map[string]*rpcserver.RPCFunc{
|
var Routes = map[string]*server.RPCFunc{
|
||||||
"status": rpcserver.NewRPCFunc(StatusResult, "arg"),
|
"echo": server.NewRPCFunc(EchoResult, "arg"),
|
||||||
|
"echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"),
|
||||||
|
"echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// an rpc function
|
func EchoResult(v string) (Result, error) {
|
||||||
func StatusResult(v string) (Result, error) {
|
return &ResultEcho{v}, nil
|
||||||
return &ResultStatus{v}, nil
|
}
|
||||||
|
|
||||||
|
func EchoWSResult(wsCtx types.WSRPCContext, v string) (Result, error) {
|
||||||
|
return &ResultEcho{v}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EchoBytesResult(v []byte) (Result, error) {
|
||||||
|
return &ResultEchoBytes{v}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// launch unix and tcp servers
|
// launch unix and tcp servers
|
||||||
func init() {
|
func init() {
|
||||||
|
cmd := exec.Command("rm", "-f", unixSocket)
|
||||||
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err = cmd.Wait(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
rpcserver.RegisterRPCFuncs(mux, Routes)
|
server.RegisterRPCFuncs(mux, Routes)
|
||||||
wm := rpcserver.NewWebsocketManager(Routes, nil)
|
wm := server.NewWebsocketManager(Routes, nil)
|
||||||
mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
|
mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := rpcserver.StartHTTPServer(tcpAddr, mux)
|
_, err := server.StartHTTPServer(tcpAddr, mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
mux2 := http.NewServeMux()
|
mux2 := http.NewServeMux()
|
||||||
rpcserver.RegisterRPCFuncs(mux2, Routes)
|
server.RegisterRPCFuncs(mux2, Routes)
|
||||||
wm = rpcserver.NewWebsocketManager(Routes, nil)
|
wm = server.NewWebsocketManager(Routes, nil)
|
||||||
mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
|
mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := rpcserver.StartHTTPServer(unixAddr, mux2)
|
_, err := server.StartHTTPServer(unixAddr, mux2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -67,136 +99,200 @@ func init() {
|
|||||||
|
|
||||||
// wait for servers to start
|
// wait for servers to start
|
||||||
time.Sleep(time.Second * 2)
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testURI(t *testing.T, cl *rpcclient.ClientURI) {
|
func echoViaHTTP(cl client.HTTPClient, val string) (string, error) {
|
||||||
val := "acbd"
|
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
"arg": val,
|
"arg": val,
|
||||||
}
|
}
|
||||||
var result Result
|
var result Result
|
||||||
_, err := cl.Call("status", params, &result)
|
if _, err := cl.Call("echo", params, &result); err != nil {
|
||||||
if err != nil {
|
return "", err
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
got := result.(*ResultStatus).Value
|
|
||||||
if got != val {
|
|
||||||
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
|
|
||||||
}
|
}
|
||||||
|
return result.(*ResultEcho).Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) {
|
func echoBytesViaHTTP(cl client.HTTPClient, bytes []byte) ([]byte, error) {
|
||||||
val := "acbd"
|
params := map[string]interface{}{
|
||||||
params := []interface{}{val}
|
"arg": bytes,
|
||||||
|
}
|
||||||
var result Result
|
var result Result
|
||||||
_, err := cl.Call("status", params, &result)
|
if _, err := cl.Call("echo_bytes", params, &result); err != nil {
|
||||||
if err != nil {
|
return []byte{}, err
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
got := result.(*ResultStatus).Value
|
return result.(*ResultEchoBytes).Value, nil
|
||||||
if got != val {
|
}
|
||||||
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
|
|
||||||
|
func testWithHTTPClient(t *testing.T, cl client.HTTPClient) {
|
||||||
|
val := "acbd"
|
||||||
|
got, err := echoViaHTTP(cl, val)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, got, val)
|
||||||
|
|
||||||
|
val2 := randBytes(t)
|
||||||
|
got2, err := echoBytesViaHTTP(cl, val2)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, got2, val2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func echoViaWS(cl *client.WSClient, val string) (string, error) {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"arg": val,
|
||||||
|
}
|
||||||
|
err := cl.Call("echo", params)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-cl.ResultsCh:
|
||||||
|
result := new(Result)
|
||||||
|
wire.ReadJSONPtr(result, msg, &err)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return (*result).(*ResultEcho).Value, nil
|
||||||
|
case err := <-cl.ErrorsCh:
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWS(t *testing.T, cl *rpcclient.WSClient) {
|
func echoBytesViaWS(cl *client.WSClient, bytes []byte) ([]byte, error) {
|
||||||
val := "acbd"
|
params := map[string]interface{}{
|
||||||
params := []interface{}{val}
|
"arg": bytes,
|
||||||
err := cl.WriteJSON(rpctypes.RPCRequest{
|
}
|
||||||
JSONRPC: "2.0",
|
err := cl.Call("echo_bytes", params)
|
||||||
ID: "",
|
|
||||||
Method: "status",
|
|
||||||
Params: params,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := <-cl.ResultsCh
|
select {
|
||||||
result := new(Result)
|
case msg := <-cl.ResultsCh:
|
||||||
wire.ReadJSONPtr(result, msg, &err)
|
result := new(Result)
|
||||||
if err != nil {
|
wire.ReadJSONPtr(result, msg, &err)
|
||||||
t.Fatal(err)
|
if err != nil {
|
||||||
}
|
return []byte{}, nil
|
||||||
got := (*result).(*ResultStatus).Value
|
}
|
||||||
if got != val {
|
return (*result).(*ResultEchoBytes).Value, nil
|
||||||
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
|
case err := <-cl.ErrorsCh:
|
||||||
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testWithWSClient(t *testing.T, cl *client.WSClient) {
|
||||||
|
val := "acbd"
|
||||||
|
got, err := echoViaWS(cl, val)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, got, val)
|
||||||
|
|
||||||
|
val2 := randBytes(t)
|
||||||
|
got2, err := echoBytesViaWS(cl, val2)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, got2, val2)
|
||||||
|
}
|
||||||
|
|
||||||
//-------------
|
//-------------
|
||||||
|
|
||||||
func TestURI_TCP(t *testing.T) {
|
func TestServersAndClientsBasic(t *testing.T) {
|
||||||
cl := rpcclient.NewClientURI(tcpAddr)
|
serverAddrs := [...]string{tcpAddr, unixAddr}
|
||||||
testURI(t, cl)
|
for _, addr := range serverAddrs {
|
||||||
}
|
cl1 := client.NewURIClient(addr)
|
||||||
|
fmt.Printf("=== testing server on %s using %v client", addr, cl1)
|
||||||
|
testWithHTTPClient(t, cl1)
|
||||||
|
|
||||||
func TestURI_UNIX(t *testing.T) {
|
cl2 := client.NewJSONRPCClient(tcpAddr)
|
||||||
cl := rpcclient.NewClientURI(unixAddr)
|
fmt.Printf("=== testing server on %s using %v client", addr, cl2)
|
||||||
testURI(t, cl)
|
testWithHTTPClient(t, cl2)
|
||||||
}
|
|
||||||
|
|
||||||
func TestJSONRPC_TCP(t *testing.T) {
|
cl3 := client.NewWSClient(tcpAddr, websocketEndpoint)
|
||||||
cl := rpcclient.NewClientJSONRPC(tcpAddr)
|
_, err := cl3.Start()
|
||||||
testJSONRPC(t, cl)
|
require.Nil(t, err)
|
||||||
}
|
fmt.Printf("=== testing server on %s using %v client", addr, cl3)
|
||||||
|
testWithWSClient(t, cl3)
|
||||||
func TestJSONRPC_UNIX(t *testing.T) {
|
cl3.Stop()
|
||||||
cl := rpcclient.NewClientJSONRPC(unixAddr)
|
|
||||||
testJSONRPC(t, cl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWS_TCP(t *testing.T) {
|
|
||||||
cl := rpcclient.NewWSClient(tcpAddr, websocketEndpoint)
|
|
||||||
_, err := cl.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
testWS(t, cl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWS_UNIX(t *testing.T) {
|
|
||||||
cl := rpcclient.NewWSClient(unixAddr, websocketEndpoint)
|
|
||||||
_, err := cl.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testWS(t, cl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHexStringArg(t *testing.T) {
|
func TestHexStringArg(t *testing.T) {
|
||||||
cl := rpcclient.NewClientURI(tcpAddr)
|
cl := client.NewURIClient(tcpAddr)
|
||||||
// should NOT be handled as hex
|
// should NOT be handled as hex
|
||||||
val := "0xabc"
|
val := "0xabc"
|
||||||
params := map[string]interface{}{
|
got, err := echoViaHTTP(cl, val)
|
||||||
"arg": val,
|
require.Nil(t, err)
|
||||||
}
|
assert.Equal(t, got, val)
|
||||||
var result Result
|
|
||||||
_, err := cl.Call("status", params, &result)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
got := result.(*ResultStatus).Value
|
|
||||||
if got != val {
|
|
||||||
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuotedStringArg(t *testing.T) {
|
func TestQuotedStringArg(t *testing.T) {
|
||||||
cl := rpcclient.NewClientURI(tcpAddr)
|
cl := client.NewURIClient(tcpAddr)
|
||||||
// should NOT be unquoted
|
// should NOT be unquoted
|
||||||
val := "\"abc\""
|
val := "\"abc\""
|
||||||
|
got, err := echoViaHTTP(cl, val)
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, got, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWSNewWSRPCFunc(t *testing.T) {
|
||||||
|
cl := client.NewWSClient(tcpAddr, websocketEndpoint)
|
||||||
|
_, err := cl.Start()
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer cl.Stop()
|
||||||
|
|
||||||
|
val := "acbd"
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
"arg": val,
|
"arg": val,
|
||||||
}
|
}
|
||||||
var result Result
|
err = cl.WriteJSON(types.RPCRequest{
|
||||||
_, err := cl.Call("status", params, &result)
|
JSONRPC: "2.0",
|
||||||
if err != nil {
|
ID: "",
|
||||||
|
Method: "echo_ws",
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-cl.ResultsCh:
|
||||||
|
result := new(Result)
|
||||||
|
wire.ReadJSONPtr(result, msg, &err)
|
||||||
|
require.Nil(t, err)
|
||||||
|
got := (*result).(*ResultEcho).Value
|
||||||
|
assert.Equal(t, got, val)
|
||||||
|
case err := <-cl.ErrorsCh:
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
got := result.(*ResultStatus).Value
|
}
|
||||||
if got != val {
|
|
||||||
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
|
func TestWSHandlesArrayParams(t *testing.T) {
|
||||||
|
cl := client.NewWSClient(tcpAddr, websocketEndpoint)
|
||||||
|
_, err := cl.Start()
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer cl.Stop()
|
||||||
|
|
||||||
|
val := "acbd"
|
||||||
|
params := []interface{}{val}
|
||||||
|
err = cl.WriteJSON(types.RPCRequest{
|
||||||
|
JSONRPC: "2.0",
|
||||||
|
ID: "",
|
||||||
|
Method: "echo_ws",
|
||||||
|
Params: params,
|
||||||
|
})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-cl.ResultsCh:
|
||||||
|
result := new(Result)
|
||||||
|
wire.ReadJSONPtr(result, msg, &err)
|
||||||
|
require.Nil(t, err)
|
||||||
|
got := (*result).(*ResultEcho).Value
|
||||||
|
assert.Equal(t, got, val)
|
||||||
|
case err := <-cl.ErrorsCh:
|
||||||
|
t.Fatalf("%+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randBytes(t *testing.T) []byte {
|
||||||
|
n := rand.Intn(10) + 2
|
||||||
|
buf := make([]byte, n)
|
||||||
|
_, err := crand.Read(buf)
|
||||||
|
require.Nil(t, err)
|
||||||
|
return bytes.Replace(buf, []byte("="), []byte{100}, -1)
|
||||||
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -14,10 +13,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
. "github.com/tendermint/go-common"
|
"github.com/pkg/errors"
|
||||||
"github.com/tendermint/go-events"
|
cmn "github.com/tendermint/go-common"
|
||||||
. "github.com/tendermint/go-rpc/types"
|
events "github.com/tendermint/go-events"
|
||||||
"github.com/tendermint/go-wire"
|
types "github.com/tendermint/go-rpc/types"
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions.
|
// Adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions.
|
||||||
@ -105,76 +105,100 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var request RPCRequest
|
var request types.RPCRequest
|
||||||
err := json.Unmarshal(b, &request)
|
err := json.Unmarshal(b, &request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error())))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(r.URL.Path) > 1 {
|
if len(r.URL.Path) > 1 {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path)))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rpcFunc := funcMap[request.Method]
|
rpcFunc := funcMap[request.Method]
|
||||||
if rpcFunc == nil {
|
if rpcFunc == nil {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if rpcFunc.ws {
|
if rpcFunc.ws {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
args, err := jsonParamsToArgs(rpcFunc, request.Params)
|
args, err := jsonParamsToArgsRPC(rpcFunc, request.Params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error())))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
returns := rpcFunc.f.Call(args)
|
returns := rpcFunc.f.Call(args)
|
||||||
log.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
|
log.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
|
||||||
result, err := unreflectResult(returns)
|
result, err := unreflectResult(returns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, result, fmt.Sprintf("Error unreflecting result: %v", err.Error())))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, result, ""))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a list of interfaces to properly typed values
|
// Convert a []interface{} OR a map[string]interface{} to properly typed values
|
||||||
func jsonParamsToArgs(rpcFunc *RPCFunc, params []interface{}) ([]reflect.Value, error) {
|
//
|
||||||
if len(rpcFunc.argNames) != len(params) {
|
// argsOffset should be 0 for RPC calls, and 1 for WS requests, where len(rpcFunc.args) != len(rpcFunc.argNames).
|
||||||
return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
|
// Example:
|
||||||
len(rpcFunc.argNames), rpcFunc.argNames, len(params), params))
|
// rpcFunc.args = [rpctypes.WSRPCContext string]
|
||||||
}
|
// rpcFunc.argNames = ["arg"]
|
||||||
values := make([]reflect.Value, len(params))
|
func jsonParamsToArgs(rpcFunc *RPCFunc, paramsI interface{}, argsOffset int) ([]reflect.Value, error) {
|
||||||
for i, p := range params {
|
values := make([]reflect.Value, len(rpcFunc.argNames))
|
||||||
ty := rpcFunc.args[i]
|
|
||||||
v, err := _jsonObjectToArg(ty, p)
|
switch params := paramsI.(type) {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
case map[string]interface{}:
|
||||||
|
for i, argName := range rpcFunc.argNames {
|
||||||
|
argType := rpcFunc.args[i+argsOffset]
|
||||||
|
|
||||||
|
// decode param if provided
|
||||||
|
if param, ok := params[argName]; ok && "" != param {
|
||||||
|
v, err := _jsonObjectToArg(argType, param)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values[i] = v
|
||||||
|
} else { // use default for that type
|
||||||
|
values[i] = reflect.Zero(argType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
values[i] = v
|
case []interface{}:
|
||||||
|
if len(rpcFunc.argNames) != len(params) {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
|
||||||
|
len(rpcFunc.argNames), rpcFunc.argNames, len(params), params))
|
||||||
|
}
|
||||||
|
values := make([]reflect.Value, len(params))
|
||||||
|
for i, p := range params {
|
||||||
|
ty := rpcFunc.args[i+argsOffset]
|
||||||
|
v, err := _jsonObjectToArg(ty, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
values[i] = v
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown type for JSON params %v. Expected map[string]interface{} or []interface{}", reflect.TypeOf(paramsI))
|
||||||
}
|
}
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert a []interface{} OR a map[string]interface{} to properly typed values
|
||||||
|
func jsonParamsToArgsRPC(rpcFunc *RPCFunc, paramsI interface{}) ([]reflect.Value, error) {
|
||||||
|
return jsonParamsToArgs(rpcFunc, paramsI, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// Same as above, but with the first param the websocket connection
|
// Same as above, but with the first param the websocket connection
|
||||||
func jsonParamsToArgsWS(rpcFunc *RPCFunc, params []interface{}, wsCtx WSRPCContext) ([]reflect.Value, error) {
|
func jsonParamsToArgsWS(rpcFunc *RPCFunc, paramsI interface{}, wsCtx types.WSRPCContext) ([]reflect.Value, error) {
|
||||||
if len(rpcFunc.argNames) != len(params) {
|
values, err := jsonParamsToArgs(rpcFunc, paramsI, 1)
|
||||||
return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
|
if err != nil {
|
||||||
len(rpcFunc.argNames)-1, rpcFunc.argNames[1:], len(params), params))
|
return nil, err
|
||||||
}
|
}
|
||||||
values := make([]reflect.Value, len(params)+1)
|
return append([]reflect.Value{reflect.ValueOf(wsCtx)}, values...), nil
|
||||||
values[0] = reflect.ValueOf(wsCtx)
|
|
||||||
for i, p := range params {
|
|
||||||
ty := rpcFunc.args[i+1]
|
|
||||||
v, err := _jsonObjectToArg(ty, p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
values[i+1] = v
|
|
||||||
}
|
|
||||||
return values, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
|
func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
|
||||||
@ -197,7 +221,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request)
|
|||||||
// Exception for websocket endpoints
|
// Exception for websocket endpoints
|
||||||
if rpcFunc.ws {
|
if rpcFunc.ws {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse("", nil, "This RPC method is only for websockets"))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, "This RPC method is only for websockets"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// All other endpoints
|
// All other endpoints
|
||||||
@ -205,33 +229,38 @@ func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request)
|
|||||||
log.Debug("HTTP HANDLER", "req", r)
|
log.Debug("HTTP HANDLER", "req", r)
|
||||||
args, err := httpParamsToArgs(rpcFunc, r)
|
args, err := httpParamsToArgs(rpcFunc, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error())))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
returns := rpcFunc.f.Call(args)
|
returns := rpcFunc.f.Call(args)
|
||||||
log.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
|
log.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
|
||||||
result, err := unreflectResult(returns)
|
result, err := unreflectResult(returns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error unreflecting result: %v", err.Error())))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse("", result, ""))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Covert an http query to a list of properly typed values.
|
// Covert an http query to a list of properly typed values.
|
||||||
// To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
|
// To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
|
||||||
func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
|
func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
|
||||||
argTypes := rpcFunc.args
|
values := make([]reflect.Value, len(rpcFunc.args))
|
||||||
argNames := rpcFunc.argNames
|
|
||||||
|
for i, name := range rpcFunc.argNames {
|
||||||
|
argType := rpcFunc.args[i]
|
||||||
|
|
||||||
|
values[i] = reflect.Zero(argType) // set default for that type
|
||||||
|
|
||||||
values := make([]reflect.Value, len(argNames))
|
|
||||||
for i, name := range argNames {
|
|
||||||
ty := argTypes[i]
|
|
||||||
arg := GetParam(r, name)
|
arg := GetParam(r, name)
|
||||||
// log.Notice("param to arg", "ty", ty, "name", name, "arg", arg)
|
// log.Notice("param to arg", "argType", argType, "name", name, "arg", arg)
|
||||||
|
|
||||||
v, err, ok := nonJsonToArg(ty, arg)
|
if "" == arg {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err, ok := nonJsonToArg(argType, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -241,11 +270,12 @@ func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pass values to go-wire
|
// Pass values to go-wire
|
||||||
values[i], err = _jsonStringToArg(ty, arg)
|
values[i], err = _jsonStringToArg(argType, arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +298,7 @@ func nonJsonToArg(ty reflect.Type, arg string) (reflect.Value, error, bool) {
|
|||||||
|
|
||||||
if isHexString {
|
if isHexString {
|
||||||
if !expectingString && !expectingByteSlice {
|
if !expectingString && !expectingByteSlice {
|
||||||
err := fmt.Errorf("Got a hex string arg, but expected '%s'",
|
err := errors.Errorf("Got a hex string arg, but expected '%s'",
|
||||||
ty.Kind().String())
|
ty.Kind().String())
|
||||||
return reflect.ValueOf(nil), err, false
|
return reflect.ValueOf(nil), err, false
|
||||||
}
|
}
|
||||||
@ -313,11 +343,11 @@ const (
|
|||||||
// contains listener id, underlying ws connection,
|
// contains listener id, underlying ws connection,
|
||||||
// and the event switch for subscribing to events
|
// and the event switch for subscribing to events
|
||||||
type wsConnection struct {
|
type wsConnection struct {
|
||||||
BaseService
|
cmn.BaseService
|
||||||
|
|
||||||
remoteAddr string
|
remoteAddr string
|
||||||
baseConn *websocket.Conn
|
baseConn *websocket.Conn
|
||||||
writeChan chan RPCResponse
|
writeChan chan types.RPCResponse
|
||||||
readTimeout *time.Timer
|
readTimeout *time.Timer
|
||||||
pingTicker *time.Ticker
|
pingTicker *time.Ticker
|
||||||
|
|
||||||
@ -330,11 +360,11 @@ func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw
|
|||||||
wsc := &wsConnection{
|
wsc := &wsConnection{
|
||||||
remoteAddr: baseConn.RemoteAddr().String(),
|
remoteAddr: baseConn.RemoteAddr().String(),
|
||||||
baseConn: baseConn,
|
baseConn: baseConn,
|
||||||
writeChan: make(chan RPCResponse, writeChanCapacity), // error when full.
|
writeChan: make(chan types.RPCResponse, writeChanCapacity), // error when full.
|
||||||
funcMap: funcMap,
|
funcMap: funcMap,
|
||||||
evsw: evsw,
|
evsw: evsw,
|
||||||
}
|
}
|
||||||
wsc.BaseService = *NewBaseService(log, "wsConnection", wsc)
|
wsc.BaseService = *cmn.NewBaseService(log, "wsConnection", wsc)
|
||||||
return wsc
|
return wsc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,12 +372,15 @@ func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw
|
|||||||
func (wsc *wsConnection) OnStart() error {
|
func (wsc *wsConnection) OnStart() error {
|
||||||
wsc.BaseService.OnStart()
|
wsc.BaseService.OnStart()
|
||||||
|
|
||||||
|
// these must be set before the readRoutine is created, as it may
|
||||||
|
// call wsc.Stop(), which accesses these timers
|
||||||
|
wsc.readTimeout = time.NewTimer(time.Second * wsReadTimeoutSeconds)
|
||||||
|
wsc.pingTicker = time.NewTicker(time.Second * wsPingTickerSeconds)
|
||||||
|
|
||||||
// Read subscriptions/unsubscriptions to events
|
// Read subscriptions/unsubscriptions to events
|
||||||
go wsc.readRoutine()
|
go wsc.readRoutine()
|
||||||
|
|
||||||
// Custom Ping handler to touch readTimeout
|
// Custom Ping handler to touch readTimeout
|
||||||
wsc.readTimeout = time.NewTimer(time.Second * wsReadTimeoutSeconds)
|
|
||||||
wsc.pingTicker = time.NewTicker(time.Second * wsPingTickerSeconds)
|
|
||||||
wsc.baseConn.SetPingHandler(func(m string) error {
|
wsc.baseConn.SetPingHandler(func(m string) error {
|
||||||
// NOTE: https://github.com/gorilla/websocket/issues/97
|
// NOTE: https://github.com/gorilla/websocket/issues/97
|
||||||
go wsc.baseConn.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds))
|
go wsc.baseConn.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds))
|
||||||
@ -368,7 +401,9 @@ func (wsc *wsConnection) OnStart() error {
|
|||||||
|
|
||||||
func (wsc *wsConnection) OnStop() {
|
func (wsc *wsConnection) OnStop() {
|
||||||
wsc.BaseService.OnStop()
|
wsc.BaseService.OnStop()
|
||||||
wsc.evsw.RemoveListener(wsc.remoteAddr)
|
if wsc.evsw != nil {
|
||||||
|
wsc.evsw.RemoveListener(wsc.remoteAddr)
|
||||||
|
}
|
||||||
wsc.readTimeout.Stop()
|
wsc.readTimeout.Stop()
|
||||||
wsc.pingTicker.Stop()
|
wsc.pingTicker.Stop()
|
||||||
// The write loop closes the websocket connection
|
// The write loop closes the websocket connection
|
||||||
@ -399,7 +434,7 @@ func (wsc *wsConnection) GetEventSwitch() events.EventSwitch {
|
|||||||
// Implements WSRPCConnection
|
// Implements WSRPCConnection
|
||||||
// Blocking write to writeChan until service stops.
|
// Blocking write to writeChan until service stops.
|
||||||
// Goroutine-safe
|
// Goroutine-safe
|
||||||
func (wsc *wsConnection) WriteRPCResponse(resp RPCResponse) {
|
func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) {
|
||||||
select {
|
select {
|
||||||
case <-wsc.Quit:
|
case <-wsc.Quit:
|
||||||
return
|
return
|
||||||
@ -410,7 +445,7 @@ func (wsc *wsConnection) WriteRPCResponse(resp RPCResponse) {
|
|||||||
// Implements WSRPCConnection
|
// Implements WSRPCConnection
|
||||||
// Nonblocking write.
|
// Nonblocking write.
|
||||||
// Goroutine-safe
|
// Goroutine-safe
|
||||||
func (wsc *wsConnection) TryWriteRPCResponse(resp RPCResponse) bool {
|
func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool {
|
||||||
select {
|
select {
|
||||||
case <-wsc.Quit:
|
case <-wsc.Quit:
|
||||||
return false
|
return false
|
||||||
@ -444,11 +479,11 @@ func (wsc *wsConnection) readRoutine() {
|
|||||||
wsc.Stop()
|
wsc.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var request RPCRequest
|
var request types.RPCRequest
|
||||||
err = json.Unmarshal(in, &request)
|
err = json.Unmarshal(in, &request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error())
|
errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error())
|
||||||
wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, errStr))
|
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, errStr))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,28 +491,28 @@ func (wsc *wsConnection) readRoutine() {
|
|||||||
|
|
||||||
rpcFunc := wsc.funcMap[request.Method]
|
rpcFunc := wsc.funcMap[request.Method]
|
||||||
if rpcFunc == nil {
|
if rpcFunc == nil {
|
||||||
wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method))
|
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var args []reflect.Value
|
var args []reflect.Value
|
||||||
if rpcFunc.ws {
|
if rpcFunc.ws {
|
||||||
wsCtx := WSRPCContext{Request: request, WSRPCConnection: wsc}
|
wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc}
|
||||||
args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx)
|
args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx)
|
||||||
} else {
|
} else {
|
||||||
args, err = jsonParamsToArgs(rpcFunc, request.Params)
|
args, err = jsonParamsToArgsRPC(rpcFunc, request.Params)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, err.Error()))
|
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
returns := rpcFunc.f.Call(args)
|
returns := rpcFunc.f.Call(args)
|
||||||
log.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns)
|
log.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns)
|
||||||
result, err := unreflectResult(returns)
|
result, err := unreflectResult(returns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, err.Error()))
|
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error()))
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
wsc.WriteRPCResponse(NewRPCResponse(request.ID, result, ""))
|
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, ""))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,7 +598,7 @@ func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
func unreflectResult(returns []reflect.Value) (interface{}, error) {
|
func unreflectResult(returns []reflect.Value) (interface{}, error) {
|
||||||
errV := returns[1]
|
errV := returns[1]
|
||||||
if errV.Interface() != nil {
|
if errV.Interface() != nil {
|
||||||
return nil, fmt.Errorf("%v", errV.Interface())
|
return nil, errors.Errorf("%v", errV.Interface())
|
||||||
}
|
}
|
||||||
rv := returns[0]
|
rv := returns[0]
|
||||||
// the result is a registered interface,
|
// the result is a registered interface,
|
||||||
|
@ -2,10 +2,11 @@ package rpcserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -39,7 +40,7 @@ func GetParamInt64(r *http.Request, param string) (int64, error) {
|
|||||||
s := GetParam(r, param)
|
s := GetParam(r, param)
|
||||||
i, err := strconv.ParseInt(s, 10, 64)
|
i, err := strconv.ParseInt(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(param, err.Error())
|
return 0, errors.Errorf(param, err.Error())
|
||||||
}
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
@ -48,7 +49,7 @@ func GetParamInt32(r *http.Request, param string) (int32, error) {
|
|||||||
s := GetParam(r, param)
|
s := GetParam(r, param)
|
||||||
i, err := strconv.ParseInt(s, 10, 32)
|
i, err := strconv.ParseInt(s, 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(param, err.Error())
|
return 0, errors.Errorf(param, err.Error())
|
||||||
}
|
}
|
||||||
return int32(i), nil
|
return int32(i), nil
|
||||||
}
|
}
|
||||||
@ -57,7 +58,7 @@ func GetParamUint64(r *http.Request, param string) (uint64, error) {
|
|||||||
s := GetParam(r, param)
|
s := GetParam(r, param)
|
||||||
i, err := strconv.ParseUint(s, 10, 64)
|
i, err := strconv.ParseUint(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(param, err.Error())
|
return 0, errors.Errorf(param, err.Error())
|
||||||
}
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
@ -66,7 +67,7 @@ func GetParamUint(r *http.Request, param string) (uint, error) {
|
|||||||
s := GetParam(r, param)
|
s := GetParam(r, param)
|
||||||
i, err := strconv.ParseUint(s, 10, 64)
|
i, err := strconv.ParseUint(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(param, err.Error())
|
return 0, errors.Errorf(param, err.Error())
|
||||||
}
|
}
|
||||||
return uint(i), nil
|
return uint(i), nil
|
||||||
}
|
}
|
||||||
@ -74,7 +75,7 @@ func GetParamUint(r *http.Request, param string) (uint, error) {
|
|||||||
func GetParamRegexp(r *http.Request, param string, re *regexp.Regexp) (string, error) {
|
func GetParamRegexp(r *http.Request, param string, re *regexp.Regexp) (string, error) {
|
||||||
s := GetParam(r, param)
|
s := GetParam(r, param)
|
||||||
if !re.MatchString(s) {
|
if !re.MatchString(s) {
|
||||||
return "", fmt.Errorf(param, "Did not match regular expression %v", re.String())
|
return "", errors.Errorf(param, "Did not match regular expression %v", re.String())
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -83,7 +84,7 @@ func GetParamFloat64(r *http.Request, param string) (float64, error) {
|
|||||||
s := GetParam(r, param)
|
s := GetParam(r, param)
|
||||||
f, err := strconv.ParseFloat(s, 64)
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(param, err.Error())
|
return 0, errors.Errorf(param, err.Error())
|
||||||
}
|
}
|
||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/tendermint/go-common"
|
"github.com/pkg/errors"
|
||||||
. "github.com/tendermint/go-rpc/types"
|
types "github.com/tendermint/go-rpc/types"
|
||||||
//"github.com/tendermint/go-wire"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartHTTPServer(listenAddr string, handler http.Handler) (listener net.Listener, err error) {
|
func StartHTTPServer(listenAddr string, handler http.Handler) (listener net.Listener, err error) {
|
||||||
@ -24,17 +23,17 @@ func StartHTTPServer(listenAddr string, handler http.Handler) (listener net.List
|
|||||||
log.Warn("WARNING (go-rpc): Please use fully formed listening addresses, including the tcp:// or unix:// prefix")
|
log.Warn("WARNING (go-rpc): Please use fully formed listening addresses, including the tcp:// or unix:// prefix")
|
||||||
// we used to allow addrs without tcp/unix prefix by checking for a colon
|
// we used to allow addrs without tcp/unix prefix by checking for a colon
|
||||||
// TODO: Deprecate
|
// TODO: Deprecate
|
||||||
proto = SocketType(listenAddr)
|
proto = types.SocketType(listenAddr)
|
||||||
addr = listenAddr
|
addr = listenAddr
|
||||||
// return nil, fmt.Errorf("Invalid listener address %s", lisenAddr)
|
// return nil, errors.Errorf("Invalid listener address %s", lisenAddr)
|
||||||
} else {
|
} else {
|
||||||
proto, addr = parts[0], parts[1]
|
proto, addr = parts[0], parts[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Notice(Fmt("Starting RPC HTTP server on %s socket %v", proto, addr))
|
log.Notice(fmt.Sprintf("Starting RPC HTTP server on %s socket %v", proto, addr))
|
||||||
listener, err = net.Listen(proto, addr)
|
listener, err = net.Listen(proto, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to listen to %v: %v", listenAddr, err)
|
return nil, errors.Errorf("Failed to listen to %v: %v", listenAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -47,7 +46,7 @@ func StartHTTPServer(listenAddr string, handler http.Handler) (listener net.List
|
|||||||
return listener, nil
|
return listener, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteRPCResponseHTTP(w http.ResponseWriter, res RPCResponse) {
|
func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) {
|
||||||
// jsonBytes := wire.JSONBytesPretty(res)
|
// jsonBytes := wire.JSONBytesPretty(res)
|
||||||
jsonBytes, err := json.Marshal(res)
|
jsonBytes, err := json.Marshal(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -83,13 +82,13 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler {
|
|||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
|
|
||||||
// If RPCResponse
|
// If RPCResponse
|
||||||
if res, ok := e.(RPCResponse); ok {
|
if res, ok := e.(types.RPCResponse); ok {
|
||||||
WriteRPCResponseHTTP(rww, res)
|
WriteRPCResponseHTTP(rww, res)
|
||||||
} else {
|
} else {
|
||||||
// For the rest,
|
// For the rest,
|
||||||
log.Error("Panic in RPC HTTP handler", "error", e, "stack", string(debug.Stack()))
|
log.Error("Panic in RPC HTTP handler", "error", e, "stack", string(debug.Stack()))
|
||||||
rww.WriteHeader(http.StatusInternalServerError)
|
rww.WriteHeader(http.StatusInternalServerError)
|
||||||
WriteRPCResponseHTTP(rww, NewRPCResponse("", nil, Fmt("Internal Server Error: %v", e)))
|
WriteRPCResponseHTTP(rww, types.NewRPCResponse("", nil, fmt.Sprintf("Internal Server Error: %v", e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"jsonrpc":"2.0",
|
"jsonrpc": "2.0",
|
||||||
"id":"",
|
"id": "",
|
||||||
"method":"hello_world",
|
"method": "hello_world",
|
||||||
"params":["my_world", 5]
|
"params": {
|
||||||
|
"name": "my_world",
|
||||||
|
"num": 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
95
test/integration_test.sh
Executable file
95
test/integration_test.sh
Executable file
@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Get the directory of where this script is.
|
||||||
|
SOURCE="${BASH_SOURCE[0]}"
|
||||||
|
while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done
|
||||||
|
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
||||||
|
|
||||||
|
# Change into that dir because we expect that.
|
||||||
|
pushd "$DIR"
|
||||||
|
|
||||||
|
echo "==> Building the server"
|
||||||
|
go build -o rpcserver main.go
|
||||||
|
|
||||||
|
echo "==> (Re)starting the server"
|
||||||
|
PID=$(pgrep rpcserver || echo "")
|
||||||
|
if [[ $PID != "" ]]; then
|
||||||
|
kill -9 "$PID"
|
||||||
|
fi
|
||||||
|
./rpcserver &
|
||||||
|
PID=$!
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
echo "==> simple request"
|
||||||
|
R1=$(curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5')
|
||||||
|
R2=$(curl -s --data @data.json http://localhost:8008)
|
||||||
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
|
echo "responses are not identical:"
|
||||||
|
echo "R1: $R1"
|
||||||
|
echo "R2: $R2"
|
||||||
|
echo "FAIL"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> request with 0x-prefixed hex string arg"
|
||||||
|
R1=$(curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123')
|
||||||
|
R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi ABCD 123"},"error":""}'
|
||||||
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
|
echo "responses are not identical:"
|
||||||
|
echo "R1: $R1"
|
||||||
|
echo "R2: $R2"
|
||||||
|
echo "FAIL"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> request with missing params"
|
||||||
|
R1=$(curl -s 'http://localhost:8008/hello_world')
|
||||||
|
R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi 0"},"error":""}'
|
||||||
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
|
echo "responses are not identical:"
|
||||||
|
echo "R1: $R1"
|
||||||
|
echo "R2: $R2"
|
||||||
|
echo "FAIL"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> request with unquoted string arg"
|
||||||
|
R1=$(curl -s 'http://localhost:8008/hello_world?name=abcd&num=123')
|
||||||
|
R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: invalid character 'a' looking for beginning of value\"}"
|
||||||
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
|
echo "responses are not identical:"
|
||||||
|
echo "R1: $R1"
|
||||||
|
echo "R2: $R2"
|
||||||
|
echo "FAIL"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> request with string type when expecting number arg"
|
||||||
|
R1=$(curl -s 'http://localhost:8008/hello_world?name="abcd"&num=0xabcd')
|
||||||
|
R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: Got a hex string arg, but expected 'int'\"}"
|
||||||
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
|
echo "responses are not identical:"
|
||||||
|
echo "R1: $R1"
|
||||||
|
echo "R2: $R2"
|
||||||
|
echo "FAIL"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "OK"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Stopping the server"
|
||||||
|
kill -9 $PID
|
||||||
|
|
||||||
|
rm -f rpcserver
|
||||||
|
|
||||||
|
popd
|
||||||
|
exit 0
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
. "github.com/tendermint/go-common"
|
cmn "github.com/tendermint/go-common"
|
||||||
rpcserver "github.com/tendermint/go-rpc/server"
|
rpcserver "github.com/tendermint/go-rpc/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,11 +25,11 @@ func main() {
|
|||||||
rpcserver.RegisterRPCFuncs(mux, routes)
|
rpcserver.RegisterRPCFuncs(mux, routes)
|
||||||
_, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux)
|
_, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Exit(err.Error())
|
cmn.Exit(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait forever
|
// Wait forever
|
||||||
TrapSignal(func() {
|
cmn.TrapSignal(func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
69
test/test.sh
69
test/test.sh
@ -1,69 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
cd $GOPATH/src/github.com/tendermint/go-rpc
|
|
||||||
|
|
||||||
# get deps
|
|
||||||
go get -u -t ./...
|
|
||||||
|
|
||||||
# go tests
|
|
||||||
go test --race github.com/tendermint/go-rpc/...
|
|
||||||
|
|
||||||
|
|
||||||
# integration tests
|
|
||||||
cd test
|
|
||||||
set -e
|
|
||||||
|
|
||||||
go build -o server main.go
|
|
||||||
./server > /dev/null &
|
|
||||||
PID=$!
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
# simple request
|
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5'`
|
|
||||||
R2=`curl -s --data @data.json http://localhost:8008`
|
|
||||||
if [[ "$R1" != "$R2" ]]; then
|
|
||||||
echo "responses are not identical:"
|
|
||||||
echo "R1: $R1"
|
|
||||||
echo "R2: $R2"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Success"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# request with 0x-prefixed hex string arg
|
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123'`
|
|
||||||
R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi ABCD 123"},"error":""}'
|
|
||||||
if [[ "$R1" != "$R2" ]]; then
|
|
||||||
echo "responses are not identical:"
|
|
||||||
echo "R1: $R1"
|
|
||||||
echo "R2: $R2"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Success"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# request with unquoted string arg
|
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name=abcd&num=123'`
|
|
||||||
R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: invalid character 'a' looking for beginning of value\"}"
|
|
||||||
if [[ "$R1" != "$R2" ]]; then
|
|
||||||
echo "responses are not identical:"
|
|
||||||
echo "R1: $R1"
|
|
||||||
echo "R2: $R2"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Success"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# request with string type when expecting number arg
|
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name="abcd"&num=0xabcd'`
|
|
||||||
R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: Got a hex string arg, but expected 'int'\"}"
|
|
||||||
if [[ "$R1" != "$R2" ]]; then
|
|
||||||
echo "responses are not identical:"
|
|
||||||
echo "R1: $R1"
|
|
||||||
echo "R2: $R2"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Success"
|
|
||||||
fi
|
|
||||||
|
|
||||||
kill -9 $PID || exit 0
|
|
@ -4,18 +4,18 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tendermint/go-events"
|
events "github.com/tendermint/go-events"
|
||||||
"github.com/tendermint/go-wire"
|
wire "github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RPCRequest struct {
|
type RPCRequest struct {
|
||||||
JSONRPC string `json:"jsonrpc"`
|
JSONRPC string `json:"jsonrpc"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Params []interface{} `json:"params"`
|
Params interface{} `json:"params"` // must be map[string]interface{} or []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRPCRequest(id string, method string, params []interface{}) RPCRequest {
|
func NewRPCRequest(id string, method string, params map[string]interface{}) RPCRequest {
|
||||||
return RPCRequest{
|
return RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: id,
|
ID: id,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
const Maj = "0"
|
const Maj = "0"
|
||||||
const Min = "6" // 0x-prefixed string args handled as hex
|
const Min = "7"
|
||||||
const Fix = "0" //
|
const Fix = "0"
|
||||||
|
|
||||||
const Version = Maj + "." + Min + "." + Fix
|
const Version = Maj + "." + Min + "." + Fix
|
||||||
|
Loading…
x
Reference in New Issue
Block a user