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 }