The first two parts of this article briefly introduced the WebSocket protocol and how to create WebSocket services using the Go standard library. In the third part of the practice, we use gorilla/ WebSocket library to help us quickly build webSocket service. It helps encapsulate the basic logic of using Go standard library to implement WebSocket service, freeing us from the tedious underlying code. Quickly build WebSocket services based on business requirements.

The source code for each article in the Go Web Programming series is printed with a corresponding version of the package for your reference. Get the source code of this article by replying to gohttp10

WebSocket is introduced

The WebSocket communication protocol provides a full-duplex communication channel over a single TCP connection. In contrast to HTTP, WebSocket does not require you to send a request in order to get a response. It allows two-way data flow, so you just wait for the message to be sent by the server. When Websocket is available, it will send you a message. WebSocket is a good solution for services that require continuous data exchange, such as instant messaging programs, online games, and real-time trading systems. A WebSocket connection is requested by the browser, responded by the server, and then established, a process commonly referred to as a handshake. A special header in a WebSocket requires only a handshake between the browser and the server to establish a connection that will remain active for its entire life. WebSocket solves many of the challenges of real-time Web development and has many advantages over traditional HTTP:

  • Lightweight headers reduce data transfer overhead.
  • A singleWebOnly one client is requiredTCPThe connection.
  • WebSocketThe server can push data toWebThe client.

The WebSocket protocol is relatively simple to implement. It uses the HTTP protocol for the initial handshake. The connection is established upon a successful handshake, and the WebSocket essentially reads/writes data using raw TCP.

The client request looks like this:

GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com
Copy the code

Here’s the server response:

HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat
Copy the code

How to create a WebSocket application in Go

To write a WebSocket server based on Go’s built-in NET/HTTP library, you need to:

  • A handshake
  • Receives data frames from the client
  • Send data frames to the client
  • Close to shake hands

A handshake

First, let’s 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)
            iferr ! = nil { // handle error }iferr = ws.Handshake(); err ! = nil {// handle error}...Copy the code

Then initialize the WebSocket structure.

The initial handshake request always comes from the client. After the server determines the WebSocket request, it needs to respond with a handshake response.

Keep in mind that you cannot write a response using http.responseWriter, because once the response is sent, it closes its underlying TCP connection (this is due to the way the HTTP protocol works, which closes the connection once the response is sent).

Therefore, you need to use HTTP hijack. By hijacking, you can take over the underlying TCP connection handler and bufio.writer. This makes it possible to read and write data without closing 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 } ..... }Copy the code

To complete the handshake, the server must respond with the appropriate header.

// 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"))... }Copy the code

The sec-websocket-key used by the client to initiate a WebSocket connection request is randomly generated and Base64 encoded. After accepting the request, the server needs to append this key to the fixed string. Suppose the secret key is x3JJHMbDL1EzLkh9GBhXDw==. In this example, the binary value can be calculated using SHA-1 and encoded using Base64. Get HSmrc0sMlYUkAGmm5OPpG2HaGWk =. It is then used as the value of the sec-websocket-Accept response header.

Transmission data frame

After the handshake completes successfully, your application can read data from or write data to the client. The WebSocket specification defines a particular frame format for use between a client and a server. Here is the framework’s bit pattern:

Decode the client payload with the following code:

// Recv receives data and returns a Frame
    func (ws *WS) Recv() (frame Frame, _ error) {
        frame = Frame{}
        head, err := ws.read(2)
        iferr ! = nil { // handle error }Copy the code

These lines, in turn, allow the data to be encoded:

// 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
        }
        .....
Copy the code

Close to shake hands

The handshake is closed when one of the parties sends a closed frame with a closed state as the payload. Optionally, the party sending the closing frame can send the closing reason in the payload. If the closure is initiated by the client, the server should send the appropriate closure 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)
    iferr := ws.Send(f); err ! = nil {return err
    }
    return ws.conn.Close()
}
Copy the code

Use third-party libraries to build quicklyWebSocketservice

As you can see from the section above, implementing WebSocket services using Go’s built-in NET/HTTP library is still too complicated. Fortunately, there are many third-party libraries that support WebSocket well, which can reduce a lot of our low-level coding. Here we use Gorilla/WebSocket, another library in the Gorilla Web Toolkit family, to implement our WebSocket service, building a simple Echo service (Echo means Echo, which is what the client sends, and the server sends the message back to the client).

We create a ws subdirectory in the http_demo project’s handler directory to store the request handler for the WebSocket service-related route.

Add two routes:

  • /ws/echo echoRoute to the application WebSocket service.
  • /ws/echo_display echoRoute to the application client page.

Create a WebSocket server

// handler/ws/echo.go
package ws

import (
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,}func EchoMessage(w http.ResponseWriter, r *http.Request) {
	conn, _ := upgrader.Upgrade(w, r, nil) // Remember to do error handling in practice

	for {
		// Read messages from the client
		msgType, msg, err := conn.ReadMessage()
		iferr ! =nil {
			return
		}

		// Print the message to standard output
		fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))

		// Write the message back to the client to complete the echo
		iferr = conn.WriteMessage(msgType, msg); err ! =nil {
			return}}}Copy the code
  • The conn variable is of type *websocket. conn, and the websocket. conn type is used to represent webSocket connections. The server application calls upgrader. Upgrade method from the HTTP request handler to get * websocket.conn

  • Call the connection’s WriteMessage and ReadMessage methods to send and receive messages. The MSG received above is then sent back to the client below. MSG is of type []byte.

Create a WebSocket client

The request handler of the front-end page route is as follows, which directly returns views/websockets. HTML to the browser to render the page.

// handler/ws/echo_display.go
package ws

import "net/http"

func DisplayEcho(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "views/websockets.html")}Copy the code

In websocket. HTML, we need to use JavaScript to connect WebScoket service for sending and receiving messages. For reasons of space, I will only post JS code.

<form>
    <input id="input" type="text" />
    <button onclick="send()">Send</button>
    <pre id="output"></pre>
</form>
...
<script>
    var input = document.getElementById("input");
    var output = document.getElementById("output");
    var socket = new WebSocket("ws://localhost:8000/ws/echo");

    socket.onopen = function () {
        output.innerHTML += "Status: Connected\n";
    };

    socket.onmessage = function (e) {
        output.innerHTML += "Server: " + e.data + "\n";
    };

    function send() {
        socket.send(input.value);
        input.value = "";
    }
</script>
...
Copy the code

Registered routing

After the server and client programs are ready, we register routes and corresponding request handlers for them according to the previously agreed path:

// router/router.go
func RegisterRoutes(r *mux.Router){... wsRouter := r.PathPrefix("/ws").Subrouter()
    wsRouter.HandleFunc("/echo", ws.EchoMessage)
    wsRouter.HandleFunc("/echo_display", ws.DisplayEcho)
}
Copy the code

The validation test

After the restart services to http://localhost:8000/ws/echo_display, enter any message in the input box can once again back to the browser.

The server prints the received message to the terminal and then calls writeMessage to send the message back to the client, where the record can be viewed.

conclusion

WebSocket is widely used in the frequently updated applications. WebSocket programming is also a necessary skill that we need to master. The practical exercises are a little simpler, and there are no error and security checks. Just to get a sense of the process. For more details on gorilla/ Websocket use, check the official documentation.

Reference links:

Yalantis.com/blog/how-to…

www.gorillatoolkit.org/pkg/websock…

These reviews

Learn more about writing HTTP servers with Go

Super detailed Go template library application guide

Create a static file server in Go

Use SecureCookie to manage client sessions