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