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 通道處理,沒有數據時,等待通道內容,有內容時,寫入通道