概述
對於 golang 的 web 開發, 之前寫過 2 篇 blog, 分別介紹了:
- 在 Gin 框架下, 各類 http API 的開發方法(包括文件上傳, 下載等) golang Web 方案
- Gin 框架下反向代理的使用: 反向代理的使用
這里再給之前的 Web 方案中加上 Websocket 的部分, 基本就能涵蓋日常開發所需的所有接口類型了.
golang websocket 庫
這里使用的 websocket 庫來自 Gorilla web toolkit
下面用代碼來演示如何在 Gin 框架中結合 websocket API
示例
后端
后端提供 2 種 API, 分別支持 text 格式和 json 格式的消息 示例中的 API 每次收到消息后, 返回 10 次
1 package main
2
3 import (
4 "log"
5 "net/http"
6 "strconv"
7 "time"
8
9 "github.com/gin-contrib/static"
10 "github.com/gin-gonic/gin"
11 "github.com/gorilla/websocket"
12 )
13
14 var upGrader = websocket.Upgrader{
15 CheckOrigin: func(r *http.Request) bool {
16 return true
17 },
18 }
19
20 // webSocket返回text 格式
21 func textApi(c *gin.Context) {
22 //升級get請求為webSocket協議
23 ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
24 if err != nil {
25 log.Println("error get connection")
26 log.Fatal(err)
27 }
28 defer ws.Close()
29 //讀取ws中的數據
30 mt, message, err := ws.ReadMessage()
31 if err != nil {
32 log.Println("error read message")
33 log.Fatal(err)
34 }
35
36 //寫入ws數據, pong 10 times
37 var count = 0
38 for {
39 count++
40 if count > 10 {
41 break
42 }
43
44 message = []byte(string(message) + " " + strconv.Itoa(count))
45 err = ws.WriteMessage(mt, message)
46 if err != nil {
47 log.Println("error write message: " + err.Error())
48 }
49 time.Sleep(1 * time.Second)
50 }
51 }
52
53 //webSocket返回 json格式
54 func jsonApi(c *gin.Context) {
55 //升級get請求為webSocket協議
56 ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
57 if err != nil {
58 log.Println("error get connection")
59 log.Fatal(err)
60 }
61 defer ws.Close()
62 var data struct {
63 A string `json:"a"`
64 B int `json:"b"`
65 }
66 //讀取ws中的數據
67 err = ws.ReadJSON(&data)
68 if err != nil {
69 log.Println("error read json")
70 log.Fatal(err)
71 }
72
73 //寫入ws數據, pong 10 times
74 var count = 0
75 for {
76 count++
77 if count > 10 {
78 break
79 }
80
81 err = ws.WriteJSON(struct {
82 A string `json:"a"`
83 B int `json:"b"`
84 C int `json:"c"`
85 }{
86 A: data.A,
87 B: data.B,
88 C: count,
89 })
90 if err != nil {
91 log.Println("error write json: " + err.Error())
92 }
93 time.Sleep(1 * time.Second)
94 }
95 }
96
97 func websocketGin() {
98 r := gin.Default()
99 r.GET("/json", jsonApi)
100 r.GET("/text", textApi)
101
102 // static files
103 r.Use(static.Serve("/", static.LocalFile("./public", true)))
104
105 r.NoRoute(func(c *gin.Context) {
106 c.File("./public/index.html")
107 })
108
109 r.Run(":8000")
110 }
后端代碼中有個靜態文件的路由 r.Use(static.Serve("/", static.LocalFile("./public", true)))
只要將下面的前端代碼命名為 index.html 並放在和后端代碼根目錄下的 public 文件夾中,
就可以在啟動后端之后, 直接通過訪問 *http://localhost:8000" 來顯示頁面了
前端
前端很簡單, 就是在頁面初始化完成后創建 websocket 連接, 然后發送消息並顯示接受到的消息
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <title>index</title>
6 </head>
7 <body>
8 <h1>test websocket</h1>
9 <p id="message-json"></p>
10 <p id="message-text"></p>
11 <script>
12 function jsonWS() {
13 var ws = new WebSocket("ws://localhost:8000/json");
14 //連接打開時觸發
15 ws.onopen = function (evt) {
16 console.log("Connection open ...");
17 var obj = { a: "bb", b: 2 };
18 ws.send(JSON.stringify(obj));
19 };
20 //接收到消息時觸發
21 ws.onmessage = function (evt) {
22 console.log("Received Message: " + evt.data);
23 document.getElementById("message-json").innerText += evt.data;
24 };
25 //連接關閉時觸發
26 ws.onclose = function (evt) {
27 console.log("Connection closed.");
28 };
29 }
30
31 function textWS() {
32 var ws = new WebSocket("ws://localhost:8000/text");
33 //連接打開時觸發
34 ws.onopen = function (evt) {
35 console.log("Connection open ...");
36 ws.send("text message");
37 };
38 //接收到消息時觸發
39 ws.onmessage = function (evt) {
40 console.log("Received Message: " + evt.data);
41 document.getElementById("message-text").innerText = evt.data;
42 };
43 //連接關閉時觸發
44 ws.onclose = function (evt) {
45 console.log("Connection closed.");
46 };
47 }
48 // 啟動 websocket
49 jsonWS();
50 textWS();
51 </script>
52 </body>
53 </html>
結論
運行之后, 就可以看到頁面上顯示的 message 了, 發送一次信息, 會收到 10 條返回.