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

随学随记,留备查

修正时间:201707121017

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

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

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

3、HTML代码 serversend.html:





    
    EventSource测试



    

服务器当前时间:



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毫秒接收到依次数据推送,很好很方便!



你可能感兴趣的:(Go,web前端)