Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/server Cleaned #3

Merged
merged 7 commits into from
Apr 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
[![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
Expand All @@ -15,20 +17,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:

* 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.
* 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.
Future objectives:

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

Expand All @@ -44,7 +46,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.

Expand Down Expand Up @@ -94,9 +95,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.
Expand Down Expand Up @@ -148,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.
Expand All @@ -176,10 +176,11 @@ 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:<version>
```
See /docker directory README for more information on how to run it.


## License

(The MIT License)
Expand Down
11 changes: 2 additions & 9 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -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.
Binary file added assets/img/chattypantz.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions chattypantz.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
40 changes: 0 additions & 40 deletions client/EchoTest.html

This file was deleted.

33 changes: 25 additions & 8 deletions server/chat_room.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,33 +129,50 @@ 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)
}

// 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.
Expand All @@ -177,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{}
Expand All @@ -189,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.
Expand Down
17 changes: 15 additions & 2 deletions server/chat_room_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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))
}
Expand All @@ -49,15 +54,19 @@ 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
}

// 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
Expand All @@ -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())
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion server/chatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 8 additions & 9 deletions server/const.go
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
21 changes: 10 additions & 11 deletions server/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 3 additions & 5 deletions server/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading