I've recently taken a liking to Go, and after an evening cruising through the Go Tour and various best practice docs, I wanted to put my knowledge to the test. Since web development is where I'm most at home, I decided to seek out a web framework. One of the first to catch my eye was Revel, which aims to be the "batteries included" web framework for Go. Being a fan of Python (the batteries included language), this felt unusually welcoming. In noticing that Revel has websocket support out of the box, I imagined this could provide an opportunity to do something interesting. However, anyone whose worked with raw websocket messaging knows it can get messy fast. In order to do anything substantial you'll find yourself re-creating similar features found in Socketio–which conveniently enough is made available via the go-socketio project. The final part of this puzzle is the focus of this month's brief blog post: combining revel and socketio into the same project, without running separate servers.

Setting Up Your Revel Project

To begin, you'll need to install Revel and go-socketio. In my case, I have my GOPATH pointing to ~/golang, which means the following commands will install the source for both projects into ~/golang/src.

go get github.com/revel/revel
go get github.com/googollee/go-socket.io

With our dependencies installed, we can use Revel's command line tool to generate a new application skeleton.

revel new github.com/iamjem/sockettest

You'll need to ensure you have $GOPATH/bin included in your PATH for the command line tool to work.

Patching the Revel Server Handler

The second part of this exercise is to patch Revel's default http.Server handler. Thankfully Revel provides a way to register app hooks that run after the public revel.Server is created. I've added a socketio directory to my project which looks like the following:

sockettest/
    app/
        socketio/
            handlers.go
            socketio.go

Within the default app/init.go file's init() function, I've registered a hook to patch the server:

package app

import "github.com/revel/revel"
import "github.com/iamjem/sockettest/app/socketio"

func init() {
    // default filters ...

    revel.OnAppStart(socketio.PatchServer)
}

Next, add the hook and socketio configuration logic to the socketio/socketio.go file:

package socketio

import (
    sio "github.com/googollee/go-socket.io"
    "github.com/revel/revel"
    "net/http"
    "strings"
)

var (
    sioServer   *sio.SocketIOServer
    revelHandle http.Handler
)

func handle(w http.ResponseWriter, r *http.Request) {
    path := r.URL.Path
    // route socketio requests to the socketio handler
    // and send everything else to the revel handler
    if strings.HasPrefix(path, "/socket.io/1/") {
        sioServer.ServeHTTP(w, r)
    } else {
        revelHandle.ServeHTTP(w, r)
    }
}

func PatchServer() {
    // create socketio server config
    config := &sio.Config{}
    config.HeartbeatTimeout = 2
    config.ClosingTimeout = 4

    // create socketio server
    sioServer = sio.NewSocketIOServer(config)

    // register global and namespace handlers
    registerHandlers(sioServer)

    // store a reference to revel's old http.Handler
    revelHandle = revel.Server.Handler

    // replace revel.Server.Handler with our new handler
    revel.Server.Handler = http.HandlerFunc(handle)
}

Finally we can add some test Socketio handlers to socketio/handlers.go:

package socketio

import (
    "fmt"
    sio "github.com/googollee/go-socket.io"
)

func onConnect(ns *sio.NameSpace) {
    fmt.Printf("Connect: %v", ns.Id())
    ns.Emit("welcome", "Welcome aboard!")
}

func onDisconnect(ns *sio.NameSpace) {
    fmt.Printf("Disconnect: %v", ns.Id())
}

func onRevelRocks(ns *sio.NameSpace) {
    msg := fmt.Sprintf("Why thank you %s", ns.Id())
    ns.Emit("revelrocks", msg)
}

func registerHandlers(sioServer *sio.SocketIOServer) {
    sioServer.On("connect", onConnect)
    sioServer.On("disconnect", onConnect)
    sioServer.On("revelrocks", onRevelRocks)
}

Testing the Application

With the backend in place, I've updated the default views/app/index.html with some Socketio code to test things out.

<!doctype html>
<html class="no-js">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title></title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="stylesheet" href="/public/css/normalize.css">
    </head>
    <body>
        <button id="rock">Tell Revel How Your Feel</button>

        <script src="/public/js/jquery-1.9.1.min.js"></script>
        <script src="/public/js/socket.io.js"></script>

        <script>
          var socket = io.connect();
          socket.on("revelrocks", function(msg){
            alert(msg);
          });

          $("#rock").on("click", function(event){
            socket.emit("revelrocks");
          });
        </script>

    </body>
</html>

Spinning up the Revel web server with revel run github.com/iamjem/sockettest, I can launch my browser and navigate to http://localhost:9000/. Immediately I see the connect message in stdout appear, and should I click the Revel rocks button on the page, I'm alerted with a response from the server showing my session ID.

Conclusion

Revel is currently a pretty lightweight framework, but its well on its way to realizing its "batteries included" dream. I hope I'm fortunate enough to find some opportunities to work with and contribute to Revel in the future. I also feel its important to applaud the work by the go-socketio project, which has been excellently crafted to make creating rich Socketio powered applications effortless. There's numerous languages and frameworks infinitely more mature than Go and Revel that still lack these tools. I hope this post has been enlightening and shown some possibilities for incorporating Socketio in your next Revel application.

comments powered by Disqus