Add Websocket to your TCP service with minimal changes

Offer your users the possibility to connect by websocket ! It allows them to use your service from javascript code :

* Javascript is very popular and lets you build prototypes very quickly
* It’s more easy to build a GUI with HTML than most desktop languages
* You can offer a true demonstration of your service directly on your website

and… let me convince you that it’s very low-cost.

What you probably have for now

import (
    "fmt"
    "net"
)
 
func StartTCP() {
    l, err := net.Listen("tcp", "localhost:8081")
    if err != nil {
        fmt.Println("Error listening:", err.Error())
        panic()
    }
    fmt.Println("Listening TCP on " + "localhost:8081")
    for {
        conn, err := l.Accept()
        if err != nil {
            fmt.Prinln("Error accepting: ", err.Error())
            panic()
        }
        go handleRequest(conn)
    }
}
 
func Handle(conn net.Conn) {
    // ...
}

Specific part : Server and handling connections

Here is the only specific code to add for websocket.

import (
	"fmt"
        "net/http"
 
	"golang.org/x/net/websocket"
)
 
func StartWS() {
 
	port := 8082
 
	http.Handle("/", websocket.Handler(HandleWS))
	err := http.ListenAndServe(port, nil)
 
        fmt.Println("Listening WS on " + "localhost:8082")
 
	if err != nil {
		fmt.Println(err)
		panic(err)
	}
}
 
// Note : When this function return, the WS closes automatically
func HandleWS(ws *websocket.Conn) {
	// ?
}

All the (simple) tricks is about that “Handle” function

Generic part : Everything else

In the net package, you can find the “net.Conn” interface :

type Conn interface {
        Read(b []byte) (n int, err error)
        Write(b []byte) (n int, err error)
        Close() error
 
        // ...
}

You already use it to handle your TCP connection.
And very good news : TCP Socket and Websocket both implement it !

So, you can call the same function

 
func HandleWS(ws *websocket.Conn) {
	Handle(ws)
}
 
func Handle(client net.Conn) {
        // Deal with client (same for TCP/Websocket)
        // Already done
}

From now on, you can use exactly the same protocol with your websocket clients and TCP clients. No more code.

Note :
A trap about buffering and packets in Websocket

In Websocket, you may think that the protocol is based on message ; you always receive the message sent in one read.
That’s not true !

Websocket RFC :

   The WebSocket protocol uses this framing so that specifications that
   use the WebSocket protocol can expose such connections using an
   event-based mechanism instead of requiring users of those
   specifications to implement buffering and piecing together of
   messages manually.

It happened not very often, and most of the time for relatively long messages. It’s why you may never paid attention to it. But it’s gonna be a problem.

As for TCP you need to “implement buffering and piecing together of messages manually.”

Exception :
Some languages like JS can guarantee that you always receive messages.

The WebSocket API within browsers does not expose a frame-based or streaming API,
but only a message-based API.
The payload of an incoming message is always completely buffered up
(within the browser's WebSocket implementation) before providing it to JavaScript.

Source

So, if you have a Go WS server and a JS client, your messages are received by JS one by one, not chunked.

Solve it without header indicating packet size

Most of the time, your messages are formatted in a popular protocol as JSON.
It’s very annoying to parse JSON in packet, finding where is the limit between to packets.

But ! There is a solution for that.

The JSON package contains a “Decoder” object, which unmarshall packets from a stream of data.

Allowing you to do this :

 
func Handle(client net.Conn) {
        reader = json.NewDecoder(client)
        for {
                inter, raw := ReadOneMessage(reader)
                // ...
        }
}
 
func ReadOneMessage(reader *json.Decoder) (inter interface{}, raw []byte) {
	err := reader.Decode(&inter)
	if err != nil {
		return
	}
	raw, _ := json.Marshal(inter)
	return tmp, raw
}

Here you get the interface{} and the buffer, for getting the packet-type with the interface{}, and unmarshall to the specific struct then.

-> If you get only one packet type, can directly decode to the specific type
-> You can also use the interface{} directly, not needing []byte conversion

Update 13/02/2017 : Added some informations about JS framing specificity.

Entrepreneur – Cofounder at Golem.ai (Paris, France)

I enjoy sharing Golang interesting patterns, experiments and tips.

2 thoughts on “Add Websocket to your TCP service with minimal changes

Leave a Reply to 62hhy.com Cancel reply

Your email address will not be published. Required fields are marked *