You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

128 lines
3.8 KiB

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
}