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