In the past days, there is a big challenge for developers to allow real-time functionality. But now WebSockets have come as a solution to the issue and made it easy to create an interactive session between server and browser of user. WebSockets allows browser to send messages to the server and get event-driven responses. There is no necessity to have to poll the server for a reply.
Now websockets are the best solution to develop real time applications, tracking apps, instant messengers, online games and so on. Here we’ll give a step by step guide of how to build websockets apps in golang language. Let’s get started.
WebSockets are upgraded HTTP connections that continue until the client or the server ends the connection. WebSocket communication protocol provides a full-duplex communication channel over a single TCP connection. As opposite to HTTPs, Websockets don’t need to send request to get a response. They enables bidirectional data flow, so you can wait only for server to respond. It’ll send you a message when it is available. Websockets are a great solution for services which need continuous data exchange like, instant messaging apps, online games and real-time trading systems. Browsers request WebSocket connections and are responded to by servers after which a connection is established.
This process is called as handshake. The special header in WebSockets needs just a single handshake between a browser and server for setting up a connection that will be active all through its lifetime. WebSockets solves many issues of real time web development and have some benefit over traditional HTTP.
1. GOWebsockets-
It offers a wide range of easy-to-use features and allows concurrency control, data compression, and setting request headers. This tools supports subprotocols for emitting and receiving text and binary data. Also developers can enable or disable SSL verification.
Client side:
// init // schema – can be ws or wss // host, port – ws server socket := gowebsocket.New({schema}://{host}:{port}) socket.Connect() ....... // send message socket.SendText({message}) or socket.SendBinary({message}) ....... // receive message socket.OnTextMessage = func(message string, socket gowebsocket.Socket) { // handle received message }; or socket.OnBinaryMessage = func(data [] byte, socket gowebsocket.Socket) { // handle received message }; .......
Server side:
// init // schema – can be ws or wss // host, port – ws server conn, _, _, err := ws.DefaultDialer.Dial(ctx, {schema}://{host}:{port}) if err != nil { // handle error } ....... // send message err = wsutil.WriteClientMessage(conn, ws.OpText, {message}) if err != nil { // handle error } .......
// receive message msg, _, err := wsutil.ReadServerData(conn) if err != nil { // handle error }
It is a small WebSocket package which has strong features such as a low-level API that allows to develop the logic of custom packet handling and a zero-copy upgrade. It doesn’t need intermediate allocations during I/O. Also it boasts high-level wrappers and helpers around the API in the WsUtil package, allowing developers to rapidly start without digging into the internals of the protocol. In spite of the fact that this library includes a flexible API, it comes at the cost of clarity and usability.
The WebSocket package in Gorilla web toolkit helps to complete and test implementation of the WebSocket protocol and also a stable package API. It is easy to use and well documented.
Initially, create an HTTP handler with a WebSocket endpoint:
// HTTP server with WebSocket endpoint func Server() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ws, err := NewHandler(w, r) if err != nil { // handle error } if err = ws.Handshake(); err != nil { // handle error } …
Then, run the WebSocket structure.
Client always send first handshake request. Once the server has authenticated a WebSocket request, it needs to reply with a handshake response. Always keep in mind that you one cannot rite response using http.ResponseWriter, because it will disconnect the basic TCP connection if start to send the response. So you must use HTTP Hijacking that allows you to take over the underlying TCP connection handler and bufio.Writer. This allows you to read and write data without preventing the TCP connection.
// NewHandler initializes a new handler func NewHandler(w http.ResponseWriter, req http.Request) (WS, error) { hj, ok := w.(http.Hijacker) if !ok { // handle error } ….. }
To complete the handshake, server should respond with relevant headers.
// Handshake creates a handshake header func (ws *WS) Handshake() error { hash := func(key string) string { h := sha1.New() h.Write([]byte(key)) h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")) return base64.StdEncoding.EncodeToString(h.Sum(nil)) }(ws.header.Get("Sec-WebSocket-Key")) ..... }
“Sec-WebSocket-key” is generated randomly and is Base64-encoded. Once the request is accepted, server should add this key to a fixed string. Assume that you have the x3JJHMbDL1EzLkh9GBhXDw== key.
In this case, you can use SHA-1 for binary value computing and Base64 for encoding it. You will get HSmrc0sMlYUkAGmm5OPpG2HaGWk=. Use this as value of Sec-WebSocket-Accept response header.
Once the handshake is completed, your app can read and write data from and to the client. WebSocket specification defines a specific frame format which is used between a client and server. Have a look at the bit pattern of frame:
Code to decode the client payload:
// Recv receives data and returns a Frame func (ws *WS) Recv() (frame Frame, _ error) { frame = Frame{} head, err := ws.read(2) if err != nil { // handle error }
These lines of code allow for encoding data:
// Send sends a Frame func (ws *WS) Send(fr Frame) error { // make a slice of bytes of length 2 data := make([]byte, 2) // Save fragmentation & opcode information in the first byte data[0] = 0x80 | fr.Opcode if fr.IsFragment { data[0] &= 0x7F } .....
When one of the parties sends a close frame alongside a close status as the payload, a handshake is closed. Unexpectedly, the party that sends the close frame can also send a close purpose in the payload. If the client initiates the closing, the server should send an equivalent close frame in response.
// Close sends a close frame and closes the TCP connection func (ws *Ws) Close() error { f := Frame{} f.Opcode = 8 f.Length = 2 f.Payload = make([]byte, 2) binary.BigEndian.PutUint16(f.Payload, ws.status) if err := ws.Send(f); err != nil { return err } return ws.conn.Close() }