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
|
|
|
chat-server
command
|
|
|
gobwas-ws-raw-chat
command
|
|
|
gobwas-ws-req-chat
command
|
|
|
gorilla-ws-chat
command
|
|
|
pinger
command
|
|
|
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. |