Only send events to clients who need it
- User events (star, rating, plays) only sent to same user - Don't send to the client (browser window) that originated the event
This commit is contained in:
@@ -16,7 +16,7 @@ func newDiode(ctx context.Context, size int, alerter diodes.Alerter) *diode {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *diode) set(data message) {
|
||||
func (d *diode) put(data message) {
|
||||
d.d.Set(diodes.GenericDataType(&data))
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -21,20 +21,20 @@ var _ = Describe("diode", func() {
|
||||
})
|
||||
|
||||
It("enqueues the data correctly", func() {
|
||||
diode.set(message{Data: "1"})
|
||||
diode.set(message{Data: "2"})
|
||||
Expect(diode.next()).To(Equal(&message{Data: "1"}))
|
||||
Expect(diode.next()).To(Equal(&message{Data: "2"}))
|
||||
diode.put(message{data: "1"})
|
||||
diode.put(message{data: "2"})
|
||||
Expect(diode.next()).To(Equal(&message{data: "1"}))
|
||||
Expect(diode.next()).To(Equal(&message{data: "2"}))
|
||||
Expect(missed).To(BeZero())
|
||||
})
|
||||
|
||||
It("drops messages when diode is full", func() {
|
||||
diode.set(message{Data: "1"})
|
||||
diode.set(message{Data: "2"})
|
||||
diode.set(message{Data: "3"})
|
||||
diode.put(message{data: "1"})
|
||||
diode.put(message{data: "2"})
|
||||
diode.put(message{data: "3"})
|
||||
next, ok := diode.tryNext()
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(next).To(Equal(&message{Data: "3"}))
|
||||
Expect(next).To(Equal(&message{data: "3"}))
|
||||
|
||||
_, ok = diode.tryNext()
|
||||
Expect(ok).To(BeFalse())
|
||||
@@ -43,9 +43,9 @@ var _ = Describe("diode", func() {
|
||||
})
|
||||
|
||||
It("returns nil when diode is empty and the context is canceled", func() {
|
||||
diode.set(message{Data: "1"})
|
||||
diode.put(message{data: "1"})
|
||||
ctxCancel()
|
||||
Expect(diode.next()).To(Equal(&message{Data: "1"}))
|
||||
Expect(diode.next()).To(Equal(&message{data: "1"}))
|
||||
Expect(diode.next()).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
+51
-25
@@ -2,6 +2,7 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
|
||||
type Broker interface {
|
||||
http.Handler
|
||||
SendMessage(event Event)
|
||||
SendMessage(ctx context.Context, event Event)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -33,23 +34,28 @@ var (
|
||||
|
||||
type (
|
||||
message struct {
|
||||
ID uint32
|
||||
Event string
|
||||
Data string
|
||||
id uint32
|
||||
event string
|
||||
data string
|
||||
senderCtx context.Context
|
||||
}
|
||||
messageChan chan message
|
||||
clientsChan chan client
|
||||
client struct {
|
||||
id string
|
||||
address string
|
||||
username string
|
||||
userAgent string
|
||||
diode *diode
|
||||
id string
|
||||
address string
|
||||
username string
|
||||
userAgent string
|
||||
clientUniqueId string
|
||||
diode *diode
|
||||
}
|
||||
)
|
||||
|
||||
func (c client) String() string {
|
||||
return fmt.Sprintf("%s (%s - %s - %s)", c.id, c.username, c.address, c.userAgent)
|
||||
if log.CurrentLevel() >= log.LevelTrace {
|
||||
return fmt.Sprintf("%s (%s - %s - %s - %s)", c.id, c.username, c.address, c.clientUniqueId, c.userAgent)
|
||||
}
|
||||
return fmt.Sprintf("%s (%s - %s - %s)", c.id, c.username, c.address, c.clientUniqueId)
|
||||
}
|
||||
|
||||
type broker struct {
|
||||
@@ -77,17 +83,18 @@ func NewBroker() Broker {
|
||||
return broker
|
||||
}
|
||||
|
||||
func (b *broker) SendMessage(evt Event) {
|
||||
func (b *broker) SendMessage(ctx context.Context, evt Event) {
|
||||
msg := b.prepareMessage(evt)
|
||||
msg.senderCtx = ctx
|
||||
log.Trace("Broker received new event", "event", msg)
|
||||
b.publish <- msg
|
||||
}
|
||||
|
||||
func (b *broker) prepareMessage(event Event) message {
|
||||
msg := message{}
|
||||
msg.ID = atomic.AddUint32(&eventId, 1)
|
||||
msg.Data = event.Data(event)
|
||||
msg.Event = event.Name(event)
|
||||
msg.id = atomic.AddUint32(&eventId, 1)
|
||||
msg.data = event.Data(event)
|
||||
msg.event = event.Name(event)
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -96,7 +103,7 @@ func writeEvent(w io.Writer, event message, timeout time.Duration) (err error) {
|
||||
flusher, _ := w.(http.Flusher)
|
||||
complete := make(chan struct{}, 1)
|
||||
go func() {
|
||||
_, err = fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", event.ID, event.Event, event.Data)
|
||||
_, err = fmt.Fprintf(w, "id: %d\nevent: %s\ndata: %s\n\n", event.id, event.event, event.data)
|
||||
// Flush the data immediately instead of buffering it for later.
|
||||
flusher.Flush()
|
||||
complete <- struct{}{}
|
||||
@@ -149,14 +156,17 @@ func (b *broker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (b *broker) subscribe(r *http.Request) client {
|
||||
user, _ := request.UserFrom(r.Context())
|
||||
ctx := r.Context()
|
||||
user, _ := request.UserFrom(ctx)
|
||||
clientUniqueId, _ := request.ClientUniqueIdFrom(ctx)
|
||||
c := client{
|
||||
id: uuid.NewString(),
|
||||
username: user.UserName,
|
||||
address: r.RemoteAddr,
|
||||
userAgent: r.UserAgent(),
|
||||
id: uuid.NewString(),
|
||||
username: user.UserName,
|
||||
address: r.RemoteAddr,
|
||||
userAgent: r.UserAgent(),
|
||||
clientUniqueId: clientUniqueId,
|
||||
}
|
||||
c.diode = newDiode(r.Context(), 1024, diodes.AlertFunc(func(missed int) {
|
||||
c.diode = newDiode(ctx, 1024, diodes.AlertFunc(func(missed int) {
|
||||
log.Trace("Dropped SSE events", "client", c.String(), "missed", missed)
|
||||
}))
|
||||
|
||||
@@ -169,6 +179,20 @@ func (b *broker) unsubscribe(c client) {
|
||||
b.unsubscribing <- c
|
||||
}
|
||||
|
||||
func (b *broker) shouldSend(msg message, c client) bool {
|
||||
clientUniqueId, originatedFromClient := request.ClientUniqueIdFrom(msg.senderCtx)
|
||||
if !originatedFromClient {
|
||||
return true
|
||||
}
|
||||
if c.clientUniqueId == clientUniqueId {
|
||||
return false
|
||||
}
|
||||
if username, ok := request.UsernameFrom(msg.senderCtx); ok {
|
||||
return username == c.username
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *broker) listen() {
|
||||
keepAlive := time.NewTicker(keepAliveFrequency)
|
||||
defer keepAlive.Stop()
|
||||
@@ -184,7 +208,7 @@ func (b *broker) listen() {
|
||||
log.Debug("Client added to event broker", "numClients", len(clients), "newClient", c.String())
|
||||
|
||||
// Send a serverStart event to new client
|
||||
c.diode.set(b.prepareMessage(&ServerStart{StartTime: consts.ServerStart}))
|
||||
c.diode.put(b.prepareMessage(&ServerStart{StartTime: consts.ServerStart}))
|
||||
|
||||
case c := <-b.unsubscribing:
|
||||
// A client has detached and we want to
|
||||
@@ -196,13 +220,15 @@ func (b *broker) listen() {
|
||||
// We got a new event from the outside!
|
||||
// Send event to all connected clients
|
||||
for c := range clients {
|
||||
log.Trace("Putting event on client's queue", "client", c.String(), "event", event)
|
||||
c.diode.set(event)
|
||||
if b.shouldSend(event, c) {
|
||||
log.Trace("Putting event on client's queue", "client", c.String(), "event", event)
|
||||
c.diode.put(event)
|
||||
}
|
||||
}
|
||||
|
||||
case ts := <-keepAlive.C:
|
||||
// Send a keep alive message every 15 seconds
|
||||
b.SendMessage(&KeepAlive{TS: ts.Unix()})
|
||||
b.SendMessage(context.Background(), &KeepAlive{TS: ts.Unix()})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Broker", func() {
|
||||
var b broker
|
||||
|
||||
BeforeEach(func() {
|
||||
b = broker{}
|
||||
})
|
||||
|
||||
Describe("shouldSend", func() {
|
||||
var c client
|
||||
var ctx context.Context
|
||||
BeforeEach(func() {
|
||||
ctx = context.Background()
|
||||
c = client{
|
||||
clientUniqueId: "1111",
|
||||
username: "janedoe",
|
||||
}
|
||||
})
|
||||
Context("request has clientUniqueId", func() {
|
||||
It("sends message for same username, different clientUniqueId", func() {
|
||||
ctx = request.WithClientUniqueId(ctx, "2222")
|
||||
ctx = request.WithUsername(ctx, "janedoe")
|
||||
m := message{senderCtx: ctx}
|
||||
Expect(b.shouldSend(m, c)).To(BeTrue())
|
||||
})
|
||||
It("does not send message for same username, same clientUniqueId", func() {
|
||||
ctx = request.WithClientUniqueId(ctx, "1111")
|
||||
ctx = request.WithUsername(ctx, "janedoe")
|
||||
m := message{senderCtx: ctx}
|
||||
Expect(b.shouldSend(m, c)).To(BeFalse())
|
||||
})
|
||||
It("does not send message for different username", func() {
|
||||
ctx = request.WithClientUniqueId(ctx, "3333")
|
||||
ctx = request.WithUsername(ctx, "johndoe")
|
||||
m := message{senderCtx: ctx}
|
||||
Expect(b.shouldSend(m, c)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
Context("request does not have clientUniqueId", func() {
|
||||
It("sends message for same username", func() {
|
||||
ctx = request.WithUsername(ctx, "janedoe")
|
||||
m := message{senderCtx: ctx}
|
||||
Expect(b.shouldSend(m, c)).To(BeTrue())
|
||||
})
|
||||
It("sends message for different username", func() {
|
||||
ctx = request.WithUsername(ctx, "johndoe")
|
||||
m := message{senderCtx: ctx}
|
||||
Expect(b.shouldSend(m, c)).To(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user