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>`))