Introduction

Gin is a famous web framework written in Go, we can easy to build our web server by following codes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main
import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/hello-world", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "hello, world",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

Saving these 11 lines codes as a hello.go file, then run go run hello.go.
Now, we can input curl statement in command line tool:
curl http://127.0.0.1:8080/hello-world And we can get:

1
2
3
{
    "message": "hello, world"
}

Now, our web server is already finished.

Using websocket in Gin

There are not so many websocket liberaies for Golang, and gorilla websocket is most important one.
We can easy to write our own websocket server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main
import (
    "log"

    "github.com/gin-gonic/gin"
    "github.com/gorilla/websocket"
)

func Websocket(c *gin.Context) {
    var upgrader = websocket.Upgrader{}
    w, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil {
        log.Print("upgrade:", err)
        return
    }
    defer w.Close()
    for {
        mt, message, err := w.ReadMessage()
        if err != nil {
            log.Println("read:", err)
            break
        }
        log.Printf("recv: %s", message)
        err = w.WriteMessage(mt, message)
        if err != nil {
            log.Println("write:", err)
            break
        }
    }
}   

func main() {
    r := gin.Default()
    r.GET("/hello-world", Websocket)
    r.Run() // listen and serve on 0.0.0.0:8080
}

We create a new function named Websocket, this is the main logic code to deal all the websocket data stream. And you still can see a forever loop here to read the msg from client and then send the same msg to client.
Let’s run and test the code:
go run hello.go Now, we can test the websocket in Postman:
websocket.png The pic shows that the msgs send and receive both are the same.

Upgrade the websocket module

Why we need to upgrade the websocket module?

As the code shows above, we can easy and short lines codes to build the websocket server, but why the websocket module needs a upgrade?

1
2
3
4
5
6
7
    var upgrader = websocket.Upgrader{}
    w, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil {
        log.Print("upgrade:", err)
        return
    }
    defer w.Close()

As you can see, we need to initial websocket every time when websocket establishes, and munually call the websocket close when websocket disconnects.

And even for the ReadMessage and WriteMsg, we may only care about he message but not others, we can use the Pool to upgrade, that’s why I import go-channel here.

utils.go

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
package utils

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

type WebsocketConnection struct {
    wsconnect               *websocket.Conn
    inChan                  chan []byte
    outChan                 chan []byte
    closeChan               chan byte

    sync.Mutex
    IsClosed bool
}

func InitWebsocket(c *gin.Context) (wc *WebsocketConnection, err error) {
    var wsupgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
        CheckOrigin: func(r *http.Request) bool {
            return true
        },
    }
    var conn *websocket.Conn
    if conn, err = wsupgrader.Upgrade(c.Writer, c.Request, nil); err != nil {
        return
    }
    wc = &WebsocketConnection{
        wsconnect:               conn,
        inChan:                  make(chan []byte, 0),
        outChan:                 make(chan []byte, 0),
        closeChan:               make(chan byte, 1),
    }
    go wc.readLoop()
    go wc.writeLoop()
    return
}

func (wc *WebsocketConnection) ReadMessage() (data []byte, err error) {
    select {
    case data = <-wc.inChan:
    case <-wc.closeChan:
        err = errors.New("Websocket disconnected.")
    }
    return
}

func (wc *WebsocketConnection) WriteMessage(data []byte) (err error) {
    select {
    case wc.outChan <- data:
    case <-wc.closeChan:
        err = errors.New("Websocket disconnected.")
    }
    return
}

func (wc *WebsocketConnection) Close() {
    wc.wsconnect.Close()
    wc.Mutex.Lock()
    defer wc.Mutex.Unlock()
    if !wc.IsClosed {
        close(wc.closeChan)
        wc.IsClosed = true
    }
}

func (wc *WebsocketConnection) readLoop() {
    var data []byte
    var err error
    for {
        if _, data, err = wc.wsconnect.ReadMessage(); err != nil {
            wc.Close()
            break
        }
        select {
        case wc.inChan <- data:
            fmt.Println("Receive data from client: " + string(data))
        case <-wc.closeChan:
            wc.Close()
            break
        }
    }
    return
}

func (wc *WebsocketConnection) writeLoop() {
    var data []byte
    var err error
    for {
        select {
        case data = <-wc.outChan:
        case <-wc.closeChan:
            wc.Close()
            break
        }
        if err = wc.wsconnect.WriteMessage(websocket.TextMessage, data); err != nil {
            wc.Close()
            break
        }
    }
    return
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
    "utils"

    "github.com/gin-gonic/gin"
    "github.com/gorilla/websocket"
)
func Websocket(c *gin.Context) {
    var wc *WebsocketConnection
    var err error
    if wc, err = utils.InitWebsocket(c); err != nil {
        fmt.Printf("Websocket connection initial failed. err: %s\n", err.Error())
        return
    }
    for {
        if msg, err = wc.ReadMessage(); err != nil {
            fmt.Println("Read message of client by websocket failed.\n")
            break
        }
        fmt.Println("Read message of client:" + string(msg))
        wc.WriteMessage(msg)
    }
}

func main() {
    r := gin.Default()
    r.Any("/hello-world", Websocket)
    r.Run()
}

Now, we use go-channel to cache the input and sender msg, the websocket module now supports million-class concurrency, and much more easy to use.