2019-02-20 20:54:46 +00:00
|
|
|
package gun
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-02-22 06:46:19 +00:00
|
|
|
"fmt"
|
2019-02-20 20:54:46 +00:00
|
|
|
"net/url"
|
2019-02-22 19:51:50 +00:00
|
|
|
"sync"
|
2019-02-25 05:14:26 +00:00
|
|
|
"time"
|
2019-02-20 20:54:46 +00:00
|
|
|
)
|
|
|
|
|
2019-02-22 09:23:14 +00:00
|
|
|
type ErrPeer struct {
|
|
|
|
Err error
|
2019-02-25 05:14:26 +00:00
|
|
|
Peer *Peer
|
2019-02-22 09:23:14 +00:00
|
|
|
}
|
|
|
|
|
2019-02-25 05:14:26 +00:00
|
|
|
func (e *ErrPeer) Error() string { return fmt.Sprintf("Error on peer %v: %v", e.Peer, e.Err) }
|
2019-02-22 09:23:14 +00:00
|
|
|
|
2019-02-25 05:14:26 +00:00
|
|
|
type Peer struct {
|
2019-02-25 22:28:19 +00:00
|
|
|
name string
|
2019-02-25 05:14:26 +00:00
|
|
|
newConn func() (PeerConn, error)
|
|
|
|
sleepOnErr time.Duration // TODO: would be better as backoff
|
|
|
|
id string
|
|
|
|
|
|
|
|
connCurrent PeerConn
|
|
|
|
connBad bool // If true, don't try anything
|
|
|
|
connLock sync.Mutex
|
|
|
|
}
|
|
|
|
|
2019-02-25 22:28:19 +00:00
|
|
|
func newPeer(name string, newConn func() (PeerConn, error), sleepOnErr time.Duration) (*Peer, error) {
|
|
|
|
p := &Peer{name: name, newConn: newConn, sleepOnErr: sleepOnErr}
|
2019-02-25 05:14:26 +00:00
|
|
|
var err error
|
|
|
|
if p.connCurrent, err = newConn(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) ID() string { return p.id }
|
|
|
|
|
|
|
|
func (p *Peer) String() string {
|
|
|
|
id := ""
|
|
|
|
if p.id != "" {
|
|
|
|
id = "(id: " + p.id + ")"
|
|
|
|
}
|
2019-02-25 22:28:19 +00:00
|
|
|
connStatus := "disconnected"
|
|
|
|
if conn := p.Conn(); conn != nil {
|
|
|
|
connStatus = "connected to " + conn.RemoteURL()
|
2019-02-25 05:14:26 +00:00
|
|
|
}
|
2019-02-25 22:28:19 +00:00
|
|
|
return fmt.Sprintf("Peer%v %v (%v)", id, p.name, connStatus)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) reconnectSupported() bool {
|
|
|
|
return p.sleepOnErr > 0
|
2019-02-25 05:14:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) reconnect() (err error) {
|
2019-02-25 22:28:19 +00:00
|
|
|
if !p.reconnectSupported() {
|
|
|
|
return fmt.Errorf("Reconnect not supported")
|
|
|
|
}
|
2019-02-25 05:14:26 +00:00
|
|
|
p.connLock.Lock()
|
|
|
|
defer p.connLock.Unlock()
|
|
|
|
if p.connCurrent == nil && p.connBad {
|
|
|
|
p.connBad = false
|
|
|
|
if p.connCurrent, err = p.newConn(); err != nil {
|
|
|
|
p.connBad = true
|
|
|
|
time.AfterFunc(p.sleepOnErr, func() { p.reconnect() })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can be nil peer if currently bad or closed
|
|
|
|
func (p *Peer) Conn() PeerConn {
|
|
|
|
p.connLock.Lock()
|
|
|
|
defer p.connLock.Unlock()
|
|
|
|
return p.connCurrent
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) markConnErrored(conn PeerConn) {
|
2019-02-25 22:28:19 +00:00
|
|
|
if !p.reconnectSupported() {
|
|
|
|
p.Close()
|
|
|
|
return
|
|
|
|
}
|
2019-02-25 05:14:26 +00:00
|
|
|
p.connLock.Lock()
|
|
|
|
defer p.connLock.Unlock()
|
|
|
|
if conn == p.connCurrent {
|
|
|
|
p.connCurrent = nil
|
|
|
|
p.connBad = true
|
|
|
|
conn.Close()
|
|
|
|
time.AfterFunc(p.sleepOnErr, func() { p.reconnect() })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) send(ctx context.Context, msg *Message, moreMsgs ...*Message) (ok bool, err error) {
|
|
|
|
conn := p.Conn()
|
|
|
|
if conn == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
// Clone them with peer "to"
|
|
|
|
updatedMsg := &Message{}
|
|
|
|
*updatedMsg = *msg
|
2019-02-25 22:28:19 +00:00
|
|
|
updatedMsg.To = conn.RemoteURL()
|
2019-02-25 05:14:26 +00:00
|
|
|
updatedMoreMsgs := make([]*Message, len(moreMsgs))
|
|
|
|
for i, moreMsg := range moreMsgs {
|
|
|
|
updatedMoreMsg := &Message{}
|
|
|
|
*updatedMoreMsg = *moreMsg
|
2019-02-25 22:28:19 +00:00
|
|
|
updatedMoreMsg.To = conn.RemoteURL()
|
2019-02-25 05:14:26 +00:00
|
|
|
updatedMoreMsgs[i] = updatedMoreMsg
|
|
|
|
}
|
|
|
|
if err = conn.Send(ctx, updatedMsg, updatedMoreMsgs...); err != nil {
|
|
|
|
p.markConnErrored(conn)
|
|
|
|
return false, err
|
|
|
|
} else {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) receive(ctx context.Context) (ok bool, msgs []*Message, err error) {
|
|
|
|
if conn := p.Conn(); conn == nil {
|
|
|
|
return false, nil, nil
|
|
|
|
} else if msgs, err = conn.Receive(ctx); err != nil {
|
|
|
|
p.markConnErrored(conn)
|
|
|
|
return false, nil, err
|
|
|
|
} else {
|
|
|
|
return true, msgs, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) Close() error {
|
|
|
|
p.connLock.Lock()
|
|
|
|
defer p.connLock.Unlock()
|
|
|
|
var err error
|
|
|
|
if p.connCurrent != nil {
|
|
|
|
err = p.connCurrent.Close()
|
|
|
|
p.connCurrent = nil
|
|
|
|
}
|
|
|
|
p.connBad = false
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Peer) Closed() bool {
|
|
|
|
p.connLock.Lock()
|
|
|
|
defer p.connLock.Unlock()
|
|
|
|
return p.connCurrent == nil && !p.connBad
|
|
|
|
}
|
|
|
|
|
|
|
|
type PeerConn interface {
|
2019-02-22 18:40:02 +00:00
|
|
|
Send(ctx context.Context, msg *Message, moreMsgs ...*Message) error
|
|
|
|
Receive(ctx context.Context) ([]*Message, error)
|
2019-02-25 22:28:19 +00:00
|
|
|
RemoteURL() string
|
2019-02-20 20:54:46 +00:00
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
2019-02-25 05:14:26 +00:00
|
|
|
var PeerURLSchemes map[string]func(context.Context, *url.URL) (PeerConn, error)
|
2019-02-25 04:23:15 +00:00
|
|
|
|
|
|
|
func init() {
|
2019-02-25 05:14:26 +00:00
|
|
|
PeerURLSchemes = map[string]func(context.Context, *url.URL) (PeerConn, error){
|
|
|
|
"http": func(ctx context.Context, peerURL *url.URL) (PeerConn, error) {
|
2019-02-25 04:23:15 +00:00
|
|
|
schemeChangedURL := &url.URL{}
|
|
|
|
*schemeChangedURL = *peerURL
|
|
|
|
schemeChangedURL.Scheme = "ws"
|
2019-02-25 22:28:19 +00:00
|
|
|
return DialPeerConnWebSocket(ctx, schemeChangedURL)
|
2019-02-25 04:23:15 +00:00
|
|
|
},
|
2019-02-26 19:06:16 +00:00
|
|
|
"https": func(ctx context.Context, peerURL *url.URL) (PeerConn, error) {
|
|
|
|
schemeChangedURL := &url.URL{}
|
|
|
|
*schemeChangedURL = *peerURL
|
|
|
|
schemeChangedURL.Scheme = "wss"
|
|
|
|
return DialPeerConnWebSocket(ctx, schemeChangedURL)
|
|
|
|
},
|
2019-02-25 05:14:26 +00:00
|
|
|
"ws": func(ctx context.Context, peerURL *url.URL) (PeerConn, error) {
|
2019-02-25 22:28:19 +00:00
|
|
|
return DialPeerConnWebSocket(ctx, peerURL)
|
2019-02-25 04:23:15 +00:00
|
|
|
},
|
2019-02-26 19:06:16 +00:00
|
|
|
"wss": func(ctx context.Context, peerURL *url.URL) (PeerConn, error) {
|
|
|
|
return DialPeerConnWebSocket(ctx, peerURL)
|
|
|
|
},
|
2019-02-25 04:23:15 +00:00
|
|
|
}
|
2019-02-20 20:54:46 +00:00
|
|
|
}
|
|
|
|
|
2019-02-25 05:14:26 +00:00
|
|
|
func NewPeerConn(ctx context.Context, peerURL string) (PeerConn, error) {
|
2019-02-22 06:46:19 +00:00
|
|
|
if parsedURL, err := url.Parse(peerURL); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if peerNew := PeerURLSchemes[parsedURL.Scheme]; peerNew == nil {
|
|
|
|
return nil, fmt.Errorf("Unknown peer URL scheme %v", parsedURL.Scheme)
|
|
|
|
} else {
|
|
|
|
return peerNew(ctx, parsedURL)
|
|
|
|
}
|
|
|
|
}
|