1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-23 00:00:01 +01:00
VILLASnode/go/pkg/nodes/webrtc/client.go
Steffen Vogel 7eec1bb753 update Steffens mail address
Signed-off-by: Steffen Vogel <post@steffenvogel.de>
2022-12-16 23:44:07 +01:00

173 lines
3.8 KiB
Go

/** Websocket signaling channel for WebRTC.
*
* @author Steffen Vogel <post@steffenvogel.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license Apache 2.0
*********************************************************************************/
package webrtc
import (
"fmt"
"net/url"
"time"
"git.rwth-aachen.de/acs/public/villas/node/go/pkg/nodes"
"github.com/gorilla/websocket"
)
type SignalingClient struct {
*websocket.Conn
logger nodes.Logger
URL *url.URL
done chan struct{}
isClosing bool
backoff ExponentialBackoff
messageCallbacks []func(msg *SignalingMessage)
connectCallbacks []func()
disconnectCallbacks []func()
}
func NewSignalingClient(u *url.URL, logger nodes.Logger) (*SignalingClient, error) {
c := &SignalingClient{
messageCallbacks: []func(msg *SignalingMessage){},
connectCallbacks: []func(){},
disconnectCallbacks: []func(){},
isClosing: false,
backoff: DefaultExponentialBackoff,
URL: u,
logger: logger,
}
return c, nil
}
func (c *SignalingClient) OnConnect(cb func()) {
c.connectCallbacks = append(c.connectCallbacks, cb)
}
func (c *SignalingClient) OnDisconnect(cb func()) {
c.disconnectCallbacks = append(c.connectCallbacks, cb)
}
func (c *SignalingClient) OnMessage(cb func(msg *SignalingMessage)) {
c.messageCallbacks = append(c.messageCallbacks, cb)
}
func (c *SignalingClient) SendSignalingMessage(msg *SignalingMessage) error {
c.logger.Infof("Sending signaling message: %s", msg)
return c.Conn.WriteJSON(msg)
}
func (c *SignalingClient) Close() error {
// Return immediatly if there is no open connection
if c.Conn == nil {
return nil
}
c.isClosing = true
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := c.Conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(5*time.Second))
if err != nil {
return fmt.Errorf("failed to send close message: %s", err)
}
select {
case <-c.done:
c.logger.Infof("Connection closed")
case <-time.After(3 * time.Second):
c.logger.Warn("Timed-out waiting for connection close")
}
return nil
}
func (c *SignalingClient) Connect() error {
var err error
dialer := websocket.Dialer{
HandshakeTimeout: 1 * time.Second,
}
c.Conn, _, err = dialer.Dial(c.URL.String(), nil)
if err != nil {
return fmt.Errorf("failed to dial %s: %w", c.URL, err)
}
for _, cb := range c.connectCallbacks {
cb()
}
go c.read()
// Reset reconnect timer
c.backoff.Reset()
c.done = make(chan struct{})
return nil
}
func (c *SignalingClient) ConnectWithBackoff() error {
t := time.NewTimer(c.backoff.Duration)
for range t.C {
if err := c.Connect(); err != nil {
t.Reset(c.backoff.Next())
c.logger.Errorf("Failed to connect: %s. Reconnecting in %s", err, c.backoff.Duration)
} else {
break
}
}
return nil
}
func (c *SignalingClient) read() {
for {
msg := &SignalingMessage{}
if err := c.Conn.ReadJSON(msg); err != nil {
if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
} else {
c.logger.Errorf("Failed to read: %s", err)
}
break
}
c.logger.Infof("Received signaling message: %s", msg)
for _, cb := range c.messageCallbacks {
cb(msg)
}
}
c.closed()
}
func (c *SignalingClient) closed() {
if err := c.Conn.Close(); err != nil {
c.logger.Errorf("Failed to close connection: %s", err)
}
c.Conn = nil
for _, cb := range c.disconnectCallbacks {
cb()
}
close(c.done)
if c.isClosing {
c.logger.Infof("Connection closed")
} else {
c.logger.Warnf("Connection lost. Reconnecting in %s", c.backoff.Duration)
go c.ConnectWithBackoff()
}
}