Merge branch 'master' into advanced-permissions-phase-1
Этот коммит содержится в:
@@ -198,6 +198,10 @@ func (c *Client4) GetTestEmailRoute() string {
|
||||
return fmt.Sprintf("/email/test")
|
||||
}
|
||||
|
||||
func (c *Client4) GetTestS3Route() string {
|
||||
return fmt.Sprintf("/file/s3_test")
|
||||
}
|
||||
|
||||
func (c *Client4) GetDatabaseRoute() string {
|
||||
return fmt.Sprintf("/database")
|
||||
}
|
||||
@@ -1919,7 +1923,8 @@ func (c *Client4) DoPostAction(postId, actionId string) (bool, *Response) {
|
||||
|
||||
// File Section
|
||||
|
||||
// UploadFile will upload a file to a channel, to be later attached to a post.
|
||||
// UploadFile will upload a file to a channel using a multipart request, to be later attached to a post.
|
||||
// This method is functionally equivalent to Client4.UploadFileAsRequestBody.
|
||||
func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
@@ -1943,6 +1948,12 @@ func (c *Client4) UploadFile(data []byte, channelId string, filename string) (*F
|
||||
return c.DoUploadFile(c.GetFilesRoute(), body.Bytes(), writer.FormDataContentType())
|
||||
}
|
||||
|
||||
// UploadFileAsRequestBody will upload a file to a channel as the body of a request, to be later attached
|
||||
// to a post. This method is functionally equivalent to Client4.UploadFile.
|
||||
func (c *Client4) UploadFileAsRequestBody(data []byte, channelId string, filename string) (*FileUploadResponse, *Response) {
|
||||
return c.DoUploadFile(c.GetFilesRoute()+fmt.Sprintf("?channel_id=%v&filename=%v", url.QueryEscape(channelId), url.QueryEscape(filename)), data, http.DetectContentType(data))
|
||||
}
|
||||
|
||||
// GetFile gets the bytes for a file by id.
|
||||
func (c *Client4) GetFile(fileId string) ([]byte, *Response) {
|
||||
if r, err := c.DoApiGet(c.GetFileRoute(fileId), ""); err != nil {
|
||||
@@ -2089,6 +2100,16 @@ func (c *Client4) TestEmail() (bool, *Response) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3Connection will attempt to connect to the AWS S3.
|
||||
func (c *Client4) TestS3Connection(config *Config) (bool, *Response) {
|
||||
if r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson()); err != nil {
|
||||
return false, BuildErrorResponse(r, err)
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig will retrieve the server config with some sanitized items.
|
||||
func (c *Client4) GetConfig() (*Config, *Response) {
|
||||
if r, err := c.DoApiGet(c.GetConfigRoute(), ""); err != nil {
|
||||
@@ -3343,3 +3364,56 @@ func (c *Client4) DeactivatePlugin(id string) (bool, *Response) {
|
||||
return CheckStatusOK(r), BuildResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTeamIcon sets team icon of the team
|
||||
func (c *Client4) SetTeamIcon(teamId string, data []byte) (bool, *Response) {
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
if part, err := writer.CreateFormFile("image", "teamIcon.png"); err != nil {
|
||||
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
} else if _, err = io.Copy(part, bytes.NewBuffer(data)); err != nil {
|
||||
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.no_file.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return false, &Response{Error: NewAppError("SetTeamIcon", "model.client.set_team_icon.writer.app_error", nil, err.Error(), http.StatusBadRequest)}
|
||||
}
|
||||
|
||||
rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetTeamRoute(teamId)+"/image", bytes.NewReader(body.Bytes()))
|
||||
rq.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
rq.Close = true
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
|
||||
}
|
||||
|
||||
if rp, err := c.HttpClient.Do(rq); err != nil || rp == nil {
|
||||
// set to http.StatusForbidden(403)
|
||||
return false, &Response{StatusCode: http.StatusForbidden, Error: NewAppError(c.GetTeamRoute(teamId)+"/image", "model.client.connecting.app_error", nil, err.Error(), 403)}
|
||||
} else {
|
||||
defer closeBody(rp)
|
||||
|
||||
if rp.StatusCode >= 300 {
|
||||
return false, BuildErrorResponse(rp, AppErrorFromJson(rp.Body))
|
||||
} else {
|
||||
return CheckStatusOK(rp), BuildResponse(rp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetTeamIcon gets the team icon of the team
|
||||
func (c *Client4) GetTeamIcon(teamId, etag string) ([]byte, *Response) {
|
||||
if r, err := c.DoApiGet(c.GetTeamRoute(teamId)+"/image", etag); err != nil {
|
||||
return nil, BuildErrorResponse(r, err)
|
||||
} else {
|
||||
defer closeBody(r)
|
||||
|
||||
if data, err := ioutil.ReadAll(r.Body); err != nil {
|
||||
return nil, BuildErrorResponse(r, NewAppError("GetTeamIcon", "model.client.get_team_icon.app_error", nil, err.Error(), r.StatusCode))
|
||||
} else {
|
||||
return data, BuildResponse(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ const (
|
||||
|
||||
type ServiceSettings struct {
|
||||
SiteURL *string
|
||||
WebsocketURL *string
|
||||
LicenseFileLocation *string
|
||||
ListenAddress *string
|
||||
ConnectionSecurity *string
|
||||
@@ -196,6 +197,7 @@ type ServiceSettings struct {
|
||||
EnforceMultifactorAuthentication *bool
|
||||
EnableUserAccessTokens *bool
|
||||
AllowCorsFrom *string
|
||||
AllowCookiesForSubdomains *bool
|
||||
SessionLengthWebInDays *int
|
||||
SessionLengthMobileInDays *int
|
||||
SessionLengthSSOInDays *int
|
||||
@@ -232,6 +234,10 @@ func (s *ServiceSettings) SetDefaults() {
|
||||
s.SiteURL = NewString(SERVICE_SETTINGS_DEFAULT_SITE_URL)
|
||||
}
|
||||
|
||||
if s.WebsocketURL == nil {
|
||||
s.WebsocketURL = NewString("")
|
||||
}
|
||||
|
||||
if s.LicenseFileLocation == nil {
|
||||
s.LicenseFileLocation = NewString("")
|
||||
}
|
||||
@@ -388,6 +394,10 @@ func (s *ServiceSettings) SetDefaults() {
|
||||
s.AllowCorsFrom = NewString(SERVICE_SETTINGS_DEFAULT_ALLOW_CORS_FROM)
|
||||
}
|
||||
|
||||
if s.AllowCookiesForSubdomains == nil {
|
||||
s.AllowCookiesForSubdomains = NewBool(false)
|
||||
}
|
||||
|
||||
if s.WebserverMode == nil {
|
||||
s.WebserverMode = NewString("gzip")
|
||||
} else if *s.WebserverMode == "regular" {
|
||||
@@ -1782,6 +1792,10 @@ func (o *Config) IsValid() *AppError {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.cluster_email_batching.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(*o.ServiceSettings.SiteURL) == 0 && *o.ServiceSettings.AllowCookiesForSubdomains {
|
||||
return NewAppError("Config.IsValid", "Allowing cookies for subdomains requires SiteURL to be set.", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := o.TeamSettings.isValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -2089,6 +2103,12 @@ func (ss *ServiceSettings) isValid() *AppError {
|
||||
}
|
||||
}
|
||||
|
||||
if len(*ss.WebsocketURL) != 0 {
|
||||
if _, err := url.ParseRequestURI(*ss.WebsocketURL); err != nil {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.websocket_url.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
if len(*ss.ListenAddress) == 0 {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.listen_address.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -15,89 +14,57 @@ type ScheduledTask struct {
|
||||
Name string `json:"name"`
|
||||
Interval time.Duration `json:"interval"`
|
||||
Recurring bool `json:"recurring"`
|
||||
function TaskFunc
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
var taskMutex = sync.Mutex{}
|
||||
var tasks = make(map[string]*ScheduledTask)
|
||||
|
||||
func addTask(task *ScheduledTask) {
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
tasks[task.Name] = task
|
||||
}
|
||||
|
||||
func removeTaskByName(name string) {
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
delete(tasks, name)
|
||||
}
|
||||
|
||||
func GetTaskByName(name string) *ScheduledTask {
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
if task, ok := tasks[name]; ok {
|
||||
return task
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAllTasks() *map[string]*ScheduledTask {
|
||||
taskMutex.Lock()
|
||||
defer taskMutex.Unlock()
|
||||
return &tasks
|
||||
function func()
|
||||
cancel chan struct{}
|
||||
cancelled chan struct{}
|
||||
}
|
||||
|
||||
func CreateTask(name string, function TaskFunc, timeToExecution time.Duration) *ScheduledTask {
|
||||
task := &ScheduledTask{
|
||||
Name: name,
|
||||
Interval: timeToExecution,
|
||||
Recurring: false,
|
||||
function: function,
|
||||
}
|
||||
|
||||
taskRunner := func() {
|
||||
go task.function()
|
||||
removeTaskByName(task.Name)
|
||||
}
|
||||
|
||||
task.timer = time.AfterFunc(timeToExecution, taskRunner)
|
||||
|
||||
addTask(task)
|
||||
|
||||
return task
|
||||
return createTask(name, function, timeToExecution, false)
|
||||
}
|
||||
|
||||
func CreateRecurringTask(name string, function TaskFunc, interval time.Duration) *ScheduledTask {
|
||||
return createTask(name, function, interval, true)
|
||||
}
|
||||
|
||||
func createTask(name string, function TaskFunc, interval time.Duration, recurring bool) *ScheduledTask {
|
||||
task := &ScheduledTask{
|
||||
Name: name,
|
||||
Interval: interval,
|
||||
Recurring: true,
|
||||
Recurring: recurring,
|
||||
function: function,
|
||||
cancel: make(chan struct{}),
|
||||
cancelled: make(chan struct{}),
|
||||
}
|
||||
|
||||
taskRecurer := func() {
|
||||
go task.function()
|
||||
task.timer.Reset(task.Interval)
|
||||
}
|
||||
go func() {
|
||||
defer close(task.cancelled)
|
||||
|
||||
task.timer = time.AfterFunc(interval, taskRecurer)
|
||||
ticker := time.NewTicker(interval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
}()
|
||||
|
||||
addTask(task)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
function()
|
||||
case <-task.cancel:
|
||||
return
|
||||
}
|
||||
|
||||
if !task.Recurring {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
func (task *ScheduledTask) Cancel() {
|
||||
task.timer.Stop()
|
||||
removeTaskByName(task.Name)
|
||||
}
|
||||
|
||||
// Executes the task immediatly. A recurring task will be run regularally after interval.
|
||||
func (task *ScheduledTask) Execute() {
|
||||
task.function()
|
||||
task.timer.Reset(task.Interval)
|
||||
close(task.cancel)
|
||||
<-task.cancelled
|
||||
}
|
||||
|
||||
func (task *ScheduledTask) String() string {
|
||||
|
||||
@@ -4,185 +4,72 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateTask(t *testing.T) {
|
||||
TASK_NAME := "Test Task"
|
||||
TASK_TIME := time.Second * 3
|
||||
TASK_TIME := time.Second * 2
|
||||
|
||||
testValue := 0
|
||||
executionCount := new(int32)
|
||||
testFunc := func() {
|
||||
testValue = 1
|
||||
atomic.AddInt32(executionCount, 1)
|
||||
}
|
||||
|
||||
task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
|
||||
if testValue != 0 {
|
||||
t.Fatal("Unexpected execuition of task")
|
||||
}
|
||||
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
|
||||
|
||||
time.Sleep(TASK_TIME + time.Second)
|
||||
|
||||
if testValue != 1 {
|
||||
t.Fatal("Task did not execute")
|
||||
}
|
||||
|
||||
if task.Name != TASK_NAME {
|
||||
t.Fatal("Bad name")
|
||||
}
|
||||
|
||||
if task.Interval != TASK_TIME {
|
||||
t.Fatal("Bad interval")
|
||||
}
|
||||
|
||||
if task.Recurring {
|
||||
t.Fatal("should not reccur")
|
||||
}
|
||||
assert.EqualValues(t, 1, atomic.LoadInt32(executionCount))
|
||||
assert.Equal(t, TASK_NAME, task.Name)
|
||||
assert.Equal(t, TASK_TIME, task.Interval)
|
||||
assert.False(t, task.Recurring)
|
||||
}
|
||||
|
||||
func TestCreateRecurringTask(t *testing.T) {
|
||||
TASK_NAME := "Test Recurring Task"
|
||||
TASK_TIME := time.Second * 3
|
||||
TASK_TIME := time.Second * 2
|
||||
|
||||
testValue := 0
|
||||
executionCount := new(int32)
|
||||
testFunc := func() {
|
||||
testValue += 1
|
||||
atomic.AddInt32(executionCount, 1)
|
||||
}
|
||||
|
||||
task := CreateRecurringTask(TASK_NAME, testFunc, TASK_TIME)
|
||||
if testValue != 0 {
|
||||
t.Fatal("Unexpected execuition of task")
|
||||
}
|
||||
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
|
||||
|
||||
time.Sleep(TASK_TIME + time.Second)
|
||||
|
||||
if testValue != 1 {
|
||||
t.Fatal("Task did not execute")
|
||||
}
|
||||
assert.EqualValues(t, 1, atomic.LoadInt32(executionCount))
|
||||
|
||||
time.Sleep(TASK_TIME)
|
||||
|
||||
if testValue != 2 {
|
||||
t.Fatal("Task did not re-execute")
|
||||
}
|
||||
|
||||
if task.Name != TASK_NAME {
|
||||
t.Fatal("Bad name")
|
||||
}
|
||||
|
||||
if task.Interval != TASK_TIME {
|
||||
t.Fatal("Bad interval")
|
||||
}
|
||||
|
||||
if !task.Recurring {
|
||||
t.Fatal("should reccur")
|
||||
}
|
||||
assert.EqualValues(t, 2, atomic.LoadInt32(executionCount))
|
||||
assert.Equal(t, TASK_NAME, task.Name)
|
||||
assert.Equal(t, TASK_TIME, task.Interval)
|
||||
assert.True(t, task.Recurring)
|
||||
|
||||
task.Cancel()
|
||||
}
|
||||
|
||||
func TestCancelTask(t *testing.T) {
|
||||
TASK_NAME := "Test Task"
|
||||
TASK_TIME := time.Second * 3
|
||||
TASK_TIME := time.Second
|
||||
|
||||
testValue := 0
|
||||
executionCount := new(int32)
|
||||
testFunc := func() {
|
||||
testValue = 1
|
||||
atomic.AddInt32(executionCount, 1)
|
||||
}
|
||||
|
||||
task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
|
||||
if testValue != 0 {
|
||||
t.Fatal("Unexpected execuition of task")
|
||||
}
|
||||
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
|
||||
task.Cancel()
|
||||
|
||||
time.Sleep(TASK_TIME + time.Second)
|
||||
|
||||
if testValue != 0 {
|
||||
t.Fatal("Unexpected execuition of task")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllTasks(t *testing.T) {
|
||||
doNothing := func() {}
|
||||
|
||||
CreateTask("Task1", doNothing, time.Hour)
|
||||
CreateTask("Task2", doNothing, time.Second)
|
||||
CreateRecurringTask("Task3", doNothing, time.Second)
|
||||
task4 := CreateRecurringTask("Task4", doNothing, time.Second)
|
||||
|
||||
task4.Cancel()
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
tasks := *GetAllTasks()
|
||||
if len(tasks) != 2 {
|
||||
t.Fatal("Wrong number of tasks got: ", len(tasks))
|
||||
}
|
||||
for _, task := range tasks {
|
||||
if task.Name != "Task1" && task.Name != "Task3" {
|
||||
t.Fatal("Wrong tasks")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteTask(t *testing.T) {
|
||||
TASK_NAME := "Test Task"
|
||||
TASK_TIME := time.Second * 5
|
||||
|
||||
testValue := 0
|
||||
testFunc := func() {
|
||||
testValue += 1
|
||||
}
|
||||
|
||||
task := CreateTask(TASK_NAME, testFunc, TASK_TIME)
|
||||
if testValue != 0 {
|
||||
t.Fatal("Unexpected execuition of task")
|
||||
}
|
||||
|
||||
task.Execute()
|
||||
|
||||
if testValue != 1 {
|
||||
t.Fatal("Task did not execute")
|
||||
}
|
||||
|
||||
time.Sleep(TASK_TIME + time.Second)
|
||||
|
||||
if testValue != 2 {
|
||||
t.Fatal("Task re-executed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteTaskRecurring(t *testing.T) {
|
||||
TASK_NAME := "Test Recurring Task"
|
||||
TASK_TIME := time.Second * 5
|
||||
|
||||
testValue := 0
|
||||
testFunc := func() {
|
||||
testValue += 1
|
||||
}
|
||||
|
||||
task := CreateRecurringTask(TASK_NAME, testFunc, TASK_TIME)
|
||||
if testValue != 0 {
|
||||
t.Fatal("Unexpected execuition of task")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
task.Execute()
|
||||
if testValue != 1 {
|
||||
t.Fatal("Task did not execute")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
if testValue != 1 {
|
||||
t.Fatal("Task should not have executed before 5 seconds")
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
if testValue != 2 {
|
||||
t.Fatal("Task did not re-execute after forced execution")
|
||||
}
|
||||
assert.EqualValues(t, 0, atomic.LoadInt32(executionCount))
|
||||
}
|
||||
|
||||
@@ -26,19 +26,20 @@ const (
|
||||
)
|
||||
|
||||
type Team struct {
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Email string `json:"email"`
|
||||
Type string `json:"type"`
|
||||
CompanyName string `json:"company_name"`
|
||||
AllowedDomains string `json:"allowed_domains"`
|
||||
InviteId string `json:"invite_id"`
|
||||
AllowOpenInvite bool `json:"allow_open_invite"`
|
||||
Id string `json:"id"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Email string `json:"email"`
|
||||
Type string `json:"type"`
|
||||
CompanyName string `json:"company_name"`
|
||||
AllowedDomains string `json:"allowed_domains"`
|
||||
InviteId string `json:"invite_id"`
|
||||
AllowOpenInvite bool `json:"allow_open_invite"`
|
||||
LastTeamIconUpdate int64 `json:"last_team_icon_update,omitempty"`
|
||||
}
|
||||
|
||||
type TeamPatch struct {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
// It should be maitained in chronological order with most current
|
||||
// release at the front of the list.
|
||||
var versions = []string{
|
||||
"4.7.1",
|
||||
"4.7.0",
|
||||
"4.6.0",
|
||||
"4.5.0",
|
||||
|
||||
@@ -5,6 +5,7 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -59,11 +60,32 @@ type WebsocketBroadcast struct {
|
||||
TeamId string `json:"team_id"` // broadcast only occurs for users in this team
|
||||
}
|
||||
|
||||
type precomputedWebSocketEventJSON struct {
|
||||
Event json.RawMessage
|
||||
Data json.RawMessage
|
||||
Broadcast json.RawMessage
|
||||
}
|
||||
|
||||
type WebSocketEvent struct {
|
||||
Event string `json:"event"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
Broadcast *WebsocketBroadcast `json:"broadcast"`
|
||||
Sequence int64 `json:"seq"`
|
||||
|
||||
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{
|
||||
Event: json.RawMessage(event),
|
||||
Data: json.RawMessage(data),
|
||||
Broadcast: json.RawMessage(broadcast),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *WebSocketEvent) Add(key string, value interface{}) {
|
||||
@@ -84,6 +106,9 @@ func (o *WebSocketEvent) EventType() string {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
b, _ := json.Marshal(o)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ package model
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWebSocketEvent(t *testing.T) {
|
||||
@@ -54,3 +56,49 @@ func TestWebSocketResponse(t *testing.T) {
|
||||
t.Fatal("Ids do not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebSocketEvent_PrecomputeJSON(t *testing.T) {
|
||||
event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil)
|
||||
event.Sequence = 7
|
||||
|
||||
before := event.ToJson()
|
||||
event.PrecomputeJSON()
|
||||
after := event.ToJson()
|
||||
|
||||
assert.JSONEq(t, before, after)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
b.Run("SerializedNTimes", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
stringSink = event.ToJson()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrecomputedNTimes", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
event.PrecomputeJSON()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("PrecomputedAndSerializedNTimes", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
event.PrecomputeJSON()
|
||||
stringSink = event.ToJson()
|
||||
}
|
||||
})
|
||||
|
||||
event.PrecomputeJSON()
|
||||
b.Run("PrecomputedOnceAndSerializedNTimes", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
stringSink = event.ToJson()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user