Documentation
¶
Overview ¶
Package go_chat_i_guess implements a generic, connection-agnostic chat server.
The chat is divided into three components:
- `ChatServer`: The interface for the actual server
- `ChatChannel`: The interface for a given chat channel/room
- `Conn`: A connection to the remote client
Internally, there's also a fourth component, the `user`, but that's never exported by the API. The user associates a `Conn` to a username, which must be unique on that `ChatChannel`.
The first step to start a Chat Server is to instantiate it through one of `NewServer`, `NewServerWithTimeout` or `NewServerConf`. The last one should be the preferred variant, as it's the one that allows the most customization:
conf := go_chat_i_guess.GetDefaultServerConf() // Modify 'conf' as desired server := go_chat_i_guess.NewServerConf(conf)
A `ChatServer` by itself doesn't do anything. It simply manages `ChatChannels` and connection tokens. Instantiating a new `ChatServer` will start its cleanup goroutine, so idle `ChatChannel`s and expired tokens may be automatically released (more on those in a moment).
`ChatChannel`s may be created on that `ChatServer`. Each channel is uniquely identified by its name and runs its own goroutine to broadcast messages to every connect user.
// This should only fail if the channel's name has already been taken err := server.CreateChannel("channel-name") if err != nil { // Handle the error }
By default, a channel must receive a connection within 5 minutes of its creation, otherwise it will automatically close. Two things are required to connect a remote client to a channel: a authentication token and a connection.
Since the `ChatServer` doesn't implement any authentication mechanism, the caller is responsible by determining whether the requester is allowed to connect to a given channel with the requested username. If the caller accepts the requester's authentication (whichever it may be), it must generate a token within the `ChatServer` for that username/channel pair:
// XXX: Authenticate the user somehow token, err := server.RequestToken("the-user", "channel-name") if err != nil { // Handle the error } // XXX: Return this token to the requester
Then, the user must connect to the `ChatServer` using the retrieved token and something that implements the `Conn` interface. `conn_test.c` implements `mockConn`, which uses chan string to send and receive messages. Another option could be to implement an interface for a WebSocket connection. The user is added to the channel by calling either `Connect`, which spawns a goroutine to wait for messages from the user, or `ConnectAndWait`, which blocks until the `Conn` gets closed. This second options may be useful if the server already spawns a goroutine to handle requests.
var conn Conn err := server.Connect(token, conn) if err != nil { // Handle the error }
From this point onward, `Conn.Recv` blocks waiting for a message, which then gets forwarded to the `ChatChannel`. The `ChatChannel` then broadcasts this message to every connected user, including the sender, by calling their `Conn.SendStr`.
It's important to note that the although any `Conn` may be used for the connection, it's also used to identify the user within the `ChatChannel`. This may either be done by using a transport that maintains a connection (TCP, WebSocket etc) or by manually associating this `Conn` to its associated user.
By default, a Chat Server simply broadcasts the received messages as strings. This behaviour may be expanded by defining a `Encoder` in the server's `ServerConf`. This `MessageEncoder` could be used to process messages (for example, listing the users in a given channel) and/or to encode the message into a JSON object.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ChannelController ¶
type ChannelController interface { MessageEncoder // OnConnect is called whenever a channel detects that a user has just // joined it. // // If the channel doesn't have a `ChannelController`, it broadcasts the // message: // // channel.NewSystemBroadcast(username + " entered " + channel.Name() + "...") OnConnect(channel ChatChannel, username string) // OnDisconnect is called whenever a channel detects that a user has // disconnected. // // Since this may be called with some internal objects locked, only a // few channel methods may be called from this function. For this // reason, the `RestrictedChatChannel` SHALL NOT be cast and used as a // `ChatChannel`! // // If the channel doesn't have a `ChannelController`, it broadcasts the // message: // // channel.NewSystemBroadcast(username + " exited " + channel.Name() + "...") OnDisconnect(channel RestrictedChatChannel, username string) }
ChannelController processes events received by the channel.
type ChatChannel ¶
type ChatChannel interface { io.Closer RestrictedChatChannel // GetUsers retrieve the list of connected users in this channel. If // `list` is supplied, the users are appended to the that list, so be // sure to empty it before calling this function. GetUsers(list []string) []string // Remove the user `username` from this channel. RemoveUser(username string) error // ConnectUser add a new user to the channel. // // It's entirely up to the caller to initialize the connection used by // this user, for example upgrading a HTTP request to a WebSocket // connection. // // The `channel` does properly synchronize this function, so it may be // called by different goroutines concurrently. // // On error, `conn` is left unchanged and must be closed by the caller. // // If `conn` is nil, then this function will panic! ConnectUser(username string, conn Conn) error // ConnectUser add a new user to the channel and blocks until the // user closes the connection to the server. // // The `channel` does properly synchronize this function, so it may be // called by different goroutines concurrently. // // On error, `conn` is left unchanged and must be closed by the caller. // // Differently from `ConnectUser`, this function handles messages // from the remote client in the calling goroutine. This may be // advantageous if the external server already spawns a new goroutine // to handle each new connection. // // If `conn` is nil, then this function will panic! ConnectUserAndWait(username string, conn Conn) error }
The public interface for a chat channel.
type ChatError ¶
type ChatError uint
Error type for this package.
const ( // Invalid token. Either the token doesn't exist, it has already been used // or it has already expired. InvalidToken ChatError = iota // Channel did not receive any connections in a timely manner. IdleChannel // There's already another channel with the requested name. DuplicatedChannel // Invalid Channel. Either the channel doesn't exist or it has already // expired (or been closed). InvalidChannel // The channel was closed before the operation completed. ChannelClosed // The requesting user is already connected to the channel UserAlreadyConnected // The connection was closed ConnEOF // A test connection timed out TestTimeout // Invalid user. InvalidUser )
type ChatServer ¶
type ChatServer interface { io.Closer // GetConf retrieve a copy of the server's configuration. As such, // changing it won't cause any change to the configurations of the // running server. GetConf() ServerConf // RequestToken generate a token temporarily associating the user identified // by `username` may connect to a `channel`. // // This token should be requested from an authenticated and secure channel. // Then, the returned token may be sent in a 'Connect()' to identify the // user and the desired channel. // // RequestToken should only fail if it somehow fails to generate a token. RequestToken(username, channel string) (string, error) // CreateChannel create and start the channel with the given `name`. // // Channels are uniquely identified by their names. Also, the chat // server automatically removes a closed channel, regardless whether // it was manually closed or whether it timed out. CreateChannel(name string) error // GetChannel retrieve the channel named `name`. GetChannel(name string) (ChatChannel, error) // Connect a user to a channel, previously associated to `token`, using // `conn` to communicate with this user. // // On error, the token must be re-generated. // // If `conn` is nil, then this function will panic! Connect(token string, conn Conn) error // ConnectAndWait connect a user to a channel, previously associated to // `token`, using `conn` to communicate with this user. // // Differently from `Connect`, this blocks until `conn` gets closed. // This may be usefull if the called already spawns a new goroutine to // handle each new incoming connection. // // On error, the token must be re-generated. // // If `conn` is nil, then this function will panic! ConnectAndWait(token string, conn Conn) error }
The public interfacer of the chat server.
func NewServer ¶
func NewServer(readBuf, writeBuf int) ChatServer
NewServer create a new chat server with the requested size for the `readBuf` and for the `writeBuf`.
See `NewServerConf()` for more details.
func NewServerConf ¶
func NewServerConf(conf ServerConf) ChatServer
NewServerConf create a new chat server, as specified by `conf`.
When a new chat server starts, a clean up goroutine is spawned to check and release expired resources periodically. This goroutine is stopped, and every resource is released, when the ChatServer gets `Close()`d.
func NewServerWithTimeout ¶
func NewServerWithTimeout(readBuf, writeBuf int, tokenDeadline, tokenCleanupDelay time.Duration) ChatServer
NewServerWithTimeout create a new chat server with the requested size for the `readBuf` and for the `writeBuf`. Additionally, the access `tokenDeadline` and `tokenCleanupDelay` may be configured.
See `NewServerConf()` for more details.
type Conn ¶
type Conn interface { io.Closer // Recv blocks until a new message was received. Recv() (string, error) // SendStr send `msg`, previously formatted by the caller. // // Note that the server may send an empty message to check if this // connection is still active. SendStr(msg string) error }
Conn is a generic interface for sending and receiving messages.
type MessageEncoder ¶
type MessageEncoder interface { // Encode the message described in the parameter into the string that // will be sent to the channel. // // Returning the empty string will cancel sending the message, which // may be useful for command processing. It may also be used to reply // directly, and exclusively, to the sender after processing the // message. // // `from` is set internally by the `ChatChannel`, based on the `Conn` // that received this message and forwarded it to the server. Using it // to determine whether the requesting user is allowed to do some // operation should be safe, as long as users are properly // authenticated before generating their connection token. Encode(channel ChatChannel, date time.Time, msg, from, to string) string }
MessageEncoder encodes a given message into the string that will be sent by the channel.
type RestrictedChatChannel ¶
type RestrictedChatChannel interface { // Name retrieve the channel's name. Name() string // NewBroadcast queue a new broadcast message from a specific sender, // setting its `Date` to the current time and setting the message's // `Message` and sender (its `From`) as `msg` and `from`, respectively. NewBroadcast(msg, from string) // NewSystemBroadcast queue a new system message (i.e., a message // without a sender), setting its `Date` to the current time and // setting `Message` to `msg`. NewSystemBroadcast(msg string) // NewSystemWhisper queue a new system message (i.e., a message without // a sender) to a specific receiver, setting its `Date` to the current // time and setting `Message` to `msg`. NewSystemWhisper(msg, to string) // IsClosed check if the channel is closed. IsClosed() bool }
Restricted public interface for a chat channel, usable while handling channel events.
type ServerConf ¶
type ServerConf struct { // Size for the read buffer on new connections. ReadBuf int // Size for the write buffer on new connections. WriteBuf int // For how long a given token should exist before being used or expiring. TokenDeadline time.Duration // Delay between executions of the token cleanup routine. TokenCleanupDelay time.Duration // For how long a given channel may stay idle (without receiving any // connection). After this timeout expires, the channel sends a message // to every user, to check whether they are still connected. // // If no user is connected to the channel when it times out, the // channel will be automatically closed! ChannelIdleTimeout time.Duration // Delay between executions of the channel cleanup routine. ChannelCleanupDelay time.Duration // Controller optionally processes and encodes messages received by // this server's channels. // // This may optionally implement `ChannelController` as well! Controller MessageEncoder // Logger used by the chat server to report events. If this is nil, no // message shall be logged! Logger *log.Logger // Whether debug messages should be logged. DebugLog bool }
ServerConf define various parameters that may be used to configure the server.
func GetDefaultServerConf ¶
func GetDefaultServerConf() ServerConf
GetDefaultServerConf retrieve a fully initialized `ServerConf`, with all fields set to some default, and non-zero, value.
Directories
¶
Path | Synopsis |
---|---|
cmd
|
|
Package gorilla_ws_conn implements the Conn interface from https://github.com/SirGFM/go-chat-i-guess over a WebSocket connection from https://github.com/gorilla/websocket.
|
Package gorilla_ws_conn implements the Conn interface from https://github.com/SirGFM/go-chat-i-guess over a WebSocket connection from https://github.com/gorilla/websocket. |