cleanup rpc/handlers

This commit is contained in:
Jae Kwon 2015-03-30 15:55:05 -07:00
parent 1a4aab4c35
commit e257307b01
4 changed files with 148 additions and 174 deletions

View File

@ -150,7 +150,7 @@ func (n *Node) DialSeed() {
}
}
func (n *Node) StartRpc() {
func (n *Node) StartRPC() {
core.SetBlockStore(n.blockStore)
core.SetConsensusState(n.consensusState)
core.SetMempoolReactor(n.mempoolReactor)
@ -185,7 +185,7 @@ func Daemon() {
// Run the RPC server.
if config.App().GetString("RPC.HTTP.ListenAddr") != "" {
n.StartRpc()
n.StartRPC()
}
// Sleep forever and then...

View File

@ -1,5 +1,9 @@
package rpc
/*
TODO: support Call && GetStorage.
*/
import (
"encoding/json"
"fmt"
@ -12,7 +16,6 @@ import (
// cache all type information about each function up front
// (func, responseStruct, argNames)
// XXX: response structs are allocated once and reused - will this cause an issue eg. if a field ever not overwritten?
var funcMap = map[string]*FuncWrapper{
"status": funcWrap(core.Status, []string{}),
"net_info": funcWrap(core.NetInfo, []string{}),
@ -26,6 +29,18 @@ var funcMap = map[string]*FuncWrapper{
"unsafe/sign_tx": funcWrap(core.SignTx, []string{"tx", "privAccounts"}),
}
func initHandlers() {
// HTTP endpoints
for funcName, funcInfo := range funcMap {
http.HandleFunc("/"+funcName, toHttpHandler(funcInfo))
}
// JSONRPC endpoints
http.HandleFunc("/", JSONRPCHandler)
}
//-------------------------------------
// holds all type information for each function
type FuncWrapper struct {
f reflect.Value // function from "rpc/core"
@ -43,170 +58,6 @@ func funcWrap(f interface{}, args []string) *FuncWrapper {
}
}
// convert from a function name to the http handler
func toHandler(funcName string) func(http.ResponseWriter, *http.Request) {
funcInfo := funcMap[funcName]
return func(w http.ResponseWriter, r *http.Request) {
values, err := queryToValues(funcInfo, r)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(values)
response, err := returnsToResponse(funcInfo, returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
}
// convert a (json) string to a given type
func jsonToArg(ty reflect.Type, arg string) (reflect.Value, error) {
var err error
v := reflect.New(ty)
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
v = v.Elem()
return v, nil
}
func jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
var err error
v := reflect.New(ty)
binary.ReadJSONFromObject(v.Interface(), object, &err)
if err != nil {
return v, err
}
v = v.Elem()
return v, nil
}
// 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 queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, error) {
argTypes := funcInfo.args
argNames := funcInfo.argNames
var err error
values := make([]reflect.Value, len(argNames))
for i, name := range argNames {
ty := argTypes[i]
arg := GetParam(r, name)
//fmt.Println("GetParam()", r, name, arg)
values[i], err = jsonToArg(ty, arg)
if err != nil {
return nil, err
}
}
return values, nil
}
// covert a list of interfaces to properly typed values
func paramsToValues(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) {
values := make([]reflect.Value, len(params))
for i, p := range params {
ty := funcInfo.args[i]
v, err := jsonObjectToArg(ty, p)
if err != nil {
return nil, err
}
values[i] = v
}
return values, nil
}
// returns is Response struct and error. If error is not nil, return it
func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) {
errV := returns[1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
return returns[0].Interface(), nil
}
/*
// convert a list of values to a populated struct with the correct types
func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) {
returnTypes := funcInfo.returns
finalType := returnTypes[len(returnTypes)-1]
if finalType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
errV := returns[len(returnTypes)-1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
}
// copy the response struct (New returns a pointer so we have to Elem() twice)
v := reflect.New(funcInfo.response.Elem().Type()).Elem()
nFields := v.NumField()
for i := 0; i < nFields; i++ {
field := v.FieldByIndex([]int{i})
field.Set(returns[i])
}
return v.Interface(), nil
}*/
// jsonrpc calls grab the given method's function info and runs reflect.Call
func JsonRpcHandler(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
var jrpc JsonRpc
err := json.Unmarshal(b, &jrpc)
if err != nil {
// TODO
}
funcInfo := funcMap[jrpc.Method]
values, err := paramsToValues(funcInfo, jrpc.Params)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(values)
response, err := returnsToResponse(funcInfo, returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
func initHandlers() {
// HTTP endpoints
// toHandler runs once for each function and caches
// all reflection data
http.HandleFunc("/status", toHandler("status"))
http.HandleFunc("/net_info", toHandler("net_info"))
http.HandleFunc("/blockchain", toHandler("blockchain"))
http.HandleFunc("/get_block", toHandler("get_block"))
http.HandleFunc("/get_account", toHandler("get_account"))
http.HandleFunc("/list_validators", toHandler("list_validators"))
http.HandleFunc("/broadcast_tx", toHandler("broadcast_tx"))
http.HandleFunc("/list_accounts", toHandler("list_accounts"))
http.HandleFunc("/unsafe/gen_priv_account", toHandler("unsafe/gen_priv_account"))
http.HandleFunc("/unsafe/sign_tx", toHandler("unsafe/sign_tx"))
//http.HandleFunc("/call", CallHandler)
//http.HandleFunc("/get_storage", GetStorageHandler)
// JsonRPC endpoints
http.HandleFunc("/", JsonRpcHandler)
// unsafe JsonRPC endpoints
//http.HandleFunc("/unsafe", UnsafeJsonRpcHandler)
}
type JsonRpc struct {
JsonRpc string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
Id int `json:"id"`
}
// this will panic if not passed a function
func funcArgTypes(f interface{}) []reflect.Type {
t := reflect.TypeOf(f)
n := t.NumIn()
@ -226,3 +77,126 @@ func funcReturnTypes(f interface{}) []reflect.Type {
}
return types
}
//-----------------------------------------------------------------------------
// rpc.json
type JSONRPC struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
Id int `json:"id"`
}
// jsonrpc calls grab the given method's function info and runs reflect.Call
func JSONRPCHandler(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
var jrpc JSONRPC
err := json.Unmarshal(b, &jrpc)
if err != nil {
// TODO
}
funcInfo := funcMap[jrpc.Method]
args, err := jsonParamsToArgs(funcInfo, jrpc.Params)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(args)
response, err := returnsToResponse(returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
// covert a list of interfaces to properly typed values
func jsonParamsToArgs(funcInfo *FuncWrapper, params []interface{}) ([]reflect.Value, error) {
values := make([]reflect.Value, len(params))
for i, p := range params {
ty := funcInfo.args[i]
v, err := _jsonObjectToArg(ty, p)
if err != nil {
return nil, err
}
values[i] = v
}
return values, nil
}
func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
var err error
v := reflect.New(ty)
binary.ReadJSONFromObject(v.Interface(), object, &err)
if err != nil {
return v, err
}
v = v.Elem()
return v, nil
}
// rpc.json
//-----------------------------------------------------------------------------
// rpc.http
// convert from a function name to the http handler
func toHttpHandler(funcInfo *FuncWrapper) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
args, err := httpParamsToArgs(funcInfo, r)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(args)
response, err := returnsToResponse(returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
}
// 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(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, error) {
argTypes := funcInfo.args
argNames := funcInfo.argNames
var err error
values := make([]reflect.Value, len(argNames))
for i, name := range argNames {
ty := argTypes[i]
arg := GetParam(r, name)
values[i], err = _jsonStringToArg(ty, arg)
if err != nil {
return nil, err
}
}
return values, nil
}
func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) {
var err error
v := reflect.New(ty)
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
v = v.Elem()
return v, nil
}
// rpc.http
//-----------------------------------------------------------------------------
// returns is Response struct and error. If error is not nil, return it
func returnsToResponse(returns []reflect.Value) (interface{}, error) {
errV := returns[1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
return returns[0].Interface(), nil
}

View File

@ -18,8 +18,8 @@ import (
)
func TestJSONStatus(t *testing.T) {
s := rpc.JsonRpc{
JsonRpc: "2.0",
s := rpc.JSONRPC{
JSONRPC: "2.0",
Method: "status",
Params: []interface{}{},
Id: 0,
@ -53,8 +53,8 @@ func TestJSONStatus(t *testing.T) {
}
func TestJSONGenPriv(t *testing.T) {
s := rpc.JsonRpc{
JsonRpc: "2.0",
s := rpc.JSONRPC{
JSONRPC: "2.0",
Method: "unsafe/gen_priv_account",
Params: []interface{}{},
Id: 0,

View File

@ -41,7 +41,7 @@ func newNode(ready chan struct{}) {
node.Start()
// Run the RPC server.
node.StartRpc()
node.StartRPC()
ready <- struct{}{}
// Sleep forever
@ -72,8 +72,8 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account {
var err error
switch typ {
case "JSONRPC":
s := rpc.JsonRpc{
JsonRpc: "2.0",
s := rpc.JSONRPC{
JSONRPC: "2.0",
Method: "get_account",
Params: []interface{}{hex.EncodeToString(addr)},
Id: 0,