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 single
Web
Only one client is requiredTCP
The connection. WebSocket
The server can push data toWeb
The 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 quicklyWebSocket
service
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
echo
Route to the application WebSocket service./ws/echo_display
echo
Route 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