Files
tendermint/rpc/jsonrpc/server/http_uri_handler.go
M. J. Fromberger 1f5e64e5b6 rpc: remove cache control settings from the HTTP server (#7568)
We should not set cache-control headers on RPC responses. HTTP caching
interacts poorly with resources that are expected to change frequently, or
whose rate of change is unpredictable.

More subtly, all calls to the POST endpoint use the same URL, which means a
cacheable response from one call may actually "hide" an uncacheable response
from a subsequent one. This is less of a problem for the GET endpoints, but
that means the behaviour of RPCs varies depending on which HTTP method your
client happens to use. Websocket requests were already marked statically
uncacheable, adding yet a third combination.

To address this:

- Stop setting cache-control headers.
- Update the tests that were checking for those headers.
- Remove the flags to request cache-control.

Apart from affecting the HTTP response headers, this change does not modify the
behaviour of any of the RPC methods.
2022-01-12 18:20:59 +00:00

245 lines
6.3 KiB
Go

package server
import (
"encoding/hex"
"errors"
"fmt"
"net/http"
"net/http/httputil"
"reflect"
"regexp"
"strings"
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/rpc/coretypes"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)
// HTTP + URI handler
var reInt = regexp.MustCompile(`^-?[0-9]+$`)
// convert from a function name to the http handler
func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWriter, *http.Request) {
// Always return -1 as there's no ID here.
dummyID := rpctypes.JSONRPCIntID(-1) // URIClientRequestID
// Exception for websocket endpoints
if rpcFunc.ws {
return func(w http.ResponseWriter, r *http.Request) {
res := rpctypes.RPCMethodNotFoundError(dummyID)
if wErr := WriteRPCResponseHTTPError(w, res); wErr != nil {
logger.Error("failed to write response", "res", res, "err", wErr)
}
}
}
// All other endpoints
return func(w http.ResponseWriter, r *http.Request) {
logger.Debug("HTTP HANDLER", "req", dumpHTTPRequest(r))
ctx := rpctypes.WithCallInfo(r.Context(), &rpctypes.CallInfo{HTTPRequest: r})
args := []reflect.Value{reflect.ValueOf(ctx)}
fnArgs, err := httpParamsToArgs(rpcFunc, r)
if err != nil {
res := rpctypes.RPCInvalidParamsError(dummyID,
fmt.Errorf("error converting http params to arguments: %w", err),
)
if wErr := WriteRPCResponseHTTPError(w, res); wErr != nil {
logger.Error("failed to write response", "res", res, "err", wErr)
}
return
}
args = append(args, fnArgs...)
returns := rpcFunc.f.Call(args)
logger.Debug("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
result, err := unreflectResult(returns)
switch e := err.(type) {
// if no error then return a success response
case nil:
res := rpctypes.NewRPCSuccessResponse(dummyID, result)
if wErr := WriteRPCResponseHTTP(w, res); wErr != nil {
logger.Error("failed to write response", "res", res, "err", wErr)
}
// if this already of type RPC error then forward that error.
case *rpctypes.RPCError:
res := rpctypes.NewRPCErrorResponse(dummyID, e.Code, e.Message, e.Data)
if wErr := WriteRPCResponseHTTPError(w, res); wErr != nil {
logger.Error("failed to write response", "res", res, "err", wErr)
}
default: // we need to unwrap the error and parse it accordingly
var res rpctypes.RPCResponse
switch errors.Unwrap(err) {
case coretypes.ErrZeroOrNegativeHeight,
coretypes.ErrZeroOrNegativePerPage,
coretypes.ErrPageOutOfRange,
coretypes.ErrInvalidRequest:
res = rpctypes.RPCInvalidRequestError(dummyID, err)
default: // ctypes.ErrHeightNotAvailable, ctypes.ErrHeightExceedsChainHead:
res = rpctypes.RPCInternalError(dummyID, err)
}
if wErr := WriteRPCResponseHTTPError(w, res); wErr != nil {
logger.Error("failed to write response", "res", res, "err", wErr)
}
}
}
}
// 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(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
// skip types.Context
const argsOffset = 1
values := make([]reflect.Value, len(rpcFunc.argNames))
for i, name := range rpcFunc.argNames {
argType := rpcFunc.args[i+argsOffset]
values[i] = reflect.Zero(argType) // set default for that type
arg := getParam(r, name)
// log.Notice("param to arg", "argType", argType, "name", name, "arg", arg)
if arg == "" {
continue
}
v, ok, err := nonJSONStringToArg(argType, arg)
if err != nil {
return nil, err
}
if ok {
values[i] = v
continue
}
values[i], err = jsonStringToArg(argType, arg)
if err != nil {
return nil, err
}
}
return values, nil
}
func jsonStringToArg(rt reflect.Type, arg string) (reflect.Value, error) {
rv := reflect.New(rt)
err := tmjson.Unmarshal([]byte(arg), rv.Interface())
if err != nil {
return rv, err
}
rv = rv.Elem()
return rv, nil
}
func nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, bool, error) {
if rt.Kind() == reflect.Ptr {
rv1, ok, err := nonJSONStringToArg(rt.Elem(), arg)
switch {
case err != nil:
return reflect.Value{}, false, err
case ok:
rv := reflect.New(rt.Elem())
rv.Elem().Set(rv1)
return rv, true, nil
default:
return reflect.Value{}, false, nil
}
} else {
return _nonJSONStringToArg(rt, arg)
}
}
// NOTE: rt.Kind() isn't a pointer.
func _nonJSONStringToArg(rt reflect.Type, arg string) (reflect.Value, bool, error) {
isIntString := reInt.Match([]byte(arg))
isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`)
isHexString := strings.HasPrefix(strings.ToLower(arg), "0x")
var expectingString, expectingByteSlice, expectingInt bool
switch rt.Kind() {
case reflect.Int,
reflect.Uint,
reflect.Int8,
reflect.Uint8,
reflect.Int16,
reflect.Uint16,
reflect.Int32,
reflect.Uint32,
reflect.Int64,
reflect.Uint64:
expectingInt = true
case reflect.String:
expectingString = true
case reflect.Slice:
expectingByteSlice = rt.Elem().Kind() == reflect.Uint8
}
if isIntString && expectingInt {
qarg := `"` + arg + `"`
rv, err := jsonStringToArg(rt, qarg)
if err != nil {
return rv, false, err
}
return rv, true, nil
}
if isHexString {
if !expectingString && !expectingByteSlice {
err := fmt.Errorf("got a hex string arg, but expected '%s'",
rt.Kind().String())
return reflect.ValueOf(nil), false, err
}
var value []byte
value, err := hex.DecodeString(arg[2:])
if err != nil {
return reflect.ValueOf(nil), false, err
}
if rt.Kind() == reflect.String {
return reflect.ValueOf(string(value)), true, nil
}
return reflect.ValueOf(value), true, nil
}
if isQuotedString && expectingByteSlice {
v := reflect.New(reflect.TypeOf(""))
err := tmjson.Unmarshal([]byte(arg), v.Interface())
if err != nil {
return reflect.ValueOf(nil), false, err
}
v = v.Elem()
return reflect.ValueOf([]byte(v.String())), true, nil
}
return reflect.ValueOf(nil), false, nil
}
func getParam(r *http.Request, param string) string {
s := r.URL.Query().Get(param)
if s == "" {
s = r.FormValue(param)
}
return s
}
func dumpHTTPRequest(r *http.Request) string {
d, e := httputil.DumpRequest(r, true)
if e != nil {
return e.Error()
}
return string(d)
}