mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-05 03:20:44 +00:00
* Set cache control in the HTTP-RPC response header * Add a simply cache policy to the RPC routes * add a condition to check the RPC request has default height settings * fix cherry pick error * update pending log * use options struct intead of single parameter * refacor FuncOptions to functional options * add functional options in WebSocket RPC function * revert doc * replace deprecated function call * revise functional options * remove unuse comment * fix revised error * adjust cache-control settings * Update rpc/jsonrpc/server/http_json_handler.go Co-authored-by: Thane Thomson <connect@thanethomson.com> * linter: Fix false positive Signed-off-by: Thane Thomson <connect@thanethomson.com> * rpc: Separate cacheable and non-cacheable HTTP response writers Allows us to roll this change out in a non-API-breaking way, since this is an additive change. Signed-off-by: Thane Thomson <connect@thanethomson.com> * rpc: Ensure consistent caching strategy Ensure a consistent caching strategy across both JSONRPC- and URI-based requests. This requires a bit of a refactor of the previous caching logic, which is complicated a little by the complex reflection-based approach taken in the Tendermint RPC. Signed-off-by: Thane Thomson <connect@thanethomson.com> * rpc: Add more tests for caching Signed-off-by: Thane Thomson <connect@thanethomson.com> * Update CHANGELOG_PENDING Signed-off-by: Thane Thomson <connect@thanethomson.com> * light: Sync routes config with RPC core Signed-off-by: Thane Thomson <connect@thanethomson.com> * rpc: Update OpenAPI docs Signed-off-by: Thane Thomson <connect@thanethomson.com> Signed-off-by: Thane Thomson <connect@thanethomson.com> Co-authored-by: jayt106 <jaytseng106@gmail.com> Co-authored-by: jay tseng <jay.tseng@crypto.com> Co-authored-by: JayT106 <JayT106@users.noreply.github.com>
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"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)))
|
|
}
|
|
|
|
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
|
|
}
|