Browse Source

Merge pull request #1919 from tendermint/jae/rpc_int_noquote

WIP RPC HTTP does not require quotes around int64/int/uint64/uint types
pull/1922/head
Ethan Buchman 6 years ago
committed by GitHub
parent
commit
8544e18eaf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 99 additions and 15 deletions
  1. +52
    -14
      rpc/lib/server/handlers.go
  2. +1
    -0
      rpc/lib/server/http_params.go
  3. +46
    -1
      rpc/lib/server/parse_test.go

+ 52
- 14
rpc/lib/server/handlers.go View File

@ -18,9 +18,9 @@ import (
"github.com/pkg/errors"
amino "github.com/tendermint/go-amino"
types "github.com/tendermint/tendermint/rpc/lib/types"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
types "github.com/tendermint/tendermint/rpc/lib/types"
)
// RegisterRPCFuncs adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions.
@ -294,7 +294,7 @@ func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]re
continue
}
v, err, ok := nonJSONToArg(cdc, argType, arg)
v, err, ok := nonJSONStringToArg(cdc, argType, arg)
if err != nil {
return nil, err
}
@ -303,7 +303,7 @@ func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]re
continue
}
values[i], err = _jsonStringToArg(cdc, argType, arg)
values[i], err = jsonStringToArg(cdc, argType, arg)
if err != nil {
return nil, err
}
@ -312,26 +312,64 @@ func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]re
return values, nil
}
func _jsonStringToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, error) {
v := reflect.New(ty)
err := cdc.UnmarshalJSON([]byte(arg), v.Interface())
func jsonStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error) {
rv := reflect.New(rt)
err := cdc.UnmarshalJSON([]byte(arg), rv.Interface())
if err != nil {
return v, err
return rv, err
}
rv = rv.Elem()
return rv, nil
}
func nonJSONStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error, bool) {
if rt.Kind() == reflect.Ptr {
rv_, err, ok := nonJSONStringToArg(cdc, rt.Elem(), arg)
if err != nil {
return reflect.Value{}, err, false
} else if ok {
rv := reflect.New(rt.Elem())
rv.Elem().Set(rv_)
return rv, nil, true
} else {
return reflect.Value{}, nil, false
}
} else {
return _nonJSONStringToArg(cdc, rt, arg)
}
v = v.Elem()
return v, nil
}
func nonJSONToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value, error, bool) {
// NOTE: rt.Kind() isn't a pointer.
func _nonJSONStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error, bool) {
isIntString := RE_INT.Match([]byte(arg))
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
var expectingString, expectingByteSlice, expectingInt bool
switch rt.Kind() {
case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
expectingInt = true
case reflect.String:
expectingString = true
case reflect.Slice:
expectingByteSlice = rt.Elem().Kind() == reflect.Uint8
}
if isIntString && expectingInt {
qarg := `"` + arg + `"`
// jsonStringToArg
rv, err := jsonStringToArg(cdc, rt, qarg)
if err != nil {
return rv, err, false
} else {
return rv, nil, true
}
}
if isHexString {
if !expectingString && !expectingByteSlice {
err := errors.Errorf("Got a hex string arg, but expected '%s'",
ty.Kind().String())
rt.Kind().String())
return reflect.ValueOf(nil), err, false
}
@ -340,7 +378,7 @@ func nonJSONToArg(cdc *amino.Codec, ty reflect.Type, arg string) (reflect.Value,
if err != nil {
return reflect.ValueOf(nil), err, false
}
if ty.Kind() == reflect.String {
if rt.Kind() == reflect.String {
return reflect.ValueOf(string(value)), nil, true
}
return reflect.ValueOf([]byte(value)), nil, true


+ 1
- 0
rpc/lib/server/http_params.go View File

@ -15,6 +15,7 @@ var (
dotAtom = atom + `(?:\.` + atom + `)*`
domain = `[A-Z0-9.-]+\.[A-Z]{2,4}`
RE_INT = regexp.MustCompile(`^-?[0-9]+$`)
RE_HEX = regexp.MustCompile(`^(?i)[a-f0-9]+$`)
RE_EMAIL = regexp.MustCompile(`^(?i)(` + dotAtom + `)@(` + dotAtom + `)$`)
RE_ADDRESS = regexp.MustCompile(`^(?i)[a-z0-9]{25,34}$`)


+ 46
- 1
rpc/lib/server/parse_test.go View File

@ -2,6 +2,8 @@ package rpcserver
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
@ -134,7 +136,7 @@ func TestParseJSONArray(t *testing.T) {
}
}
func TestParseRPC(t *testing.T) {
func TestParseJSONRPC(t *testing.T) {
assert := assert.New(t)
demo := func(height int, name string) {}
@ -172,5 +174,48 @@ func TestParseRPC(t *testing.T) {
}
}
}
func TestParseURI(t *testing.T) {
demo := func(height int, name string) {}
call := NewRPCFunc(demo, "height,name")
cdc := amino.NewCodec()
cases := []struct {
raw []string
height int64
name string
fail bool
}{
// can parse numbers unquoted and strings quoted
{[]string{"7", `"flew"`}, 7, "flew", false},
{[]string{"22", `"john"`}, 22, "john", false},
{[]string{"-10", `"bob"`}, -10, "bob", false},
// can parse numbers quoted, too
{[]string{`"7"`, `"flew"`}, 7, "flew", false},
{[]string{`"-10"`, `"bob"`}, -10, "bob", false},
// cant parse strings uquoted
{[]string{`"-10"`, `bob`}, -10, "bob", true},
}
for idx, tc := range cases {
i := strconv.Itoa(idx)
// data := []byte(tc.raw)
url := fmt.Sprintf(
"test.com/method?height=%v&name=%v",
tc.raw[0], tc.raw[1])
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
vals, err := httpParamsToArgs(call, cdc, req)
if tc.fail {
assert.NotNil(t, err, i)
} else {
assert.Nil(t, err, "%s: %+v", i, err)
if assert.Equal(t, 2, len(vals), i) {
assert.Equal(t, tc.height, vals[0].Int(), i)
assert.Equal(t, tc.name, vals[1].String(), i)
}
}
}
}

Loading…
Cancel
Save