|
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
)
|
|
|
|
// RegisterRPCFuncs adds a route for each function in the funcMap, as well as
|
|
// general jsonrpc and websocket handlers for all functions. "result" is the
|
|
// interface on which the result objects are registered, and is popualted with
|
|
// every RPCResponse
|
|
func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger log.Logger) {
|
|
// HTTP endpoints
|
|
for funcName, rpcFunc := range funcMap {
|
|
mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc, logger))
|
|
}
|
|
|
|
// JSONRPC endpoints
|
|
mux.HandleFunc("/", handleInvalidJSONRPCPaths(makeJSONRPCHandler(funcMap, logger)))
|
|
}
|
|
|
|
// Function introspection
|
|
|
|
// RPCFunc contains the introspected type information for a function.
|
|
type RPCFunc struct {
|
|
f reflect.Value // underlying rpc function
|
|
args []reflect.Type // type of each function arg
|
|
returns []reflect.Type // type of each return arg
|
|
argNames []string // name of each argument
|
|
ws bool // websocket only
|
|
}
|
|
|
|
// NewRPCFunc constructs an RPCFunc for f, which must be a function whose type
|
|
// signature matches one of these schemes:
|
|
//
|
|
// func(context.Context, T1, T2, ...) error
|
|
// func(context.Context, T1, T2, ...) (R, error)
|
|
//
|
|
// for arbitrary types T_i and R. The number of argNames must exactly match the
|
|
// number of non-context arguments to f. Otherwise, NewRPCFunc panics.
|
|
//
|
|
// The parameter names given are used to map JSON object keys to the
|
|
// corresonding parameter of the function. The names do not need to match the
|
|
// declared names, but must match what the client sends in a request.
|
|
func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc {
|
|
rf, err := newRPCFunc(f, argNames)
|
|
if err != nil {
|
|
panic("invalid RPC function: " + err.Error())
|
|
}
|
|
return rf
|
|
}
|
|
|
|
// NewWSRPCFunc behaves as NewRPCFunc, but marks the resulting function for use
|
|
// via websocket.
|
|
func NewWSRPCFunc(f interface{}, argNames ...string) *RPCFunc {
|
|
rf := NewRPCFunc(f, argNames...)
|
|
rf.ws = true
|
|
return rf
|
|
}
|
|
|
|
var (
|
|
ctxType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
|
errType = reflect.TypeOf((*error)(nil)).Elem()
|
|
)
|
|
|
|
// newRPCFunc constructs an RPCFunc for f. See the comment at NewRPCFunc.
|
|
func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) {
|
|
if f == nil {
|
|
return nil, errors.New("nil function")
|
|
}
|
|
|
|
// Check the type and signature of f.
|
|
fv := reflect.ValueOf(f)
|
|
if fv.Kind() != reflect.Func {
|
|
return nil, errors.New("not a function")
|
|
}
|
|
|
|
ft := fv.Type()
|
|
if np := ft.NumIn(); np == 0 {
|
|
return nil, errors.New("wrong number of parameters")
|
|
} else if ft.In(0) != ctxType {
|
|
return nil, errors.New("first parameter is not context.Context")
|
|
} else if np-1 != len(argNames) {
|
|
return nil, fmt.Errorf("have %d names for %d parameters", len(argNames), np-1)
|
|
}
|
|
|
|
if no := ft.NumOut(); no < 1 || no > 2 {
|
|
return nil, errors.New("wrong number of results")
|
|
} else if ft.Out(no-1) != errType {
|
|
return nil, errors.New("last result is not error")
|
|
}
|
|
|
|
args := make([]reflect.Type, ft.NumIn())
|
|
for i := 0; i < ft.NumIn(); i++ {
|
|
args[i] = ft.In(i)
|
|
}
|
|
outs := make([]reflect.Type, ft.NumOut())
|
|
for i := 0; i < ft.NumOut(); i++ {
|
|
outs[i] = ft.Out(i)
|
|
}
|
|
return &RPCFunc{
|
|
f: fv,
|
|
args: args,
|
|
returns: outs,
|
|
argNames: argNames,
|
|
}, nil
|
|
}
|
|
|
|
//-------------------------------------------------------------
|
|
|
|
// NOTE: assume returns is result struct and error. If error is not nil, return it
|
|
func unreflectResult(returns []reflect.Value) (interface{}, error) {
|
|
errV := returns[1]
|
|
if err, ok := errV.Interface().(error); ok && err != nil {
|
|
return nil, err
|
|
}
|
|
rv := returns[0]
|
|
// the result is a registered interface,
|
|
// we need a pointer to it so we can marshal with type byte
|
|
rvp := reflect.New(rv.Type())
|
|
rvp.Elem().Set(rv)
|
|
return rvp.Interface(), nil
|
|
}
|