github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/server/http_json_handler.go (about)

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"html/template"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/ari-anchor/sei-tendermint/libs/log"
    13  	rpctypes "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/types"
    14  )
    15  
    16  // HTTP + JSON handler
    17  
    18  // jsonrpc calls grab the given method's function info and runs reflect.Call
    19  func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.HandlerFunc {
    20  	return func(w http.ResponseWriter, hreq *http.Request) {
    21  		// For POST requests, reject a non-root URL path. This should not happen
    22  		// in the standard configuration, since the wrapper checks the path.
    23  		if hreq.URL.Path != "/" {
    24  			writeRPCResponse(w, logger, rpctypes.RPCRequest{}.MakeErrorf(
    25  				rpctypes.CodeInvalidRequest, "invalid path: %q", hreq.URL.Path))
    26  			return
    27  		}
    28  
    29  		b, err := io.ReadAll(hreq.Body)
    30  		if err != nil {
    31  			writeRPCResponse(w, logger, rpctypes.RPCRequest{}.MakeErrorf(
    32  				rpctypes.CodeInvalidRequest, "reading request body: %v", err))
    33  			return
    34  		}
    35  
    36  		// if its an empty request (like from a browser), just display a list of
    37  		// functions
    38  		if len(b) == 0 {
    39  			writeListOfEndpoints(w, hreq, funcMap)
    40  			return
    41  		}
    42  
    43  		requests, err := parseRequests(b)
    44  		if err != nil {
    45  			writeRPCResponse(w, logger, rpctypes.RPCRequest{}.MakeErrorf(
    46  				rpctypes.CodeParseError, "decoding request: %v", err))
    47  			return
    48  		}
    49  
    50  		var responses []rpctypes.RPCResponse
    51  		for _, req := range requests {
    52  			// Ignore notifications, which this service does not support.
    53  			if req.IsNotification() {
    54  				logger.Debug("Ignoring notification", "req", req)
    55  				continue
    56  			}
    57  
    58  			rpcFunc, ok := funcMap[req.Method]
    59  			if !ok || rpcFunc.ws {
    60  				responses = append(responses, req.MakeErrorf(rpctypes.CodeMethodNotFound, req.Method))
    61  				continue
    62  			}
    63  
    64  			req := req
    65  			ctx := rpctypes.WithCallInfo(hreq.Context(), &rpctypes.CallInfo{
    66  				RPCRequest:  &req,
    67  				HTTPRequest: hreq,
    68  			})
    69  			result, err := rpcFunc.Call(ctx, req.Params)
    70  			if err != nil {
    71  				responses = append(responses, req.MakeError(err))
    72  			} else {
    73  				responses = append(responses, req.MakeResponse(result))
    74  			}
    75  		}
    76  
    77  		if len(responses) == 0 {
    78  			return
    79  		}
    80  		writeRPCResponse(w, logger, responses...)
    81  	}
    82  }
    83  
    84  func ensureBodyClose(next http.HandlerFunc) http.HandlerFunc {
    85  	return func(w http.ResponseWriter, r *http.Request) {
    86  		defer r.Body.Close()
    87  		next(w, r)
    88  	}
    89  }
    90  
    91  func handleInvalidJSONRPCPaths(next http.HandlerFunc) http.HandlerFunc {
    92  	return func(w http.ResponseWriter, r *http.Request) {
    93  		//  we check whether the path is indeed "/", otherwise return a 404 error
    94  		if r.URL.Path != "/" {
    95  			http.NotFound(w, r)
    96  			return
    97  		}
    98  
    99  		next(w, r)
   100  	}
   101  }
   102  
   103  // parseRequests parses a JSON-RPC request or request batch from data.
   104  func parseRequests(data []byte) ([]rpctypes.RPCRequest, error) {
   105  	var reqs []rpctypes.RPCRequest
   106  	var err error
   107  
   108  	isArray := bytes.HasPrefix(bytes.TrimSpace(data), []byte("["))
   109  	if isArray {
   110  		err = json.Unmarshal(data, &reqs)
   111  	} else {
   112  		reqs = append(reqs, rpctypes.RPCRequest{})
   113  		err = json.Unmarshal(data, &reqs[0])
   114  	}
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return reqs, nil
   119  }
   120  
   121  // writes a list of available rpc endpoints as an html page
   122  func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) {
   123  	hasArgs := make(map[string]string)
   124  	noArgs := make(map[string]string)
   125  	for name, rf := range funcMap {
   126  		base := fmt.Sprintf("//%s/%s", r.Host, name)
   127  		if len(rf.args) == 0 {
   128  			noArgs[name] = base
   129  			continue
   130  		}
   131  		var query []string
   132  		for _, arg := range rf.args {
   133  			query = append(query, arg.name+"=_")
   134  		}
   135  		hasArgs[name] = base + "?" + strings.Join(query, "&")
   136  	}
   137  	w.Header().Set("Content-Type", "text/html")
   138  	_ = listOfEndpoints.Execute(w, map[string]map[string]string{
   139  		"NoArgs":  noArgs,
   140  		"HasArgs": hasArgs,
   141  	})
   142  }
   143  
   144  var listOfEndpoints = template.Must(template.New("list").Parse(`<html>
   145  <head><title>List of RPC Endpoints</title></head>
   146  <body>
   147  
   148  <h1>Available RPC endpoints:</h1>
   149  
   150  {{if .NoArgs}}
   151  <hr />
   152  <h2>Endpoints with no arguments:</h2>
   153  
   154  <ul>
   155  {{range $link := .NoArgs}}  <li><a href="{{$link}}">{{$link}}</a></li>
   156  {{end -}}
   157  </ul>{{end}}
   158  
   159  {{if .HasArgs}}
   160  <hr />
   161  <h2>Endpoints that require arguments:</h2>
   162  
   163  <ul>
   164  {{range $link := .HasArgs}}  <li><a href="{{$link}}">{{$link}}</a></li>
   165  {{end -}}
   166  </ul>{{end}}
   167  
   168  </body></html>`))