[MM-19914] Fix data races in WebSocketEvent (#13039)
* Make WebSocketEvent type immutable * Update code to use updated immutable WebSocketEvent type * Export WebSocketEvent fields and mark them as deprecated
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
9ab7bee0a6
Коммит
80dd2915db
@@ -1367,7 +1367,7 @@ func TestConvertChannelToPrivate(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_CHANNEL_CONVERTED && resp.Data["channel_id"].(string) == publicChannel2.Id {
|
||||
if resp.EventType() == model.WEBSOCKET_EVENT_CHANNEL_CONVERTED && resp.GetData()["channel_id"].(string) == publicChannel2.Id {
|
||||
eventHit = true
|
||||
}
|
||||
case <-stop:
|
||||
@@ -2426,7 +2426,7 @@ func TestRemoveChannelMember(t *testing.T) {
|
||||
})
|
||||
|
||||
wsr := <-wsClient.EventChannel
|
||||
require.Equal(t, model.WEBSOCKET_EVENT_HELLO, wsr.Event)
|
||||
require.Equal(t, model.WEBSOCKET_EVENT_HELLO, wsr.EventType())
|
||||
|
||||
// requirePost listens for websocket events and tries to find the post matching
|
||||
// the expected post's channel and message.
|
||||
@@ -2435,7 +2435,7 @@ func TestRemoveChannelMember(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case event := <-wsClient.EventChannel:
|
||||
postData, ok := event.Data["post"]
|
||||
postData, ok := event.GetData()["post"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case resp := <-webSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.Data["plugin_statuses"].([]interface{})) == 0 {
|
||||
if resp.EventType() == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.GetData()["plugin_statuses"].([]interface{})) == 0 {
|
||||
done <- true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -487,7 +487,7 @@ func TestCreatePostSendOutOfChannelMentions(t *testing.T) {
|
||||
for waiting {
|
||||
select {
|
||||
case event := <-WebSocketClient.EventChannel:
|
||||
require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.Event, "should not have ephemeral message event")
|
||||
require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.EventType(), "should not have ephemeral message event")
|
||||
case <-timeout:
|
||||
waiting = false
|
||||
}
|
||||
@@ -506,12 +506,12 @@ func TestCreatePostSendOutOfChannelMentions(t *testing.T) {
|
||||
for waiting {
|
||||
select {
|
||||
case event := <-WebSocketClient.EventChannel:
|
||||
if event.Event != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE {
|
||||
if event.EventType() != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE {
|
||||
// Ignore any other events
|
||||
continue
|
||||
}
|
||||
|
||||
wpost := model.PostFromJson(strings.NewReader(event.Data["post"].(string)))
|
||||
wpost := model.PostFromJson(strings.NewReader(event.GetData()["post"].(string)))
|
||||
|
||||
acm, ok := wpost.Props[model.PROPS_ADD_CHANNEL_MEMBER].(map[string]interface{})
|
||||
require.True(t, ok, "should have received ephemeral post with 'add_channel_member' in props")
|
||||
|
||||
@@ -259,6 +259,7 @@ func TestUpdatePreferencesWebsocket(t *testing.T) {
|
||||
Name: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
_, resp := th.Client.UpdatePreferences(userId, preferences)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
@@ -268,12 +269,12 @@ func TestUpdatePreferencesWebsocket(t *testing.T) {
|
||||
for waiting {
|
||||
select {
|
||||
case event := <-WebSocketClient.EventChannel:
|
||||
if event.Event != model.WEBSOCKET_EVENT_PREFERENCES_CHANGED {
|
||||
if event.EventType() != model.WEBSOCKET_EVENT_PREFERENCES_CHANGED {
|
||||
// Ignore any other events
|
||||
continue
|
||||
}
|
||||
|
||||
received, err := model.PreferencesFromJson(strings.NewReader(event.Data["preferences"].(string)))
|
||||
received, err := model.PreferencesFromJson(strings.NewReader(event.GetData()["preferences"].(string)))
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, p := range *preferences {
|
||||
@@ -376,12 +377,12 @@ func TestDeletePreferencesWebsocket(t *testing.T) {
|
||||
for waiting {
|
||||
select {
|
||||
case event := <-WebSocketClient.EventChannel:
|
||||
if event.Event != model.WEBSOCKET_EVENT_PREFERENCES_DELETED {
|
||||
if event.EventType() != model.WEBSOCKET_EVENT_PREFERENCES_DELETED {
|
||||
// Ignore any other events
|
||||
continue
|
||||
}
|
||||
|
||||
received, err := model.PreferencesFromJson(strings.NewReader(event.Data["preferences"].(string)))
|
||||
received, err := model.PreferencesFromJson(strings.NewReader(event.GetData()["preferences"].(string)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -323,11 +323,11 @@ func TestCreateUserWebSocketEvent(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case ev := <-userWSClient.EventChannel:
|
||||
if ev.Event == model.WEBSOCKET_EVENT_NEW_USER {
|
||||
if ev.EventType() == model.WEBSOCKET_EVENT_NEW_USER {
|
||||
userHasReceived = true
|
||||
}
|
||||
case ev := <-guestWSClient.EventChannel:
|
||||
if ev.Event == model.WEBSOCKET_EVENT_NEW_USER {
|
||||
if ev.EventType() == model.WEBSOCKET_EVENT_NEW_USER {
|
||||
guestHasReceived = true
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
@@ -1675,7 +1675,7 @@ func assertExpectedWebsocketEvent(t *testing.T, client *model.WebSocketClient, e
|
||||
select {
|
||||
case resp, ok := <-client.EventChannel:
|
||||
require.Truef(t, ok, "channel closed before receiving expected event %s", model.WEBSOCKET_EVENT_USER_UPDATED)
|
||||
if resp.Event == model.WEBSOCKET_EVENT_USER_UPDATED {
|
||||
if resp.EventType() == model.WEBSOCKET_EVENT_USER_UPDATED {
|
||||
test(resp)
|
||||
return
|
||||
}
|
||||
@@ -1687,7 +1687,7 @@ func assertExpectedWebsocketEvent(t *testing.T, client *model.WebSocketClient, e
|
||||
|
||||
func assertWebsocketEventUserUpdatedWithEmail(t *testing.T, client *model.WebSocketClient, email string) {
|
||||
assertExpectedWebsocketEvent(t, client, model.WEBSOCKET_EVENT_USER_UPDATED, func(event *model.WebSocketEvent) {
|
||||
eventUser, ok := event.Data["user"].(map[string]interface{})
|
||||
eventUser, ok := event.GetData()["user"].(map[string]interface{})
|
||||
require.True(t, ok, "expected user")
|
||||
userEmail, ok := eventUser["email"].(string)
|
||||
require.Truef(t, ok, "expected email %s, but got nil", email)
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestWebSocketEvent(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" {
|
||||
if resp.EventType() == model.WEBSOCKET_EVENT_TYPING && resp.GetData()["user_id"].(string) == "somerandomid" {
|
||||
eventHit = true
|
||||
}
|
||||
case <-stop:
|
||||
@@ -140,7 +140,7 @@ func TestWebSocketEvent(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_TYPING {
|
||||
if resp.EventType() == model.WEBSOCKET_EVENT_TYPING {
|
||||
eventHit = true
|
||||
}
|
||||
case <-stop:
|
||||
@@ -180,7 +180,7 @@ func TestCreateDirectChannelWithSocket(t *testing.T) {
|
||||
require.Equal(t, resp.Status, model.STATUS_OK, "should have responded OK to authentication challenge")
|
||||
|
||||
wsr := <-WebSocketClient.EventChannel
|
||||
require.Equal(t, wsr.Event, model.WEBSOCKET_EVENT_HELLO, "missing hello")
|
||||
require.Equal(t, wsr.EventType(), model.WEBSOCKET_EVENT_HELLO, "missing hello")
|
||||
|
||||
stop := make(chan bool)
|
||||
count := 0
|
||||
@@ -189,7 +189,7 @@ func TestCreateDirectChannelWithSocket(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case wsr := <-WebSocketClient.EventChannel:
|
||||
if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED {
|
||||
if wsr != nil && wsr.EventType() == model.WEBSOCKET_EVENT_DIRECT_ADDED {
|
||||
count = count + 1
|
||||
}
|
||||
|
||||
@@ -378,8 +378,8 @@ func TestWebSocketStatuses(t *testing.T) {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id {
|
||||
status := resp.Data["status"].(string)
|
||||
if resp.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.GetData()["user_id"].(string) == th.BasicUser.Id {
|
||||
status := resp.GetData()["status"].(string)
|
||||
if status == model.STATUS_ONLINE {
|
||||
onlineHit = true
|
||||
} else if status == model.STATUS_AWAY {
|
||||
|
||||
@@ -734,11 +734,9 @@ func (api *PluginAPI) KVList(page, perPage int) ([]string, *model.AppError) {
|
||||
}
|
||||
|
||||
func (api *PluginAPI) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) {
|
||||
api.app.Publish(&model.WebSocketEvent{
|
||||
Event: fmt.Sprintf("custom_%v_%v", api.id, event),
|
||||
Data: payload,
|
||||
Broadcast: broadcast,
|
||||
})
|
||||
ev := model.NewWebSocketEvent(fmt.Sprintf("custom_%v_%v", api.id, event), "", "", "", nil)
|
||||
ev = ev.SetBroadcast(broadcast).SetData(payload)
|
||||
api.app.Publish(ev)
|
||||
}
|
||||
|
||||
func (api *PluginAPI) HasPermissionTo(userId string, permission *model.Permission) bool {
|
||||
|
||||
@@ -79,7 +79,7 @@ func (a *App) notifyPluginStatusesChanged() error {
|
||||
// Notify any system admins.
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED, "", "", "", nil)
|
||||
message.Add("plugin_statuses", pluginStatuses)
|
||||
message.Broadcast.ContainsSensitiveData = true
|
||||
message.GetBroadcast().ContainsSensitiveData = true
|
||||
a.Publish(message)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1112,13 +1112,13 @@ func (a *App) sendUpdatedUserEvent(user model.User) {
|
||||
a.SanitizeProfile(adminCopyOfUser, true)
|
||||
adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
|
||||
adminMessage.Add("user", &adminCopyOfUser)
|
||||
adminMessage.Broadcast.ContainsSensitiveData = true
|
||||
adminMessage.GetBroadcast().ContainsSensitiveData = true
|
||||
a.Publish(adminMessage)
|
||||
|
||||
a.SanitizeProfile(&user, false)
|
||||
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil)
|
||||
message.Add("user", &user)
|
||||
message.Broadcast.ContainsSanitizedData = true
|
||||
message.GetBroadcast().ContainsSanitizedData = true
|
||||
a.Publish(message)
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ func (c *WebConn) writePump() {
|
||||
"websocket.slow: dropping message",
|
||||
mlog.String("user_id", c.UserId),
|
||||
mlog.String("type", msg.EventType()),
|
||||
mlog.String("channel_id", evt.Broadcast.ChannelId),
|
||||
mlog.String("channel_id", evt.GetBroadcast().ChannelId),
|
||||
)
|
||||
skipSend = true
|
||||
}
|
||||
@@ -194,9 +194,7 @@ func (c *WebConn) writePump() {
|
||||
if !skipSend {
|
||||
var msgBytes []byte
|
||||
if evtOk {
|
||||
cpyEvt := &model.WebSocketEvent{}
|
||||
*cpyEvt = *evt
|
||||
cpyEvt.Sequence = c.Sequence
|
||||
cpyEvt := evt.SetSequence(c.Sequence)
|
||||
msgBytes = []byte(cpyEvt.ToJson())
|
||||
c.Sequence++
|
||||
} else {
|
||||
@@ -209,7 +207,7 @@ func (c *WebConn) writePump() {
|
||||
"websocket.full",
|
||||
mlog.String("user_id", c.UserId),
|
||||
mlog.String("type", msg.EventType()),
|
||||
mlog.String("channel_id", evt.Broadcast.ChannelId),
|
||||
mlog.String("channel_id", evt.GetBroadcast().ChannelId),
|
||||
mlog.Int("size", len(msg.ToJson())),
|
||||
)
|
||||
} else {
|
||||
@@ -305,11 +303,11 @@ func (webCon *WebConn) shouldSendEventToGuest(msg *model.WebSocketEvent) bool {
|
||||
var userId string
|
||||
var canSee bool
|
||||
|
||||
switch msg.Event {
|
||||
switch msg.EventType() {
|
||||
case model.WEBSOCKET_EVENT_USER_UPDATED:
|
||||
userId = msg.Data["user"].(*model.User).Id
|
||||
userId = msg.GetData()["user"].(*model.User).Id
|
||||
case model.WEBSOCKET_EVENT_NEW_USER:
|
||||
userId = msg.Data["user_id"].(string)
|
||||
userId = msg.GetData()["user_id"].(string)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
@@ -332,7 +330,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||
// If the event contains sanitized data, only send to users that don't have permission to
|
||||
// see sensitive data. Prevents admin clients from receiving events with bad data
|
||||
var hasReadPrivateDataPermission *bool
|
||||
if msg.Broadcast.ContainsSanitizedData {
|
||||
if msg.GetBroadcast().ContainsSanitizedData {
|
||||
hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id))
|
||||
|
||||
if *hasReadPrivateDataPermission {
|
||||
@@ -341,7 +339,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||
}
|
||||
|
||||
// If the event contains sensitive data, only send to users with permission to see it
|
||||
if msg.Broadcast.ContainsSensitiveData {
|
||||
if msg.GetBroadcast().ContainsSensitiveData {
|
||||
if hasReadPrivateDataPermission == nil {
|
||||
hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id))
|
||||
}
|
||||
@@ -352,19 +350,19 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||
}
|
||||
|
||||
// If the event is destined to a specific user
|
||||
if len(msg.Broadcast.UserId) > 0 {
|
||||
return webCon.UserId == msg.Broadcast.UserId
|
||||
if len(msg.GetBroadcast().UserId) > 0 {
|
||||
return webCon.UserId == msg.GetBroadcast().UserId
|
||||
}
|
||||
|
||||
// if the user is omitted don't send the message
|
||||
if len(msg.Broadcast.OmitUsers) > 0 {
|
||||
if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
|
||||
if len(msg.GetBroadcast().OmitUsers) > 0 {
|
||||
if _, ok := msg.GetBroadcast().OmitUsers[webCon.UserId]; ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Only report events to users who are in the channel for the event
|
||||
if len(msg.Broadcast.ChannelId) > 0 {
|
||||
if len(msg.GetBroadcast().ChannelId) > 0 {
|
||||
if model.GetMillis()-webCon.LastAllChannelMembersTime > WEBCONN_MEMBER_CACHE_TIME {
|
||||
webCon.AllChannelMembers = nil
|
||||
webCon.LastAllChannelMembersTime = 0
|
||||
@@ -380,15 +378,15 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
|
||||
webCon.LastAllChannelMembersTime = model.GetMillis()
|
||||
}
|
||||
|
||||
if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok {
|
||||
if _, ok := webCon.AllChannelMembers[msg.GetBroadcast().ChannelId]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Only report events to users who are in the team for the event
|
||||
if len(msg.Broadcast.TeamId) > 0 {
|
||||
return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
|
||||
if len(msg.GetBroadcast().TeamId) > 0 {
|
||||
return webCon.IsMemberOfTeam(msg.GetBroadcast().TeamId)
|
||||
}
|
||||
|
||||
if webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" {
|
||||
|
||||
@@ -72,9 +72,9 @@ func TestWebConnShouldSendEvent(t *testing.T) {
|
||||
// needs more cases to get full coverage
|
||||
}
|
||||
|
||||
event := &model.WebSocketEvent{Event: "some_event"}
|
||||
event := model.NewWebSocketEvent("some_event", "", "", "", nil)
|
||||
for _, c := range cases {
|
||||
event.Broadcast = c.Broadcast
|
||||
event = event.SetBroadcast(c.Broadcast)
|
||||
assert.Equal(t, c.User1Expected, basicUserWc.ShouldSendEvent(event), c.Description)
|
||||
assert.Equal(t, c.User2Expected, basicUser2Wc.ShouldSendEvent(event), c.Description)
|
||||
assert.Equal(t, c.AdminExpected, adminUserWc.ShouldSendEvent(event), c.Description)
|
||||
|
||||
@@ -164,7 +164,7 @@ func (a *App) HubUnregister(webConn *WebConn) {
|
||||
|
||||
func (a *App) Publish(message *model.WebSocketEvent) {
|
||||
if metrics := a.Metrics; metrics != nil {
|
||||
metrics.IncrementWebsocketEvent(message.Event)
|
||||
metrics.IncrementWebsocketEvent(message.EventType())
|
||||
}
|
||||
|
||||
a.PublishSkipClusterSend(message)
|
||||
@@ -176,11 +176,11 @@ func (a *App) Publish(message *model.WebSocketEvent) {
|
||||
Data: message.ToJson(),
|
||||
}
|
||||
|
||||
if message.Event == model.WEBSOCKET_EVENT_POSTED ||
|
||||
message.Event == model.WEBSOCKET_EVENT_POST_EDITED ||
|
||||
message.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED ||
|
||||
message.Event == model.WEBSOCKET_EVENT_GROUP_ADDED ||
|
||||
message.Event == model.WEBSOCKET_EVENT_ADDED_TO_TEAM {
|
||||
if message.EventType() == model.WEBSOCKET_EVENT_POSTED ||
|
||||
message.EventType() == model.WEBSOCKET_EVENT_POST_EDITED ||
|
||||
message.EventType() == model.WEBSOCKET_EVENT_DIRECT_ADDED ||
|
||||
message.EventType() == model.WEBSOCKET_EVENT_GROUP_ADDED ||
|
||||
message.EventType() == model.WEBSOCKET_EVENT_ADDED_TO_TEAM {
|
||||
cm.SendType = model.CLUSTER_SEND_RELIABLE
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ func (a *App) Publish(message *model.WebSocketEvent) {
|
||||
}
|
||||
|
||||
func (a *App) PublishSkipClusterSend(message *model.WebSocketEvent) {
|
||||
if message.Broadcast.UserId != "" {
|
||||
hub := a.GetHubForUserId(message.Broadcast.UserId)
|
||||
if message.GetBroadcast().UserId != "" {
|
||||
hub := a.GetHubForUserId(message.GetBroadcast().UserId)
|
||||
if hub != nil {
|
||||
hub.Broadcast(message)
|
||||
}
|
||||
@@ -485,10 +485,10 @@ func (h *Hub) Start() {
|
||||
}
|
||||
case msg := <-h.broadcast:
|
||||
candidates := connections.All()
|
||||
if msg.Broadcast.UserId != "" {
|
||||
candidates = connections.ForUser(msg.Broadcast.UserId)
|
||||
if msg.GetBroadcast().UserId != "" {
|
||||
candidates = connections.ForUser(msg.GetBroadcast().UserId)
|
||||
}
|
||||
msg.PrecomputeJSON()
|
||||
msg = msg.PrecomputeJSON()
|
||||
for _, webCon := range candidates {
|
||||
if webCon.ShouldSendEvent(msg) {
|
||||
select {
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestHubStopRaceCondition(t *testing.T) {
|
||||
hub.UpdateActivity("userId", "sessionToken", 0)
|
||||
|
||||
for i := 0; i <= BROADCAST_QUEUE_SIZE; i++ {
|
||||
hub.Broadcast(&model.WebSocketEvent{})
|
||||
hub.Broadcast(model.NewWebSocketEvent("", "", "", "", nil))
|
||||
}
|
||||
|
||||
hub.InvalidateUser("userId")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -121,9 +122,9 @@ func (wsc *WebSocketClient) Listen() {
|
||||
return
|
||||
}
|
||||
|
||||
var event WebSocketEvent
|
||||
if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() {
|
||||
wsc.EventChannel <- &event
|
||||
event := WebSocketEventFromJson(bytes.NewReader(rawMsg))
|
||||
if event.IsValid() {
|
||||
wsc.EventChannel <- event
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -76,30 +76,41 @@ type precomputedWebSocketEventJSON struct {
|
||||
Broadcast json.RawMessage
|
||||
}
|
||||
|
||||
type WebSocketEvent struct {
|
||||
// webSocketEventJSON mirrors WebSocketEvent to make some of its unexported fields serializable
|
||||
type webSocketEventJSON struct {
|
||||
Event string `json:"event"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
Broadcast *WebsocketBroadcast `json:"broadcast"`
|
||||
Sequence int64 `json:"seq"`
|
||||
}
|
||||
|
||||
// **NOTE**: Direct access to WebSocketEvent fields is deprecated. They will be
|
||||
// made unexported in next major version release. Provided getter functions should be used instead.
|
||||
type WebSocketEvent struct {
|
||||
Event string // Deprecated: use EventType()
|
||||
Data map[string]interface{} // Deprecated: use GetData()
|
||||
Broadcast *WebsocketBroadcast // Deprecated: use GetBroadcast()
|
||||
Sequence int64 // Deprecated: use GetSequence()
|
||||
precomputedJSON *precomputedWebSocketEventJSON
|
||||
}
|
||||
|
||||
// PrecomputeJSON precomputes and stores the serialized JSON for all fields other than Sequence.
|
||||
// This makes ToJson much more efficient when sending the same event to multiple connections.
|
||||
func (m *WebSocketEvent) PrecomputeJSON() {
|
||||
event, _ := json.Marshal(m.Event)
|
||||
data, _ := json.Marshal(m.Data)
|
||||
broadcast, _ := json.Marshal(m.Broadcast)
|
||||
m.precomputedJSON = &precomputedWebSocketEventJSON{
|
||||
func (ev *WebSocketEvent) PrecomputeJSON() *WebSocketEvent {
|
||||
copy := ev.Copy()
|
||||
event, _ := json.Marshal(copy.Event)
|
||||
data, _ := json.Marshal(copy.Data)
|
||||
broadcast, _ := json.Marshal(copy.Broadcast)
|
||||
copy.precomputedJSON = &precomputedWebSocketEventJSON{
|
||||
Event: json.RawMessage(event),
|
||||
Data: json.RawMessage(data),
|
||||
Broadcast: json.RawMessage(broadcast),
|
||||
}
|
||||
return copy
|
||||
}
|
||||
|
||||
func (m *WebSocketEvent) Add(key string, value interface{}) {
|
||||
m.Data[key] = value
|
||||
func (ev *WebSocketEvent) Add(key string, value interface{}) {
|
||||
ev.Data[key] = value
|
||||
}
|
||||
|
||||
func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent {
|
||||
@@ -107,26 +118,85 @@ func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[st
|
||||
Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}}
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) IsValid() bool {
|
||||
return o.Event != ""
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) EventType() string {
|
||||
return o.Event
|
||||
}
|
||||
|
||||
func (o *WebSocketEvent) ToJson() string {
|
||||
if o.precomputedJSON != nil {
|
||||
return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, o.precomputedJSON.Event, o.precomputedJSON.Data, o.precomputedJSON.Broadcast, o.Sequence)
|
||||
func (ev *WebSocketEvent) Copy() *WebSocketEvent {
|
||||
copy := &WebSocketEvent{
|
||||
Event: ev.Event,
|
||||
Data: ev.Data,
|
||||
Broadcast: ev.Broadcast,
|
||||
Sequence: ev.Sequence,
|
||||
precomputedJSON: ev.precomputedJSON,
|
||||
}
|
||||
b, _ := json.Marshal(o)
|
||||
return copy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) GetData() map[string]interface{} {
|
||||
return ev.Data
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) GetBroadcast() *WebsocketBroadcast {
|
||||
return ev.Broadcast
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) GetSequence() int64 {
|
||||
return ev.Sequence
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetEvent(event string) *WebSocketEvent {
|
||||
copy := ev.Copy()
|
||||
copy.Event = event
|
||||
return copy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetData(data map[string]interface{}) *WebSocketEvent {
|
||||
copy := ev.Copy()
|
||||
copy.Data = data
|
||||
return copy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetBroadcast(broadcast *WebsocketBroadcast) *WebSocketEvent {
|
||||
copy := ev.Copy()
|
||||
copy.Broadcast = broadcast
|
||||
return copy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetSequence(seq int64) *WebSocketEvent {
|
||||
copy := ev.Copy()
|
||||
copy.Sequence = seq
|
||||
return copy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) IsValid() bool {
|
||||
return ev.Event != ""
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) EventType() string {
|
||||
return ev.Event
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) ToJson() string {
|
||||
if ev.precomputedJSON != nil {
|
||||
return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.Sequence)
|
||||
}
|
||||
b, _ := json.Marshal(webSocketEventJSON{
|
||||
ev.Event,
|
||||
ev.Data,
|
||||
ev.Broadcast,
|
||||
ev.Sequence,
|
||||
})
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func WebSocketEventFromJson(data io.Reader) *WebSocketEvent {
|
||||
var o *WebSocketEvent
|
||||
json.NewDecoder(data).Decode(&o)
|
||||
return o
|
||||
var ev WebSocketEvent
|
||||
var o webSocketEventJSON
|
||||
if err := json.NewDecoder(data).Decode(&o); err != nil {
|
||||
return nil
|
||||
}
|
||||
ev.Event = o.Event
|
||||
ev.Data = o.Data
|
||||
ev.Broadcast = o.Broadcast
|
||||
ev.Sequence = o.Sequence
|
||||
return &ev
|
||||
}
|
||||
|
||||
type WebSocketResponse struct {
|
||||
|
||||
@@ -17,14 +17,68 @@ func TestWebSocketEvent(t *testing.T) {
|
||||
json := m.ToJson()
|
||||
result := WebSocketEventFromJson(strings.NewReader(json))
|
||||
|
||||
badresult := WebSocketEventFromJson(strings.NewReader("junk"))
|
||||
require.Nil(t, badresult, "should not have parsed")
|
||||
|
||||
require.True(t, m.IsValid(), "should be valid")
|
||||
require.Equal(t, m.GetBroadcast().TeamId, result.GetBroadcast().TeamId, "Ids do not match")
|
||||
require.Equal(t, m.GetData()["RootId"], result.GetData()["RootId"], "Ids do not match")
|
||||
}
|
||||
|
||||
require.Equal(t, m.Broadcast.TeamId, result.Broadcast.TeamId, "Ids do not match")
|
||||
func TestWebSocketEventImmutable(t *testing.T) {
|
||||
m := NewWebSocketEvent("some_event", NewId(), NewId(), NewId(), nil)
|
||||
|
||||
require.Equal(t, m.Data["RootId"], result.Data["RootId"], "Ids do not match")
|
||||
new := m.SetEvent("new_event")
|
||||
if new == m {
|
||||
require.Fail(t, "pointers should not be the same")
|
||||
}
|
||||
require.NotEqual(t, m.Event, new.Event)
|
||||
require.Equal(t, new.Event, "new_event")
|
||||
require.Equal(t, new.Event, new.EventType())
|
||||
|
||||
new = m.SetSequence(45)
|
||||
if new == m {
|
||||
require.Fail(t, "pointers should not be the same")
|
||||
}
|
||||
require.NotEqual(t, m.Sequence, new.Sequence)
|
||||
require.Equal(t, new.Sequence, int64(45))
|
||||
require.Equal(t, new.Sequence, new.GetSequence())
|
||||
|
||||
broadcast := &WebsocketBroadcast{}
|
||||
new = m.SetBroadcast(broadcast)
|
||||
if new == m {
|
||||
require.Fail(t, "pointers should not be the same")
|
||||
}
|
||||
require.NotEqual(t, m.Broadcast, new.Broadcast)
|
||||
require.Equal(t, new.Broadcast, broadcast)
|
||||
require.Equal(t, new.Broadcast, new.GetBroadcast())
|
||||
|
||||
data := map[string]interface{}{
|
||||
"key": "val",
|
||||
"key2": "val2",
|
||||
}
|
||||
new = m.SetData(data)
|
||||
if new == m {
|
||||
require.Fail(t, "pointers should not be the same")
|
||||
}
|
||||
require.NotEqual(t, m, new)
|
||||
require.Equal(t, new.Data, data)
|
||||
require.Equal(t, new.Data, new.GetData())
|
||||
|
||||
copy := m.Copy()
|
||||
if copy == m {
|
||||
require.Fail(t, "pointers should not be the same")
|
||||
}
|
||||
require.Equal(t, m, copy)
|
||||
}
|
||||
|
||||
func TestWebSocketEventFromJson(t *testing.T) {
|
||||
ev := WebSocketEventFromJson(strings.NewReader("junk"))
|
||||
require.Nil(t, ev, "should not have parsed")
|
||||
data := `{"event": "test", "data": {"key": "val"}, "seq": 45, "broadcast": {"user_id": "userid"}}`
|
||||
ev = WebSocketEventFromJson(strings.NewReader(data))
|
||||
require.NotNil(t, ev, "should have parsed")
|
||||
require.Equal(t, ev.Event, "test")
|
||||
require.Equal(t, ev.Sequence, int64(45))
|
||||
require.Equal(t, ev.Data, map[string]interface{}{"key": "val"})
|
||||
require.Equal(t, ev.Broadcast, &WebsocketBroadcast{UserId: "userid"})
|
||||
}
|
||||
|
||||
func TestWebSocketResponse(t *testing.T) {
|
||||
@@ -46,7 +100,7 @@ func TestWebSocketResponse(t *testing.T) {
|
||||
|
||||
func TestWebSocketEvent_PrecomputeJSON(t *testing.T) {
|
||||
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
|
||||
event.Sequence = 7
|
||||
event = event.SetSequence(7)
|
||||
|
||||
before := event.ToJson()
|
||||
event.PrecomputeJSON()
|
||||
@@ -60,7 +114,7 @@ var stringSink string
|
||||
func BenchmarkWebSocketEvent_ToJson(b *testing.B) {
|
||||
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
|
||||
for i := 0; i < 100; i++ {
|
||||
event.Data[NewId()] = NewId()
|
||||
event.GetData()[NewId()] = NewId()
|
||||
}
|
||||
|
||||
b.Run("SerializedNTimes", func(b *testing.B) {
|
||||
|
||||
Ссылка в новой задаче
Block a user