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  }