Merge pull request #17 from tendermint/develop

v0.7.0
This commit is contained in:
Ethan Buchman 2017-04-21 13:08:14 -04:00 committed by GitHub
commit 2c8df0ee6b
17 changed files with 603 additions and 358 deletions

15
.editorconfig Normal file
View 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
View 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

View File

@ -1,9 +1,18 @@
.PHONY: all test get_deps
PACKAGES=$(shell go list ./... | grep -v "test")
all: test
all: get_deps test
test:
bash ./test/test.sh
test:
@echo "--> Running go test --race"
@go test --race $(PACKAGES)
@echo "--> Running integration tests"
@bash ./test/integration_test.sh
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

View File

@ -32,16 +32,16 @@ As a POST request, we use JSONRPC. For instance, the same request would have thi
```
{
"jsonrpc":"2.0",
"id":"anything",
"method":"hello_world",
"params":["my_world", 5]
"jsonrpc": "2.0",
"id": "anything",
"method": "hello_world",
"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
```
@ -50,8 +50,8 @@ curl --data @data.json http://localhost:8008
## WebSocket (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`,
All requests are exposed over websocket in the same form as the POST JSONRPC.
Websocket connections are available at their own endpoint, typically `/websocket`,
though this is configurable when starting the server.
# 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`)
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
* [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)

View File

@ -11,12 +11,10 @@ checkout:
- rm -rf $REPO
- mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME
- mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO
# - git submodule sync
# - git submodule update --init # use submodules
dependencies:
override:
- "cd $REPO"
- "cd $REPO && make get_deps"
test:
override:

View File

@ -3,7 +3,6 @@ package rpcclient
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net"
@ -12,11 +11,16 @@ import (
"reflect"
"strings"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-rpc/types"
"github.com/tendermint/go-wire"
"github.com/pkg/errors"
types "github.com/tendermint/go-rpc/types"
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
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
if len(parts) != 2 {
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
} else {
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
type ClientJSONRPC struct {
type JSONRPCClient struct {
address string
client *http.Client
}
func NewClientJSONRPC(remote string) *ClientJSONRPC {
func NewJSONRPCClient(remote string) *JSONRPCClient {
address, client := makeHTTPClient(remote)
return &ClientJSONRPC{
return &JSONRPCClient{
address: address,
client: client,
}
}
func (c *ClientJSONRPC) Call(method string, params []interface{}, result interface{}) (interface{}, error) {
return c.call(method, params, result)
}
func (c *ClientJSONRPC) call(method string, params []interface{}, result interface{}) (interface{}, error) {
// Make request and get responseBytes
request := rpctypes.RPCRequest{
func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
// 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{})
for k, v := range params {
bytes := json.RawMessage(wire.JSONBytes(v))
encodedParams[k] = &bytes
}
request := types.RPCRequest{
JSONRPC: "2.0",
Method: method,
Params: params,
Params: encodedParams,
ID: "",
}
requestBytes := wire.JSONBytes(request)
requestBytes, err := json.Marshal(request)
if err != nil {
return nil, err
}
// log.Info(string(requestBytes))
requestBuf := bytes.NewBuffer(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)
@ -99,24 +104,20 @@ func (c *ClientJSONRPC) call(method string, params []interface{}, result interfa
//-------------------------------------------------------------
// URI takes params as a map
type ClientURI struct {
type URIClient struct {
address string
client *http.Client
}
func NewClientURI(remote string) *ClientURI {
func NewURIClient(remote string) *URIClient {
address, client := makeHTTPClient(remote)
return &ClientURI{
return &URIClient{
address: address,
client: client,
}
}
func (c *ClientURI) 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) {
func (c *URIClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) {
values, err := argsToURLValues(params)
if err != nil {
return nil, err
@ -142,19 +143,19 @@ func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface
// into the correct type
// log.Notice("response", "response", string(responseBytes))
var err error
response := &rpctypes.RPCResponse{}
response := &types.RPCResponse{}
err = json.Unmarshal(responseBytes, response)
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
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
result = wire.ReadJSONPtr(result, *response.Result, &err)
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
}

View File

@ -2,14 +2,15 @@ package rpcclient
import (
"encoding/json"
"fmt"
"net"
"net/http"
"time"
"github.com/gorilla/websocket"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-rpc/types"
"github.com/pkg/errors"
cmn "github.com/tendermint/go-common"
types "github.com/tendermint/go-rpc/types"
wire "github.com/tendermint/go-wire"
)
const (
@ -19,7 +20,7 @@ const (
)
type WSClient struct {
BaseService
cmn.BaseService
Address string // IP:PORT or /path/to/socket
Endpoint string // /websocket/url/endpoint
Dialer func(string, string) (net.Conn, error)
@ -32,14 +33,12 @@ type WSClient struct {
func NewWSClient(remoteAddr, endpoint string) *WSClient {
addr, dialer := makeHTTPDialer(remoteAddr)
wsClient := &WSClient{
Address: addr,
Dialer: dialer,
Endpoint: endpoint,
Conn: nil,
ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity),
ErrorsCh: make(chan error, wsErrorsChannelCapacity),
Address: addr,
Dialer: dialer,
Endpoint: endpoint,
Conn: nil,
}
wsClient.BaseService = *NewBaseService(log, "WSClient", wsClient)
wsClient.BaseService = *cmn.NewBaseService(log, "WSClient", wsClient)
return wsClient
}
@ -47,16 +46,24 @@ func (wsc *WSClient) String() string {
return wsc.Address + ", " + wsc.Endpoint
}
// OnStart implements cmn.BaseService interface
func (wsc *WSClient) OnStart() error {
wsc.BaseService.OnStart()
err := wsc.dial()
if err != nil {
return err
}
wsc.ResultsCh = make(chan json.RawMessage, wsResultsChannelCapacity)
wsc.ErrorsCh = make(chan error, wsErrorsChannelCapacity)
go wsc.receiveEventsRoutine()
return nil
}
// OnReset implements cmn.BaseService interface
func (wsc *WSClient) OnReset() error {
return nil
}
func (wsc *WSClient) dial() error {
// Dial
@ -83,8 +90,10 @@ func (wsc *WSClient) dial() error {
return nil
}
// OnStop implements cmn.BaseService interface
func (wsc *WSClient) OnStop() {
wsc.BaseService.OnStop()
wsc.Conn.Close()
// ResultsCh/ErrorsCh is closed in receiveEventsRoutine.
}
@ -96,7 +105,7 @@ func (wsc *WSClient) receiveEventsRoutine() {
wsc.Stop()
break
} else {
var response rpctypes.RPCResponse
var response types.RPCResponse
err := json.Unmarshal(data, &response)
if err != nil {
log.Info("WSClient failed to parse message", "error", err, "data", string(data))
@ -104,36 +113,60 @@ func (wsc *WSClient) receiveEventsRoutine() {
continue
}
if response.Error != "" {
wsc.ErrorsCh <- fmt.Errorf(response.Error)
wsc.ErrorsCh <- errors.Errorf(response.Error)
continue
}
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
close(wsc.ResultsCh)
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 {
err := wsc.WriteJSON(rpctypes.RPCRequest{
err := wsc.WriteJSON(types.RPCRequest{
JSONRPC: "2.0",
ID: "",
Method: "subscribe",
Params: []interface{}{eventid},
Params: map[string]interface{}{"event": eventid},
})
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 {
err := wsc.WriteJSON(rpctypes.RPCRequest{
err := wsc.WriteJSON(types.RPCRequest{
JSONRPC: "2.0",
ID: "",
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
}

View File

@ -1,20 +1,29 @@
package rpc
import (
"bytes"
crand "crypto/rand"
"fmt"
"math/rand"
"net/http"
"os/exec"
"testing"
"time"
"github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-rpc/server"
"github.com/tendermint/go-rpc/types"
"github.com/tendermint/go-wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
client "github.com/tendermint/go-rpc/client"
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
var (
tcpAddr = "tcp://0.0.0.0:46657"
unixAddr = "unix:///tmp/go-rpc.sock" // NOTE: must remove file for test to run again
const (
tcpAddr = "tcp://0.0.0.0:46657"
unixSocket = "/tmp/go-rpc.sock"
unixAddr = "unix:///tmp/go-rpc.sock"
websocketEndpoint = "/websocket/endpoint"
)
@ -22,44 +31,67 @@ var (
// Define a type for results and register concrete versions
type Result interface{}
type ResultStatus struct {
type ResultEcho struct {
Value string
}
type ResultEchoBytes struct {
Value []byte
}
var _ = wire.RegisterInterface(
struct{ Result }{},
wire.ConcreteType{&ResultStatus{}, 0x1},
wire.ConcreteType{&ResultEcho{}, 0x1},
wire.ConcreteType{&ResultEchoBytes{}, 0x2},
)
// Define some routes
var Routes = map[string]*rpcserver.RPCFunc{
"status": rpcserver.NewRPCFunc(StatusResult, "arg"),
var Routes = map[string]*server.RPCFunc{
"echo": server.NewRPCFunc(EchoResult, "arg"),
"echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"),
"echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"),
}
// an rpc function
func StatusResult(v string) (Result, error) {
return &ResultStatus{v}, nil
func EchoResult(v string) (Result, error) {
return &ResultEcho{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
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()
rpcserver.RegisterRPCFuncs(mux, Routes)
wm := rpcserver.NewWebsocketManager(Routes, nil)
server.RegisterRPCFuncs(mux, Routes)
wm := server.NewWebsocketManager(Routes, nil)
mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
go func() {
_, err := rpcserver.StartHTTPServer(tcpAddr, mux)
_, err := server.StartHTTPServer(tcpAddr, mux)
if err != nil {
panic(err)
}
}()
mux2 := http.NewServeMux()
rpcserver.RegisterRPCFuncs(mux2, Routes)
wm = rpcserver.NewWebsocketManager(Routes, nil)
server.RegisterRPCFuncs(mux2, Routes)
wm = server.NewWebsocketManager(Routes, nil)
mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler)
go func() {
_, err := rpcserver.StartHTTPServer(unixAddr, mux2)
_, err := server.StartHTTPServer(unixAddr, mux2)
if err != nil {
panic(err)
}
@ -67,136 +99,200 @@ func init() {
// wait for servers to start
time.Sleep(time.Second * 2)
}
func testURI(t *testing.T, cl *rpcclient.ClientURI) {
val := "acbd"
func echoViaHTTP(cl client.HTTPClient, val string) (string, error) {
params := map[string]interface{}{
"arg": 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)
if _, err := cl.Call("echo", params, &result); err != nil {
return "", err
}
return result.(*ResultEcho).Value, nil
}
func testJSONRPC(t *testing.T, cl *rpcclient.ClientJSONRPC) {
val := "acbd"
params := []interface{}{val}
func echoBytesViaHTTP(cl client.HTTPClient, bytes []byte) ([]byte, error) {
params := map[string]interface{}{
"arg": bytes,
}
var result Result
_, err := cl.Call("status", params, &result)
if err != nil {
t.Fatal(err)
if _, err := cl.Call("echo_bytes", params, &result); err != nil {
return []byte{}, err
}
got := result.(*ResultStatus).Value
if got != val {
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
return result.(*ResultEchoBytes).Value, nil
}
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) {
val := "acbd"
params := []interface{}{val}
err := cl.WriteJSON(rpctypes.RPCRequest{
JSONRPC: "2.0",
ID: "",
Method: "status",
Params: params,
})
func echoBytesViaWS(cl *client.WSClient, bytes []byte) ([]byte, error) {
params := map[string]interface{}{
"arg": bytes,
}
err := cl.Call("echo_bytes", params)
if err != nil {
t.Fatal(err)
return []byte{}, err
}
msg := <-cl.ResultsCh
result := new(Result)
wire.ReadJSONPtr(result, msg, &err)
if err != nil {
t.Fatal(err)
}
got := (*result).(*ResultStatus).Value
if got != val {
t.Fatalf("Got: %v .... Expected: %v \n", got, val)
select {
case msg := <-cl.ResultsCh:
result := new(Result)
wire.ReadJSONPtr(result, msg, &err)
if err != nil {
return []byte{}, nil
}
return (*result).(*ResultEchoBytes).Value, nil
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) {
cl := rpcclient.NewClientURI(tcpAddr)
testURI(t, cl)
}
func TestServersAndClientsBasic(t *testing.T) {
serverAddrs := [...]string{tcpAddr, unixAddr}
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) {
cl := rpcclient.NewClientURI(unixAddr)
testURI(t, cl)
}
cl2 := client.NewJSONRPCClient(tcpAddr)
fmt.Printf("=== testing server on %s using %v client", addr, cl2)
testWithHTTPClient(t, cl2)
func TestJSONRPC_TCP(t *testing.T) {
cl := rpcclient.NewClientJSONRPC(tcpAddr)
testJSONRPC(t, cl)
}
func TestJSONRPC_UNIX(t *testing.T) {
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)
cl3 := client.NewWSClient(tcpAddr, websocketEndpoint)
_, err := cl3.Start()
require.Nil(t, err)
fmt.Printf("=== testing server on %s using %v client", addr, cl3)
testWithWSClient(t, cl3)
cl3.Stop()
}
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) {
cl := rpcclient.NewClientURI(tcpAddr)
cl := client.NewURIClient(tcpAddr)
// should NOT be handled as hex
val := "0xabc"
params := map[string]interface{}{
"arg": 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)
}
got, err := echoViaHTTP(cl, val)
require.Nil(t, err)
assert.Equal(t, got, val)
}
func TestQuotedStringArg(t *testing.T) {
cl := rpcclient.NewClientURI(tcpAddr)
cl := client.NewURIClient(tcpAddr)
// should NOT be unquoted
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{}{
"arg": val,
}
var result Result
_, err := cl.Call("status", params, &result)
if err != nil {
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.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)
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -14,10 +13,11 @@ import (
"time"
"github.com/gorilla/websocket"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-events"
. "github.com/tendermint/go-rpc/types"
"github.com/tendermint/go-wire"
"github.com/pkg/errors"
cmn "github.com/tendermint/go-common"
events "github.com/tendermint/go-events"
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.
@ -105,76 +105,100 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc {
return
}
var request RPCRequest
var request types.RPCRequest
err := json.Unmarshal(b, &request)
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
}
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
}
rpcFunc := funcMap[request.Method]
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
}
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
}
args, err := jsonParamsToArgs(rpcFunc, request.Params)
args, err := jsonParamsToArgsRPC(rpcFunc, request.Params)
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
}
returns := rpcFunc.f.Call(args)
log.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
result, err := unreflectResult(returns)
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
}
WriteRPCResponseHTTP(w, NewRPCResponse(request.ID, result, ""))
WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, ""))
}
}
// Convert a list of interfaces to properly typed values
func jsonParamsToArgs(rpcFunc *RPCFunc, params []interface{}) ([]reflect.Value, error) {
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]
v, err := _jsonObjectToArg(ty, p)
if err != nil {
return nil, err
// Convert a []interface{} OR a map[string]interface{} to properly typed values
//
// argsOffset should be 0 for RPC calls, and 1 for WS requests, where len(rpcFunc.args) != len(rpcFunc.argNames).
// Example:
// rpcFunc.args = [rpctypes.WSRPCContext string]
// rpcFunc.argNames = ["arg"]
func jsonParamsToArgs(rpcFunc *RPCFunc, paramsI interface{}, argsOffset int) ([]reflect.Value, error) {
values := make([]reflect.Value, len(rpcFunc.argNames))
switch params := paramsI.(type) {
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
}
// 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
func jsonParamsToArgsWS(rpcFunc *RPCFunc, params []interface{}, wsCtx WSRPCContext) ([]reflect.Value, error) {
if len(rpcFunc.argNames) != len(params) {
return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
len(rpcFunc.argNames)-1, rpcFunc.argNames[1:], len(params), params))
func jsonParamsToArgsWS(rpcFunc *RPCFunc, paramsI interface{}, wsCtx types.WSRPCContext) ([]reflect.Value, error) {
values, err := jsonParamsToArgs(rpcFunc, paramsI, 1)
if err != nil {
return nil, err
}
values := make([]reflect.Value, len(params)+1)
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
return append([]reflect.Value{reflect.ValueOf(wsCtx)}, values...), nil
}
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
if rpcFunc.ws {
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
@ -205,33 +229,38 @@ func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request)
log.Debug("HTTP HANDLER", "req", r)
args, err := httpParamsToArgs(rpcFunc, r)
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
}
returns := rpcFunc.f.Call(args)
log.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
result, err := unreflectResult(returns)
if err != nil {
WriteRPCResponseHTTP(w, NewRPCResponse("", nil, fmt.Sprintf("Error unreflecting result: %v", err.Error())))
WriteRPCResponseHTTP(w, types.NewRPCResponse("", nil, err.Error()))
return
}
WriteRPCResponseHTTP(w, NewRPCResponse("", result, ""))
WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, ""))
}
}
// 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).
func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
argTypes := rpcFunc.args
argNames := rpcFunc.argNames
values := make([]reflect.Value, len(rpcFunc.args))
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)
// 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 {
return nil, err
}
@ -241,11 +270,12 @@ func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error
}
// Pass values to go-wire
values[i], err = _jsonStringToArg(ty, arg)
values[i], err = _jsonStringToArg(argType, arg)
if err != nil {
return nil, err
}
}
return values, nil
}
@ -268,7 +298,7 @@ func nonJsonToArg(ty reflect.Type, arg string) (reflect.Value, error, bool) {
if isHexString {
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())
return reflect.ValueOf(nil), err, false
}
@ -313,11 +343,11 @@ const (
// contains listener id, underlying ws connection,
// and the event switch for subscribing to events
type wsConnection struct {
BaseService
cmn.BaseService
remoteAddr string
baseConn *websocket.Conn
writeChan chan RPCResponse
writeChan chan types.RPCResponse
readTimeout *time.Timer
pingTicker *time.Ticker
@ -330,11 +360,11 @@ func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw
wsc := &wsConnection{
remoteAddr: baseConn.RemoteAddr().String(),
baseConn: baseConn,
writeChan: make(chan RPCResponse, writeChanCapacity), // error when full.
writeChan: make(chan types.RPCResponse, writeChanCapacity), // error when full.
funcMap: funcMap,
evsw: evsw,
}
wsc.BaseService = *NewBaseService(log, "wsConnection", wsc)
wsc.BaseService = *cmn.NewBaseService(log, "wsConnection", wsc)
return wsc
}
@ -342,12 +372,15 @@ func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw
func (wsc *wsConnection) OnStart() error {
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
go wsc.readRoutine()
// 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 {
// NOTE: https://github.com/gorilla/websocket/issues/97
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() {
wsc.BaseService.OnStop()
wsc.evsw.RemoveListener(wsc.remoteAddr)
if wsc.evsw != nil {
wsc.evsw.RemoveListener(wsc.remoteAddr)
}
wsc.readTimeout.Stop()
wsc.pingTicker.Stop()
// The write loop closes the websocket connection
@ -399,7 +434,7 @@ func (wsc *wsConnection) GetEventSwitch() events.EventSwitch {
// Implements WSRPCConnection
// Blocking write to writeChan until service stops.
// Goroutine-safe
func (wsc *wsConnection) WriteRPCResponse(resp RPCResponse) {
func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) {
select {
case <-wsc.Quit:
return
@ -410,7 +445,7 @@ func (wsc *wsConnection) WriteRPCResponse(resp RPCResponse) {
// Implements WSRPCConnection
// Nonblocking write.
// Goroutine-safe
func (wsc *wsConnection) TryWriteRPCResponse(resp RPCResponse) bool {
func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool {
select {
case <-wsc.Quit:
return false
@ -444,11 +479,11 @@ func (wsc *wsConnection) readRoutine() {
wsc.Stop()
return
}
var request RPCRequest
var request types.RPCRequest
err = json.Unmarshal(in, &request)
if err != nil {
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
}
@ -456,28 +491,28 @@ func (wsc *wsConnection) readRoutine() {
rpcFunc := wsc.funcMap[request.Method]
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
}
var args []reflect.Value
if rpcFunc.ws {
wsCtx := WSRPCContext{Request: request, WSRPCConnection: wsc}
wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc}
args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx)
} else {
args, err = jsonParamsToArgs(rpcFunc, request.Params)
args, err = jsonParamsToArgsRPC(rpcFunc, request.Params)
}
if err != nil {
wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, err.Error()))
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error()))
continue
}
returns := rpcFunc.f.Call(args)
log.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns)
result, err := unreflectResult(returns)
if err != nil {
wsc.WriteRPCResponse(NewRPCResponse(request.ID, nil, err.Error()))
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error()))
continue
} else {
wsc.WriteRPCResponse(NewRPCResponse(request.ID, result, ""))
wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, ""))
continue
}
@ -563,7 +598,7 @@ func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Requ
func unreflectResult(returns []reflect.Value) (interface{}, error) {
errV := returns[1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
return nil, errors.Errorf("%v", errV.Interface())
}
rv := returns[0]
// the result is a registered interface,

View File

@ -2,10 +2,11 @@ package rpcserver
import (
"encoding/hex"
"fmt"
"net/http"
"regexp"
"strconv"
"github.com/pkg/errors"
)
var (
@ -39,7 +40,7 @@ func GetParamInt64(r *http.Request, param string) (int64, error) {
s := GetParam(r, param)
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, fmt.Errorf(param, err.Error())
return 0, errors.Errorf(param, err.Error())
}
return i, nil
}
@ -48,7 +49,7 @@ func GetParamInt32(r *http.Request, param string) (int32, error) {
s := GetParam(r, param)
i, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0, fmt.Errorf(param, err.Error())
return 0, errors.Errorf(param, err.Error())
}
return int32(i), nil
}
@ -57,7 +58,7 @@ func GetParamUint64(r *http.Request, param string) (uint64, error) {
s := GetParam(r, param)
i, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, fmt.Errorf(param, err.Error())
return 0, errors.Errorf(param, err.Error())
}
return i, nil
}
@ -66,7 +67,7 @@ func GetParamUint(r *http.Request, param string) (uint, error) {
s := GetParam(r, param)
i, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, fmt.Errorf(param, err.Error())
return 0, errors.Errorf(param, err.Error())
}
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) {
s := GetParam(r, param)
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
}
@ -83,7 +84,7 @@ func GetParamFloat64(r *http.Request, param string) (float64, error) {
s := GetParam(r, param)
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, fmt.Errorf(param, err.Error())
return 0, errors.Errorf(param, err.Error())
}
return f, nil
}

View File

@ -11,9 +11,8 @@ import (
"strings"
"time"
. "github.com/tendermint/go-common"
. "github.com/tendermint/go-rpc/types"
//"github.com/tendermint/go-wire"
"github.com/pkg/errors"
types "github.com/tendermint/go-rpc/types"
)
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")
// we used to allow addrs without tcp/unix prefix by checking for a colon
// TODO: Deprecate
proto = SocketType(listenAddr)
proto = types.SocketType(listenAddr)
addr = listenAddr
// return nil, fmt.Errorf("Invalid listener address %s", lisenAddr)
// return nil, errors.Errorf("Invalid listener address %s", lisenAddr)
} else {
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)
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() {
@ -47,7 +46,7 @@ func StartHTTPServer(listenAddr string, handler http.Handler) (listener net.List
return listener, nil
}
func WriteRPCResponseHTTP(w http.ResponseWriter, res RPCResponse) {
func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) {
// jsonBytes := wire.JSONBytesPretty(res)
jsonBytes, err := json.Marshal(res)
if err != nil {
@ -83,13 +82,13 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler {
if e := recover(); e != nil {
// If RPCResponse
if res, ok := e.(RPCResponse); ok {
if res, ok := e.(types.RPCResponse); ok {
WriteRPCResponseHTTP(rww, res)
} else {
// For the rest,
log.Error("Panic in RPC HTTP handler", "error", e, "stack", string(debug.Stack()))
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)))
}
}

View File

@ -1,6 +1,9 @@
{
"jsonrpc":"2.0",
"id":"",
"method":"hello_world",
"params":["my_world", 5]
"jsonrpc": "2.0",
"id": "",
"method": "hello_world",
"params": {
"name": "my_world",
"num": 5
}
}

95
test/integration_test.sh Executable file
View 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

View File

@ -4,7 +4,7 @@ import (
"fmt"
"net/http"
. "github.com/tendermint/go-common"
cmn "github.com/tendermint/go-common"
rpcserver "github.com/tendermint/go-rpc/server"
)
@ -25,11 +25,11 @@ func main() {
rpcserver.RegisterRPCFuncs(mux, routes)
_, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux)
if err != nil {
Exit(err.Error())
cmn.Exit(err.Error())
}
// Wait forever
TrapSignal(func() {
cmn.TrapSignal(func() {
})
}

View File

@ -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

View File

@ -4,18 +4,18 @@ import (
"encoding/json"
"strings"
"github.com/tendermint/go-events"
"github.com/tendermint/go-wire"
events "github.com/tendermint/go-events"
wire "github.com/tendermint/go-wire"
)
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
ID string `json:"id"`
Method string `json:"method"`
Params []interface{} `json:"params"`
JSONRPC string `json:"jsonrpc"`
ID string `json:"id"`
Method string `json:"method"`
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{
JSONRPC: "2.0",
ID: id,

View File

@ -1,7 +1,7 @@
package rpc
const Maj = "0"
const Min = "6" // 0x-prefixed string args handled as hex
const Fix = "0" //
const Min = "7"
const Fix = "0"
const Version = Maj + "." + Min + "." + Fix