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