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