Getting Started with WebSockets in Go

Written by: Nick Gauthier

This article was originally published on MeetSpace by Nick Gauthier, and with his kind permission, we are sharing it here for Codeship readers.

WebSockets are one of the core technologies MeetSpace uses to connect people on a call. In this post, we’ll look at how to get started with WebSockets in JavaScript and Go.

WebSockets on the Server

We’ll dive right into the HTTP handler for a WebSocket. If you’re unfamiliar with writing HTTP servers in Go, start with my tutorial on http servers. At this point, we have a handler like this:

func socket(w http.ResponseWriter, r *http.Request) {
  // ok now what?
}

WebSockets work by sending an Upgrade header back on the response, at which point the browser switches over to processing the server data as a WebSocket connection. We could do this ourselves, but Go provides a package as part of their extended subrepository packages. So, we’ll use golang.org/x/net/websocket.

We need to get the WebSocket library to hook into our HTTP requests, and to do that, they provide websocket.Handler which is a type of function that provides ServeHTTP. That means all we have to do is create our own handler function and cast it to that type, and we can use it on our server. Here’s the basics:

func socket(ws *websocket.Conn) {
  // let's do websocket stuff!
}
// later, in your router initialization:
http.Handle("/socket", websocket.Handler(socket))

This syntax in Go confused me at first, because it totally looks like we’re calling the websocket.Handler() function on socket as a parameter. But, websocket.Handler is a type, so websocket.Handler(socket) is casting socket to the websocket.Handler type. Then, websocket.Handler’s ServeHTTP function means it’s a valid http.Handler interface implementer.

Now that we have our server harness set up, we need to process data. The simplest way to work with WebSockets is by using the websocket.Conn object (our ws) as an io.Reader. We could use Read and Write to write data, as in this simple example:

// make a buffer
var buf = make([]byte, 1024)
n, err := ws.Read(buf)
if err != nil {
  log.Println(err)
  return
}
fmt.Println("We read", n, "bytes, and they were", string(buf))

However, this has a couple of problems. First, we have to allocate a fixed size buffer. Go won’t overflow the buffer, but it’s possible to receive more than 1024 bytes, and we’ll only get the first 1024. We’d have to loop the read until we got all the data we wanted to process. We’d probably have some kind of delimiter on the data too, to know when we got a whole message. Also, when we get less than 1024 characters, we have to trim up to the delimiter.

This is a very common situation, so the websocket package provides a Codec struct that encapsulates a Marshal and Unmarshal function. If you construct a Codec with those two functions, you can then call Codec.Send and Codec.Receive and the frames will automatically be written and read using websocket’s internal framereader. That means you don’t have to worry about buffer sizes and frame delimiters, yay!

We could send our WebSocket data around as plain old bytes, but our client is going to need to make sense of it too. So we should probably agree upon some kind of cross platform interchangeable format. Like JSON! JSON has the benefit of being native to JavaScript, which is where our client will live. Additionally, Go’s support for JSON is great.

Lucky for us, this is a common plan, so the websocket package provides a JSON Codec. That makes reading and writing JSON data as simple as this:

type message struct {
  // the json tag means this will serialize as a lowercased field
  Message string `json:"message"`
}
func socket(ws *Conn) {
  for {
    // allocate our container struct
    var m message
    // receive a message using the codec
    if err := websocket.JSON.Receive(ws, &m); err != nil {
      log.Println(err)
      break
    }
    log.Println("Received message:", m.Message)
    // send a response
    m2 := message{"Thanks for the message!"}
    if err := websocket.JSON.Send(ws, m2); err != nil {
      log.Println(err)
      break
    }
  }
}

Client

Now that we have a server that can receive messages and respond, we need a client. I’ll leave the HTML and JavaScript scaffolding to you, and cut right to the JavaScript code that interacts with our server. Here’s a basic example that sends a single message and receives the response:

var ws = new WebSocket("ws://" + window.location.host + "/socket");
ws.onopen = function() {
  ws.send(JSON.stringify({message: "hello server!"}))
}
ws.onmessage = function(event) {
  var m = JSON.parse(event.data);
  console.debug("Received message", m.message);
}
ws.onerror = function(event) {
  console.debug(event)
}
That should create the following (riveting) conversation:
Go: "Received Message: hello server!"
JS: "Received Message: Thanks for the message!"

Conclusion and Warnings

That’s the basics of WebSockets in Go and JavaScript. However, there are a couple more things we ran into, and I wanted to mention them here with a brief description of the solutions so you wouldn’t get stuck on them right away:

  1. Typing. We found that having a message structure like {type: string, data: string} was very useful. On the server and client we use a switch on type and then send the data to the appropriate function based on type. The data is usually a plain string or JSON depending on how complex it is.

  2. Keepalive. After about a minute many browsers will close the socket if there’s no activity. We added a server-side heartbeat message using Go’s time.Tick in a Goroutine that kept it alive.

  3. Reconnect. If for some reason the socket connection is interrupted, you want to reconnect it. You can bind to onclose in JS to reconnect the socket. There is no actual “reconnect” so you make a new socket and rebind the event handlers. Hopefully that will help with getting started with WebSockets in Go and JavaScript.

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.