How To Write Your First Golang Websocket Client

03/11/2021 at 04:35PM

I heard a lot about Go (Golang)and decided to give it a try. As my area of expertise lies in dealing with live forex data I decided to pull live forex data using Go.

Here is what I found.

  1. It is probably the easiest to set up.
  2. And works with little or no issues.


Now I am sure a lot of experts in Go and other languages will disagree with my second point. I myself like Python despite the above two points not being its strong suit (I may have done it again).

Before we start a few requirements, you will need prior programming knowledge, an internet connection and an email address.


Let’s begin

Get Websocket API Key

Before we set up our Go environment let us get our forex WebSocket API key from https://marketdata.tradermade.com/signup. It’s free. Once you log in to your account generate your API key and keep it on hold.



Setup Environment

First, download and install Go from https://golang.org/doc/install



Its three simple steps.

Once you have it installed open the command prompt and write:

go version



Install dependencies

go get github.com/gorilla/websocket


You now have to Go run!


Let’s write some code, We will start by creating the basic program. The main() function will be called when the program is run, we are also going to import the libraries that we need. For this example, we are going to use the following: 


// +build ignore

package main

import (
  "log"
  "net/url"
  "os"
  "os/signal"
  "time"
  "github.com/gorilla/websocket"
)
func main() {
//Add progrma content here 
}



We are going to add to this a couple of variables one to store the incoming messages and another to handle interrupt events when the program terminates. These variables are the channel through which the message passes similar to buffer in other languages. In Go, channels can be bi-directional or one way. We are also going to add a signal notifier to our interrupt variable so we can clean up nicely when the program terminates.


messageOut := make(chan string)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)


Now we are going to create our URL for the connection to the WebSocket server and we will output this so we can check the URL.


u := url.URL{Scheme: "wss", Host: "marketdata.tradermade.com", Path: "/feedadv",}
log.Printf("connecting to %s", u.String())


Next, we are going to write some code to handle establishing the connection to the server and dealing with different outcomes from the connection. On creation we look for an error and if we find one we display a message to the user. The last line of this section adds “defer c.Close()” this says to the program when this function is completed call Close() on this connection. The first defer will execute last, think of it as the last thing to execute on the stack.


c, resp, err := websocket.DefaultDialer.Dial(u.String(), nil);

  if err != nil {
    log.Printf("handshake failed with status %d", resp.StatusCode)
    log.Fatal("dial:", err)
  }
  //When the program closes close the connection
  defer c.Close()


Now we will write the function to handle the connection, this is a goroutine so runs asynchronously reading the process from the feed. As it loops through it reads the message using c.ReadMessage() this returns a message or an error. On the receipt of an error the code prints an error and exits, On receipt of a message it is checked to see if it's a connection message, if this is the case then a string is returned with the user_key and the symbols required. Once this has been sent you will receive a price message back to the client. In this example, we just print the message to the screen but it would be here that you processed the message data as required.


done := make(chan struct{})
  go func() {
    defer close(done)
    for {
      _, message, err := c.ReadMessage()
      if err != nil {
        log.Println("read:", err)
        return
      }
      log.Printf("recv: %s", message)
      if string(message) == "Connected"{
        log.Printf("Send Sub Details: %s", message)
        messageOut <- "{"userKey":"YOUR_API_KEY", "symbol":"EURUSD"}"
      }
    }
  }()


The last section in this program looks complicated but is actually really simple. This is a constant loop that is used to keep the program running as the goroutine is async it does not lock the main thread so without this section the program would just return and your feed handler would stop.


ticker := time.NewTicker(time.Second)

  defer ticker.Stop()

  for {

    select {

    case <-done:

      return

    case m := <-messageOut:

      log.Printf("Send Message %s", m)

      err := c.WriteMessage(websocket.TextMessage, []byte(m))

      if err != nil {

        log.Println("write:", err)

        return

      }

    case t := <-ticker.C:

      err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))

      if err != nil {

        log.Println("write:", err)

        return

      }

    case <-interrupt:

      log.Println("interrupt")

      // Cleanly close the connection by sending a close message and then

      // waiting (with timeout) for the server to close the connection.

      err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))

      if err != nil {

        log.Println("write close:", err)

        return

      }

      select {

      case <-done:

      case <-time.After(time.Second):

      }

      return

    }

  }


Now we can put all the code together and set your API _KEY, save the file as webSocketClient.go 

then run the following command from the terminal to run your code:

go run websocketClient.go



You can see live prices coming in. You have now successfully run your first Websocket client pulling FX data.


Full application code:


 // +build ignore

 
package main

import (
  "log"
  "net/url"
  "os"
  "os/signal"
  "time"
  "github.com/gorilla/websocket"
)
 

func main() {
//Create Message Out   
  messageOut := make(chan string)
  interrupt := make(chan os.Signal, 1)
  signal.Notify(interrupt, os.Interrupt)
  u := url.URL{Scheme: "wss", Host: "marketdata.tradermade.com", Path: "/feedadv",}
  log.Printf("connecting to %s", u.String())
  c, resp, err := websocket.DefaultDialer.Dial(u.String(), nil);
  if err != nil {
    log.Printf("handshake failed with status %d", resp.StatusCode)
    log.Fatal("dial:", err)
  }

  //When the program closes close the connection
  defer c.Close()
  done := make(chan struct{})
  go func() {
    defer close(done)
    for {
      _, message, err := c.ReadMessage()
      if err != nil {
        log.Println("read:", err)
        return
      }
      log.Printf("recv: %s", message)
      if string(message) == "Connected"{
        log.Printf("Send Sub Details: %s", message)
        messageOut <- "{"userKey":"YOUR_API_KEY", "symbol":"EURUSD"}"
      }
    }

  }()

  ticker := time.NewTicker(time.Second)
  defer ticker.Stop()
  for {
    select {
    case <-done:
      return
    case m := <-messageOut:
      log.Printf("Send Message %s", m)
      err := c.WriteMessage(websocket.TextMessage, []byte(m))
      if err != nil {
        log.Println("write:", err)
        return
      }
    case t := <-ticker.C:
      err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
      if err != nil {
        log.Println("write:", err)
        return
      }
    case <-interrupt:
      log.Println("interrupt")
      // Cleanly close the connection by sending a close message and then
      // waiting (with timeout) for the server to close the connection.
      err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
      if err != nil {
        log.Println("write close:", err)
        return
      }
      select {
      case <-done:
      case <-time.After(time.Second):
      }
      return
    }
  }
}