github.com/franono/tendermint@v0.32.2-0.20200527150959-749313264ce9/rpc/jsonrpc/server/http_json_handler.go (about)

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"reflect"
    10  	"sort"
    11  
    12  	amino "github.com/tendermint/go-amino"
    13  
    14  	"github.com/franono/tendermint/libs/log"
    15  	types "github.com/franono/tendermint/rpc/jsonrpc/types"
    16  )
    17  
    18  ///////////////////////////////////////////////////////////////////////////////
    19  // HTTP + JSON handler
    20  ///////////////////////////////////////////////////////////////////////////////
    21  
    22  // jsonrpc calls grab the given method's function info and runs reflect.Call
    23  func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger log.Logger) http.HandlerFunc {
    24  	return func(w http.ResponseWriter, r *http.Request) {
    25  		b, err := ioutil.ReadAll(r.Body)
    26  		if err != nil {
    27  			WriteRPCResponseHTTP(
    28  				w,
    29  				types.RPCInvalidRequestError(
    30  					nil,
    31  					fmt.Errorf("error reading request body: %w", err),
    32  				),
    33  			)
    34  			return
    35  		}
    36  
    37  		// if its an empty request (like from a browser), just display a list of
    38  		// functions
    39  		if len(b) == 0 {
    40  			writeListOfEndpoints(w, r, funcMap)
    41  			return
    42  		}
    43  
    44  		// first try to unmarshal the incoming request as an array of RPC requests
    45  		var (
    46  			requests  []types.RPCRequest
    47  			responses []types.RPCResponse
    48  		)
    49  		if err := json.Unmarshal(b, &requests); err != nil {
    50  			// next, try to unmarshal as a single request
    51  			var request types.RPCRequest
    52  			if err := json.Unmarshal(b, &request); err != nil {
    53  				WriteRPCResponseHTTP(
    54  					w,
    55  					types.RPCParseError(
    56  						fmt.Errorf("error unmarshalling request: %w", err),
    57  					),
    58  				)
    59  				return
    60  			}
    61  			requests = []types.RPCRequest{request}
    62  		}
    63  
    64  		for _, request := range requests {
    65  			request := request
    66  
    67  			// A Notification is a Request object without an "id" member.
    68  			// The Server MUST NOT reply to a Notification, including those that are within a batch request.
    69  			if request.ID == nil {
    70  				logger.Debug(
    71  					"HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)",
    72  					"req", request,
    73  				)
    74  				continue
    75  			}
    76  			if len(r.URL.Path) > 1 {
    77  				responses = append(
    78  					responses,
    79  					types.RPCInvalidRequestError(request.ID, fmt.Errorf("path %s is invalid", r.URL.Path)),
    80  				)
    81  				continue
    82  			}
    83  			rpcFunc, ok := funcMap[request.Method]
    84  			if !ok || rpcFunc.ws {
    85  				responses = append(responses, types.RPCMethodNotFoundError(request.ID))
    86  				continue
    87  			}
    88  			ctx := &types.Context{JSONReq: &request, HTTPReq: r}
    89  			args := []reflect.Value{reflect.ValueOf(ctx)}
    90  			if len(request.Params) > 0 {
    91  				fnArgs, err := jsonParamsToArgs(rpcFunc, cdc, request.Params)
    92  				if err != nil {
    93  					responses = append(
    94  						responses,
    95  						types.RPCInvalidParamsError(request.ID, fmt.Errorf("error converting json params to arguments: %w", err)),
    96  					)
    97  					continue
    98  				}
    99  				args = append(args, fnArgs...)
   100  			}
   101  			returns := rpcFunc.f.Call(args)
   102  			logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
   103  			result, err := unreflectResult(returns)
   104  			if err != nil {
   105  				responses = append(responses, types.RPCInternalError(request.ID, err))
   106  				continue
   107  			}
   108  			responses = append(responses, types.NewRPCSuccessResponse(cdc, request.ID, result))
   109  		}
   110  		if len(responses) > 0 {
   111  			WriteRPCResponseArrayHTTP(w, responses)
   112  		}
   113  	}
   114  }
   115  
   116  func handleInvalidJSONRPCPaths(next http.HandlerFunc) http.HandlerFunc {
   117  	return func(w http.ResponseWriter, r *http.Request) {
   118  		// Since the pattern "/" matches all paths not matched by other registered patterns,
   119  		//  we check whether the path is indeed "/", otherwise return a 404 error
   120  		if r.URL.Path != "/" {
   121  			http.NotFound(w, r)
   122  			return
   123  		}
   124  
   125  		next(w, r)
   126  	}
   127  }
   128  
   129  func mapParamsToArgs(
   130  	rpcFunc *RPCFunc,
   131  	cdc *amino.Codec,
   132  	params map[string]json.RawMessage,
   133  	argsOffset int,
   134  ) ([]reflect.Value, error) {
   135  
   136  	values := make([]reflect.Value, len(rpcFunc.argNames))
   137  	for i, argName := range rpcFunc.argNames {
   138  		argType := rpcFunc.args[i+argsOffset]
   139  
   140  		if p, ok := params[argName]; ok && p != nil && len(p) > 0 {
   141  			val := reflect.New(argType)
   142  			err := cdc.UnmarshalJSON(p, val.Interface())
   143  			if err != nil {
   144  				return nil, err
   145  			}
   146  			values[i] = val.Elem()
   147  		} else { // use default for that type
   148  			values[i] = reflect.Zero(argType)
   149  		}
   150  	}
   151  
   152  	return values, nil
   153  }
   154  
   155  func arrayParamsToArgs(
   156  	rpcFunc *RPCFunc,
   157  	cdc *amino.Codec,
   158  	params []json.RawMessage,
   159  	argsOffset int,
   160  ) ([]reflect.Value, error) {
   161  
   162  	if len(rpcFunc.argNames) != len(params) {
   163  		return nil, fmt.Errorf("expected %v parameters (%v), got %v (%v)",
   164  			len(rpcFunc.argNames), rpcFunc.argNames, len(params), params)
   165  	}
   166  
   167  	values := make([]reflect.Value, len(params))
   168  	for i, p := range params {
   169  		argType := rpcFunc.args[i+argsOffset]
   170  		val := reflect.New(argType)
   171  		err := cdc.UnmarshalJSON(p, val.Interface())
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  		values[i] = val.Elem()
   176  	}
   177  	return values, nil
   178  }
   179  
   180  // raw is unparsed json (from json.RawMessage) encoding either a map or an
   181  // array.
   182  //
   183  // Example:
   184  //   rpcFunc.args = [rpctypes.Context string]
   185  //   rpcFunc.argNames = ["arg"]
   186  func jsonParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, raw []byte) ([]reflect.Value, error) {
   187  	const argsOffset = 1
   188  
   189  	// TODO: Make more efficient, perhaps by checking the first character for '{' or '['?
   190  	// First, try to get the map.
   191  	var m map[string]json.RawMessage
   192  	err := json.Unmarshal(raw, &m)
   193  	if err == nil {
   194  		return mapParamsToArgs(rpcFunc, cdc, m, argsOffset)
   195  	}
   196  
   197  	// Otherwise, try an array.
   198  	var a []json.RawMessage
   199  	err = json.Unmarshal(raw, &a)
   200  	if err == nil {
   201  		return arrayParamsToArgs(rpcFunc, cdc, a, argsOffset)
   202  	}
   203  
   204  	// Otherwise, bad format, we cannot parse
   205  	return nil, fmt.Errorf("unknown type for JSON params: %v. Expected map or array", err)
   206  }
   207  
   208  // writes a list of available rpc endpoints as an html page
   209  func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) {
   210  	noArgNames := []string{}
   211  	argNames := []string{}
   212  	for name, funcData := range funcMap {
   213  		if len(funcData.args) == 0 {
   214  			noArgNames = append(noArgNames, name)
   215  		} else {
   216  			argNames = append(argNames, name)
   217  		}
   218  	}
   219  	sort.Strings(noArgNames)
   220  	sort.Strings(argNames)
   221  	buf := new(bytes.Buffer)
   222  	buf.WriteString("<html><body>")
   223  	buf.WriteString("<br>Available endpoints:<br>")
   224  
   225  	for _, name := range noArgNames {
   226  		link := fmt.Sprintf("//%s/%s", r.Host, name)
   227  		buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
   228  	}
   229  
   230  	buf.WriteString("<br>Endpoints that require arguments:<br>")
   231  	for _, name := range argNames {
   232  		link := fmt.Sprintf("//%s/%s?", r.Host, name)
   233  		funcData := funcMap[name]
   234  		for i, argName := range funcData.argNames {
   235  			link += argName + "=_"
   236  			if i < len(funcData.argNames)-1 {
   237  				link += "&"
   238  			}
   239  		}
   240  		buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link))
   241  	}
   242  	buf.WriteString("</body></html>")
   243  	w.Header().Set("Content-Type", "text/html")
   244  	w.WriteHeader(200)
   245  	w.Write(buf.Bytes()) // nolint: errcheck
   246  }