github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/rpc/jsonrpc/server/ws_handler.go (about) 1 package server 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "reflect" 9 "runtime/debug" 10 "time" 11 12 "github.com/gorilla/websocket" 13 14 "github.com/tendermint/tendermint/libs/log" 15 "github.com/tendermint/tendermint/libs/service" 16 types "github.com/tendermint/tendermint/rpc/jsonrpc/types" 17 ) 18 19 /////////////////////////////////////////////////////////////////////////////// 20 // WebSocket handler 21 /////////////////////////////////////////////////////////////////////////////// 22 23 const ( 24 defaultWSWriteChanCapacity = 1000 25 defaultWSWriteWait = 10 * time.Second 26 defaultWSReadWait = 30 * time.Second 27 defaultWSPingPeriod = (defaultWSReadWait * 9) / 10 28 ) 29 30 // WebsocketManager provides a WS handler for incoming connections and passes a 31 // map of functions along with any additional params to new connections. 32 // NOTE: The websocket path is defined externally, e.g. in node/node.go 33 type WebsocketManager struct { 34 websocket.Upgrader 35 36 funcMap map[string]*RPCFunc 37 logger log.Logger 38 wsConnOptions []func(*wsConnection) 39 } 40 41 // NewWebsocketManager returns a new WebsocketManager that passes a map of 42 // functions, connection options and logger to new WS connections. 43 func NewWebsocketManager( 44 funcMap map[string]*RPCFunc, 45 wsConnOptions ...func(*wsConnection), 46 ) *WebsocketManager { 47 return &WebsocketManager{ 48 funcMap: funcMap, 49 Upgrader: websocket.Upgrader{ 50 CheckOrigin: func(r *http.Request) bool { 51 // TODO ??? 52 // 53 // The default behaviour would be relevant to browser-based clients, 54 // afaik. I suppose having a pass-through is a workaround for allowing 55 // for more complex security schemes, shifting the burden of 56 // AuthN/AuthZ outside the Tendermint RPC. 57 // I can't think of other uses right now that would warrant a TODO 58 // though. The real backstory of this TODO shall remain shrouded in 59 // mystery 60 return true 61 }, 62 }, 63 logger: log.NewNopLogger(), 64 wsConnOptions: wsConnOptions, 65 } 66 } 67 68 // SetLogger sets the logger. 69 func (wm *WebsocketManager) SetLogger(l log.Logger) { 70 wm.logger = l 71 } 72 73 // WebsocketHandler upgrades the request/response (via http.Hijack) and starts 74 // the wsConnection. 75 func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) { 76 wsConn, err := wm.Upgrade(w, r, nil) 77 if err != nil { 78 // TODO - return http error 79 wm.logger.Error("Failed to upgrade connection", "err", err) 80 return 81 } 82 defer func() { 83 if err := wsConn.Close(); err != nil { 84 wm.logger.Error("Failed to close connection", "err", err) 85 } 86 }() 87 88 // register connection 89 con := newWSConnection(wsConn, wm.funcMap, wm.wsConnOptions...) 90 con.SetLogger(wm.logger.With("remote", wsConn.RemoteAddr())) 91 wm.logger.Info("New websocket connection", "remote", con.remoteAddr) 92 err = con.Start() // BLOCKING 93 if err != nil { 94 wm.logger.Error("Failed to start connection", "err", err) 95 return 96 } 97 con.Stop() 98 } 99 100 /////////////////////////////////////////////////////////////////////////////// 101 // WebSocket connection 102 /////////////////////////////////////////////////////////////////////////////// 103 104 // A single websocket connection contains listener id, underlying ws 105 // connection, and the event switch for subscribing to events. 106 // 107 // In case of an error, the connection is stopped. 108 type wsConnection struct { 109 service.BaseService 110 111 remoteAddr string 112 baseConn *websocket.Conn 113 // writeChan is never closed, to allow WriteRPCResponse() to fail. 114 writeChan chan types.RPCResponse 115 116 // chan, which is closed when/if readRoutine errors 117 // used to abort writeRoutine 118 readRoutineQuit chan struct{} 119 120 funcMap map[string]*RPCFunc 121 122 // write channel capacity 123 writeChanCapacity int 124 125 // each write times out after this. 126 writeWait time.Duration 127 128 // Connection times out if we haven't received *anything* in this long, not even pings. 129 readWait time.Duration 130 131 // Send pings to server with this period. Must be less than readWait, but greater than zero. 132 pingPeriod time.Duration 133 134 // Maximum message size. 135 readLimit int64 136 137 // callback which is called upon disconnect 138 onDisconnect func(remoteAddr string) 139 140 ctx context.Context 141 cancel context.CancelFunc 142 } 143 144 // NewWSConnection wraps websocket.Conn. 145 // 146 // See the commentary on the func(*wsConnection) functions for a detailed 147 // description of how to configure ping period and pong wait time. NOTE: if the 148 // write buffer is full, pongs may be dropped, which may cause clients to 149 // disconnect. see https://github.com/gorilla/websocket/issues/97 150 func newWSConnection( 151 baseConn *websocket.Conn, 152 funcMap map[string]*RPCFunc, 153 options ...func(*wsConnection), 154 ) *wsConnection { 155 wsc := &wsConnection{ 156 remoteAddr: baseConn.RemoteAddr().String(), 157 baseConn: baseConn, 158 funcMap: funcMap, 159 writeWait: defaultWSWriteWait, 160 writeChanCapacity: defaultWSWriteChanCapacity, 161 readWait: defaultWSReadWait, 162 pingPeriod: defaultWSPingPeriod, 163 readRoutineQuit: make(chan struct{}), 164 } 165 for _, option := range options { 166 option(wsc) 167 } 168 wsc.baseConn.SetReadLimit(wsc.readLimit) 169 wsc.BaseService = *service.NewBaseService(nil, "wsConnection", wsc) 170 return wsc 171 } 172 173 // OnDisconnect sets a callback which is used upon disconnect - not 174 // Goroutine-safe. Nop by default. 175 func OnDisconnect(onDisconnect func(remoteAddr string)) func(*wsConnection) { 176 return func(wsc *wsConnection) { 177 wsc.onDisconnect = onDisconnect 178 } 179 } 180 181 // WriteWait sets the amount of time to wait before a websocket write times out. 182 // It should only be used in the constructor - not Goroutine-safe. 183 func WriteWait(writeWait time.Duration) func(*wsConnection) { 184 return func(wsc *wsConnection) { 185 wsc.writeWait = writeWait 186 } 187 } 188 189 // WriteChanCapacity sets the capacity of the websocket write channel. 190 // It should only be used in the constructor - not Goroutine-safe. 191 func WriteChanCapacity(cap int) func(*wsConnection) { 192 return func(wsc *wsConnection) { 193 wsc.writeChanCapacity = cap 194 } 195 } 196 197 // ReadWait sets the amount of time to wait before a websocket read times out. 198 // It should only be used in the constructor - not Goroutine-safe. 199 func ReadWait(readWait time.Duration) func(*wsConnection) { 200 return func(wsc *wsConnection) { 201 wsc.readWait = readWait 202 } 203 } 204 205 // PingPeriod sets the duration for sending websocket pings. 206 // It should only be used in the constructor - not Goroutine-safe. 207 func PingPeriod(pingPeriod time.Duration) func(*wsConnection) { 208 return func(wsc *wsConnection) { 209 wsc.pingPeriod = pingPeriod 210 } 211 } 212 213 // ReadLimit sets the maximum size for reading message. 214 // It should only be used in the constructor - not Goroutine-safe. 215 func ReadLimit(readLimit int64) func(*wsConnection) { 216 return func(wsc *wsConnection) { 217 wsc.readLimit = readLimit 218 } 219 } 220 221 // OnStart implements service.Service by starting the read and write routines. It 222 // blocks until there's some error. 223 func (wsc *wsConnection) OnStart() error { 224 wsc.writeChan = make(chan types.RPCResponse, wsc.writeChanCapacity) 225 226 // Read subscriptions/unsubscriptions to events 227 go wsc.readRoutine() 228 // Write responses, BLOCKING. 229 wsc.writeRoutine() 230 231 return nil 232 } 233 234 // OnStop implements service.Service by unsubscribing remoteAddr from all 235 // subscriptions. 236 func (wsc *wsConnection) OnStop() { 237 if wsc.onDisconnect != nil { 238 wsc.onDisconnect(wsc.remoteAddr) 239 } 240 241 if wsc.ctx != nil { 242 wsc.cancel() 243 } 244 } 245 246 // GetRemoteAddr returns the remote address of the underlying connection. 247 // It implements WSRPCConnection 248 func (wsc *wsConnection) GetRemoteAddr() string { 249 return wsc.remoteAddr 250 } 251 252 // WriteRPCResponse pushes a response to the writeChan, and blocks until it is accepted. 253 // It implements WSRPCConnection. It is Goroutine-safe. 254 func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) { 255 select { 256 case <-wsc.Quit(): 257 return 258 case wsc.writeChan <- resp: 259 } 260 } 261 262 // TryWriteRPCResponse attempts to push a response to the writeChan, but does not block. 263 // It implements WSRPCConnection. It is Goroutine-safe 264 func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool { 265 select { 266 case <-wsc.Quit(): 267 return false 268 case wsc.writeChan <- resp: 269 return true 270 default: 271 return false 272 } 273 } 274 275 // Context returns the connection's context. 276 // The context is canceled when the client's connection closes. 277 func (wsc *wsConnection) Context() context.Context { 278 if wsc.ctx != nil { 279 return wsc.ctx 280 } 281 wsc.ctx, wsc.cancel = context.WithCancel(context.Background()) 282 return wsc.ctx 283 } 284 285 // Read from the socket and subscribe to or unsubscribe from events 286 func (wsc *wsConnection) readRoutine() { 287 defer func() { 288 if r := recover(); r != nil { 289 err, ok := r.(error) 290 if !ok { 291 err = fmt.Errorf("WSJSONRPC: %v", r) 292 } 293 wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack())) 294 wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCIntID(-1), err)) 295 go wsc.readRoutine() 296 } 297 }() 298 299 wsc.baseConn.SetPongHandler(func(m string) error { 300 return wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)) 301 }) 302 303 for { 304 select { 305 case <-wsc.Quit(): 306 return 307 default: 308 // reset deadline for every type of message (control or data) 309 if err := wsc.baseConn.SetReadDeadline(time.Now().Add(wsc.readWait)); err != nil { 310 wsc.Logger.Error("failed to set read deadline", "err", err) 311 } 312 var in []byte 313 _, in, err := wsc.baseConn.ReadMessage() 314 if err != nil { 315 if websocket.IsCloseError(err, websocket.CloseNormalClosure) { 316 wsc.Logger.Info("Client closed the connection") 317 } else { 318 wsc.Logger.Error("Failed to read request", "err", err) 319 } 320 wsc.Stop() 321 close(wsc.readRoutineQuit) 322 return 323 } 324 325 var request types.RPCRequest 326 err = json.Unmarshal(in, &request) 327 if err != nil { 328 wsc.WriteRPCResponse(types.RPCParseError(fmt.Errorf("error unmarshaling request: %w", err))) 329 continue 330 } 331 332 // A Notification is a Request object without an "id" member. 333 // The Server MUST NOT reply to a Notification, including those that are within a batch request. 334 if request.ID == nil { 335 wsc.Logger.Debug( 336 "WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)", 337 "req", request, 338 ) 339 continue 340 } 341 342 // Now, fetch the RPCFunc and execute it. 343 rpcFunc := wsc.funcMap[request.Method] 344 if rpcFunc == nil { 345 wsc.WriteRPCResponse(types.RPCMethodNotFoundError(request.ID)) 346 continue 347 } 348 349 ctx := &types.Context{JSONReq: &request, WSConn: wsc} 350 args := []reflect.Value{reflect.ValueOf(ctx)} 351 if len(request.Params) > 0 { 352 fnArgs, err := jsonParamsToArgs(rpcFunc, request.Params) 353 if err != nil { 354 wsc.WriteRPCResponse( 355 types.RPCInternalError(request.ID, fmt.Errorf("error converting json params to arguments: %w", err)), 356 ) 357 continue 358 } 359 args = append(args, fnArgs...) 360 } 361 362 returns := rpcFunc.f.Call(args) 363 364 // TODO: Need to encode args/returns to string if we want to log them 365 wsc.Logger.Info("WSJSONRPC", "method", request.Method) 366 367 result, err := unreflectResult(returns) 368 if err != nil { 369 wsc.WriteRPCResponse(types.RPCInternalError(request.ID, err)) 370 continue 371 } 372 373 wsc.WriteRPCResponse(types.NewRPCSuccessResponse(request.ID, result)) 374 } 375 } 376 } 377 378 // receives on a write channel and writes out on the socket 379 func (wsc *wsConnection) writeRoutine() { 380 pingTicker := time.NewTicker(wsc.pingPeriod) 381 defer func() { 382 pingTicker.Stop() 383 }() 384 385 // https://github.com/gorilla/websocket/issues/97 386 pongs := make(chan string, 1) 387 wsc.baseConn.SetPingHandler(func(m string) error { 388 select { 389 case pongs <- m: 390 default: 391 } 392 return nil 393 }) 394 395 for { 396 select { 397 case <-wsc.Quit(): 398 return 399 case <-wsc.readRoutineQuit: // error in readRoutine 400 return 401 case m := <-pongs: 402 err := wsc.writeMessageWithDeadline(websocket.PongMessage, []byte(m)) 403 if err != nil { 404 wsc.Logger.Info("Failed to write pong (client may disconnect)", "err", err) 405 } 406 case <-pingTicker.C: 407 err := wsc.writeMessageWithDeadline(websocket.PingMessage, []byte{}) 408 if err != nil { 409 wsc.Logger.Error("Failed to write ping", "err", err) 410 return 411 } 412 case msg := <-wsc.writeChan: 413 jsonBytes, err := json.MarshalIndent(msg, "", " ") 414 if err != nil { 415 wsc.Logger.Error("Failed to marshal RPCResponse to JSON", "err", err) 416 } else if err = wsc.writeMessageWithDeadline(websocket.TextMessage, jsonBytes); err != nil { 417 wsc.Logger.Error("Failed to write response", "msg", msg, "err", err) 418 return 419 } 420 } 421 } 422 } 423 424 // All writes to the websocket must (re)set the write deadline. 425 // If some writes don't set it while others do, they may timeout incorrectly 426 // (https://github.com/tendermint/tendermint/issues/553) 427 func (wsc *wsConnection) writeMessageWithDeadline(msgType int, msg []byte) error { 428 if err := wsc.baseConn.SetWriteDeadline(time.Now().Add(wsc.writeWait)); err != nil { 429 return err 430 } 431 return wsc.baseConn.WriteMessage(msgType, msg) 432 }