diff --git a/Makefile b/Makefile index 4cc7c1594..3b005ea36 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,8 @@ all: test -test: - go test --race github.com/tendermint/go-rpc/... - cd ./test && bash test.sh - +test: + bash ./test/test.sh get_deps: - go get -t -d github.com/tendermint/go-rpc/... + go get -t -u github.com/tendermint/go-rpc/... diff --git a/circle.yml b/circle.yml index f022d5a6b..99af678c6 100644 --- a/circle.yml +++ b/circle.yml @@ -16,7 +16,7 @@ checkout: dependencies: override: - - "cd $REPO && make get_deps" + - "cd $REPO" test: override: diff --git a/client/http_client.go b/client/http_client.go index 816791b5c..57da5d6ec 100644 --- a/client/http_client.go +++ b/client/http_client.go @@ -4,10 +4,12 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io/ioutil" "net" "net/http" "net/url" + "reflect" "strings" . "github.com/tendermint/go-common" @@ -119,7 +121,7 @@ func (c *ClientURI) call(method string, params map[string]interface{}, result in if err != nil { return nil, err } - //log.Info(Fmt("URI request to %v (%v): %v", c.address, method, values)) + // log.Info(Fmt("URI request to %v (%v): %v", c.address, method, values)) resp, err := c.client.PostForm(c.address+"/"+method, values) if err != nil { return nil, err @@ -176,6 +178,14 @@ func argsToJson(args map[string]interface{}) error { var n int var err error for k, v := range args { + // Convert byte slices to "0x"-prefixed hex + byteSlice, isByteSlice := reflect.ValueOf(v).Interface().([]byte) + if isByteSlice { + args[k] = fmt.Sprintf("0x%X", byteSlice) + continue + } + + // Pass everything else to go-wire buf := new(bytes.Buffer) wire.WriteJSON(v, buf, &n, &err) if err != nil { diff --git a/client/ws_client.go b/client/ws_client.go index 00e4222ad..4d975f8ec 100644 --- a/client/ws_client.go +++ b/client/ws_client.go @@ -19,7 +19,7 @@ const ( ) type WSClient struct { - QuitService + BaseService Address string // IP:PORT or /path/to/socket Endpoint string // /websocket/url/endpoint Dialer func(string, string) (net.Conn, error) @@ -39,7 +39,7 @@ func NewWSClient(remoteAddr, endpoint string) *WSClient { ResultsCh: make(chan json.RawMessage, wsResultsChannelCapacity), ErrorsCh: make(chan error, wsErrorsChannelCapacity), } - wsClient.QuitService = *NewQuitService(log, "WSClient", wsClient) + wsClient.BaseService = *NewBaseService(log, "WSClient", wsClient) return wsClient } @@ -48,7 +48,7 @@ func (wsc *WSClient) String() string { } func (wsc *WSClient) OnStart() error { - wsc.QuitService.OnStart() + wsc.BaseService.OnStart() err := wsc.dial() if err != nil { return err @@ -84,7 +84,7 @@ func (wsc *WSClient) dial() error { } func (wsc *WSClient) OnStop() { - wsc.QuitService.OnStop() + wsc.BaseService.OnStop() // ResultsCh/ErrorsCh is closed in receiveEventsRoutine. } diff --git a/rpc_test.go b/rpc_test.go index 0c6e1dbdb..1fcda0e5e 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -164,3 +164,39 @@ func TestWS_UNIX(t *testing.T) { } testWS(t, cl) } + +func TestHexStringArg(t *testing.T) { + cl := rpcclient.NewClientURI(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) + } +} + +func TestQuotedStringArg(t *testing.T) { + cl := rpcclient.NewClientURI(tcpAddr) + // should NOT be unquoted + val := "\"abc\"" + 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) + } +} diff --git a/server/handlers.go b/server/handlers.go index 2b2bb90ce..7a4ec1a45 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -2,6 +2,7 @@ package rpcserver import ( "bytes" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -224,12 +225,22 @@ func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error argTypes := rpcFunc.args argNames := rpcFunc.argNames - var err error 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", "ty", ty, "name", name, "arg", arg) + + v, err, ok := nonJsonToArg(ty, arg) + if err != nil { + return nil, err + } + if ok { + values[i] = v + continue + } + + // Pass values to go-wire values[i], err = _jsonStringToArg(ty, arg) if err != nil { return nil, err @@ -249,6 +260,44 @@ func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) { return v, nil } +func nonJsonToArg(ty reflect.Type, arg string) (reflect.Value, error, bool) { + isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) + isHexString := strings.HasPrefix(strings.ToLower(arg), "0x") + expectingString := ty.Kind() == reflect.String + expectingByteSlice := ty.Kind() == reflect.Slice && ty.Elem().Kind() == reflect.Uint8 + + if isHexString { + if !expectingString && !expectingByteSlice { + err := fmt.Errorf("Got a hex string arg, but expected '%s'", + ty.Kind().String()) + return reflect.ValueOf(nil), err, false + } + + var value []byte + value, err := hex.DecodeString(arg[2:]) + if err != nil { + return reflect.ValueOf(nil), err, false + } + if ty.Kind() == reflect.String { + return reflect.ValueOf(string(value)), nil, true + } + return reflect.ValueOf([]byte(value)), nil, true + } + + if isQuotedString && expectingByteSlice { + var err error + v := reflect.New(reflect.TypeOf("")) + wire.ReadJSONPtr(v.Interface(), []byte(arg), &err) + if err != nil { + return reflect.ValueOf(nil), err, false + } + v = v.Elem() + return reflect.ValueOf([]byte(v.String())), nil, true + } + + return reflect.ValueOf(nil), nil, false +} + // rpc.http //----------------------------------------------------------------------------- // rpc.websocket @@ -264,7 +313,7 @@ const ( // contains listener id, underlying ws connection, // and the event switch for subscribing to events type wsConnection struct { - QuitService + BaseService remoteAddr string baseConn *websocket.Conn @@ -285,13 +334,13 @@ func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw funcMap: funcMap, evsw: evsw, } - wsc.QuitService = *NewQuitService(log, "wsConnection", wsc) + wsc.BaseService = *NewBaseService(log, "wsConnection", wsc) return wsc } // wsc.Start() blocks until the connection closes. func (wsc *wsConnection) OnStart() error { - wsc.QuitService.OnStart() + wsc.BaseService.OnStart() // Read subscriptions/unsubscriptions to events go wsc.readRoutine() @@ -318,7 +367,7 @@ func (wsc *wsConnection) OnStart() error { } func (wsc *wsConnection) OnStop() { - wsc.QuitService.OnStop() + wsc.BaseService.OnStop() wsc.evsw.RemoveListener(wsc.remoteAddr) wsc.readTimeout.Stop() wsc.pingTicker.Stop() diff --git a/test/test.sh b/test/test.sh old mode 100644 new mode 100755 index 3d1f599dd..f5e740241 --- a/test/test.sh +++ b/test/test.sh @@ -1,4 +1,16 @@ #! /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 @@ -6,18 +18,52 @@ go build -o server main.go 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 -kill -9 $PID +# 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 -echo "Success" + +kill -9 $PID || exit 0 diff --git a/version.go b/version.go index edf8d40da..33eb7fe51 100644 --- a/version.go +++ b/version.go @@ -1,7 +1,7 @@ package rpc const Maj = "0" -const Min = "5" // refactored out of tendermint/tendermint; RPCResponse.Result is RawJSON -const Fix = "1" // support tcp:// or unix:// prefixes +const Min = "6" // 0x-prefixed string args handled as hex +const Fix = "0" // const Version = Maj + "." + Min + "." + Fix