go-gun/gun/peer.go

137 lines
3.3 KiB
Go
Raw Normal View History

2019-02-20 20:54:46 +00:00
package gun
import (
"context"
2019-02-22 18:40:02 +00:00
"encoding/json"
2019-02-22 06:46:19 +00:00
"fmt"
2019-02-20 20:54:46 +00:00
"net/url"
"sync"
2019-02-20 20:54:46 +00:00
"github.com/gorilla/websocket"
)
2019-02-22 09:23:14 +00:00
type ErrPeer struct {
Err error
2019-02-22 18:40:02 +00:00
peer *gunPeer
2019-02-22 09:23:14 +00:00
}
2019-02-22 18:40:02 +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-20 20:54:46 +00:00
type Peer interface {
2019-02-22 18:40:02 +00:00
Send(ctx context.Context, msg *Message, moreMsgs ...*Message) error
// Chan is closed on first err, when context is closed, or when peer is closed
Receive(ctx context.Context) ([]*Message, error)
2019-02-20 20:54:46 +00:00
Close() error
}
var PeerURLSchemes = map[string]func(context.Context, *url.URL) (Peer, error){
"http": func(ctx context.Context, peerURL *url.URL) (Peer, error) {
schemeChangedURL := &url.URL{}
*schemeChangedURL = *peerURL
schemeChangedURL.Scheme = "ws"
return NewPeerWebSocket(ctx, schemeChangedURL)
},
"ws": func(ctx context.Context, peerURL *url.URL) (Peer, error) {
return NewPeerWebSocket(ctx, peerURL)
},
2019-02-20 20:54:46 +00:00
}
2019-02-22 06:46:19 +00:00
func NewPeer(ctx context.Context, peerURL string) (Peer, error) {
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)
}
}
2019-02-20 20:54:46 +00:00
type PeerWebSocket struct {
2019-02-22 18:40:02 +00:00
Underlying *websocket.Conn
WriteLock sync.Mutex
2019-02-20 20:54:46 +00:00
}
func NewPeerWebSocket(ctx context.Context, peerUrl *url.URL) (*PeerWebSocket, error) {
conn, _, err := websocket.DefaultDialer.DialContext(ctx, peerUrl.String(), nil)
if err != nil {
return nil, err
}
return &PeerWebSocket{Underlying: conn}, nil
2019-02-20 20:54:46 +00:00
}
2019-02-22 06:46:19 +00:00
2019-02-22 18:40:02 +00:00
func (p *PeerWebSocket) Send(ctx context.Context, msg *Message, moreMsgs ...*Message) error {
// If there are more, send all as an array of JSON strings, otherwise just the msg
var toWrite interface{}
if len(moreMsgs) == 0 {
toWrite = msg
} else {
b, err := json.Marshal(msg)
if err != nil {
return err
}
msgs := []string{string(b)}
for _, nextMsg := range moreMsgs {
if b, err = json.Marshal(nextMsg); err != nil {
return err
}
msgs = append(msgs, string(b))
}
toWrite = msgs
}
// Send async so we can wait on context
errCh := make(chan error, 1)
go func() {
p.WriteLock.Lock()
defer p.WriteLock.Unlock()
errCh <- p.Underlying.WriteJSON(toWrite)
}()
2019-02-22 18:40:02 +00:00
select {
case err := <-errCh:
return err
case <-ctx.Done():
return ctx.Err()
}
}
func (p *PeerWebSocket) Receive(ctx context.Context) ([]*Message, error) {
bytsCh := make(chan []byte, 1)
errCh := make(chan error, 1)
go func() {
if _, b, err := p.Underlying.ReadMessage(); err != nil {
errCh <- err
} else {
bytsCh <- b
}
}()
select {
case err := <-errCh:
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
case byts := <-bytsCh:
// If it's a JSON array, it means it's an array of JSON strings, otherwise it's one message
if byts[0] != '[' {
var msg Message
if err := json.Unmarshal(byts, &msg); err != nil {
return nil, err
}
return []*Message{&msg}, nil
}
var jsonStrs []string
if err := json.Unmarshal(byts, &jsonStrs); err != nil {
return nil, err
}
msgs := make([]*Message, len(jsonStrs))
for i, jsonStr := range jsonStrs {
if err := json.Unmarshal([]byte(jsonStr), &(msgs[i])); err != nil {
return nil, err
}
}
return msgs, nil
}
2019-02-22 06:46:19 +00:00
}
2019-02-22 18:40:02 +00:00
func (p *PeerWebSocket) Close() error {
return p.Underlying.Close()
2019-02-22 06:46:19 +00:00
}