2019-06-06 09:58:50 +00:00
|
|
|
package xmpp // import "gosrc.io/xmpp"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
2019-06-07 13:23:23 +00:00
|
|
|
|
|
|
|
"golang.org/x/xerrors"
|
2019-06-06 09:58:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type PostConnect func(c *Client)
|
|
|
|
|
|
|
|
// ClientManager supervises an XMPP client connection. Its role is to handle connection events and
|
|
|
|
// apply reconnection strategy.
|
|
|
|
type ClientManager struct {
|
|
|
|
Client *Client
|
|
|
|
Session *Session
|
|
|
|
PostConnect PostConnect
|
|
|
|
|
|
|
|
// Store low level metrics
|
|
|
|
Metrics *Metrics
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewClientManager creates a new client manager structure, intended to support
|
|
|
|
// handling XMPP client state event changes and auto-trigger reconnection
|
|
|
|
// based on ClientManager configuration.
|
|
|
|
func NewClientManager(client *Client, pc PostConnect) *ClientManager {
|
|
|
|
return &ClientManager{
|
|
|
|
Client: client,
|
|
|
|
PostConnect: pc,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start launch the connection loop
|
2019-06-07 13:23:23 +00:00
|
|
|
func (cm *ClientManager) Start() error {
|
2019-06-06 09:58:50 +00:00
|
|
|
cm.Client.Handler = func(e Event) {
|
|
|
|
switch e.State {
|
|
|
|
case StateConnected:
|
|
|
|
cm.Metrics.setConnectTime()
|
|
|
|
case StateSessionEstablished:
|
|
|
|
cm.Metrics.setLoginTime()
|
|
|
|
case StateDisconnected:
|
|
|
|
// Reconnect on disconnection
|
|
|
|
cm.connect()
|
|
|
|
}
|
|
|
|
}
|
2019-06-07 13:23:23 +00:00
|
|
|
|
|
|
|
return cm.connect()
|
2019-06-06 09:58:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop cancels pending operations and terminates existing XMPP client.
|
|
|
|
func (cm *ClientManager) Stop() {
|
|
|
|
// Remove on disconnect handler to avoid triggering reconnect
|
|
|
|
cm.Client.Handler = nil
|
|
|
|
cm.Client.Disconnect()
|
|
|
|
}
|
|
|
|
|
|
|
|
// connect manages the reconnection loop and apply the define backoff to avoid overloading the server.
|
2019-06-07 13:23:23 +00:00
|
|
|
func (cm *ClientManager) connect() error {
|
2019-06-06 09:58:50 +00:00
|
|
|
var backoff Backoff // TODO: Group backoff calculation features with connection manager?
|
|
|
|
|
|
|
|
for {
|
|
|
|
var err error
|
2019-06-07 13:23:23 +00:00
|
|
|
// TODO: Make it possible to define logger to log disconnect and reconnection attempts
|
2019-06-06 09:58:50 +00:00
|
|
|
cm.Metrics = initMetrics()
|
|
|
|
|
2019-06-06 17:12:31 +00:00
|
|
|
// TODO: Test for non recoverable errors (invalid username and password) and return an error
|
|
|
|
// to start caller. We do not want to retry on non recoverable errors.
|
2019-06-06 09:58:50 +00:00
|
|
|
if cm.Client.Session, err = cm.Client.Connect(); err != nil {
|
2019-06-07 13:23:23 +00:00
|
|
|
var actualErr ConnError
|
|
|
|
if xerrors.As(err, &actualErr) {
|
|
|
|
if actualErr.Permanent {
|
|
|
|
return xerrors.Errorf("unrecoverable connect error %w", actualErr)
|
|
|
|
}
|
|
|
|
}
|
2019-06-06 09:58:50 +00:00
|
|
|
backoff.Wait()
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if cm.PostConnect != nil {
|
|
|
|
cm.PostConnect(cm.Client)
|
|
|
|
}
|
2019-06-07 13:23:23 +00:00
|
|
|
return nil
|
2019-06-06 09:58:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Client Metrics
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
type Metrics struct {
|
|
|
|
startTime time.Time
|
|
|
|
// ConnectTime returns the duration between client initiation of the TCP/IP
|
|
|
|
// connection to the server and actual TCP/IP session establishment.
|
|
|
|
// This time includes DNS resolution and can be slightly higher if the DNS
|
|
|
|
// resolution result was not in cache.
|
|
|
|
ConnectTime time.Duration
|
|
|
|
// LoginTime returns the between client initiation of the TCP/IP
|
|
|
|
// connection to the server and the return of the login result.
|
|
|
|
// This includes ConnectTime, but also XMPP level protocol negociation
|
|
|
|
// like starttls.
|
|
|
|
LoginTime time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
// initMetrics set metrics with default value and define the starting point
|
|
|
|
// for duration calculation (connect time, login time, etc).
|
|
|
|
func initMetrics() *Metrics {
|
|
|
|
return &Metrics{
|
|
|
|
startTime: time.Now(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Metrics) setConnectTime() {
|
|
|
|
m.ConnectTime = time.Since(m.startTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *Metrics) setLoginTime() {
|
|
|
|
m.LoginTime = time.Since(m.startTime)
|
|
|
|
}
|