Back to all entries

Blog

TechForce: Building a Web Chat with the Go programming language and HTML5 Websockets

This application demonstrates how to use the Google Go language and HTML5 Websockets to implement a simple web chat application.

Here is the screenshot of the demo: Chat demo

You can join the chat room by entering your email, which will be used as your name and generate an avatar from Gravatar. When you are in the chat, you can find buddies with avatars and names who are already in the room from the top right area. And then, of course, you are able to type and input messages to chat with others.

Now, let me walk you through the main concepts of this application.

Server side

First, we need a chat engine named ActiveRoom as the core of the whole application. You can see the type definition below, and one instance will be initialized as a global variable at the time when the application’s main function starts. Also the run method will be executed through a goroutine.

The running instance is used to maintain all of the websocket connections and handle messages by looping over them. Once there is a new message received through the Broadcast channel, it will send the message to all connections’ Send channel.

The Message type is the basic data exchanged which the sever will send to the client. In this case, it defines two message types, one is the text message, another is the real-time user online status.

type ActiveRoom struct {
     OnlineUsers map[string]*OnlineUser
     Broadcast   chan Message
     CloseSign   chan bool
}

type Message struct {
     MType       string
     TextMessage TextMessage
     UserStatus  UserStatus
}

func (this *ActiveRoom) run() {
     for {
          select {
          case b := <-this.Broadcast:
               for _, online := range this.OnlineUsers {
                    online.Send <- b
               }
          case c := <-this.CloseSign:
               if c == true {
                    close(this.Broadcast)
                    close(this.CloseSign)
                    return
               }
          }
     }
}

The OnlineUser type represents a successful connected user who is chatting in the room. It maintains the pointer of the running ActiveRoom, the websocket connection instance that is built between server and client, the chatting user and a Go Channel which allows other goroutine communication.

The OnlineUser defines two pointer methods:

PushToClient:
Reads messages from its Send channel and pushes the messages to the client via a websocket.

PullFromClient:
Reads messages from the websocket sent from the client and sends them to the running ActiveRoom.

Both methods use “for” statements to wait for new messages unless the websocket connection breaks.

type OnlineUser struct {
     InRoom     *ActiveRoom
     Connection *websocket.Conn
     UserInfo   *User
     Send       chan Message
}

func (this *OnlineUser) PullFromClient() {
     for {
          var content string
          err := websocket.Message.Receive(this.Connection, &content)

          if err != nil {
               return
          }

          m := Message{
               MType: TEXT_MTYPE,
               TextMessage: TextMessage{
                    UserInfo: this.UserInfo,
                    Time:     humanCreatedAt(),
                    Content:  content,
               },
          }
          this.InRoom.Broadcast <- m
     }
}

func (this *OnlineUser) PushToClient() {
     for b := range this.Send {
          err := websocket.JSON.Send(this.Connection, b)
          if err != nil {
               break
          }
     }
}

Now about the routing, the BuildConnection function is registered by the application’s main function as a websocket handler.

http.Handle("/chat", websocket.Handler(wscon.BuildConnection))

When there is a websocket connection request coming and matching the URL, the handler function will serve it and do some initialization work with gorountines for holding the new connection.

func BuildConnection(ws *websocket.Conn) {
     email := ws.Request().URL.Query().Get("email")
     onlineUser := &OnlineUser{
          InRoom:     runningActiveRoom,
          Connection: ws,
          Send:       make(chan Message, 256),
          UserInfo: &User{
               Email:    email,
               Name:     strings.Split(email, "@")[0],
               Gravatar: libs.UrlSize(email, 20),
          },
     }

     runningActiveRoom.OnlineUsers[email] = onlineUser

     m := Message{
          MType: STATUS_MTYPE,
          UserStatus: UserStatus{
               Users: runningActiveRoom.GetOnlineUsers(),
          },
     }

     runningActiveRoom.Broadcast <- m
     go onlineUser.PushToClient()
     onlineUser.PullFromClient()
     onlineUser.killUserResource()
}

Client side

The last part is the client that is implemented with Javascript. It instructs the browser to open a new websocket connection to the server and register callbacks to handle messages from the server. You can see when the connection receives a new message, the conn.onmessage will be called. Now you just need to dispatch the receiving messages which are pushed from the server to different javascript functions.

if (window["WebSocket"]) {
    conn = new WebSocket("ws://{{.WebSocketHost}}/chat?email={{.Email}}");
    conn.onopen = function() {};

    conn.onmessage = function(evt) {
         var data = JSON.parse(evt.data);
         switch(data.MType) {
              case "text_mtype":
                   addMessage(data.TextMessage)
                   break;
              case "status_mtype":
                   updateUsers(data.UserStatus)
                   break;
              default:
         }
    };

    conn.onerror = function() {
           errorMessage("<strong> An error just occured.<strong>")
    };

    conn.onclose = function() {
           errorMessage("<strong>Connection closed.<strong>")
    };
} else {
    errorMessage("Your browser does not support WebSockets.");
}

Last but not least, if you are interested in getting more detail about this demo, you can find the source of this example in gochatting.


TechForce is a weekly meeting held at The Plant for developers to present and discuss new technologies and projects they are working on. Each week a different developer presents. We are starting to post summaries of these presentations here.