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