[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
Этот коммит содержится в:
Claudio Costa
2019-12-24 09:32:11 +01:00
коммит произвёл GitHub
родитель 9ab7bee0a6
Коммит 80dd2915db
16 изменённых файлов: 216 добавлений и 94 удалений

Просмотреть файл

@@ -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) {