diff --git a/socketserver/server/backend.go b/socketserver/server/backend.go index b7577f6f..eb3dbe55 100644 --- a/socketserver/server/backend.go +++ b/socketserver/server/backend.go @@ -14,9 +14,10 @@ import ( "strings" "time" + "sync" + "github.com/pmylund/go-cache" "golang.org/x/crypto/nacl/box" - "sync" ) const bPathAnnounceStartup = "/startup" @@ -80,7 +81,7 @@ func getCacheKey(remoteCommand, data string) string { // HTTPBackendUncachedPublish handles the /uncached_pub route. // The backend can POST here to publish a message to clients with no caching. // The POST arguments are `cmd`, `args`, `channel`, and `scope`. -// The `scope` argument is required because no attempt is made to infer the scope from the command, unlike /cached_pub. +// If "scope" is "global", then "channel" is not used. func HTTPBackendUncachedPublish(w http.ResponseWriter, r *http.Request) { r.ParseForm() formData, err := Backend.UnsealRequest(r.Form) @@ -95,14 +96,12 @@ func HTTPBackendUncachedPublish(w http.ResponseWriter, r *http.Request) { channel := formData.Get("channel") scope := formData.Get("scope") - target := MessageTargetTypeByName(scope) - if cmd == "" { w.WriteHeader(422) fmt.Fprintf(w, "Error: cmd cannot be blank") return } - if channel == "" && (target == MsgTargetTypeChat || target == MsgTargetTypeMultichat) { + if channel == "" && scope != "global" { w.WriteHeader(422) fmt.Fprintf(w, "Error: channel must be specified") return @@ -112,19 +111,11 @@ func HTTPBackendUncachedPublish(w http.ResponseWriter, r *http.Request) { cm.parseOrigArguments() var count int - switch target { - case MsgTargetTypeChat: - count = PublishToChannel(channel, cm) - case MsgTargetTypeMultichat: - count = PublishToMultiple(strings.Split(channel, ","), cm) - case MsgTargetTypeGlobal: - count = PublishToAll(cm) - case MsgTargetTypeInvalid: - fallthrough + switch scope { default: - w.WriteHeader(422) - fmt.Fprint(w, "Invalid 'scope'. must be chat, multichat, channel, or global") - return + count = PublishToMultiple(strings.Split(channel, ","), cm) + case "global": + count = PublishToAll(cm) } fmt.Fprint(w, count) } @@ -294,7 +285,7 @@ func (backend *backendInfo) sendTopicNotice(topic string, added bool) error { if resp.StatusCode < 200 || resp.StatusCode > 299 { respBytes, err := ioutil.ReadAll(resp.Body) if err != nil { - return ErrBackendNotOK{Code: resp.StatusCode, Response: fmt.Sprintf("(error reading non-2xx response): %s", err.Error()} + return ErrBackendNotOK{Code: resp.StatusCode, Response: fmt.Sprintf("(error reading non-2xx response): %s", err.Error())} } return ErrBackendNotOK{Code: resp.StatusCode, Response: string(respBytes)} } diff --git a/socketserver/server/publisher.go b/socketserver/server/publisher.go index 7a1726f9..305140f9 100644 --- a/socketserver/server/publisher.go +++ b/socketserver/server/publisher.go @@ -1,7 +1,6 @@ package server import ( - "errors" "fmt" "net/http" "strconv" @@ -10,64 +9,6 @@ import ( "time" ) -type PushCommandCacheInfo struct { - Caching BacklogCacheType - Target MessageTargetType -} - -// S2CCommandsCacheInfo details what the behavior is of each command that can be sent to /cached_pub. -var S2CCommandsCacheInfo = map[Command]PushCommandCacheInfo{ - /// Channel data - // follow_sets: extra emote sets included in the chat - // follow_buttons: extra follow buttons below the stream - "follow_sets": {CacheTypePersistent, MsgTargetTypeChat}, - "follow_buttons": {CacheTypePersistent, MsgTargetTypeChat}, - "srl_race": {CacheTypeLastOnly, MsgTargetTypeMultichat}, - - /// Chatter/viewer counts - "chatters": {CacheTypeLastOnly, MsgTargetTypeChat}, - "viewers": {CacheTypeLastOnly, MsgTargetTypeChat}, -} - -var PersistentCachingCommands = []Command{"follow_sets", "follow_buttons"} -var HourlyCachingCommands = []Command{"chatters", "viewers"} /* srl_race */ - -type BacklogCacheType int - -const ( - // CacheTypeInvalid is the sentinel value. - CacheTypeInvalid BacklogCacheType = iota - // CacheTypeNever is a message that cannot be cached. - CacheTypeNever - // CacheTypeLastOnly means to save only the last copy of this message, - // and always send it when the backlog is requested. - CacheTypeLastOnly - // CacheTypePersistent means to save the last copy of this message, - // and always send it when the backlog is requested, but do not clean it periodically. - CacheTypePersistent -) - -type MessageTargetType int - -const ( - // MsgTargetTypeInvalid is the sentinel value. - MsgTargetTypeInvalid MessageTargetType = iota - // MsgTargetTypeChat is a message is targeted to all users in a particular chat. - MsgTargetTypeChat - // MsgTargetTypeMultichat is a message is targeted to all users in multiple chats. - MsgTargetTypeMultichat - // MsgTargetTypeGlobal is a message sent to all FFZ users. - MsgTargetTypeGlobal -) - -// note: see types.go for methods on these - -// ErrorUnrecognizedCacheType is returned by BacklogCacheType.UnmarshalJSON() -var ErrorUnrecognizedCacheType = errors.New("Invalid value for cachetype") - -// ErrorUnrecognizedTargetType is returned by MessageTargetType.UnmarshalJSON() -var ErrorUnrecognizedTargetType = errors.New("Invalid value for message target") - type LastSavedMessage struct { Timestamp time.Time Data string @@ -80,19 +21,11 @@ type LastSavedMessage struct { var CachedLastMessages = make(map[Command]map[string]LastSavedMessage) var CachedLSMLock sync.RWMutex -// PersistentLastMessages is of CacheTypePersistent. Never cleaned. -var PersistentLastMessages = CachedLastMessages -var PersistentLSMLock = CachedLSMLock - // DumpBacklogData drops all /cached_pub data. func DumpBacklogData() { CachedLSMLock.Lock() CachedLastMessages = make(map[Command]map[string]LastSavedMessage) CachedLSMLock.Unlock() - - //PersistentLSMLock.Lock() - //PersistentLastMessages = make(map[Command]map[string]LastSavedMessage) - //PersistentLSMLock.Unlock() } // SendBacklogForNewClient sends any backlog data relevant to a new client. @@ -104,26 +37,8 @@ func SendBacklogForNewClient(client *ClientInfo) { copy(curChannels, client.CurrentChannels) client.Mutex.Unlock() - PersistentLSMLock.RLock() - for _, cmd := range GetCommandsOfType(CacheTypePersistent) { - chanMap := PersistentLastMessages[cmd] - if chanMap == nil { - continue - } - for _, channel := range curChannels { - msg, ok := chanMap[channel] - if ok { - msg := ClientMessage{MessageID: -1, Command: cmd, origArguments: msg.Data} - msg.parseOrigArguments() - client.MessageChannel <- msg - } - } - } - PersistentLSMLock.RUnlock() - CachedLSMLock.RLock() - for _, cmd := range GetCommandsOfType(CacheTypeLastOnly) { - chanMap := CachedLastMessages[cmd] + for cmd, chanMap := range CachedLastMessages { if chanMap == nil { continue } @@ -140,23 +55,8 @@ func SendBacklogForNewClient(client *ClientInfo) { } func SendBacklogForChannel(client *ClientInfo, channel string) { - PersistentLSMLock.RLock() - for _, cmd := range GetCommandsOfType(CacheTypePersistent) { - chanMap := PersistentLastMessages[cmd] - if chanMap == nil { - continue - } - if msg, ok := chanMap[channel]; ok { - msg := ClientMessage{MessageID: -1, Command: cmd, origArguments: msg.Data} - msg.parseOrigArguments() - client.MessageChannel <- msg - } - } - PersistentLSMLock.RUnlock() - CachedLSMLock.RLock() - for _, cmd := range GetCommandsOfType(CacheTypeLastOnly) { - chanMap := CachedLastMessages[cmd] + for cmd, chanMap := range CachedLastMessages { if chanMap == nil { continue } @@ -194,16 +94,6 @@ func SaveLastMessage(which map[Command]map[string]LastSavedMessage, locker sync. } } -func GetCommandsOfType(match BacklogCacheType) []Command { - var ret []Command - for cmd, info := range S2CCommandsCacheInfo { - if info.Caching == match { - ret = append(ret, cmd) - } - } - return ret -} - func HTTPBackendDropBacklog(w http.ResponseWriter, r *http.Request) { r.ParseForm() formData, err := Backend.UnsealRequest(r.Form) @@ -245,33 +135,18 @@ func HTTPBackendCachedPublish(w http.ResponseWriter, r *http.Request) { } timestamp := time.Unix(timeNum, 0) - cacheinfo, ok := S2CCommandsCacheInfo[cmd] - if !ok { - w.WriteHeader(422) - fmt.Fprintf(w, "Caching semantics unknown for command '%s'. Post to /addcachedcommand first.", cmd) - return - } - var count int msg := ClientMessage{MessageID: -1, Command: cmd, origArguments: json} msg.parseOrigArguments() - if cacheinfo.Caching == CacheTypeLastOnly && cacheinfo.Target == MsgTargetTypeChat { - SaveLastMessage(CachedLastMessages, &CachedLSMLock, cmd, channel, timestamp, json, deleteMode) - count = PublishToChannel(channel, msg) - } else if cacheinfo.Caching == CacheTypePersistent && cacheinfo.Target == MsgTargetTypeChat { - SaveLastMessage(PersistentLastMessages, &PersistentLSMLock, cmd, channel, timestamp, json, deleteMode) - count = PublishToChannel(channel, msg) - } else if cacheinfo.Caching == CacheTypeLastOnly && cacheinfo.Target == MsgTargetTypeMultichat { - channels := strings.Split(channel, ",") - var dummyLock sync.Mutex - CachedLSMLock.Lock() - for _, channel := range channels { - SaveLastMessage(CachedLastMessages, &dummyLock, cmd, channel, timestamp, json, deleteMode) - } - CachedLSMLock.Unlock() - count = PublishToMultiple(channels, msg) + channels := strings.Split(channel, ",") + var dummyLock sync.Mutex + CachedLSMLock.Lock() + for _, channel := range channels { + SaveLastMessage(CachedLastMessages, &dummyLock, cmd, channel, timestamp, json, deleteMode) } + CachedLSMLock.Unlock() + count = PublishToMultiple(channels, msg) w.Write([]byte(strconv.Itoa(count))) } diff --git a/socketserver/server/subscriptions_test.go b/socketserver/server/subscriptions_test.go index 9450d609..fea4f533 100644 --- a/socketserver/server/subscriptions_test.go +++ b/socketserver/server/subscriptions_test.go @@ -33,9 +33,7 @@ func TestSubscriptionAndPublish(t *testing.T) { const TestData3 = false var TestData4 = []interface{}{"str1", "str2", "str3"} - S2CCommandsCacheInfo[TestCommandChan] = PushCommandCacheInfo{Caching: CacheTypeLastOnly, Target: MsgTargetTypeChat} - S2CCommandsCacheInfo[TestCommandMulti] = PushCommandCacheInfo{Caching: CacheTypeLastOnly, Target: MsgTargetTypeMultichat} - S2CCommandsCacheInfo[TestCommandGlobal] = PushCommandCacheInfo{Caching: CacheTypeLastOnly, Target: MsgTargetTypeGlobal} + t.Log("TestSubscriptionAndPublish") var server *httptest.Server var urls TURLs @@ -195,7 +193,7 @@ func TestSubscriptionAndPublish(t *testing.T) { // Publish message 4 - should go to clients 1, 2, 3 - form, err = TSealForUncachedPubMsg(t, TestCommandGlobal, "", TestData4, MsgTargetTypeGlobal, false) + form, err = TSealForUncachedPubMsg(t, TestCommandGlobal, "", TestData4, "global", false) if err != nil { t.FailNow() } @@ -254,6 +252,8 @@ func TestRestrictedCommands(t *testing.T) { var server *httptest.Server var urls TURLs + t.Log("TestRestrictedCommands") + var backendExpected = NewTBackendRequestChecker(t, TExpectedBackendRequest{200, bPathAnnounceStartup, &url.Values{"startup": []string{"1"}}, "", nil}, TExpectedBackendRequest{401, fmt.Sprintf("%s%s", bPathOtherCommand, TestCommandNeedsAuth), &url.Values{"authenticated": []string{"0"}, "username": []string{""}, "clientData": []string{TestRequestDataJSON}}, "", nil}, diff --git a/socketserver/server/testinfra_test.go b/socketserver/server/testinfra_test.go index e52b7a22..5d8a4570 100644 --- a/socketserver/server/testinfra_test.go +++ b/socketserver/server/testinfra_test.go @@ -284,7 +284,7 @@ func TSealForSavePubMsg(tb testing.TB, cmd Command, channel string, arguments in return sealed, nil } -func TSealForUncachedPubMsg(tb testing.TB, cmd Command, channel string, arguments interface{}, scope MessageTargetType, deleteMode bool) (url.Values, error) { +func TSealForUncachedPubMsg(tb testing.TB, cmd Command, channel string, arguments interface{}, scope string, deleteMode bool) (url.Values, error) { form := url.Values{} form.Set("cmd", string(cmd)) argsBytes, err := json.Marshal(arguments) @@ -298,7 +298,7 @@ func TSealForUncachedPubMsg(tb testing.TB, cmd Command, channel string, argument form.Set("delete", "1") } form.Set("time", time.Now().Format(time.UnixDate)) - form.Set("scope", scope.String()) + form.Set("scope", scope) sealed, err := Backend.SealRequest(form) if err != nil { diff --git a/socketserver/server/types.go b/socketserver/server/types.go index e775acb5..41c0d010 100644 --- a/socketserver/server/types.go +++ b/socketserver/server/types.go @@ -1,7 +1,6 @@ package server import ( - "encoding/json" "fmt" "net" "sync" @@ -152,109 +151,3 @@ func (cv *ClientVersion) After(cv2 *ClientVersion) bool { func (cv *ClientVersion) Equal(cv2 *ClientVersion) bool { return cv.Major == cv2.Major && cv.Minor == cv2.Minor && cv.Revision == cv2.Revision } - -func (bct BacklogCacheType) Name() string { - switch bct { - case CacheTypeInvalid: - return "" - case CacheTypeNever: - return "never" - case CacheTypeLastOnly: - return "last" - case CacheTypePersistent: - return "persist" - } - panic("Invalid BacklogCacheType value") -} - -var CacheTypesByName = map[string]BacklogCacheType{ - "never": CacheTypeNever, - "last": CacheTypeLastOnly, - "persist": CacheTypePersistent, -} - -func BacklogCacheTypeByName(name string) (bct BacklogCacheType) { - // CacheTypeInvalid is the zero value so it doesn't matter - bct, _ = CacheTypesByName[name] - return -} - -// String implements Stringer -func (bct BacklogCacheType) String() string { return bct.Name() } - -// MarshalJSON implements json.Marshaler -func (bct BacklogCacheType) MarshalJSON() ([]byte, error) { - return json.Marshal(bct.Name()) -} - -// UnmarshalJSON implements json.Unmarshaler -func (bct *BacklogCacheType) UnmarshalJSON(data []byte) error { - var str string - err := json.Unmarshal(data, &str) - if err != nil { - return err - } - if str == "" { - *bct = CacheTypeInvalid - return nil - } - newBct := BacklogCacheTypeByName(str) - if newBct != CacheTypeInvalid { - *bct = newBct - return nil - } - return ErrorUnrecognizedCacheType -} - -func (mtt MessageTargetType) Name() string { - switch mtt { - case MsgTargetTypeInvalid: - return "" - case MsgTargetTypeChat: - return "chat" - case MsgTargetTypeMultichat: - return "multichat" - case MsgTargetTypeGlobal: - return "global" - } - panic("Invalid MessageTargetType value") -} - -var TargetTypesByName = map[string]MessageTargetType{ - "chat": MsgTargetTypeChat, - "multichat": MsgTargetTypeMultichat, - "global": MsgTargetTypeGlobal, -} - -func MessageTargetTypeByName(name string) (mtt MessageTargetType) { - // MsgTargetTypeInvalid is the zero value so it doesn't matter - mtt, _ = TargetTypesByName[name] - return -} - -// String implements Stringer -func (mtt MessageTargetType) String() string { return mtt.Name() } - -// MarshalJSON implements json.Marshaler -func (mtt MessageTargetType) MarshalJSON() ([]byte, error) { - return json.Marshal(mtt.Name()) -} - -// UnmarshalJSON implements json.Unmarshaler -func (mtt *MessageTargetType) UnmarshalJSON(data []byte) error { - var str string - err := json.Unmarshal(data, &str) - if err != nil { - return err - } - if str == "" { - *mtt = MsgTargetTypeInvalid - return nil - } - newMtt := MessageTargetTypeByName(str) - if newMtt != MsgTargetTypeInvalid { - *mtt = newMtt - return nil - } - return ErrorUnrecognizedTargetType -}