- package server
-
- import (
- "context"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "reflect"
- "strconv"
- "strings"
-
- "github.com/tendermint/tendermint/libs/log"
- "github.com/tendermint/tendermint/rpc/coretypes"
- rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
- )
-
- // HTTP + URI handler
-
- // 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
- //
- // TODO(creachadair): Rather than reporting errors for these, we should
- // remove them from the routing list entirely on this endpoint.
- if rpcFunc.ws {
- return func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- }
- }
-
- // All other endpoints
- return func(w http.ResponseWriter, req *http.Request) {
- ctx := rpctypes.WithCallInfo(req.Context(), &rpctypes.CallInfo{
- HTTPRequest: req,
- })
- args, err := parseURLParams(ctx, rpcFunc, req)
- if err != nil {
- w.Header().Set("Content-Type", "text/plain")
- w.WriteHeader(http.StatusBadRequest)
- fmt.Fprintln(w, err.Error())
- return
- }
- outs := rpcFunc.f.Call(args)
-
- logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs)
- result, err := unreflectResult(outs)
- switch e := err.(type) {
- // if no error then return a success response
- case nil:
- writeHTTPResponse(w, logger, rpctypes.NewRPCSuccessResponse(dummyID, result))
-
- // if this already of type RPC error then forward that error.
- case *rpctypes.RPCError:
- writeHTTPResponse(w, logger, rpctypes.NewRPCErrorResponse(dummyID, e.Code, e.Message, e.Data))
-
- default: // we need to unwrap the error and parse it accordingly
- switch errors.Unwrap(err) {
- case coretypes.ErrZeroOrNegativeHeight,
- coretypes.ErrZeroOrNegativePerPage,
- coretypes.ErrPageOutOfRange,
- coretypes.ErrInvalidRequest:
- writeHTTPResponse(w, logger, rpctypes.RPCInvalidRequestError(dummyID, err))
- default: // ctypes.ErrHeightNotAvailable, ctypes.ErrHeightExceedsChainHead:
- writeHTTPResponse(w, logger, rpctypes.RPCInternalError(dummyID, err))
- }
- }
- }
- }
-
- func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]reflect.Value, error) {
- if err := req.ParseForm(); err != nil {
- return nil, fmt.Errorf("invalid HTTP request: %w", err)
- }
- getArg := func(name string) (string, bool) {
- if req.Form.Has(name) {
- return req.Form.Get(name), true
- }
- return "", false
- }
-
- vals := make([]reflect.Value, len(rf.argNames)+1)
- vals[0] = reflect.ValueOf(ctx)
- for i, name := range rf.argNames {
- atype := rf.args[i+1]
-
- text, ok := getArg(name)
- if !ok {
- vals[i+1] = reflect.Zero(atype)
- continue
- }
-
- val, err := parseArgValue(atype, text)
- if err != nil {
- return nil, fmt.Errorf("decoding parameter %q: %w", name, err)
- }
- vals[i+1] = val
- }
- return vals, nil
- }
-
- func parseArgValue(atype reflect.Type, text string) (reflect.Value, error) {
- // Regardless whether the argument is a pointer type, allocate a pointer so
- // we can set the computed value.
- var out reflect.Value
- isPtr := atype.Kind() == reflect.Ptr
- if isPtr {
- out = reflect.New(atype.Elem())
- } else {
- out = reflect.New(atype)
- }
-
- baseType := out.Type().Elem()
- if isIntType(baseType) {
- // Integral type: Require a base-10 digit string. For compatibility with
- // existing use allow quotation marks.
- v, err := decodeInteger(text)
- if err != nil {
- return reflect.Value{}, fmt.Errorf("invalid integer: %w", err)
- }
- out.Elem().Set(reflect.ValueOf(v).Convert(baseType))
- } else if isStringOrBytes(baseType) {
- // String or byte slice: Check for quotes, hex encoding.
- dec, err := decodeString(text)
- if err != nil {
- return reflect.Value{}, err
- }
- out.Elem().Set(reflect.ValueOf(dec).Convert(baseType))
-
- } else if baseType.Kind() == reflect.Bool {
- b, err := strconv.ParseBool(text)
- if err != nil {
- return reflect.Value{}, fmt.Errorf("invalid boolean: %w", err)
- }
- out.Elem().Set(reflect.ValueOf(b))
-
- } else {
- // We don't know how to represent other types.
- return reflect.Value{}, fmt.Errorf("unsupported argument type %v", baseType)
- }
-
- // If the argument wants a pointer, return the value as-is, otherwise
- // indirect the pointer back off.
- if isPtr {
- return out, nil
- }
- return out.Elem(), nil
- }
-
- var uint64Type = reflect.TypeOf(uint64(0))
-
- // isIntType reports whether atype is an integer-shaped type.
- func isIntType(atype reflect.Type) bool {
- switch atype.Kind() {
- case reflect.Float32, reflect.Float64:
- return false
- default:
- return atype.ConvertibleTo(uint64Type)
- }
- }
-
- // isStringOrBytes reports whether atype is a string or []byte.
- func isStringOrBytes(atype reflect.Type) bool {
- switch atype.Kind() {
- case reflect.String:
- return true
- case reflect.Slice:
- return atype.Elem().Kind() == reflect.Uint8
- default:
- return false
- }
- }
-
- // isQuotedString reports whether s is enclosed in double quotes.
- func isQuotedString(s string) bool {
- return len(s) >= 2 && strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`)
- }
-
- // decodeInteger decodes s into an int64. If s is "double quoted" the quotes
- // are removed; otherwise s must be a base-10 digit string.
- func decodeInteger(s string) (int64, error) {
- if isQuotedString(s) {
- s = s[1 : len(s)-1]
- }
- return strconv.ParseInt(s, 10, 64)
- }
-
- // decodeString decodes s into a byte slice. If s has an 0x prefix, it is
- // treated as a hex-encoded string. If it is "double quoted" it is treated as a
- // JSON string value. Otherwise, s is converted to bytes directly.
- func decodeString(s string) ([]byte, error) {
- if lc := strings.ToLower(s); strings.HasPrefix(lc, "0x") {
- return hex.DecodeString(lc[2:])
- } else if isQuotedString(s) {
- var dec string
- if err := json.Unmarshal([]byte(s), &dec); err != nil {
- return nil, fmt.Errorf("invalid quoted string: %w", err)
- }
- return []byte(dec), nil
- }
- return []byte(s), nil
- }
|