mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-25 18:51:39 +00:00
support key-value params in JSONRPC (Refs #1)
More changes: - remove Client interface (reason: empty) - introduce HTTPClient interface, which can be used for both ClientURI and ClientJSONRPC clients (so our users don't have to create their own) (Refs #8) - rename integration tests script to `integration_test.sh` - do not update deps on `get_deps`
This commit is contained in:
12
Makefile
12
Makefile
@ -1,9 +1,15 @@
|
|||||||
.PHONY: all test get_deps
|
PACKAGES=$(shell go list ./...)
|
||||||
|
|
||||||
all: test
|
all: 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)
|
||||||
|
|
||||||
|
.PHONY: all test get_deps
|
||||||
|
16
README.md
16
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
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -102,7 +102,7 @@ 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
|
||||||
|
@ -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"
|
// cmn "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-rpc/types"
|
rpctypes "github.com/tendermint/go-rpc/types"
|
||||||
"github.com/tendermint/go-wire"
|
wire "github.com/tendermint/go-wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HTTPClient is a common interface for ClientJSONRPC and ClientURI.
|
||||||
|
type HTTPClient interface {
|
||||||
|
Call(method string, params []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)) {
|
||||||
|
|
||||||
@ -49,11 +53,6 @@ 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 ClientJSONRPC struct {
|
||||||
address string
|
address string
|
||||||
@ -68,11 +67,11 @@ func NewClientJSONRPC(remote string) *ClientJSONRPC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientJSONRPC) Call(method string, params []interface{}, result interface{}) (interface{}, error) {
|
func (c *ClientJSONRPC) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
||||||
return c.call(method, params, result)
|
return c.call(method, params, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientJSONRPC) call(method string, params []interface{}, result interface{}) (interface{}, error) {
|
func (c *ClientJSONRPC) call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
|
||||||
// Make request and get responseBytes
|
// Make request and get responseBytes
|
||||||
request := rpctypes.RPCRequest{
|
request := rpctypes.RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
@ -80,7 +79,10 @@ func (c *ClientJSONRPC) call(method string, params []interface{}, result interfa
|
|||||||
Params: params,
|
Params: params,
|
||||||
ID: "",
|
ID: "",
|
||||||
}
|
}
|
||||||
requestBytes := wire.JSONBytes(request)
|
requestBytes, err := json.Marshal(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
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)
|
||||||
@ -145,16 +147,16 @@ func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface
|
|||||||
response := &rpctypes.RPCResponse{}
|
response := &rpctypes.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, fmt.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, fmt.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, fmt.Errorf("Error unmarshalling rpc response result: %v", err)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
. "github.com/tendermint/go-common"
|
cmn "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-rpc/types"
|
rpctypes "github.com/tendermint/go-rpc/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -19,7 +19,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)
|
||||||
@ -39,7 +39,7 @@ func NewWSClient(remoteAddr, endpoint string) *WSClient {
|
|||||||
ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity),
|
ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity),
|
||||||
ErrorsCh: make(chan error, wsErrorsChannelCapacity),
|
ErrorsCh: make(chan error, wsErrorsChannelCapacity),
|
||||||
}
|
}
|
||||||
wsClient.BaseService = *NewBaseService(log, "WSClient", wsClient)
|
wsClient.BaseService = *cmn.NewBaseService(log, "WSClient", wsClient)
|
||||||
return wsClient
|
return wsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ func (wsc *WSClient) Subscribe(eventid string) error {
|
|||||||
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
|
||||||
}
|
}
|
||||||
@ -133,7 +133,7 @@ func (wsc *WSClient) Unsubscribe(eventid string) error {
|
|||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: "",
|
ID: "",
|
||||||
Method: "unsubscribe",
|
Method: "unsubscribe",
|
||||||
Params: []interface{}{eventid},
|
Params: map[string]interface{}{"event": eventid},
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
16
rpc_test.go
16
rpc_test.go
@ -5,10 +5,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tendermint/go-rpc/client"
|
rpcclient "github.com/tendermint/go-rpc/client"
|
||||||
"github.com/tendermint/go-rpc/server"
|
rpcserver "github.com/tendermint/go-rpc/server"
|
||||||
"github.com/tendermint/go-rpc/types"
|
rpctypes "github.com/tendermint/go-rpc/types"
|
||||||
"github.com/tendermint/go-wire"
|
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
|
||||||
@ -88,7 +88,9 @@ func testURI(t *testing.T, cl *rpcclient.ClientURI) {
|
|||||||
|
|
||||||
func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) {
|
func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) {
|
||||||
val := "acbd"
|
val := "acbd"
|
||||||
params := []interface{}{val}
|
params := map[string]interface{}{
|
||||||
|
"arg": val,
|
||||||
|
}
|
||||||
var result Result
|
var result Result
|
||||||
_, err := cl.Call("status", params, &result)
|
_, err := cl.Call("status", params, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -102,7 +104,9 @@ func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) {
|
|||||||
|
|
||||||
func testWS(t *testing.T, cl *rpcclient.WSClient) {
|
func testWS(t *testing.T, cl *rpcclient.WSClient) {
|
||||||
val := "acbd"
|
val := "acbd"
|
||||||
params := []interface{}{val}
|
params := map[string]interface{}{
|
||||||
|
"arg": val,
|
||||||
|
}
|
||||||
err := cl.WriteJSON(rpctypes.RPCRequest{
|
err := cl.WriteJSON(rpctypes.RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: "",
|
ID: "",
|
||||||
|
@ -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,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
. "github.com/tendermint/go-common"
|
cmn "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-events"
|
events "github.com/tendermint/go-events"
|
||||||
. "github.com/tendermint/go-rpc/types"
|
types "github.com/tendermint/go-rpc/types"
|
||||||
"github.com/tendermint/go-wire"
|
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,75 +104,99 @@ 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 := jsonParamsToArgs(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, fmt.Sprintf("Error unreflecting result: %v", 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 list of interfaces to properly typed values
|
||||||
func jsonParamsToArgs(rpcFunc *RPCFunc, params []interface{}) ([]reflect.Value, error) {
|
func jsonParamsToArgs(rpcFunc *RPCFunc, params map[string]interface{}) ([]reflect.Value, error) {
|
||||||
if len(rpcFunc.argNames) != len(params) {
|
if len(rpcFunc.argNames) != len(params) {
|
||||||
return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
|
return nil, fmt.Errorf("Expected %v parameters (%v), got %v (%v)",
|
||||||
len(rpcFunc.argNames), rpcFunc.argNames, len(params), params))
|
len(rpcFunc.argNames), rpcFunc.argNames, len(params), params)
|
||||||
}
|
}
|
||||||
|
|
||||||
values := make([]reflect.Value, len(params))
|
values := make([]reflect.Value, len(params))
|
||||||
for i, p := range params {
|
|
||||||
|
for name, param := range params {
|
||||||
|
i := indexOf(name, rpcFunc.argNames)
|
||||||
|
if -1 == i {
|
||||||
|
return nil, fmt.Errorf("%s is not an argument (args: %v)", name, rpcFunc.argNames)
|
||||||
|
}
|
||||||
ty := rpcFunc.args[i]
|
ty := rpcFunc.args[i]
|
||||||
v, err := _jsonObjectToArg(ty, p)
|
v, err := _jsonObjectToArg(ty, param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
values[i] = v
|
values[i] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same as above, but with the first param the websocket connection
|
// indexOf returns index of a string in a slice of strings, -1 if not found.
|
||||||
func jsonParamsToArgsWS(rpcFunc *RPCFunc, params []interface{}, wsCtx WSRPCContext) ([]reflect.Value, error) {
|
func indexOf(value string, values []string) int {
|
||||||
if len(rpcFunc.argNames) != len(params) {
|
for i, v := range values {
|
||||||
return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
|
if v == value {
|
||||||
len(rpcFunc.argNames)-1, rpcFunc.argNames[1:], len(params), params))
|
return i
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as above, but with the first param the websocket connection
|
||||||
|
func jsonParamsToArgsWS(rpcFunc *RPCFunc, params map[string]interface{}, wsCtx types.WSRPCContext) ([]reflect.Value, error) {
|
||||||
|
if len(rpcFunc.argNames) != len(params) {
|
||||||
|
return nil, fmt.Errorf("Expected %v parameters (%v), got %v (%v)",
|
||||||
|
len(rpcFunc.argNames)-1, rpcFunc.argNames[1:], len(params), params)
|
||||||
|
}
|
||||||
|
|
||||||
values := make([]reflect.Value, len(params)+1)
|
values := make([]reflect.Value, len(params)+1)
|
||||||
values[0] = reflect.ValueOf(wsCtx)
|
values[0] = reflect.ValueOf(wsCtx)
|
||||||
for i, p := range params {
|
|
||||||
|
for name, param := range params {
|
||||||
|
i := indexOf(name, rpcFunc.argNames)
|
||||||
|
if -1 == i {
|
||||||
|
return nil, fmt.Errorf("%s is not an argument (args: %v)", name, rpcFunc.argNames)
|
||||||
|
}
|
||||||
ty := rpcFunc.args[i+1]
|
ty := rpcFunc.args[i+1]
|
||||||
v, err := _jsonObjectToArg(ty, p)
|
v, err := _jsonObjectToArg(ty, param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
values[i+1] = v
|
values[i+1] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +220,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,17 +228,17 @@ 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, fmt.Sprintf("Error unreflecting result: %v", err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteRPCResponseHTTP(w, NewRPCResponse("", result, ""))
|
WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,11 +336,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 +353,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +422,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 +433,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 +467,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 +479,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 = jsonParamsToArgs(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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,22 @@
|
|||||||
#! /bin/bash
|
#!/usr/bin/env 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
|
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"
|
||||||
|
|
||||||
go build -o server main.go
|
go build -o server main.go
|
||||||
./server > /dev/null &
|
./server > /dev/null &
|
||||||
PID=$!
|
PID=$!
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
# simple request
|
# simple request
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5'`
|
R1=$(curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5')
|
||||||
R2=`curl -s --data @data.json http://localhost:8008`
|
R2=$(curl -s --data @data.json http://localhost:8008)
|
||||||
if [[ "$R1" != "$R2" ]]; then
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
echo "responses are not identical:"
|
echo "responses are not identical:"
|
||||||
echo "R1: $R1"
|
echo "R1: $R1"
|
||||||
@ -31,7 +27,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# request with 0x-prefixed hex string arg
|
# request with 0x-prefixed hex string arg
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123'`
|
R1=$(curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123')
|
||||||
R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi ABCD 123"},"error":""}'
|
R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi ABCD 123"},"error":""}'
|
||||||
if [[ "$R1" != "$R2" ]]; then
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
echo "responses are not identical:"
|
echo "responses are not identical:"
|
||||||
@ -43,7 +39,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# request with unquoted string arg
|
# request with unquoted string arg
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name=abcd&num=123'`
|
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\"}"
|
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
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
echo "responses are not identical:"
|
echo "responses are not identical:"
|
||||||
@ -55,7 +51,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# request with string type when expecting number arg
|
# request with string type when expecting number arg
|
||||||
R1=`curl -s 'http://localhost:8008/hello_world?name="abcd"&num=0xabcd'`
|
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'\"}"
|
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
|
if [[ "$R1" != "$R2" ]]; then
|
||||||
echo "responses are not identical:"
|
echo "responses are not identical:"
|
||||||
@ -67,3 +63,4 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
kill -9 $PID || exit 0
|
kill -9 $PID || exit 0
|
||||||
|
popd
|
@ -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() {
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 map[string]interface{} `json:"params"`
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
Reference in New Issue
Block a user