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 }