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 通道处理,没有数据时,等待通道内容,有内容时,写入通道

相关文章
相关标签/搜索