github.com/evdatsion/aphelion-dpos-bft@v0.32.1/rpc/lib/server/handlers.go (about) 1 package rpcserver 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 "reflect" 12 "runtime/debug" 13 "sort" 14 "strings" 15 "time" 16 17 "github.com/gorilla/websocket" 18 "github.com/pkg/errors" 19 20 amino "github.com/evdatsion/go-amino" 21 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 22 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 23 types "github.com/evdatsion/aphelion-dpos-bft/rpc/lib/types" 24 ) 25 26 // RegisterRPCFuncs adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions. 27 // "result" is the interface on which the result objects are registered, and is popualted with every RPCResponse 28 func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, cdc *amino.Codec, logger log.Logger) { 29 // HTTP endpoints 30 for funcName, rpcFunc := range funcMap { 31 mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc, cdc, logger)) 32 } 33 34 // JSONRPC endpoints 35 mux.HandleFunc("/", handleInvalidJSONRPCPaths(makeJSONRPCHandler(funcMap, cdc, logger))) 36 } 37 38 //------------------------------------- 39 // function introspection 40 41 // RPCFunc contains the introspected type information for a function 42 type RPCFunc struct { 43 f reflect.Value // underlying rpc function 44 args []reflect.Type // type of each function arg 45 returns []reflect.Type // type of each return arg 46 argNames []string // name of each argument 47 ws bool // websocket only 48 } 49 50 // NewRPCFunc wraps a function for introspection. 51 // f is the function, args are comma separated argument names 52 func NewRPCFunc(f interface{}, args string) *RPCFunc { 53 return newRPCFunc(f, args, false) 54 } 55 56 // NewWSRPCFunc wraps a function for introspection and use in the websockets. 57 func NewWSRPCFunc(f interface{}, args string) *RPCFunc { 58 return newRPCFunc(f, args, true) 59 } 60 61 func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc { 62 var argNames []string 63 if args != "" { 64 argNames = strings.Split(args, ",") 65 } 66 return &RPCFunc{ 67 f: reflect.ValueOf(f), 68 args: funcArgTypes(f), 69 returns: funcReturnTypes(f), 70 argNames: argNames, 71 ws: ws, 72 } 73 } 74 75 // return a function's argument types 76 func funcArgTypes(f interface{}) []reflect.Type { 77 t := reflect.TypeOf(f) 78 n := t.NumIn() 79 typez := make([]reflect.Type, n) 80 for i := 0; i < n; i++ { 81 typez[i] = t.In(i) 82 } 83 return typez 84 } 85 86 // return a function's return types 87 func funcReturnTypes(f interface{}) []reflect.Type { 88 t := reflect.TypeOf(f) 89 n := t.NumOut() 90 typez := make([]reflect.Type, n) 91 for i := 0; i < n; i++ { 92 typez[i] = t.Out(i) 93 } 94 return typez 95 } 96 97 // function introspection 98 //----------------------------------------------------------------------------- 99 // rpc.json 100 101 // jsonrpc calls grab the given method's function info and runs reflect.Call 102 func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger log.Logger) http.HandlerFunc { 103 return func(w http.ResponseWriter, r *http.Request) { 104 b, err := ioutil.ReadAll(r.Body) 105 if err != nil { 106 WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(types.JSONRPCStringID(""), errors.Wrap(err, "error reading request body"))) 107 return 108 } 109 // if its an empty request (like from a browser), 110 // just display a list of functions 111 if len(b) == 0 { 112 writeListOfEndpoints(w, r, funcMap) 113 return 114 } 115 116 // first try to unmarshal the incoming request as an array of RPC requests 117 var ( 118 requests []types.RPCRequest 119 responses []types.RPCResponse 120 ) 121 if err := json.Unmarshal(b, &requests); err != nil { 122 // next, try to unmarshal as a single request 123 var request types.RPCRequest 124 if err := json.Unmarshal(b, &request); err != nil { 125 WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "error unmarshalling request"))) 126 return 127 } 128 requests = []types.RPCRequest{request} 129 } 130 131 for _, request := range requests { 132 // A Notification is a Request object without an "id" member. 133 // The Server MUST NOT reply to a Notification, including those that are within a batch request. 134 if request.ID == types.JSONRPCStringID("") { 135 logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") 136 continue 137 } 138 if len(r.URL.Path) > 1 { 139 responses = append(responses, types.RPCInvalidRequestError(request.ID, errors.Errorf("path %s is invalid", r.URL.Path))) 140 continue 141 } 142 rpcFunc, ok := funcMap[request.Method] 143 if !ok || rpcFunc.ws { 144 responses = append(responses, types.RPCMethodNotFoundError(request.ID)) 145 continue 146 } 147 ctx := &types.Context{JSONReq: &request, HTTPReq: r} 148 args := []reflect.Value{reflect.ValueOf(ctx)} 149 if len(request.Params) > 0 { 150 fnArgs, err := jsonParamsToArgs(rpcFunc, cdc, request.Params) 151 if err != nil { 152 responses = append(responses, types.RPCInvalidParamsError(request.ID, errors.Wrap(err, "error converting json params to arguments"))) 153 continue 154 } 155 args = append(args, fnArgs...) 156 } 157 returns := rpcFunc.f.Call(args) 158 logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) 159 result, err := unreflectResult(returns) 160 if err != nil { 161 responses = append(responses, types.RPCInternalError(request.ID, err)) 162 continue 163 } 164 responses = append(responses, types.NewRPCSuccessResponse(cdc, request.ID, result)) 165 } 166 if len(responses) > 0 { 167 WriteRPCResponseArrayHTTP(w, responses) 168 } 169 } 170 } 171 172 func handleInvalidJSONRPCPaths(next http.HandlerFunc) http.HandlerFunc { 173 return func(w http.ResponseWriter, r *http.Request) { 174 // Since the pattern "/" matches all paths not matched by other registered patterns we check whether the path is indeed 175 // "/", otherwise return a 404 error 176 if r.URL.Path != "/" { 177 http.NotFound(w, r) 178 return 179 } 180 181 next(w, r) 182 } 183 } 184 185 func mapParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, params map[string]json.RawMessage, argsOffset int) ([]reflect.Value, error) { 186 values := make([]reflect.Value, len(rpcFunc.argNames)) 187 for i, argName := range rpcFunc.argNames { 188 argType := rpcFunc.args[i+argsOffset] 189 190 if p, ok := params[argName]; ok && p != nil && len(p) > 0 { 191 val := reflect.New(argType) 192 err := cdc.UnmarshalJSON(p, val.Interface()) 193 if err != nil { 194 return nil, err 195 } 196 values[i] = val.Elem() 197 } else { // use default for that type 198 values[i] = reflect.Zero(argType) 199 } 200 } 201 202 return values, nil 203 } 204 205 func arrayParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, params []json.RawMessage, argsOffset int) ([]reflect.Value, error) { 206 if len(rpcFunc.argNames) != len(params) { 207 return nil, errors.Errorf("expected %v parameters (%v), got %v (%v)", 208 len(rpcFunc.argNames), rpcFunc.argNames, len(params), params) 209 } 210 211 values := make([]reflect.Value, len(params)) 212 for i, p := range params { 213 argType := rpcFunc.args[i+argsOffset] 214 val := reflect.New(argType) 215 err := cdc.UnmarshalJSON(p, val.Interface()) 216 if err != nil { 217 return nil, err 218 } 219 values[i] = val.Elem() 220 } 221 return values, nil 222 } 223 224 // raw is unparsed json (from json.RawMessage) encoding either a map or an 225 // array. 226 // 227 // Example: 228 // rpcFunc.args = [rpctypes.Context string] 229 // rpcFunc.argNames = ["arg"] 230 func jsonParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, raw []byte) ([]reflect.Value, error) { 231 const argsOffset = 1 232 233 // TODO: Make more efficient, perhaps by checking the first character for '{' or '['? 234 // First, try to get the map. 235 var m map[string]json.RawMessage 236 err := json.Unmarshal(raw, &m) 237 if err == nil { 238 return mapParamsToArgs(rpcFunc, cdc, m, argsOffset) 239 } 240 241 // Otherwise, try an array. 242 var a []json.RawMessage 243 err = json.Unmarshal(raw, &a) 244 if err == nil { 245 return arrayParamsToArgs(rpcFunc, cdc, a, argsOffset) 246 } 247 248 // Otherwise, bad format, we cannot parse 249 return nil, errors.Errorf("unknown type for JSON params: %v. Expected map or array", err) 250 } 251 252 // rpc.json 253 //----------------------------------------------------------------------------- 254 // rpc.http 255 256 // convert from a function name to the http handler 257 func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func(http.ResponseWriter, *http.Request) { 258 // Exception for websocket endpoints 259 if rpcFunc.ws { 260 return func(w http.ResponseWriter, r *http.Request) { 261 WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(types.JSONRPCStringID(""))) 262 } 263 } 264 265 // All other endpoints 266 return func(w http.ResponseWriter, r *http.Request) { 267 logger.Debug("HTTP HANDLER", "req", r) 268 269 ctx := &types.Context{HTTPReq: r} 270 args := []reflect.Value{reflect.ValueOf(ctx)} 271 272 fnArgs, err := httpParamsToArgs(rpcFunc, cdc, r) 273 if err != nil { 274 WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(types.JSONRPCStringID(""), errors.Wrap(err, "error converting http params to arguments"))) 275 return 276 } 277 args = append(args, fnArgs...) 278 279 returns := rpcFunc.f.Call(args) 280 281 logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) 282 result, err := unreflectResult(returns) 283 if err != nil { 284 WriteRPCResponseHTTP(w, types.RPCInternalError(types.JSONRPCStringID(""), err)) 285 return 286 } 287 WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, types.JSONRPCStringID(""), result)) 288 } 289 } 290 291 // Covert an http query to a list of properly typed values. 292 // To be properly decoded the arg must be a concrete type from tendermint (if its an interface). 293 func httpParamsToArgs(rpcFunc *RPCFunc, cdc *amino.Codec, r *http.Request) ([]reflect.Value, error) { 294 // skip types.Context 295 const argsOffset = 1 296 297 values := make([]reflect.Value, len(rpcFunc.argNames)) 298 299 for i, name := range rpcFunc.argNames { 300 argType := rpcFunc.args[i+argsOffset] 301 302 values[i] = reflect.Zero(argType) // set default for that type 303 304 arg := GetParam(r, name) 305 // log.Notice("param to arg", "argType", argType, "name", name, "arg", arg) 306 307 if "" == arg { 308 continue 309 } 310 311 v, err, ok := nonJSONStringToArg(cdc, argType, arg) 312 if err != nil { 313 return nil, err 314 } 315 if ok { 316 values[i] = v 317 continue 318 } 319 320 values[i], err = jsonStringToArg(cdc, argType, arg) 321 if err != nil { 322 return nil, err 323 } 324 } 325 326 return values, nil 327 } 328 329 func jsonStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error) { 330 rv := reflect.New(rt) 331 err := cdc.UnmarshalJSON([]byte(arg), rv.Interface()) 332 if err != nil { 333 return rv, err 334 } 335 rv = rv.Elem() 336 return rv, nil 337 } 338 339 func nonJSONStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error, bool) { 340 if rt.Kind() == reflect.Ptr { 341 rv_, err, ok := nonJSONStringToArg(cdc, rt.Elem(), arg) 342 if err != nil { 343 return reflect.Value{}, err, false 344 } else if ok { 345 rv := reflect.New(rt.Elem()) 346 rv.Elem().Set(rv_) 347 return rv, nil, true 348 } else { 349 return reflect.Value{}, nil, false 350 } 351 } else { 352 return _nonJSONStringToArg(cdc, rt, arg) 353 } 354 } 355 356 // NOTE: rt.Kind() isn't a pointer. 357 func _nonJSONStringToArg(cdc *amino.Codec, rt reflect.Type, arg string) (reflect.Value, error, bool) { 358 isIntString := RE_INT.Match([]byte(arg)) 359 isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) 360 isHexString := strings.HasPrefix(strings.ToLower(arg), "0x") 361 362 var expectingString, expectingByteSlice, expectingInt bool 363 switch rt.Kind() { 364 case reflect.Int, reflect.Uint, reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64: 365 expectingInt = true 366 case reflect.String: 367 expectingString = true 368 case reflect.Slice: 369 expectingByteSlice = rt.Elem().Kind() == reflect.Uint8 370 } 371 372 if isIntString && expectingInt { 373 qarg := `"` + arg + `"` 374 // jsonStringToArg 375 rv, err := jsonStringToArg(cdc, rt, qarg) 376 if err != nil { 377 return rv, err, false 378 } else { 379 return rv, nil, true 380 } 381 } 382 383 if isHexString { 384 if !expectingString && !expectingByteSlice { 385 err := errors.Errorf("got a hex string arg, but expected '%s'", 386 rt.Kind().String()) 387 return reflect.ValueOf(nil), err, false 388 } 389 390 var value []byte 391 value, err := hex.DecodeString(arg[2:]) 392 if err != nil { 393 return reflect.ValueOf(nil), err, false 394 } 395 if rt.Kind() == reflect.String { 396 return reflect.ValueOf(string(value)), nil, true 397 } 398 return reflect.ValueOf([]byte(value)), nil, true 399 } 400 401 if isQuotedString && expectingByteSlice { 402 v := reflect.New(reflect.TypeOf("")) 403 err := cdc.UnmarshalJSON([]byte(arg), v.Interface()) 404 if err != nil { 405 return reflect.ValueOf(nil), err, false 406 } 407 v = v.Elem() 408 return reflect.ValueOf([]byte(v.String())), nil, true 409 } 410 411 return reflect.ValueOf(nil), nil, false 412 } 413 414 // rpc.http 415 //----------------------------------------------------------------------------- 416 // rpc.websocket 417 418 const ( 419 defaultWSWriteChanCapacity = 1000 420 defaultWSWriteWait = 10 * time.Second 421 defaultWSReadWait = 30 * time.Second 422 defaultWSPingPeriod = (defaultWSReadWait * 9) / 10 423 ) 424 425 // A single websocket connection contains listener id, underlying ws 426 // connection, and the event switch for subscribing to events. 427 // 428 // In case of an error, the connection is stopped. 429 type wsConnection struct { 430 cmn.BaseService 431 432 remoteAddr string 433 baseConn *websocket.Conn 434 writeChan chan types.RPCResponse 435 436 funcMap map[string]*RPCFunc 437 cdc *amino.Codec 438 439 // write channel capacity 440 writeChanCapacity int 441 442 // each write times out after this. 443 writeWait time.Duration 444 445 // Connection times out if we haven't received *anything* in this long, not even pings. 446 readWait time.Duration 447 448 // Send pings to server with this period. Must be less than readWait, but greater than zero. 449 pingPeriod time.Duration 450 451 // callback which is called upon disconnect 452 onDisconnect func(remoteAddr string) 453 454 ctx context.Context 455 cancel context.CancelFunc 456 } 457 458 // NewWSConnection wraps websocket.Conn. 459 // 460 // See the commentary on the func(*wsConnection) functions for a detailed 461 // description of how to configure ping period and pong wait time. NOTE: if the 462 // write buffer is full, pongs may be dropped, which may cause clients to 463 // disconnect. see https://github.com/gorilla/websocket/issues/97 464 func NewWSConnection( 465 baseConn *websocket.Conn, 466 funcMap map[string]*RPCFunc, 467 cdc *amino.Codec, 468 options ...func(*wsConnection), 469 ) *wsConnection { 470 baseConn.SetReadLimit(maxBodyBytes) 471 wsc := &wsConnection{ 472 remoteAddr: baseConn.RemoteAddr().String(), 473 baseConn: baseConn, 474 funcMap: funcMap, 475 cdc: cdc, 476 writeWait: defaultWSWriteWait, 477 writeChanCapacity: defaultWSWriteChanCapacity, 478 readWait: defaultWSReadWait, 479 pingPeriod: defaultWSPingPeriod, 480 } 481 for _, option := range options { 482 option(wsc) 483 } 484 wsc.BaseService = *cmn.NewBaseService(nil, "wsConnection", wsc) 485 return wsc 486 } 487 488 // OnDisconnect sets a callback which is used upon disconnect - not 489 // Goroutine-safe. Nop by default. 490 func OnDisconnect(onDisconnect func(remoteAddr string)) func(*wsConnection) { 491 return func(wsc *wsConnection) { 492 wsc.onDisconnect = onDisconnect 493 } 494 } 495 496 // WriteWait sets the amount of time to wait before a websocket write times out. 497 // It should only be used in the constructor - not Goroutine-safe. 498 func WriteWait(writeWait time.Duration) func(*wsConnection) { 499 return func(wsc *wsConnection) { 500 wsc.writeWait = writeWait 501 } 502 } 503 504 // WriteChanCapacity sets the capacity of the websocket write channel. 505 // It should only be used in the constructor - not Goroutine-safe. 506 func WriteChanCapacity(cap int) func(*wsConnection) { 507 return func(wsc *wsConnection) { 508 wsc.writeChanCapacity = cap 509 } 510 } 511 512 // ReadWait sets the amount of time to wait before a websocket read times out. 513 // It should only be used in the constructor - not Goroutine-safe. 514 func ReadWait(readWait time.Duration) func(*wsConnection) { 515 return func(wsc *wsConnection) { 516 wsc.readWait = readWait 517 } 518 } 519 520 // PingPeriod sets the duration for sending websocket pings. 521 // It should only be used in the constructor - not Goroutine-safe. 522 func PingPeriod(pingPeriod time.Duration) func(*wsConnection) { 523 return func(wsc *wsConnection) { 524 wsc.pingPeriod = pingPeriod 525 } 526 } 527 528 // OnStart implements cmn.Service by starting the read and write routines. It 529 // blocks until the connection closes. 530 func (wsc *wsConnection) OnStart() error { 531 wsc.writeChan = make(chan types.RPCResponse, wsc.writeChanCapacity) 532 533 // Read subscriptions/unsubscriptions to events 534 go wsc.readRoutine() 535 // Write responses, BLOCKING. 536 wsc.writeRoutine() 537 538 return nil 539 } 540 541 // OnStop implements cmn.Service by unsubscribing remoteAddr from all subscriptions. 542 func (wsc *wsConnection) OnStop() { 543 // Both read and write loops close the websocket connection when they exit their loops. 544 // The writeChan is never closed, to allow WriteRPCResponse() to fail. 545 546 if wsc.onDisconnect != nil { 547 wsc.onDisconnect(wsc.remoteAddr) 548 } 549 550 if wsc.ctx != nil { 551 wsc.cancel() 552 } 553 } 554 555 // GetRemoteAddr returns the remote address of the underlying connection. 556 // It implements WSRPCConnection 557 func (wsc *wsConnection) GetRemoteAddr() string { 558 return wsc.remoteAddr 559 } 560 561 // WriteRPCResponse pushes a response to the writeChan, and blocks until it is accepted. 562 // It implements WSRPCConnection. It is Goroutine-safe. 563 func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) { 564 select { 565 case <-wsc.Quit(): 566 return 567 case wsc.writeChan <- resp: 568 } 569 } 570 571 // TryWriteRPCResponse attempts to push a response to the writeChan, but does not block. 572 // It implements WSRPCConnection. It is Goroutine-safe 573 func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool { 574 select { 575 case <-wsc.Quit(): 576 return false 577 case wsc.writeChan <- resp: 578 return true 579 default: 580 return false 581 } 582 } 583 584 // Codec returns an amino codec used to decode parameters and encode results. 585 // It implements WSRPCConnection. 586 func (wsc *wsConnection) Codec() *amino.Codec { 587 return wsc.cdc 588 } 589 590 // Context returns the connection's context. 591 // The context is canceled when the client's connection closes. 592 func (wsc *wsConnection) Context() context.Context { 593 if wsc.ctx != nil { 594 return wsc.ctx 595 } 596 wsc.ctx, wsc.cancel = context.WithCancel(context.Background()) 597 return wsc.ctx 598 } 599 600 // Read from the socket and subscribe to or unsubscribe from events 601 func (wsc *wsConnection) readRoutine() { 602 defer func() { 603 if r := recover(); r != nil { 604 err, ok := r.(error) 605 if !ok { 606 err = fmt.Errorf("WSJSONRPC: %v", r) 607 } 608 wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack())) 609 wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCStringID("unknown"), err)) 610 go wsc.readRoutine() 611 } else { 612 wsc.baseConn.Close() // nolint: errcheck 613 } 614 }() 615 616 wsc.baseConn.SetPongHandler(func(m string) error { 617 return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)) 618 }) 619 620 for { 621 select { 622 case <-wsc.Quit(): 623 return 624 default: 625 // reset deadline for every type of message (control or data) 626 if err := wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)); err != nil { 627 wsc.Logger.Error("failed to set read deadline", "err", err) 628 } 629 var in []byte 630 _, in, err := wsc.baseConn.ReadMessage() 631 if err != nil { 632 if websocket.IsCloseError(err, websocket.CloseNormalClosure) { 633 wsc.Logger.Info("Client closed the connection") 634 } else { 635 wsc.Logger.Error("Failed to read request", "err", err) 636 } 637 wsc.Stop() 638 return 639 } 640 641 var request types.RPCRequest 642 err = json.Unmarshal(in, &request) 643 if err != nil { 644 wsc.WriteRPCResponse(types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "error unmarshaling request"))) 645 continue 646 } 647 648 // A Notification is a Request object without an "id" member. 649 // The Server MUST NOT reply to a Notification, including those that are within a batch request. 650 if request.ID == types.JSONRPCStringID("") { 651 wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") 652 continue 653 } 654 655 // Now, fetch the RPCFunc and execute it. 656 rpcFunc := wsc.funcMap[request.Method] 657 if rpcFunc == nil { 658 wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID)) 659 continue 660 } 661 662 ctx := &types.Context{JSONReq: &request, WSConn: wsc} 663 args := []reflect.Value{reflect.ValueOf(ctx)} 664 if len(request.Params) > 0 { 665 fnArgs, err := jsonParamsToArgs(rpcFunc, wsc.cdc, request.Params) 666 if err != nil { 667 wsc.WriteRPCResponse(types.RPCInternalError(request.ID, errors.Wrap(err, "error converting json params to arguments"))) 668 continue 669 } 670 args = append(args, fnArgs...) 671 } 672 673 returns := rpcFunc.f.Call(args) 674 675 // TODO: Need to encode args/returns to string if we want to log them 676 wsc.Logger.Info("WSJSONRPC", "method", request.Method) 677 678 result, err := unreflectResult(returns) 679 if err != nil { 680 wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err)) 681 continue 682 } 683 684 wsc.WriteRPCResponse(types.NewRPCSuccessResponse(wsc.cdc, request.ID, result)) 685 } 686 } 687 } 688 689 // receives on a write channel and writes out on the socket 690 func (wsc *wsConnection) writeRoutine() { 691 pingTicker := time.NewTicker(wsc.pingPeriod) 692 defer func() { 693 pingTicker.Stop() 694 if err := wsc.baseConn.Close(); err != nil { 695 wsc.Logger.Error("Error closing connection", "err", err) 696 } 697 }() 698 699 // https://github.com/gorilla/websocket/issues/97 700 pongs := make(chan string, 1) 701 wsc.baseConn.SetPingHandler(func(m string) error { 702 select { 703 case pongs <- m: 704 default: 705 } 706 return nil 707 }) 708 709 for { 710 select { 711 case m := <-pongs: 712 err := wsc.writeMessageWithDeadline(websocket.PongMessage, []byte(m)) 713 if err != nil { 714 wsc.Logger.Info("Failed to write pong (client may disconnect)", "err", err) 715 } 716 case <-pingTicker.C: 717 err := wsc.writeMessageWithDeadline(websocket.PingMessage, []byte{}) 718 if err != nil { 719 wsc.Logger.Error("Failed to write ping", "err", err) 720 wsc.Stop() 721 return 722 } 723 case msg := <-wsc.writeChan: 724 jsonBytes, err := json.MarshalIndent(msg, "", " ") 725 if err != nil { 726 wsc.Logger.Error("Failed to marshal RPCResponse to JSON", "err", err) 727 } else { 728 if err = wsc.writeMessageWithDeadline(websocket.TextMessage, jsonBytes); err != nil { 729 wsc.Logger.Error("Failed to write response", "err", err) 730 wsc.Stop() 731 return 732 } 733 } 734 case <-wsc.Quit(): 735 return 736 } 737 } 738 } 739 740 // All writes to the websocket must (re)set the write deadline. 741 // If some writes don't set it while others do, they may timeout incorrectly (https://github.com/evdatsion/aphelion-dpos-bft/issues/553) 742 func (wsc *wsConnection) writeMessageWithDeadline(msgType int, msg []byte) error { 743 if err := wsc.baseConn.SetWriteDeadline(time.Now().Add(wsc.writeWait)); err != nil { 744 return err 745 } 746 return wsc.baseConn.WriteMessage(msgType, msg) 747 } 748 749 //---------------------------------------- 750 751 // WebsocketManager provides a WS handler for incoming connections and passes a 752 // map of functions along with any additional params to new connections. 753 // NOTE: The websocket path is defined externally, e.g. in node/node.go 754 type WebsocketManager struct { 755 websocket.Upgrader 756 757 funcMap map[string]*RPCFunc 758 cdc *amino.Codec 759 logger log.Logger 760 wsConnOptions []func(*wsConnection) 761 } 762 763 // NewWebsocketManager returns a new WebsocketManager that passes a map of 764 // functions, connection options and logger to new WS connections. 765 func NewWebsocketManager(funcMap map[string]*RPCFunc, cdc *amino.Codec, wsConnOptions ...func(*wsConnection)) *WebsocketManager { 766 return &WebsocketManager{ 767 funcMap: funcMap, 768 cdc: cdc, 769 Upgrader: websocket.Upgrader{ 770 CheckOrigin: func(r *http.Request) bool { 771 // TODO ??? 772 return true 773 }, 774 }, 775 logger: log.NewNopLogger(), 776 wsConnOptions: wsConnOptions, 777 } 778 } 779 780 // SetLogger sets the logger. 781 func (wm *WebsocketManager) SetLogger(l log.Logger) { 782 wm.logger = l 783 } 784 785 // WebsocketHandler upgrades the request/response (via http.Hijack) and starts 786 // the wsConnection. 787 func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) { 788 wsConn, err := wm.Upgrade(w, r, nil) 789 if err != nil { 790 // TODO - return http error 791 wm.logger.Error("Failed to upgrade to websocket connection", "err", err) 792 return 793 } 794 795 // register connection 796 con := NewWSConnection(wsConn, wm.funcMap, wm.cdc, wm.wsConnOptions...) 797 con.SetLogger(wm.logger.With("remote", wsConn.RemoteAddr())) 798 wm.logger.Info("New websocket connection", "remote", con.remoteAddr) 799 err = con.Start() // Blocking 800 if err != nil { 801 wm.logger.Error("Error starting connection", "err", err) 802 } 803 } 804 805 // rpc.websocket 806 //----------------------------------------------------------------------------- 807 808 // NOTE: assume returns is result struct and error. If error is not nil, return it 809 func unreflectResult(returns []reflect.Value) (interface{}, error) { 810 errV := returns[1] 811 if errV.Interface() != nil { 812 return nil, errors.Errorf("%v", errV.Interface()) 813 } 814 rv := returns[0] 815 // the result is a registered interface, 816 // we need a pointer to it so we can marshal with type byte 817 rvp := reflect.New(rv.Type()) 818 rvp.Elem().Set(rv) 819 return rvp.Interface(), nil 820 } 821 822 // writes a list of available rpc endpoints as an html page 823 func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) { 824 noArgNames := []string{} 825 argNames := []string{} 826 for name, funcData := range funcMap { 827 if len(funcData.args) == 0 { 828 noArgNames = append(noArgNames, name) 829 } else { 830 argNames = append(argNames, name) 831 } 832 } 833 sort.Strings(noArgNames) 834 sort.Strings(argNames) 835 buf := new(bytes.Buffer) 836 buf.WriteString("<html><body>") 837 buf.WriteString("<br>Available endpoints:<br>") 838 839 for _, name := range noArgNames { 840 link := fmt.Sprintf("//%s/%s", r.Host, name) 841 buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link)) 842 } 843 844 buf.WriteString("<br>Endpoints that require arguments:<br>") 845 for _, name := range argNames { 846 link := fmt.Sprintf("//%s/%s?", r.Host, name) 847 funcData := funcMap[name] 848 for i, argName := range funcData.argNames { 849 link += argName + "=_" 850 if i < len(funcData.argNames)-1 { 851 link += "&" 852 } 853 } 854 buf.WriteString(fmt.Sprintf("<a href=\"%s\">%s</a></br>", link, link)) 855 } 856 buf.WriteString("</body></html>") 857 w.Header().Set("Content-Type", "text/html") 858 w.WriteHeader(200) 859 w.Write(buf.Bytes()) // nolint: errcheck 860 }