A full example using websockets and a bunch of other features is available: celerity-example-chat

What are channels

Channels are the real time communication implement inside Celerity. You can serve a web socket route from within the application in a fashion similar to any other route. It uses a specialized handler specific to Channel communication. Channels allow full web socket communication between the server and the browser allowing you to respond to incoming web socket events and broadcast messages from anywhere within the application.

What are rooms

Each channel exists on a specific url path. This channel maintains a list of all of its clients and messages can be sent to an individual client, to the current client (if responding to an event), or a message can be broadcast to all clients in the channel.

Sometimes it is useful to group clients together in order to broadcast to select groups easier. This is where rooms come in. A room is an arbitrary grouping of clients. Rooms can be created on the fly and clients can be added and removed from them. Later you can broadcast messages to a specific room or iterate over just that room’s clients.

Creating a channel

Channels are created similar to other routes and can be nested inside scopes, or utilize URL variables; just like any route. Creating a channel which uses a URL variable to accept a session token could be built like this:

svr := celerity.New()
svr.Channel("main", "/ws/:token", func(client *celerity.SocketClient, e celerity.ChannelEvent) {
  //Respond to events
})

The Channel function takes three parameters: The first is the name of the channel and can be any string. This is used to lookup the channel later while in another route handler. The second is the URL path the channel should be served at. In our case it will serve a url like /ws/1231231af21343eefe11ba1. The last parameter is the route handler for the channel. This function will be called for events that take place on the channel. This could be Connect, Disconnect, or Message events.

The channel route handler

To respond to an event check the event type, extract and process the data in the message, if applicable, and send a response.

For example the code below will expect a message in the form of:

{
  "name": "Alice"
}

It will send back a message in the form of:

{
  "greeting": "Hello, Alice"
}
svr := celerity.New()
svr.Channel("main", "/ws/:token", func(client *celerity.SocketClient, e celerity.ChannelEvent) {
  if e.Event == celerity.ChannelEvents.Message {
    name := e.Get("name").String()
    response := struct {
      Greeting string `json:"greeting"`
    }{
      Greeting: fmt.Sprintf("Hello, %s", name),
    }
    client.Send(response)
  }
})

The Client.Send function will accept any structure and serialize it into JSON before sending it to the client. The Event.Get function can be used to easily retrieve single values from the incoming JSON message.

You can also send data to the client using SocketClient.SendRaw or SocketClient.SendString.

Deserialize incoming messages into structures

You could also extract the incoming message into a structure if it was more complicated:

message := struct {
  Name string `json:"name"`
}{}
e.Extract(&message)

Accessing the request context

The request context is available and can be used like any other route. Inside a channel the context is attached to the client. In the previous example the chanenl route had a token url variable at the end. We could get access to that variable through the context:

client.Context.URLParams.String("token")

If we assume we have some way of validating that token that returns a User structure we might want to validate that on connect and attach the user to the Context.

func(client *celerity.SocketClient, e celerity.ChannelEvent) {
  if e.Event == celerity.ChannelEvents.Connect {
    token := client.Context.URLParams.String("token")
    user := validateSession(token)
    client.Context.Set("user", user)
  }
} 

Now we can access that user when we process messages events or at any later time, such as iterating over clients.

user, ok := client.Context.Get("user").(*User)

Using rooms

Rooms can be created using the Room function on a channel. It accepts a room name and will either return that room if, it exists, or create a new room with that name.

From within a channel’s event handler you can access the channel through the client. For example we can assume we have users that belong to a company and we want to assign clients to a room based on some kind of identifier for the company. This will make it easier later to send notifications to all users in that company.

func(client *celerity.SocketClient, e celerity.ChannelEvent) {
  if e.Event == celerity.ChannelEvents.Connect {
    user := validateSession(token)
    companyId := user.Company.Identifier
    client.Channel.Room(companyId).Add(client)
  }
} 

Clients are automatically removed from any rooms they belong to on disconnect. So it is not necessary to remove them manually.

Sending messages from outside channel handlers

It may be useful to send channel events outside the channel event’s handler. For instance sending a notification of a performed action from within a standard route. The request Context has a reference to the server itself which maintains a map of all channels.

If we had a route which added a new ToDo item and wanted to notify users that it had been added we might end up with something like below:

svr := celerity.New()

svr.Channel("main", "/ws", func(client celerity.SocketClient, e celerity.ChannelEvent) {
  // For our example we dont need to process any incoming events from the client
})

svr.POST("/new", func(c celerity.Context) celerity.Response {
  var todo Todo
  if !c.ExtractAt("data", &todo) {
    return c.Error(400, errors.New("badly formed request"))
  }
  AddTodoItem(todo)
  c.Server.Channel("main").Broadcast(todo)
  return c.R(todo)
})
last modified Sunday, July 29, 2018
Previous in Guides
Serving static content and files
Serving static files and folders from a local path
Celerity is maintained by 5Sigma. Source code is available at Github.