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