github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/integration/messagebus/glue/socket.go (about) 1 /* 2 * Glue - Robust Go and Javascript Socket Library 3 * Copyright (C) 2015 Roland Singer <roland.singer[at]desertbit.com> 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 package glue 20 21 import ( 22 "encoding/json" 23 "errors" 24 "fmt" 25 "runtime/debug" 26 "sync" 27 "time" 28 29 "github.com/blang/semver" 30 "github.com/mdaxf/iac/integration/messagebus/glue/backend" 31 "github.com/mdaxf/iac/integration/messagebus/glue/log" 32 "github.com/mdaxf/iac/integration/messagebus/glue/utils" 33 "github.com/sirupsen/logrus" 34 ) 35 36 //#################// 37 //### Constants ###// 38 //#################// 39 40 // Public 41 // ###### 42 const ( 43 // Version holds the Glue Socket Protocol Version as string. 44 // This project follows the Semantic Versioning (http://semver.org/). 45 Version = "1.9.1" 46 ) 47 48 // Private 49 // ####### 50 const ( 51 // The constant length of the random socket ID. 52 socketIDLength = 20 53 54 // Send pings to the peer with this period. 55 pingPeriod = 30 * time.Second 56 57 // Kill the socket after this timeout. 58 pingResponseTimeout = 7 * time.Second 59 60 // The main channel name. 61 mainChannelName = "m" 62 63 // Socket commands. Must be two character long. 64 // ############################################ 65 cmdLen = 2 66 cmdInit = "in" 67 cmdPing = "pi" 68 cmdPong = "po" 69 cmdClose = "cl" 70 cmdInvalid = "iv" 71 cmdDontAutoReconnect = "dr" 72 cmdChannelData = "cd" 73 ) 74 75 //#################// 76 //### Variables ###// 77 //#################// 78 79 // Public errors: 80 var ( 81 ErrSocketClosed = errors.New("the socket connection is closed") 82 ErrReadTimeout = errors.New("the read timeout was reached") 83 ) 84 85 // Private 86 var ( 87 serverVersion semver.Version 88 ) 89 90 //####################// 91 //### Public Types ###// 92 //####################// 93 94 // ClosedChan is a channel which doesn't block as soon as the socket is closed. 95 type ClosedChan <-chan struct{} 96 97 // OnCloseFunc is an event function. 98 type OnCloseFunc func() 99 100 // OnReadFunc is an event function. 101 type OnReadFunc func(data string) 102 103 //#####################// 104 //### Private Types ###// 105 //#####################// 106 107 type initData struct { 108 SocketID string `json:"socketID"` 109 } 110 111 type clientInitData struct { 112 Version string `json:"version"` 113 } 114 115 //###################// 116 //### Socket Type ###// 117 //###################// 118 119 // A Socket represents a single socket connections to a client. 120 type Socket struct { 121 // A Value is a placeholder for custom data. 122 // Use this to attach socket specific data. 123 Value interface{} 124 125 // Private 126 // ####### 127 server *Server 128 bs backend.BackendSocket 129 130 id string // Unique socket ID. 131 isInitialized bool 132 133 channels *channels 134 mainChannel *Channel 135 136 writeChan chan string 137 readChan chan string 138 isClosedChan ClosedChan 139 140 pingTimer *time.Timer 141 pingTimeout *time.Timer 142 sendPingMutex sync.Mutex 143 pingRequestActive bool 144 } 145 146 // newSocket creates a new socket and initializes it. 147 func newSocket(server *Server, bs backend.BackendSocket) *Socket { 148 // Create a new socket value. 149 s := &Socket{ 150 server: server, 151 bs: bs, 152 153 id: utils.RandomString(socketIDLength), 154 channels: newChannels(), 155 156 writeChan: bs.WriteChan(), 157 readChan: bs.ReadChan(), 158 isClosedChan: bs.ClosedChan(), 159 160 pingTimer: time.NewTimer(pingPeriod), 161 pingTimeout: time.NewTimer(pingResponseTimeout), 162 } 163 164 // Create the main channel. 165 s.mainChannel = s.Channel(mainChannelName) 166 167 // Call the on close method as soon as the socket closes. 168 go func() { 169 <-s.isClosedChan 170 s.onClose() 171 }() 172 173 // Stop the timeout again. It will be started by the ping timer. 174 s.pingTimeout.Stop() 175 176 // Add the new socket to the active sockets map. 177 // If the ID is already present, then generate a new one. 178 func() { 179 // Lock the mutex. 180 s.server.socketsMutex.Lock() 181 defer s.server.socketsMutex.Unlock() 182 183 // Be sure that the ID is unique. 184 for { 185 if _, ok := s.server.sockets[s.id]; !ok { 186 break 187 } 188 189 s.id = utils.RandomString(socketIDLength) 190 } 191 192 // Add the socket to the map. 193 s.server.sockets[s.id] = s 194 }() 195 196 // Start the loops and handlers in new goroutines. 197 go s.pingTimeoutHandler() 198 go s.readLoop() 199 go s.pingLoop() 200 201 return s 202 } 203 204 // ID returns the socket's unique ID. 205 // This is a cryptographically secure pseudorandom number. 206 func (s *Socket) ID() string { 207 return s.id 208 } 209 210 // IsInitialized returns a boolean indicating if a socket is initialized 211 // and ready to be used. This flag is set to true after the OnNewSocket function 212 // has returned for this socket. 213 func (s *Socket) IsInitialized() bool { 214 return s.isInitialized 215 } 216 217 // RemoteAddr returns the remote address of the client. 218 func (s *Socket) RemoteAddr() string { 219 return s.bs.RemoteAddr() 220 } 221 222 // UserAgent returns the user agent of the client. 223 func (s *Socket) UserAgent() string { 224 return s.bs.UserAgent() 225 } 226 227 // Close the socket connection. 228 func (s *Socket) Close() { 229 s.bs.Close() 230 } 231 232 // IsClosed returns a boolean whenever the connection is closed. 233 func (s *Socket) IsClosed() bool { 234 return s.bs.IsClosed() 235 } 236 237 // OnClose sets the functions which is triggered if the socket connection is closed. 238 // This method can be called multiple times to bind multiple functions. 239 func (s *Socket) OnClose(f OnCloseFunc) { 240 go func() { 241 // Recover panics and log the error. 242 defer func() { 243 if e := recover(); e != nil { 244 log.L.Errorf("glue: panic while calling onClose function: %v\n%s", e, debug.Stack()) 245 } 246 }() 247 248 <-s.isClosedChan 249 f() 250 }() 251 } 252 253 // ClosedChan returns a channel which is non-blocking (closed) 254 // as soon as the socket is closed. 255 func (s *Socket) ClosedChan() ClosedChan { 256 return s.isClosedChan 257 } 258 259 // Write data to the client. 260 func (s *Socket) Write(data string) { 261 // Write to the main channel. 262 s.mainChannel.Write(data) 263 } 264 265 // Read the next message from the socket. This method is blocking. 266 // One variadic argument sets a timeout duration. 267 // If no timeout is specified, this method will block forever. 268 // ErrSocketClosed is returned, if the socket connection is closed. 269 // ErrReadTimeout is returned, if the timeout is reached. 270 func (s *Socket) Read(timeout ...time.Duration) (string, error) { 271 return s.mainChannel.Read(timeout...) 272 } 273 274 // OnRead sets the function which is triggered if new data is received. 275 // If this event function based method of reading data from the socket is used, 276 // then don't use the socket Read method. 277 // Either use the OnRead or the Read approach. 278 func (s *Socket) OnRead(f OnReadFunc) { 279 s.mainChannel.OnRead(f) 280 } 281 282 // DiscardRead ignores and discars the data received from the client. 283 // Call this method during initialization, if you don't read any data from 284 // the socket. If received data is not discarded, then the read buffer will block as soon 285 // as it is full, which will also block the keep-alive mechanism of the socket. The result 286 // would be a closed socket... 287 func (s *Socket) DiscardRead() { 288 s.mainChannel.DiscardRead() 289 } 290 291 //##############################// 292 //### Private Socket methods ###// 293 //##############################// 294 295 func (s *Socket) write(rawData string) { 296 // Write to the stream and check if the buffer is full. 297 select { 298 case <-s.isClosedChan: 299 // Just return because the socket is closed. 300 return 301 case s.writeChan <- rawData: 302 default: 303 // The buffer if full. No data was send. 304 // Send a ping. If no pong is received within 305 // the timeout, the socket is closed. 306 s.sendPing() 307 308 // Now write the current data to the socket. 309 // This will block if the buffer is still full. 310 s.writeChan <- rawData 311 } 312 } 313 314 func (s *Socket) onClose() { 315 // Remove the socket again from the active sockets map. 316 func() { 317 // Lock the mutex. 318 s.server.socketsMutex.Lock() 319 defer s.server.socketsMutex.Unlock() 320 321 delete(s.server.sockets, s.id) 322 }() 323 324 // Clear the write channel to release blocked goroutines. 325 // The pingLoop might be blocked... 326 for i := 0; i < len(s.writeChan); i++ { 327 select { 328 case <-s.writeChan: 329 default: 330 break 331 } 332 } 333 } 334 335 func (s *Socket) resetPingTimeout() { 336 // Lock the mutex. 337 s.sendPingMutex.Lock() 338 defer s.sendPingMutex.Unlock() 339 340 // Stop the timeout timer. 341 s.pingTimeout.Stop() 342 343 // Update the flag. 344 s.pingRequestActive = false 345 346 // Reset the ping timer again to request 347 // a pong repsonse during the next timeout. 348 s.pingTimer.Reset(pingPeriod) 349 } 350 351 // SendPing sends a ping to the client. If no pong response is 352 // received within the timeout, the socket will be closed. 353 // Multiple calls to this method won't send multiple ping requests, 354 // if a ping request is already active. 355 func (s *Socket) sendPing() { 356 // Lock the mutex. 357 s.sendPingMutex.Lock() 358 359 // Check if a ping request is already active. 360 if s.pingRequestActive { 361 // Unlock the mutex again. 362 s.sendPingMutex.Unlock() 363 return 364 } 365 366 // Update the flag and unlock the mutex again. 367 s.pingRequestActive = true 368 s.sendPingMutex.Unlock() 369 370 // Start the timeout timer. This will close 371 // the socket if no pong response is received 372 // within the timeout. 373 // Do this before the write. The write channel might block 374 // if the buffers are full. 375 s.pingTimeout.Reset(pingResponseTimeout) 376 377 // Send a ping request by writing to the stream. 378 s.writeChan <- cmdPing 379 } 380 381 // Close the socket during a ping response timeout. 382 func (s *Socket) pingTimeoutHandler() { 383 defer func() { 384 s.pingTimeout.Stop() 385 }() 386 387 select { 388 case <-s.pingTimeout.C: 389 // Close the socket due to the timeout. 390 s.bs.Close() 391 case <-s.isClosedChan: 392 // Just release this goroutine. 393 } 394 } 395 396 func (s *Socket) pingLoop() { 397 defer func() { 398 // Stop the timeout timer. 399 s.pingTimeout.Stop() 400 401 // Stop the ping timer. 402 s.pingTimer.Stop() 403 }() 404 405 for { 406 select { 407 case <-s.pingTimer.C: 408 // Send a ping. If no pong is received within 409 // the timeout, the socket is closed. 410 s.sendPing() 411 412 case <-s.isClosedChan: 413 // Just exit the loop. 414 return 415 } 416 } 417 } 418 419 func (s *Socket) readLoop() { 420 // Wait for data received from the read channel. 421 for { 422 select { 423 case data := <-s.readChan: 424 // Reset the ping timeout. 425 s.resetPingTimeout() 426 427 // Get the command. The command is always prepended to the data message. 428 cmd := data[:cmdLen] 429 data = data[cmdLen:] 430 431 // Handle the received data and log error messages. 432 if err := s.handleRead(cmd, data); err != nil { 433 log.L.WithFields(logrus.Fields{ 434 "remoteAddress": s.RemoteAddr(), 435 "userAgent": s.UserAgent(), 436 "cmd": cmd, 437 }).Warningf("glue: handle received data: %v", err) 438 } 439 case <-s.isClosedChan: 440 // Just exit the loop 441 return 442 } 443 } 444 } 445 446 func (s *Socket) handleRead(cmd, data string) error { 447 // Perform the command request. 448 switch cmd { 449 case cmdPing: 450 // Send a pong reply. 451 s.write(cmdPong) 452 453 case cmdPong: 454 // Don't do anything, The ping timer was already reset. 455 456 case cmdClose: 457 // Close the socket. 458 s.bs.Close() 459 460 case cmdInit: 461 // Handle the initialization. 462 initSocket(s, data) 463 464 case cmdChannelData: 465 // Unmarshal the channel name and data string. 466 name, data, err := utils.UnmarshalValues(data) 467 if err != nil { 468 return err 469 } 470 471 // Push the data to the corresponding channel. 472 if err = s.channels.triggerReadForChannel(name, data); err != nil { 473 return err 474 } 475 default: 476 // Send an invalid command response. 477 s.write(cmdInvalid) 478 479 // Return an error. 480 return fmt.Errorf("received invalid socket command") 481 } 482 483 return nil 484 } 485 486 //###############// 487 //### Private ###// 488 //###############// 489 490 func init() { 491 var err error 492 493 // Parses the server version string and returns a validated Version. 494 serverVersion, err = semver.Make(Version) 495 if err != nil { 496 log.L.Fatalf("failed to parse glue server protocol version: %v", err) 497 } 498 } 499 500 func initSocket(s *Socket, dataJSON string) { 501 // Handle the socket initialization in an anonymous function 502 // to handle the error in a clean and simple way. 503 dontAutoReconnect, err := func() (bool, error) { 504 // Handle received initialization data: 505 // #################################### 506 507 // Unmarshal the data JSON. 508 var cData clientInitData 509 err := json.Unmarshal([]byte(dataJSON), &cData) 510 if err != nil { 511 return false, fmt.Errorf("json unmarshal init data: %v", err) 512 } 513 514 // Parses the client version string and returns a validated Version. 515 clientVersion, err := semver.Make(cData.Version) 516 if err != nil { 517 return false, fmt.Errorf("invalid client protocol version: %v", err) 518 } 519 520 // Check if the client protocol version is supported. 521 if clientVersion.Major != serverVersion.Major || 522 clientVersion.Minor > serverVersion.Minor || 523 (clientVersion.Minor == serverVersion.Minor && clientVersion.Patch > serverVersion.Patch) { 524 // The client should not automatically reconnect. Return true... 525 return true, fmt.Errorf("client socket protocol version is not supported: %s", cData.Version) 526 } 527 528 // Send initialization data: 529 // ######################### 530 531 // Create the new initialization data value. 532 data := initData{ 533 SocketID: s.ID(), 534 } 535 536 // Marshal the data to a JSON string. 537 dataJSON, err := json.Marshal(&data) 538 if err != nil { 539 return false, fmt.Errorf("json marshal init data: %v", err) 540 } 541 542 // Send the init data to the client. 543 s.write(cmdInit + string(dataJSON)) 544 545 return false, nil 546 }() 547 548 // Handle the error. 549 if err != nil { 550 if dontAutoReconnect { 551 // Tell the client to not automatically reconnect. 552 s.write(cmdDontAutoReconnect) 553 554 // Pause to be sure that the previous socket command gets send to the client. 555 time.Sleep(time.Second) 556 } 557 558 // Close the socket. 559 s.Close() 560 561 // Log the error. 562 log.L.WithFields(logrus.Fields{ 563 "remoteAddress": s.RemoteAddr(), 564 "userAgent": s.UserAgent(), 565 }).Warningf("glue: init socket: %v", err) 566 567 return 568 } 569 570 // Trigger the on new socket event function. 571 func() { 572 // Recover panics and log the error. 573 defer func() { 574 if e := recover(); e != nil { 575 // Close the socket and log the error message. 576 s.Close() 577 log.L.Errorf("glue: panic while calling on new socket function: %v\n%s", e, debug.Stack()) 578 } 579 }() 580 581 // Trigger the event function. 582 s.server.onNewSocket(s) 583 }() 584 585 // Update the initialized flag. 586 s.isInitialized = true 587 }