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