golang gin websocket io tailf

html.gojavascript

 

package wshtml

 

import "text/template"前端

 

var (java

    tailfHtml = `git

    <!DOCTYPE html>github

    <html lang="en">web

        <head>跨域

            <meta charset="utf-8">websocket

            <title></title>app

            <meta name="viewport" content="width=device-width, initial-scale=1.0">

            <style>

                body {background:#2C3E50; color:#2ECC71;}

                pre {width:100%; word-break:break-all; white-space:pre-wrap; word-wrap:break-word;}

            </style>

        </head>

        <body>

            <pre id="message"></pre>

            <script type="text/javascript">

                (function() {

                    var msg = document.getElementById('message');

                    var ws = new WebSocket(window.location.href.replace('http', 'ws'));

                    ws.onopen = function(evt) {

                        console.log('Connection opened');

                    }

                    ws.onclose = function(evt) {

                        console.log('Connection closed');

                    }

                    ws.onmessage = function(evt) {

                        try {

                            msg.innerText += decodeURIComponent(evt.data);

                        } catch(e) {

                        }

                    }

                })();

            </script>

        </body>

    </html>

`

    tailfTmpl = template.Must(template.New("").Parse(tailfHtml))

)

 wss.go

 

package ws

 

import (

    "net/http"

 

    "github.com/gorilla/websocket"

)

 

var (

    upgrader = websocket.Upgrader{

        ReadBufferSize: 1024,

        WriteBufferSize: 1024,

        CheckOrigin: func(r *http.Request) bool { // 解決跨域問題

            return true

        },

    }

)

tailf.go

 

package ws

 

import (

    "bytes"

    "goo"

    "net/url"

    "os"

    "time"

 

    "github.com/gin-gonic/gin"

    "github.com/gorilla/websocket"

)

 

type tailF struct {

    filename string

    conn *websocket.Conn

    send chan []byte

    fileOffset int64

}

 

func TailF(filename string) gin.HandlerFunc {

    return func(c *gin.Context) {

        if c.IsWebsocket() {

            conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)

            if err != nil {

                goo.Log().Error(err.Error())

                return

            }

            client := &tailF{

                filename: filename,

                conn: conn,

                send: make(chan []byte, 256),

            }

            go client.write()

            go client.read()

        } else {

            c.Header("Content-Type", "text/html; charset=utf-8")

            tailfTmpl.Execute(c.Writer, nil)

        }

    }

}

 

func (this *tailF) read() {

    ticker := time.NewTicker(1 * time.Second) // 定時器,每隔1秒執行一次

    defer func() {

        ticker.Stop()

        this.conn.Close()

    }()

    for {

        select {

        case <-ticker.C: // 捕獲到一秒後的執行

            this.getData()

        }

    }

}

 

func (this *tailF) write() {

    ticker := time.NewTicker(pingPeriod)

    defer func() {

        ticker.Stop()

        this.conn.Close()

    }()

    for {

        select {

        case <-ticker.C:  // websocket ping

            this.conn.SetWriteDeadline(time.Now().Add(writeWait))

            if err := this.conn.WriteMessage(websocket.PingMessage, nil); err != nil {

                return

            }

        case message := <-this.send:

            this.conn.SetWriteDeadline(time.Now().Add(writeWait))

            if err := this.conn.WriteMessage(websocket.TextMessage, message); err != nil {

                return

            }

        }

    }

}

 

func (this *tailF) getData() {

    if this.filename == "" {

        this.filename = "log.log"

    }

 

    file, err := os.Open(this.filename)

    if err != nil {

        goo.Log().Error(err.Error())

        return

    }

    defer file.Close()

 

    info, _ := file.Stat()

    size := info.Size()

 

    if this.fileOffset == size {

        return

    }

 

    if this.fileOffset == 0 && size > maxMessageSize*2 {

        this.fileOffset = size - maxMessageSize*2

    }

 

    file.Seek(this.fileOffset, 1) // 0 means relative to the origin of the file, 1 means relative to the current offset, and 2 means relative to the end.

    this.fileOffset = size

 

    data := []byte{}

    for {

        buf := make([]byte, 1)  // 每次讀取1個字節,直到讀取到最後(避免產生多餘的byte,當buf的初始大小 大於 內容的大小時,就會有多餘的byte)

        _, err := file.Read(buf)

        if err != nil {

            break

        }

        data = append(data, buf...)

    }

 

    data = bytes.Replace(data, []byte("\x1b[0m"), []byte(""), -1)

    data = bytes.Replace(data, []byte("\x1b[31m"), []byte(""), -1)

    data = bytes.Replace(data, []byte("\x1b[32m"), []byte(""), -1)

    data = bytes.Replace(data, []byte("\x1b[33m"), []byte(""), -1)

    data = bytes.Replace(data, []byte("\x1b[34m"), []byte(""), -1)

    data = bytes.Replace(data, []byte("\x1b[35m"), []byte(""), -1)

    data = bytes.Replace(data, []byte("\x1b[36m"), []byte(""), -1)

 

    this.send <- []byte(url.PathEscape(string(data))) // urlencode編碼處理特殊字符,url.PathEscape() 對應js的 decodeURIComponent()

}

const.go

 

package ws

 

import "time"

 

const (

    writeWait = 10 * time.Second

    pongWait = 60 * time.Second

    pingPeriod = (pongWait * 9) / 10

    filePeriod = 3 * time.Second

    maxMessageSize = 512

 說明:

一、http的url和ws的url一直,程序裏面作了判斷,並執行到不一樣的業務邏輯裏面

二、文件內容包含有特殊字符,須要進行urlencode編碼,服務端使用url.PathEscape()編碼,前端使用decodeURIComponent()解密

三、每一個url訪問,都會單獨創建一個websocket,因此監控文件時,每一個ws連接,對應的是本身的fileoffset,因此須要把fileoffset提到tailF對象裏面

四、ticker := time.NewTicker(1*time.Second) 用戶建立定時器,定時器不用時須要不關閉, defer ticker.close()

五、發送ping消息能夠經過定時器處理

六、發送文本消息,能夠經過 chan []byte 通道處理,沒有數據時,等待通道內容,有內容時,寫入通道

相關文章
相關標籤/搜索