Some checks are pending
docker-build-cometbft / vars (push) Waiting to run
docker-build-cometbft / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-cometbft / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-cometbft / merge-images (push) Blocked by required conditions
docker-build-e2e-node / vars (push) Waiting to run
docker-build-e2e-node / build-images (amd64, ubuntu-24.04) (push) Blocked by required conditions
docker-build-e2e-node / build-images (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
docker-build-e2e-node / merge-images (push) Blocked by required conditions
154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"git.cw.tr/mukan-network/mukan-consensus/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)))
|
|
}
|
|
|
|
type Option func(*RPCFunc)
|
|
|
|
// Cacheable enables returning a cache control header from RPC functions to
|
|
// which it is applied.
|
|
//
|
|
// `noCacheDefArgs` is a list of argument names that, if omitted or set to
|
|
// their defaults when calling the RPC function, will skip the response
|
|
// caching.
|
|
func Cacheable(noCacheDefArgs ...string) Option {
|
|
return func(r *RPCFunc) {
|
|
r.cacheable = true
|
|
r.noCacheDefArgs = make(map[string]interface{})
|
|
for _, arg := range noCacheDefArgs {
|
|
r.noCacheDefArgs[arg] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ws enables WebSocket communication.
|
|
func Ws() Option {
|
|
return func(r *RPCFunc) {
|
|
r.ws = true
|
|
}
|
|
}
|
|
|
|
// 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
|
|
cacheable bool // enable cache control
|
|
ws bool // enable websocket communication
|
|
noCacheDefArgs map[string]interface{} // a lookup table of args that, if not supplied or are set to default values, cause us to not cache
|
|
}
|
|
|
|
// NewRPCFunc wraps a function for introspection.
|
|
// f is the function, args are comma separated argument names
|
|
func NewRPCFunc(f interface{}, args string, options ...Option) *RPCFunc {
|
|
return newRPCFunc(f, args, options...)
|
|
}
|
|
|
|
// NewWSRPCFunc wraps a function for introspection and use in the websockets.
|
|
func NewWSRPCFunc(f interface{}, args string, options ...Option) *RPCFunc {
|
|
options = append(options, Ws())
|
|
return newRPCFunc(f, args, options...)
|
|
}
|
|
|
|
// cacheableWithArgs returns whether or not a call to this function is cacheable,
|
|
// given the specified arguments.
|
|
func (f *RPCFunc) cacheableWithArgs(args []reflect.Value) bool {
|
|
if !f.cacheable {
|
|
return false
|
|
}
|
|
// Skip the context variable common to all RPC functions
|
|
for i := 1; i < len(f.args); i++ {
|
|
// f.argNames does not include the context variable
|
|
argName := f.argNames[i-1]
|
|
if _, hasDefault := f.noCacheDefArgs[argName]; hasDefault {
|
|
// Argument with default value was not supplied
|
|
if i >= len(args) {
|
|
return false
|
|
}
|
|
// Argument with default value is set to its zero value
|
|
if args[i].IsZero() {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func newRPCFunc(f interface{}, args string, options ...Option) *RPCFunc {
|
|
var argNames []string
|
|
if args != "" {
|
|
argNames = strings.Split(args, ",")
|
|
}
|
|
|
|
r := &RPCFunc{
|
|
f: reflect.ValueOf(f),
|
|
args: funcArgTypes(f),
|
|
returns: funcReturnTypes(f),
|
|
argNames: argNames,
|
|
}
|
|
|
|
for _, opt := range options {
|
|
opt(r)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
// return a function's argument types
|
|
func funcArgTypes(f interface{}) []reflect.Type {
|
|
t := reflect.TypeOf(f)
|
|
n := t.NumIn()
|
|
typez := make([]reflect.Type, n)
|
|
for i := 0; i < n; i++ {
|
|
typez[i] = t.In(i)
|
|
}
|
|
return typez
|
|
}
|
|
|
|
// return a function's return types
|
|
func funcReturnTypes(f interface{}) []reflect.Type {
|
|
t := reflect.TypeOf(f)
|
|
n := t.NumOut()
|
|
typez := make([]reflect.Type, n)
|
|
for i := 0; i < n; i++ {
|
|
typez[i] = t.Out(i)
|
|
}
|
|
return typez
|
|
}
|
|
|
|
//-------------------------------------------------------------
|
|
|
|
// 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 errV.Interface() != nil {
|
|
return nil, fmt.Errorf("%v", errV.Interface())
|
|
}
|
|
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
|
|
}
|