fayec/internal/dispatcher/dispatcher.go
Marcelo Pires 33a61d6d27 add dispatcher
simplify transport implementation
support wildcard subscriptions
2018-09-24 16:13:57 +02:00

270 lines
7.1 KiB
Go

package dispatcher
import (
"fmt"
"github.com/thesyncim/faye/internal/store"
"github.com/thesyncim/faye/message"
"github.com/thesyncim/faye/subscription"
"github.com/thesyncim/faye/transport"
"log"
"strconv"
"sync"
"sync/atomic"
)
type dispatcher interface {
SetTransport(t transport.Transport)
//Subscribe informs the server that messages published to that channel are delivered to itself.
Subscribe(channel string) (*subscription.Subscription, error)
//Unsubscribe informs the server that the client will no longer listen to incoming event messages on
//the specified channel/subscription
Unsubscribe(sub *subscription.Subscription) error
//Publish publishes events on a channel by sending event messages, the server MAY respond to a publish event
//if this feature is supported by the server use the OnPublishResponse to get the publish status.
Publish(subscription string, message message.Data) (id string, err error)
//OnPublishResponse sets the handler to be triggered if the server replies to the publish request
//according to the spec the server MAY reply to the publish request, so its not guaranteed that this handler will
//ever be triggered
//can be used to identify the status of the published request and for example retry failed published requests
OnPublishResponse(subscription string, onMsg func(message *message.Message))
}
var _ dispatcher = (*Dispatcher)(nil)
type Dispatcher struct {
endpoint string
//transports map[string]transport.Transport
transport transport.Transport
transportOpts transport.Options
msgID *uint64
extensions message.Extensions
//map requestID
pendingSubs map[string]chan error //todo wrap in structure
pendingSubsMu sync.Mutex
store *store.SubscriptionsStore
onPublishResponseMu sync.Mutex //todo sync.Map
onPublishResponse map[string]func(message *message.Message)
clientID string
}
func NewDispatcher(endpoint string, tOpts transport.Options, ext message.Extensions) *Dispatcher {
var msgID uint64
return &Dispatcher{
endpoint: endpoint,
msgID: &msgID,
store: store.NewStore(100),
transportOpts: tOpts,
extensions: ext,
onPublishResponse: map[string]func(message *message.Message){},
pendingSubs: map[string]chan error{},
}
}
//todo allow multiple transports
func (d *Dispatcher) Connect() error {
var err error
if err = d.transport.Init(d.endpoint, &d.transportOpts); err != nil {
return err
}
if err = d.metaHandshake(); err != nil {
return err
}
return d.metaConnect()
}
func (d *Dispatcher) metaHandshake() error {
m := &message.Message{
Channel: message.MetaHandshake,
Version: "1.0", //todo const
SupportedConnectionTypes: []string{d.transport.Name()}, //todo list all tranports
}
d.extensions.ApplyOutExtensions(m)
handshakeResp, err := d.transport.Handshake(m)
if err != nil {
return err
}
d.extensions.ApplyInExtensions(handshakeResp)
if handshakeResp.GetError() != nil {
return err
}
d.clientID = handshakeResp.ClientId
return nil
}
func (d *Dispatcher) metaConnect() error {
m := &message.Message{
Channel: message.MetaConnect,
ClientId: d.clientID,
ConnectionType: d.transport.Name(),
Id: d.nextMsgID(),
}
return d.transport.Connect(m)
}
func (d *Dispatcher) Disconnect() error {
m := &message.Message{
Channel: message.MetaDisconnect,
ClientId: d.clientID,
Id: d.nextMsgID(),
}
return d.transport.Disconnect(m)
}
func (d *Dispatcher) dispatchMessage(msg *message.Message) {
d.extensions.ApplyInExtensions(msg)
if message.IsMetaMessage(msg) {
//handle it
switch msg.Channel {
case message.MetaSubscribe:
//handle MetaSubscribe resp
d.pendingSubsMu.Lock()
confirmCh, ok := d.pendingSubs[msg.Id]
d.pendingSubsMu.Unlock()
if !ok {
panic("BUG: subscription not registered `" + msg.Subscription + "`")
}
if !msg.Successful {
if msg.GetError() == nil {
//inject the error if the server returns unsuccessful without error
msg.Error = fmt.Sprintf("susbscription `%s` failed", msg.Subscription)
}
confirmCh <- msg.GetError()
//v2
} else {
confirmCh <- nil
}
return
}
}
//is Event Message
//there are 2 types of Event Message
// 1. Publish
// 2. Delivery
if message.IsEventDelivery(msg) {
subscriptions := d.store.Match(msg.Channel)
//send to all listeners
log.Println(subscriptions, msg)
for i := range subscriptions {
if subscriptions[i].MsgChannel() != nil {
select {
case subscriptions[i].MsgChannel() <- msg:
default:
log.Println("subscription has no listeners") //todo remove
}
}
}
return
}
if message.IsEventPublish(msg) {
d.onPublishResponseMu.Lock()
onPublish, ok := d.onPublishResponse[msg.Channel]
d.onPublishResponseMu.Unlock()
if ok {
onPublish(msg)
}
}
}
func (d *Dispatcher) SetTransport(t transport.Transport) {
t.SetOnMessageReceivedHandler(d.dispatchMessage)
d.transport = t
}
func (d *Dispatcher) nextMsgID() string {
return strconv.Itoa(int(atomic.AddUint64(d.msgID, 1)))
}
//sendMessage send applies the out extensions and sends a message throught the transport
func (d *Dispatcher) sendMessage(m *message.Message) error {
d.extensions.ApplyOutExtensions(m)
return d.transport.SendMessage(m)
}
func (d *Dispatcher) Subscribe(channel string) (*subscription.Subscription, error) {
id := d.nextMsgID()
m := &message.Message{
Channel: message.MetaSubscribe,
ClientId: d.clientID,
Subscription: channel,
Id: id,
}
if err := d.transport.SendMessage(m); err != nil {
return nil, err
}
inMsgCh := make(chan *message.Message, 0)
subscriptionConfirmation := make(chan error)
d.pendingSubsMu.Lock()
d.pendingSubs[id] = subscriptionConfirmation
d.pendingSubsMu.Unlock()
sub, err := subscription.NewSubscription(channel, d.Unsubscribe, inMsgCh)
if err != nil {
return nil, err
}
//todo timeout here
err = <-subscriptionConfirmation
if err != nil {
//log.Println(err)
return nil, err
}
d.store.Add(sub)
return sub, nil
}
func (d *Dispatcher) Unsubscribe(sub *subscription.Subscription) error {
//https://docs.cometd.org/current/reference/#_bayeux_meta_unsubscribe
d.store.Remove(sub)
//if this is last subscription we will send meta unsubscribe to the server
if d.store.Count(sub.Name()) == 0 {
d.onPublishResponseMu.Lock()
delete(d.onPublishResponse, sub.Name())
d.onPublishResponseMu.Unlock()
m := &message.Message{
Channel: message.MetaUnsubscribe,
Subscription: sub.Name(),
ClientId: d.clientID,
Id: d.nextMsgID(),
}
return d.transport.SendMessage(m)
}
return nil
}
func (d *Dispatcher) Publish(subscription string, data message.Data) (id string, err error) {
id = d.nextMsgID()
m := &message.Message{
Channel: subscription,
Data: data,
ClientId: d.clientID,
Id: id,
}
if err = d.sendMessage(m); err != nil {
return "", err
}
return id, nil
}
func (d *Dispatcher) OnPublishResponse(subscription string, onMsg func(message *message.Message)) {
d.onPublishResponseMu.Lock()
d.onPublishResponse[subscription] = onMsg
d.onPublishResponseMu.Unlock()
}