From 4df066e450eac2f16d415944ad0969f8aab0766d Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 12:56:43 -0700 Subject: [PATCH 1/7] More test cleanup and stats --- README.md | 3 +- server/chat_room.go | 29 +++++++++++---- server/chatter.go | 2 +- server/server.go | 40 +++++++++++++-------- server/server_test.go | 74 ++++++++++++++++++++++++-------------- server/stats.go | 18 +++++----- server/stats_test.go | 6 ++-- test/README.md | 12 +++++++ test/rested/Alive.request | 47 ++++++++++++++++++++++++ test/rested/Status.request | 47 ++++++++++++++++++++++++ 10 files changed, 218 insertions(+), 60 deletions(-) create mode 100644 test/README.md create mode 100644 test/rested/Alive.request create mode 100644 test/rested/Status.request diff --git a/README.md b/README.md index afe1739..7500187 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,10 @@ Spaces must be encoded in JSON calls. # ChatReqTypeListRooms = 103 /send {"reqType":103} -# Join a room or join a room with hidden name. +# Join a room # ChatReqTypeJoin = 104 /send {"roomName":"Your\ Room","reqType":104} +# or join a room with hidden name. /send {"roomName":"Your\ Room","reqType":104,"content":"hidden"} # Get a list of nicknames in a room. diff --git a/server/chat_room.go b/server/chat_room.go index 25570fd..7e86cc6 100644 --- a/server/chat_room.go +++ b/server/chat_room.go @@ -136,26 +136,41 @@ func (r *ChatRoom) leave(q *ChatRequest) { // ChatRoomStats is a simple structure for returning statistic information on the room. type ChatRoomStats struct { - Name string `json:"name"` // The name of the room. - Start time.Time `json:"start"` // The start time of the room. - LastReq time.Time `json:"lastReq"` // The last request time to the room. - LastRsp time.Time `json:"lastRsp"` // The last response time from the room. - ReqCount uint64 `json:"reqcount"` // Total requests received. - RspCount uint64 `json:"rspCount"` // Total responses sent. + Name string `json:"name"` // The name of the room. + Start time.Time `json:"start"` // The start time of the room. + LastReq time.Time `json:"lastReq"` // The last request time to the room. + LastRsp time.Time `json:"lastRsp"` // The last response time from the room. + ReqCount uint64 `json:"reqcount"` // Total requests received. + RspCount uint64 `json:"rspCount"` // Total responses sent. + Chatters []*ChatRoomChatterStat `json:"chatters"` // Stats on chatters in the room +} + +type ChatRoomChatterStat struct { + Nickname string `json:"nickname"` // The nickname of the chatter. + RemoteAddr string `json:"remoteAddr"` // The remote IP and port of the chatter. } // stats returns status information on the room. func (r *ChatRoom) stats() *ChatRoomStats { r.mu.Lock() defer r.mu.Unlock() - return &ChatRoomStats{ + s := &ChatRoomStats{ Name: r.name, Start: r.start, LastReq: r.lastReq, LastRsp: r.lastRsp, ReqCount: r.reqCount, RspCount: r.rspCount, + Chatters: []*ChatRoomChatterStat{}, + } + for c := range r.chatters { + st := c.stats() + s.Chatters = append(s.Chatters, &ChatRoomChatterStat{ + Nickname: st.Nickname, + RemoteAddr: st.RemoteAddr, + }) } + return s } // isMember validates if the member exists in the room. diff --git a/server/chatter.go b/server/chatter.go index 010f156..9574016 100644 --- a/server/chatter.go +++ b/server/chatter.go @@ -163,7 +163,7 @@ func (c *Chatter) listRooms() { // ChatterStats is a simple structure for returning statistic information on the chatter. type ChatterStats struct { Nickname string `json:"nickname"` // The nickname of the chatter. - RemoteAddr string `json:"remoteAddr"` // The remote IP and port of the chatter + RemoteAddr string `json:"remoteAddr"` // The remote IP and port of the chatter. Start time.Time `json:"start"` // The start time of the chatter. LastReq time.Time `json:"lastReq"` // The last request time from the chatter. LastRsp time.Time `json:"lastRsp"` // The last response time to the chatter. diff --git a/server/server.go b/server/server.go index ddfeb0e..c80eb7e 100644 --- a/server/server.go +++ b/server/server.go @@ -23,16 +23,17 @@ import ( // Server is the main structure that represents a server instance. type Server struct { - info *Info // Basic server information used to run the server. - opts *Options // Original options used to create the server. - stats *Stats // Server statistics since it started. - mu sync.Mutex // For locking access to server attributes. - running bool // Is the server running? - log *ChatLogger // Log instance for recording error and other messages. - roomMngr *ChatRoomManager // Manager of chat rooms. - done chan bool // A channel to signal to web socked to close. - srvr *http.Server // HTTP server. - wg sync.WaitGroup // Synchronization of channel close. + info *Info // Basic server information used to run the server. + opts *Options // Original options used to create the server. + stats *Stats // Server statistics since it started. + mu sync.Mutex // For locking access to server attributes. + running bool // Is the server running? + log *ChatLogger // Log instance for recording error and other messages. + roomMngr *ChatRoomManager // Manager of chat rooms. + chatters map[*Chatter]bool // A list of chatters connected to the server. + done chan bool // A channel to signal to web socked to close. + srvr *http.Server // HTTP server. + wg sync.WaitGroup // Synchronization of channel close. } // New is a factory function that returns a new server instance. @@ -49,10 +50,11 @@ func New(ops *Options) *Server { i.MaxIdle = ops.MaxIdle i.Debug = ops.Debug }), - opts: ops, - stats: StatsNew(), - log: ChatLoggerNew(), - running: false, + opts: ops, + stats: StatsNew(), + log: ChatLoggerNew(), + chatters: map[*Chatter]bool{}, + running: false, } if s.info.Debug { @@ -169,7 +171,13 @@ func (s *Server) chatHandler(ws *websocket.Conn) { s.log.LogConnect(ws.Request()) s.incrementStats(ws.Request()) c := ChatterNew(s, ws) + s.mu.Lock() + s.chatters[c] = true // register chatter + s.mu.Unlock() c.Run() + s.mu.Lock() + delete(s.chatters, c) // unregister chatter + s.mu.Unlock() } // aliveHandler handles a client http:// "is the server alive?" request. @@ -186,6 +194,10 @@ func (s *Server) statsHandler(w http.ResponseWriter, r *http.Request) { s.initResponseHeader(w) s.mu.Lock() defer s.mu.Unlock() + s.stats.ChatterStats = []*ChatterStats{} + for c := range s.chatters { + s.stats.ChatterStats = append(s.stats.ChatterStats, c.stats()) + } s.stats.RoomStats = s.roomMngr.getRoomStats() mStats := &runtime.MemStats{} runtime.ReadMemStats(mStats) diff --git a/server/server_test.go b/server/server_test.go index 2432948..6c2eb9e 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -60,11 +60,12 @@ var ( ChatRspTypeListRooms, testChatRoomName1) TestServerJoin = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName1, ChatReqTypeJoin) - TestServerJoinHidden = fmt.Sprintf(`{"roomName":"%s","reqType":%d,"content":"hidden"}`, testChatRoomName1, ChatReqTypeJoin) - TestServerJoinErr = fmt.Sprintf(`{"roomName":"","reqType":%d}`, ChatReqTypeJoin) - TestServerJoin2 = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName2, ChatReqTypeJoin) - TestServerJoin3 = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName3, ChatReqTypeJoin) - TestServerJoinExp = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ + TestServerJoinHidden = fmt.Sprintf(`{"roomName":"%s","reqType":%d,"content":"hidden"}`, + testChatRoomName1, ChatReqTypeJoin) + TestServerJoinErr = fmt.Sprintf(`{"roomName":"","reqType":%d}`, ChatReqTypeJoin) + TestServerJoin2 = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName2, ChatReqTypeJoin) + TestServerJoin3 = fmt.Sprintf(`{"roomName":"%s","reqType":%d}`, testChatRoomName3, ChatReqTypeJoin) + TestServerJoinExp = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ `"content":"%s has joined the room.","list":[]}`, testChatRoomName1, ChatRspTypeJoin, testChatterNickname) TestServerJoinExp2 = fmt.Sprintf(`{"roomName":"%s","rspType":%d,`+ `"content":"%s has joined the room.","list":[]}`, testChatRoomName2, ChatRspTypeJoin, testChatterNickname) @@ -314,7 +315,8 @@ func TestServerValidWSSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerListNamesExp0 { - t.Errorf("Test hidden nickname. Get list of names error.\nExpected: %s\n\nActual: %s\n", TestServerListNamesExp0, result) + t.Errorf("Test hidden nickname. Get list of names error.\nExpected: %s\n\nActual: %s\n", + TestServerListNamesExp0, result) } // Unhide nickname @@ -354,7 +356,8 @@ func TestServerValidWSSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerListNamesExp1 { - t.Errorf("Test unhidden nickname. Get list of names error.\nExpected: %s\n\nActual: %s\n", TestServerListNamesExp1, result) + t.Errorf("Test unhidden nickname. Get list of names error.\nExpected: %s\n\nActual: %s\n", + TestServerListNamesExp1, result) } // Send a message to the room. @@ -415,7 +418,8 @@ func TestServerValidWSSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerListNamesExp0 { - t.Errorf("Test leave room. Get list of names error.\nExpected: %s\n\nActual: %s\n", TestServerListNamesExp0, result) + t.Errorf("Test leave room. Get list of names error.\nExpected: %s\n\nActual: %s\n", + TestServerListNamesExp0, result) } // Join the room as hidden @@ -455,7 +459,8 @@ func TestServerValidWSSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerListNamesExp0 { - t.Errorf("Test hidden nickname. Get list of names error.\nExpected: %s\n\nActual: %s\n", TestServerListNamesExp0, result) + t.Errorf("Test hidden nickname. Get list of names error.\nExpected: %s\n\nActual: %s\n", + TestServerListNamesExp0, result) } // Leave the room @@ -486,7 +491,8 @@ func TestServerValidWSSession(t *testing.T) { // Validate Chat Room statistics from this session. s := rm.stats() if s.Name != testChatRoomName1 { - t.Errorf("Room stats error. Name is incorrect. \nExpected: %s\n\nActual: %s\n", testChatRoomName1, s.Name) + t.Errorf("Room stats error. Name is incorrect. \nExpected: %s\n\nActual: %s\n", + testChatRoomName1, s.Name) } if s.Start.Before(testRoomStartTime) || s.Start.Equal(testRoomStartTime) { t.Errorf("Room stats error. Start Time is out of range.") @@ -498,10 +504,12 @@ func TestServerValidWSSession(t *testing.T) { t.Errorf("Room stats error. Last Response Time is out of range.") } if s.ReqCount != testRoomReqs { - t.Errorf("Room stats error. ReqCount is incorrect.\nExpected: %d\n\nActual: %d\n", testRoomReqs, s.ReqCount) + t.Errorf("Room stats error. ReqCount is incorrect.\nExpected: %d\n\nActual: %d\n", + testRoomReqs, s.ReqCount) } if s.RspCount != testRoomRsps { - t.Errorf("Room stats error. RsqCount is incorrect.\nExpected: %d\n\nActual: %d\n", testRoomRsps, s.RspCount) + t.Errorf("Room stats error. RsqCount is incorrect.\nExpected: %d\n\nActual: %d\n", + testRoomRsps, s.RspCount) } // Validate Chatter statistics for this session. @@ -522,10 +530,12 @@ func TestServerValidWSSession(t *testing.T) { t.Errorf("Chatter stats error. Last Response Time is out of range.") } if cs.ReqCount != testChatterReqs { - t.Errorf("Chatter stats error. ReqCount is incorrect.\nExpected: %d\n\nActual: %d\n", testChatterReqs, cs.ReqCount) + t.Errorf("Chatter stats error. ReqCount is incorrect.\nExpected: %d\n\nActual: %d\n", + testChatterReqs, cs.ReqCount) } if cs.RspCount != testChatterRsps { - t.Errorf("Chatter stats error. RsqCount is incorrect.\nExpected: %d\n\nActual: %d\n", testChatterRsps, cs.RspCount) + t.Errorf("Chatter stats error. RsqCount is incorrect.\nExpected: %d\n\nActual: %d\n", + testChatterRsps, cs.RspCount) } } @@ -560,7 +570,8 @@ func TestServerWSErrorSession(t *testing.T) { } result := string(rsp[:n]) if result != TestServerSetNicknameExpErr { - t.Errorf("Set nickname did not receive an error.\nExpected: %s\n\nActual: %s\n", TestServerSetNicknameExpErr, result) + t.Errorf("Set nickname did not receive an error.\nExpected: %s\n\nActual: %s\n", + TestServerSetNicknameExpErr, result) } // Join a room test err conditions @@ -578,7 +589,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerJoinExpErr { - t.Errorf("Join room did not receive an error.\nExpected: %s\n\nActual: %s\n", TestServerJoinExpErr, result) + t.Errorf("Join room did not receive an error.\nExpected: %s\n\nActual: %s\n", + TestServerJoinExpErr, result) } // Set nickname correctly for user 1 @@ -596,7 +608,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerSetNicknameExp { - t.Errorf("Set nickname received an error.\nExpected: %s\n\nActual: %s\n", TestServerSetNicknameExp, result) + t.Errorf("Set nickname received an error.\nExpected: %s\n\nActual: %s\n", + TestServerSetNicknameExp, result) } // Set nickname user 2 same as user 1 @@ -614,7 +627,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerSetNicknameExp { - t.Errorf("Set nickname received an error.\nExpected: %s\n\nActual: %s\n", TestServerSetNicknameExp, result) + t.Errorf("Set nickname received an error.\nExpected: %s\n\nActual: %s\n", + TestServerSetNicknameExp, result) } // User 1 joins room 1 @@ -650,7 +664,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerJoinExpErrX2 { - t.Errorf("Join 2X should have received an error.\nExpected: %s\n\nActual: %s\n", TestServerJoinExpErrX2, result) + t.Errorf("Join 2X should have received an error.\nExpected: %s\n\nActual: %s\n", + TestServerJoinExpErrX2, result) } // Hide user 1 nickname from room. @@ -668,7 +683,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerHideNicknameExp { - t.Errorf("Hide nickname received an error.\nExpected: %s\n\nActual: %s\n", TestServerHideNicknameExp, result) + t.Errorf("Hide nickname received an error.\nExpected: %s\n\nActual: %s\n", + TestServerHideNicknameExp, result) } // Posting ability should be disabled if name is hidden. @@ -686,7 +702,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerMsgExpErrHide { - t.Errorf("Message with hidded name should have received an error.\nExpected: %s\n\nActual: %s\n", TestServerMsgExpErrHide, result) + t.Errorf("Message with hidded name should have received an error.\nExpected: %s\n\nActual: %s\n", + TestServerMsgExpErrHide, result) } // Nickname already used in room should prevent joining. User 2 joins room 1 w/ same name User 1 @@ -704,7 +721,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerJoinExpErrSame { - t.Errorf("Join with same name should have received an error.\nExpected: %s\n\nActual: %s\n", TestServerJoinExpErrSame, result) + t.Errorf("Join with same name should have received an error.\nExpected: %s\n\nActual: %s\n", + TestServerJoinExpErrSame, result) } // Should not be able to grow room limitation. @@ -722,7 +740,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerJoinExp2 { - t.Errorf("Join should not receive an error.\nExpected: %s\n\nActual: %s\n", TestServerJoinExp2, result) + t.Errorf("Join should not receive an error.\nExpected: %s\n\nActual: %s\n", + TestServerJoinExp2, result) } if _, err := ws1.Write([]byte(TestServerJoin3)); err != nil { @@ -739,7 +758,8 @@ func TestServerWSErrorSession(t *testing.T) { } result = string(rsp[:n]) if result != TestServerJoinExpErrRoom { - t.Errorf("Join should have received an error.\nExpected: %s\n\nActual: %s\n", TestServerJoinExpErrRoom, result) + t.Errorf("Join should have received an error.\nExpected: %s\n\nActual: %s\n", + TestServerJoinExpErrRoom, result) } // Test Max timeout @@ -781,7 +801,8 @@ func TestHTTPRoutes(t *testing.T) { t.Errorf("/alive body should be empty.") } if r.StatusCode != http.StatusOK { - t.Errorf("/alive returned invalid status code.\nExpected: %d\n\nActual: %d\n", http.StatusOK, r.StatusCode) + t.Errorf("/alive returned invalid status code.\nExpected: %d\n\nActual: %d\n", + http.StatusOK, r.StatusCode) } rq, _ = http.NewRequest("GET", testSrvrURLStats, nil) @@ -796,7 +817,8 @@ func TestHTTPRoutes(t *testing.T) { t.Errorf("/status body should not be empty.") } if r.StatusCode != http.StatusOK { - t.Errorf("/status returned invalid status code.\nExpected: %d\n\nActual: %d\n", http.StatusOK, r.StatusCode) + t.Errorf("/status returned invalid status code.\nExpected: %d\n\nActual: %d\n", + http.StatusOK, r.StatusCode) } } diff --git a/server/stats.go b/server/stats.go index b04bf7a..8558443 100644 --- a/server/stats.go +++ b/server/stats.go @@ -7,20 +7,22 @@ import ( // Stats contains runtime statistics for the server. type Stats struct { - Start time.Time `json:"startTime"` // The start time of the server. - ReqCount int64 `json:"reqCount"` // How many requests came in to the server. - ReqBytes int64 `json:"reqBytes"` // Size of the requests in bytes. - RouteStats map[string]map[string]int64 `json:"routeStats"` // How many requests/bytes came into each route. - RoomStats []*ChatRoomStats `json:"roomStats"` // How many requests etc came into each room. + Start time.Time `json:"startTime"` // The start time of the server. + ReqCount int64 `json:"reqCount"` // How many requests came in to the server. + ReqBytes int64 `json:"reqBytes"` // Size of the requests in bytes. + RouteStats map[string]map[string]int64 `json:"routeStats"` // How many requests/bytes came into each route. + ChatterStats []*ChatterStats `json:"chatterStats"` // Statistics about each logged in chatter. + RoomStats []*ChatRoomStats `json:"roomStats"` // How many requests etc came into each room. } // StatsNew is a factory function that returns a new instance of statistics. // options is an optional list of functions that initialize the structure func StatsNew(opts ...func(*Stats)) *Stats { s := &Stats{ - Start: time.Now(), - RouteStats: make(map[string]map[string]int64), - RoomStats: []*ChatRoomStats{}, + Start: time.Now(), + RouteStats: make(map[string]map[string]int64), + ChatterStats: []*ChatterStats{}, + RoomStats: []*ChatRoomStats{}, } for _, f := range opts { f(s) diff --git a/server/stats_test.go b/server/stats_test.go index b1d6cea..d529c07 100644 --- a/server/stats_test.go +++ b/server/stats_test.go @@ -8,9 +8,9 @@ import ( ) const ( - testStatsExpectedJSONResult = `{"startTime":"2006-01-02T13:24:56Z","reqCount":0,"reqBytes":0,` + - `"routeStats":{"route1":{"requesBytes":202,"requestCounts":101},"route2":{"requesBytes":204,` + - `"requestCounts":103}},"roomStats":[]}` + testStatsExpectedJSONResult = `{"startTime":"2006-01-02T13:24:56Z","reqCount":0,` + + `"reqBytes":0,"routeStats":{"route1":{"requesBytes":202,"requestCounts":101},` + + `"route2":{"requesBytes":204,"requestCounts":103}},"chatterStats":[],"roomStats":[]}` ) func TestStatsNew(t *testing.T) { diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..160c11f --- /dev/null +++ b/test/README.md @@ -0,0 +1,12 @@ +## Additional Test Scripts and Applications + +This folder contains additional scripts and files for testing requests against the server from the client side. + +see README.md for main project for additional tools. + +### Rested.App scripts - see: http://www.helloresolven.com/portfolio/rested/ + +./rested/* + +- Alive.request - Validate alive ping is returning 200 OK. +- Status.request - Validate server is returning statistics, information and 200 OK. diff --git a/test/rested/Alive.request b/test/rested/Alive.request new file mode 100644 index 0000000..04066c9 --- /dev/null +++ b/test/rested/Alive.request @@ -0,0 +1,47 @@ + + + + + baseURL + http://localhost:6660/v1.0/alive + followRedirect + + handleJSONPCallbacks + + headers + + + header + Accept + inUse + + value + application/json + + + header + Content-Type + inUse + + value + application/json + + + httpMethod + GET + jsonpScript + + paramBodyUIChoice + 0 + parameters + + parametersType + 1 + presentBeforeChallenge + + stringEncoding + 4 + usingHTTPBody + + + diff --git a/test/rested/Status.request b/test/rested/Status.request new file mode 100644 index 0000000..a5bb83a --- /dev/null +++ b/test/rested/Status.request @@ -0,0 +1,47 @@ + + + + + baseURL + http://localhost:6660/v1.0/stats + followRedirect + + handleJSONPCallbacks + + headers + + + header + Accept + inUse + + value + application/json + + + header + Content-Type + inUse + + value + application/json + + + httpMethod + GET + jsonpScript + + paramBodyUIChoice + 0 + parameters + + parametersType + 1 + presentBeforeChallenge + + stringEncoding + 4 + usingHTTPBody + + + From 0f79063549afdab4008c365bd717a25f1f342a63 Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 18:38:50 -0700 Subject: [PATCH 2/7] Cleanup and pruning. --- README.md | 4 +++- client/EchoTest.html | 40 ---------------------------------------- test/README.md | 18 ++++++++++++++---- test/chattypantz.dark | 12 ++++++++++++ 4 files changed, 29 insertions(+), 45 deletions(-) delete mode 100644 client/EchoTest.html create mode 100644 test/chattypantz.dark diff --git a/README.md b/README.md index 7500187..5fdf141 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Additional objectives: * Only one connection per nickname allowed per chat room. * The server should provide an idle timeout setting. If a user doesn't interact withing n-seconds, the user should be automatically disconnected. * The server should provide a parameter to limit connections and number of rooms. -* Alive and statistics should be provided by http:// API endpoints. +* Heartbeat status and statistics should be provided by http:// API endpoints. * Chat history for each room should be stored in a file for each chat. When the user logs in to a room, the history should be provided to the client. A max history option should be provided. For TODOs, please see TODO.md @@ -177,6 +177,8 @@ A prebuilt docker image is available at (http://www.docker.com) [chattypantz](ht If you have docker installed, run: ``` docker pull composer22/chattypantz:latest +or +docker pull composer22/chattypantz: ``` See /docker directory README for more information on how to run it. diff --git a/client/EchoTest.html b/client/EchoTest.html deleted file mode 100644 index 2608acd..0000000 --- a/client/EchoTest.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - -

WebSocket Echo Test

-
-

- Message: -

-
- - - diff --git a/test/README.md b/test/README.md index 160c11f..57da094 100644 --- a/test/README.md +++ b/test/README.md @@ -1,12 +1,22 @@ ## Additional Test Scripts and Applications -This folder contains additional scripts and files for testing requests against the server from the client side. +This folder contains scripts and files for testing requests against the server from the client side. -see README.md for main project for additional tools. +### Dark Websocket Terminal -### Rested.App scripts - see: http://www.helloresolven.com/portfolio/rested/ +https://github.com/cyberixae/dwst -./rested/* +A chrome extention/application that allows you to send socket commands to the server. + +chattypantz.dark is a text file of commands to save typing. + +### Rested.App scripts + +see: http://www.helloresolven.com/portfolio/rested/ + +For http API testing. + +./rested/ - Alive.request - Validate alive ping is returning 200 OK. - Status.request - Validate server is returning statistics, information and 200 OK. diff --git a/test/chattypantz.dark b/test/chattypantz.dark new file mode 100644 index 0000000..d95d86b --- /dev/null +++ b/test/chattypantz.dark @@ -0,0 +1,12 @@ +/connect ws://127.0.0.1:6660/v1.0/chat +/send {"reqType":101,"content":"ChatMonkey"} +/send {"reqType":102} +/send {"reqType":103} +/send {"roomName":"Your\ Room","reqType":104} +/send {"roomName":"Your\ Room","reqType":104,"content":"hidden"} +/send {"roomName":"Your\ Room","reqType":105} +/send {"roomName":"Your\ Room","reqType":106} +/send {"roomName":"Your\ Room","reqType":107} +/send {"roomName":"Your\ Room","reqType":108,"content":"Hello world!"} +/send {"roomName":"Your\ Room","reqType":109} +/disconnect From 2af6eca5d01082a38d43f27403430a393a07b1cb Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 19:12:53 -0700 Subject: [PATCH 3/7] deferred history feature --- README.md | 19 +++++++++---------- TODO.md | 11 ++--------- chattypantz.go | 2 -- server/const.go | 17 ++++++++--------- server/info.go | 21 ++++++++++----------- server/info_test.go | 8 +++----- server/options.go | 19 +++++++++---------- server/options_test.go | 22 ++++++++++------------ server/server.go | 1 - server/server_test.go | 36 +++++++++++++++++------------------- server/usage.go | 1 - 11 files changed, 68 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 5fdf141..c0e01de 100644 --- a/README.md +++ b/README.md @@ -15,20 +15,20 @@ Some key objectives in this demonstration: * Clients connect to the server on ws://{host:port} * Messages sent by a client are broadcasted to other clients connected to the same chat room. * The server only supports JSON text messages. Binary websocket frames will be discarded and the clients sending those frames will be disconnected with a message. -* When a client connects to a chat room, the server broadcasts "{nickname} joined the room." to clients that were already connected to the same chat room. +* When a client connects to a chat room, the server broadcasts "{nickname} has joined the room." to clients that were already connected to the same chat room. * When a client disconnects, the server broadcasts "{nickname} has left the room." to clients connected to the same chat room. * An unlimited amount of chat rooms can be created on the server (unless it runs out of memory or file descriptors). * An unlimited amount of clients can join each chat room on the server (unless it runs out of memory or file descriptors). +* Only one connection per unique nickname allowed per chat room. +* The server should provide an optional idle timeout setting. If a user doesn't interact withing n-seconds, the user should be automatically disconnected. +* The server should provide an optional parameter to limit connections to the server. +* The server should provide an optional parameter to limit the number of rooms created. +* Heartbeat (alive) and statistics should be provided via http:// API endpoints. -Additional objectives: +Future objectives: -* Only one connection per nickname allowed per chat room. -* The server should provide an idle timeout setting. If a user doesn't interact withing n-seconds, the user should be automatically disconnected. -* The server should provide a parameter to limit connections and number of rooms. -* Heartbeat status and statistics should be provided by http:// API endpoints. -* Chat history for each room should be stored in a file for each chat. When the user logs in to a room, the history should be provided to the client. A max history option should be provided. - -For TODOs, please see TODO.md +* Chat history for each room should be stored in a file. When the user logs in to a room, the history should be provided to the client. A max history option should be provided. +* More sophisticated client example code in html and js. ## Usage @@ -44,7 +44,6 @@ Server options: -L, --profiler_port PORT *PORT the profiler is listening on (default: off). -n, --connections MAX *MAX client connections allowed (default: unlimited). -r, --rooms MAX *MAX chatrooms allowed (default: unlimited). - -y, --history MAX *MAX num of history records per room (default: 15). -i, --idle MAX *MAX idle time in seconds allowed (default: unlimited). -X, --procs MAX *MAX processor cores to use from the machine. diff --git a/TODO.md b/TODO.md index dc0d5c4..3fe3fca 100644 --- a/TODO.md +++ b/TODO.md @@ -1,12 +1,5 @@ # OnDeck -- [ ] Server - stats aggregation + API -- [ ] Server - API for alive -- [ ] Front end code. -- [ ] Enable history in a room and load n records from that log. # Backlog -- [ ] Token login -- [ ] TLS access - -# Done - +- [ ] Enable history for each room and load n-ary records from that log. +- [ ] More sophisticated client example code. diff --git a/chattypantz.go b/chattypantz.go index f11560e..5b96c2c 100644 --- a/chattypantz.go +++ b/chattypantz.go @@ -30,8 +30,6 @@ func main() { flag.IntVar(&opts.MaxConns, "--connections", server.DefaultMaxConns, "Maximum client connections allowed.") flag.IntVar(&opts.MaxRooms, "r", server.DefaultMaxRooms, "Maximum chat rooms allowed.") flag.IntVar(&opts.MaxRooms, "--rooms", server.DefaultMaxRooms, "Maximum chat rooms allowed.") - flag.IntVar(&opts.MaxHistory, "y", server.DefaultMaxHistory, "Maximum chat room history allowed.") - flag.IntVar(&opts.MaxHistory, "--history", server.DefaultMaxHistory, "Maximum chat room history allowed.") flag.IntVar(&opts.MaxIdle, "i", server.DefaultMaxIdle, "Maximum client idle allowed.") flag.IntVar(&opts.MaxIdle, "--idle", server.DefaultMaxIdle, "Maximum client idle allowed.") flag.IntVar(&opts.MaxProcs, "X", server.DefaultMaxProcs, "Maximum processor cores to use.") diff --git a/server/const.go b/server/const.go index d68f583..7375783 100644 --- a/server/const.go +++ b/server/const.go @@ -1,15 +1,14 @@ package server const ( - version = "0.1.0" // Application and server version. - DefaultHostname = "localhost" // The hostname of the server. - DefaultPort = 6660 // Port to receive requests: see IANA Port Numbers. - DefaultProfPort = 0 // Profiler port to receive requests. * - DefaultMaxConns = 0 // Maximum number of connections allowed. * - DefaultMaxRooms = 0 // Maximum number of chat rooms allowed. * - DefaultMaxHistory = 15 // Maximum number of chat history records per room. - DefaultMaxIdle = 0 // Maximum idle seconds per user connection. * - DefaultMaxProcs = 0 // Maximum number of computer processors to utilize. * + version = "0.1.0" // Application and server version. + DefaultHostname = "localhost" // The hostname of the server. + DefaultPort = 6660 // Port to receive requests: see IANA Port Numbers. + DefaultProfPort = 0 // Profiler port to receive requests. * + DefaultMaxConns = 0 // Maximum number of connections allowed. * + DefaultMaxRooms = 0 // Maximum number of chat rooms allowed. * + DefaultMaxIdle = 0 // Maximum idle seconds per user connection. * + DefaultMaxProcs = 0 // Maximum number of computer processors to utilize. * // * zeros = no change or no limitation or not enabled. diff --git a/server/info.go b/server/info.go index 91d7b30..96747e1 100644 --- a/server/info.go +++ b/server/info.go @@ -4,17 +4,16 @@ import "encoding/json" // Info provides basic config information to/about the running server. type Info struct { - Version string `json:"version"` // Version of the server. - Name string `json:"name"` // The name of the server. - Hostname string `json:"hostname"` // The hostname of the server. - UUID string `json:"UUID"` // Unique ID of the server. - Port int `json:"port"` // Port the server is listening on. - ProfPort int `json:"profPort"` // Profiler port the server is listening on. - MaxConns int `json:"maxConns"` // The maximum concurrent clients accepted. - MaxRooms int `json:"maxRooms"` // The maximum number of chat rooms allowed. - MaxHistory int `json:"maxHistory"` // The maximum number of history recs to retain per room. - MaxIdle int `json:"maxIdle"` // The maximum client idle time in seconds before disconnect. - Debug bool `json:"debugEnabled"` // Is debugging enabled on the server. + Version string `json:"version"` // Version of the server. + Name string `json:"name"` // The name of the server. + Hostname string `json:"hostname"` // The hostname of the server. + UUID string `json:"UUID"` // Unique ID of the server. + Port int `json:"port"` // Port the server is listening on. + ProfPort int `json:"profPort"` // Profiler port the server is listening on. + MaxConns int `json:"maxConns"` // The maximum concurrent clients accepted. + MaxRooms int `json:"maxRooms"` // The maximum number of chat rooms allowed. + MaxIdle int `json:"maxIdle"` // The maximum client idle time in seconds before disconnect. + Debug bool `json:"debugEnabled"` // Is debugging enabled on the server. } // InfoNew is a factory function that returns a new instance of Info. diff --git a/server/info_test.go b/server/info_test.go index 998f318..4282801 100644 --- a/server/info_test.go +++ b/server/info_test.go @@ -9,7 +9,7 @@ import ( const ( testInfoExpectedJSONResult = `{"version":"9.8.7","name":"Test Server","hostname":"0.0.0.0",` + `"UUID":"ABCDEFGHIJKLMNOPQRSTUVWXYZ","port":6661,"profPort":6061,"maxConns":999,` + - `"maxRooms":888,"maxHistory":777,"maxIdle":666,"debugEnabled":true}` + `"maxRooms":888,"maxIdle":777,"debugEnabled":true}` ) func TestInfoNew(t *testing.T) { @@ -22,8 +22,7 @@ func TestInfoNew(t *testing.T) { i.ProfPort = 6061 i.MaxConns = 999 i.MaxRooms = 888 - i.MaxHistory = 777 - i.MaxIdle = 666 + i.MaxIdle = 777 i.Debug = true }) tp := reflect.TypeOf(info) @@ -55,8 +54,7 @@ func TestInfoString(t *testing.T) { i.ProfPort = 6061 i.MaxConns = 999 i.MaxRooms = 888 - i.MaxHistory = 777 - i.MaxIdle = 666 + i.MaxIdle = 777 i.Debug = true }) actual := fmt.Sprint(info) diff --git a/server/options.go b/server/options.go index deb3800..93e5e84 100644 --- a/server/options.go +++ b/server/options.go @@ -5,16 +5,15 @@ import "encoding/json" // Options represents parameters that are passed to the application to be used in constructing // the server. type Options struct { - Name string `json:"name"` // The name of the server. - Hostname string `json:"hostname"` // The hostname of the server. - Port int `json:"port"` // The default port of the server. - ProfPort int `json:"profPort"` // The profiler port of the server. - MaxConns int `json:"maxConns"` // The maximum concurrent clients accepted. - MaxRooms int `json:"maxRooms"` // The maximum number of chat rooms allowed. - MaxHistory int `json:"maxHistory"` // The maximum number of history to retain per room. - MaxIdle int `json:"maxIdle"` // The maximum client idle time in seconds before disconnect. - MaxProcs int `json:"maxProcs"` // The maximum number of processor cores available. - Debug bool `json:"debugEnabled"` // Is debugging enabled in the application or server. + Name string `json:"name"` // The name of the server. + Hostname string `json:"hostname"` // The hostname of the server. + Port int `json:"port"` // The default port of the server. + ProfPort int `json:"profPort"` // The profiler port of the server. + MaxConns int `json:"maxConns"` // The maximum concurrent clients accepted. + MaxRooms int `json:"maxRooms"` // The maximum number of chat rooms allowed. + MaxIdle int `json:"maxIdle"` // The maximum client idle time in seconds before disconnect. + MaxProcs int `json:"maxProcs"` // The maximum number of processor cores available. + Debug bool `json:"debugEnabled"` // Is debugging enabled in the application or server. } // String is an implentation of the Stringer interface so the structure is returned as a string diff --git a/server/options_test.go b/server/options_test.go index 74fd2d2..c7d4aa5 100644 --- a/server/options_test.go +++ b/server/options_test.go @@ -7,23 +7,21 @@ import ( const ( testOptionsExpectedJSONResult = `{"name":"Test Options","hostname":"0.0.0.0","port":6661,` + - `"profPort":6061,"maxConns":1001,"maxRooms":999,"maxHistory":888,"maxIdle":777,` + - `"maxProcs":666,"debugEnabled":true}` + `"profPort":6061,"maxConns":1001,"maxRooms":999,"maxIdle":888,"maxProcs":777,"debugEnabled":true}` ) func TestOptionsString(t *testing.T) { t.Parallel() opts := &Options{ - Name: "Test Options", - Hostname: "0.0.0.0", - Port: 6661, - ProfPort: 6061, - MaxConns: 1001, - MaxRooms: 999, - MaxHistory: 888, - MaxIdle: 777, - MaxProcs: 666, - Debug: true, + Name: "Test Options", + Hostname: "0.0.0.0", + Port: 6661, + ProfPort: 6061, + MaxConns: 1001, + MaxRooms: 999, + MaxIdle: 888, + MaxProcs: 777, + Debug: true, } actual := fmt.Sprint(opts) if actual != testOptionsExpectedJSONResult { diff --git a/server/server.go b/server/server.go index c80eb7e..7a20da4 100644 --- a/server/server.go +++ b/server/server.go @@ -46,7 +46,6 @@ func New(ops *Options) *Server { i.ProfPort = ops.ProfPort i.MaxConns = ops.MaxConns i.MaxRooms = ops.MaxRooms - i.MaxHistory = ops.MaxHistory i.MaxIdle = ops.MaxIdle i.Debug = ops.Debug }), diff --git a/server/server_test.go b/server/server_test.go index 6c2eb9e..4421d4e 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -12,15 +12,14 @@ import ( ) const ( - testServerHostname = "localhost" - testServerPort = 6660 - testServerMaxConns = 4 - testServerMaxRooms = 2 - testServerMaxHistory = 5 - testChatRoomName1 = "Room1" - testChatRoomName2 = "Room2" - testChatRoomName3 = "Room3" - testChatterNickname = "ChatMonkey" + testServerHostname = "localhost" + testServerPort = 6660 + testServerMaxConns = 4 + testServerMaxRooms = 2 + testChatRoomName1 = "Room1" + testChatRoomName2 = "Room2" + testChatRoomName3 = "Room3" + testChatterNickname = "ChatMonkey" ) var ( @@ -122,16 +121,15 @@ func tTestIncrRoomStats() { func TestServerStartup(t *testing.T) { opts := &Options{ - Name: "Test Server", - Hostname: testServerHostname, - Port: testServerPort, - ProfPort: 6060, - MaxConns: testServerMaxConns, - MaxRooms: testServerMaxRooms, - MaxHistory: testServerMaxHistory, - MaxIdle: 0, - MaxProcs: 1, - Debug: true, + Name: "Test Server", + Hostname: testServerHostname, + Port: testServerPort, + ProfPort: 6060, + MaxConns: testServerMaxConns, + MaxRooms: testServerMaxRooms, + MaxIdle: 0, + MaxProcs: 1, + Debug: true, } runtime.GOMAXPROCS(1) testSrvr = New(opts) diff --git a/server/usage.go b/server/usage.go index 91564b7..4bf8883 100644 --- a/server/usage.go +++ b/server/usage.go @@ -17,7 +17,6 @@ Server options: -L, --profiler_port PORT *PORT the profiler is listening on (default: off). -n, --connections MAX *MAX client connections allowed (default: unlimited). -r, --rooms MAX *MAX chatrooms allowed (default: unlimited). - -y, --history MAX *MAX num of history records per room (default: 15). -i, --idle MAX *MAX idle time in seconds allowed (default: unlimited). -X, --procs MAX *MAX processor cores to use from the machine. From bb9e5ac842bc384e80bda69fae8a0f161a522b7c Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 19:25:50 -0700 Subject: [PATCH 4/7] add some pantz --- README.md | 4 ++++ assets/img/chattypantz.png | Bin 0 -> 15698 bytes 2 files changed, 4 insertions(+) create mode 100644 assets/img/chattypantz.png diff --git a/README.md b/README.md index c0e01de..30c5032 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,12 @@ [![Current Release](https://img.shields.io/badge/release-v0.1.0-brightgreen.svg)](https://github.com/composer22/chattypantz/releases/tag/v0.1.0) [![Coverage Status](https://coveralls.io/repos/composer22/chattypantz/badge.svg?branch=master)](https://coveralls.io/r/composer22/chattypantz?branch=master) +![chattypantz-logo](assets/img/chattypantz.png) + A demo chat server and client written in [Go.](http://golang.org) + + ## About This is a small server and client that demonstrates some Golang network socket functions and features. diff --git a/assets/img/chattypantz.png b/assets/img/chattypantz.png new file mode 100644 index 0000000000000000000000000000000000000000..9cfbd374e8b686cc1822d2cc5b289f9d59ee83c6 GIT binary patch literal 15698 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 literal 0 HcmV?d00001 From 10d6369a1f74a946dacd1f6112884fc065475322 Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 19:27:11 -0700 Subject: [PATCH 5/7] remove spaces --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index 30c5032..f329189 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ A demo chat server and client written in [Go.](http://golang.org) - - ## About This is a small server and client that demonstrates some Golang network socket functions and features. @@ -152,9 +150,7 @@ Date: Fri, 03 Apr 2015 17:29:17 +0000 Server: San Francisco X-Request-Id: DC8D9C2E-8161-4FC0-937F-4CA7037970D5 Content-Length: 0 - ``` - ## Building This code currently requires version 1.42 or higher of Go. @@ -185,7 +181,6 @@ docker pull composer22/chattypantz: ``` See /docker directory README for more information on how to run it. - ## License (The MIT License) From a9c51ae187af88e7801a6b609b74ebab9c72330f Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 19:44:41 -0700 Subject: [PATCH 6/7] race condition in manager --- server/chat_room_manager.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/server/chat_room_manager.go b/server/chat_room_manager.go index a0e3215..bb8f11c 100644 --- a/server/chat_room_manager.go +++ b/server/chat_room_manager.go @@ -9,9 +9,10 @@ import ( // ChatRoomManager represents a hub of chat rooms for the server. type ChatRoomManager struct { rooms map[string]*ChatRoom // A list of rooms on the server. - maxRooms int // Maximum number of rooms allowed to be created + maxRooms int // Maximum number of rooms allowed to be created. log *ChatLogger // Application log for events. - wg sync.WaitGroup // Synchronizer for manager reqq + wg sync.WaitGroup // Synchronizer for manager reqq. + mu sync.Mutex // Lock for update. } // ChatRoomManagerNew is a factory function that returns a new instance of a chat room manager. @@ -25,6 +26,8 @@ func ChatRoomManagerNew(n int, cl *ChatLogger) *ChatRoomManager { // list returns a list of chat room names. func (m *ChatRoomManager) list() []string { + m.mu.Lock() + defer m.mu.Unlock() var names []string for n := range m.rooms { names = append(names, n) @@ -34,7 +37,9 @@ func (m *ChatRoomManager) list() []string { // find will find a chat room for a given name. func (m *ChatRoomManager) find(n string) (*ChatRoom, error) { + m.mu.Lock() r, ok := m.rooms[n] + m.mu.Unlock() if !ok { return nil, errors.New(fmt.Sprintf(`Chatroom "%s" not found.`, n)) } @@ -49,8 +54,10 @@ func (m *ChatRoomManager) findCreate(n string) (*ChatRoom, error) { return nil, errors.New("Maximum number of rooms reached. Cannot create new room.") } r = ChatRoomNew(n, m.log, &m.wg) + m.mu.Lock() m.rooms[n] = r m.wg.Add(1) + m.mu.Unlock() go r.Run() } return r, nil @@ -58,6 +65,8 @@ func (m *ChatRoomManager) findCreate(n string) (*ChatRoom, error) { // removeChatterAllRooms releases the chatter from any rooms. func (m *ChatRoomManager) removeChatterAllRooms(c *Chatter) { + m.mu.Lock() + defer m.mu.Unlock() for _, r := range m.rooms { if q, err := ChatRequestNew(c, r.name, ChatReqTypeLeave, ""); err == nil { r.reqq <- q @@ -67,6 +76,8 @@ func (m *ChatRoomManager) removeChatterAllRooms(c *Chatter) { // getRoomStats returns statistics from each room. func (m *ChatRoomManager) getRoomStats() []*ChatRoomStats { + m.mu.Lock() + defer m.mu.Unlock() var s = []*ChatRoomStats{} for _, r := range m.rooms { s = append(s, r.stats()) @@ -76,6 +87,8 @@ func (m *ChatRoomManager) getRoomStats() []*ChatRoomStats { // shutDownRooms releases all rooms from processing and memory. func (m *ChatRoomManager) shutDownRooms() { + m.mu.Lock() + defer m.mu.Unlock() // Close the channel which signals a stop run for _, r := range m.rooms { close(r.reqq) From ac78f1b8ce8ad3c071fda38917180c929506b5ad Mon Sep 17 00:00:00 2001 From: Bryan-Kirk Reinhardt Date: Thu, 23 Apr 2015 20:12:55 -0700 Subject: [PATCH 7/7] Chasing cats --- server/chat_room.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/chat_room.go b/server/chat_room.go index 7e86cc6..43e5e9f 100644 --- a/server/chat_room.go +++ b/server/chat_room.go @@ -129,7 +129,9 @@ func (r *ChatRoom) message(q *ChatRequest) { // leave removes the chatter from the room and notifies the group the chatter has left. func (r *ChatRoom) leave(q *ChatRequest) { name := q.Who.nickname + r.mu.Lock() delete(r.chatters, q.Who) + r.mu.Unlock() r.sendResponse(q.Who, ChatRspTypeLeave, fmt.Sprintf(`You have left room "%s".`, r.name), nil) r.sendResponseAll(ChatRspTypeLeave, fmt.Sprintf("%s has left the room.", name), nil) } @@ -192,6 +194,7 @@ func (r *ChatRoom) isMemberName(n string) bool { // sendResponse sends a message to a single chatter in the room. func (r *ChatRoom) sendResponse(c *Chatter, rt int, ct string, l []string) { c.mu.Lock() + defer c.mu.Unlock() if c.connected { if l == nil { l = []string{} @@ -204,7 +207,6 @@ func (r *ChatRoom) sendResponse(c *Chatter, rt int, ct string, l []string) { c.rspq <- rsp } } - c.mu.Unlock() } // sendResponseAll sends a message to all chatters in the room.