您的位置:首页 > Web前端 > HTML5

使用HTML5的Server-sent技术,Go服务器向页面推送消息

2017-06-05 15:44 661 查看
随学随记,留备查

修正时间:201707121017

修正原因:进一步学习server-sent后,发现先前描述的不当之处,现改之。

1、本来今天学习worker,想实验服务器与页面推送数据,却偶然的发现了HTML5的新web api:EventSource

2、看了下w3school样例,简单好用。于是乎试试看

3、HTML代码 serversend.html:

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>EventSource测试</title>
</head>

<body>
<h1>服务器当前时间:</h1>
<div id="result"></div>

<script>
if (typeof(EventSource) !== "undefined") {
var source = new EventSource("getdate");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
} else {
document.getElementById("result").innerHTML = "抱歉,你的浏览器不支持 server-sent 事件...";
}
</script>

</body>

</html>


4、Go代码 main.go:

package main

import (
"fmt"
"html/template"
"net/http"
"time"
)

func viewDate(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("serversend.html")
_ = t.Execute(w, nil)
}
func getDate(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
dtstr := "data: " + fmt.Sprint(time.Now())
w.Write([]byte(dtstr))
}

func main() {
http.HandleFunc("/", viewDate)
http.HandleFunc("/getdate", getDate)
http.ListenAndServe("", nil)
}


5、运行Go程序,打开浏览器。页面上只显示“服务器当前时间:”,等了1分钟也没显示时间……

6、搜索无果,用chrome的开发者工具也没与w3school样例对比出个所以然,于是想到用Fiddler看看到底哪里不对了。

7、费了半天劲在Fiddler中也没发现两者返回的不一致之处,后来突然在HexView中看到,w3school返回的数据末尾有两个"..",16进制是0A0A;我的没有。难道是这个问题?

8、试试看,在Go程序中把返回字符串结尾加上,“\n\n”,在运行试试看。哎我去,出来时间了。当时真是一万个xxx奔跑而过……原来w3school里面的php代码示例中的“\n\n”不是为了好看的,是关键所在!

9、痛定思痛,还是应该好好查看下W3C官方的技术文档才是正道。偷下懒却浪费的更多时间!

10、测试发现,chrome刷新间隔3秒钟,firefox刷新间隔5秒钟。这有点太慢了。要是能改刷新时间就好了。

11、这回学乖了,查阅MDN文档,发现有如下格式:

event:事件类型.

data:消息的数据字段.

id:事件ID.

retry:一个整数值,指定了重新连接的时间(单位为毫秒),如果该字段值不是整数,则会被忽略.

12、这里的retry不就是我所需要的吗?马上加上试试,运行之!OK,正如所需!

13、至此,server-sent发送事件练习告一段落,也踩了一个不大不小的坑!学友们一定要记住在消息末尾加上“\n\n”,切记!

14、main.go 最终版:

package main

import (
"fmt"
"html/template"
"net/http"
"time"
)

func viewDate(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("serversend.html")
_ = t.Execute(w, nil)
}
func getDate(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
dtstr := "data: " + fmt.Sprint(time.Now()) + "\n\n"
dtstr = dtstr + "retry:" + "1000" + "\n\n"
w.Write([]byte(dtstr))
}

//主程序
func main() {
http.HandleFunc("/", viewDate)
http.HandleFunc("/getdate", getDate)
http.ListenAndServe("", nil)
}


修正如下:

1、当时只是简单的测试server-sent,没细看。今天发现先前理解的偏差。

2、“retry”实际上是,服务器通知浏览器多长时间重新创建连接。而不是数据刷新的时间。

3、但是,不设置"retry",数据并不会实时刷新!为什么?原来,当前go函数getDate接收到请求,推送一次时间数据后,函数就执行完毕退出了,那么连接也就被断开了;所以浏览器只能是过了超时时间,再重新连接,依次重复。

4、那就需要改造下getDate函数,让他一直发数据不退出。那就加个for循环圈住。但是,实际效果却是,浏览器接收不到数据或者依次接收一大堆数据。原因是,http.ResponseWriter默认带发送数据缓冲区的,所以for循环内会不断的填充发送缓冲区,而不会立即发送给浏览器。

5、这时,就想http.ResponseWriter应该有类似flush的方法就好了。但是看代码有点不好找,go语言这一点确实没有声明接口继承来的方便。在server.go看到type response struct {},应该就是getDate函数的w参数的实参了。其实现了Flush()方法。而Flush()方法对应的接口是Flusher。那就加上类型断言,来将w转成Flusher接口。

6、最终代码如下;

func getDate(w http.ResponseWriter, r *http.Request) {
for {
w.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
dtstr := "data: " + fmt.Sprint(time.Now()) + "\n\n"
w.Write([]byte(dtstr))
if f, ok := w.(http.Flusher); ok {
f.Flush()
} else {
fmt.Println("no flush")
}

time.Sleep(500 * time.Millisecond)
}
}


7、运行起来,浏览器就会每隔500毫秒接收到依次数据推送,很好很方便!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息