随学随记,留备查
修正时间: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毫秒接收到依次数据推送,很好很方便!