GOLANG探测HTTP连接断开

来源:互联网 发布:小黄鸡软件下载 编辑:程序博客网 时间:2024/06/05 15:45

考虑基于HTTP的RPC,或者HTTP服务器主动通知客户端的机制,就是HTTP Long-Polling,意思就是客户端发起一个长连接,服务器阻塞忍住不响应直到:

  1. 超时,比如5秒后,我们给客户端响应一个keepalive,意思是现在还没有啥事,请继续polling。
  2. 拿到结果,这个可能是任何时候,比如300毫秒、1100毫秒、2300毫秒拿到一个事件,响应给客户端,实现了有事件异步通知。

这样客户端和服务器之间RPC的效率就非常高,只有在有事件时才会通知。但是,实际上还有一种情况需要处理:

  1. 当客户端断开连接,比如客户端设置了3秒钟TCP请求超时,或者因为客户端Crash时OS回收了FD等等,这个时候服务器应该要终止polling事务,停止获取事件。因为如果这个时候获取了事件,那么如何处理这个事件?只能丢弃,如果客户端再次发起请求,就拿不到这个事件了。

问题就来了,如何在HTTP Handler中探测客户端断开?例如:

var incoming chan []bytehttp.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {    select {    case b := <- incoming:        w.Write(b)    case <-time.After(5 * time.Second):        w.Write("keepalive")    // how to detect TCP disconnect event?    }})

可能有以下方式:

  1. 读取r.Body,如果发现断开应该会有错误。
  2. 有朋友用reflect或hijack取到底层的TCPConn,然后Peek。
  3. w转换成http.CloseNotifier,在TCP连接关闭时拿到事件。

r.Body Read

这种方式是不靠谱的,假设没有Body内容,直接读取检测是否有error:

nn,err := io.Copy(ioutil.Discard, r.Body)

实际上返回的是nn=0err=nil,也就是没有Body,没有错误。因为这个读取的含义是指Request结束。

如果读取完Body后再读呢?收到的是io.EOF,在没有发送Response之前,Request已经结束了,所以就是io.EOF,并不能检测到底层TCP断开。

Peek TcpConn

使用reflect获取底层的TCPConn对象,是知道w http.ResponseWriter实际上是http.response

// A response represents the server side of an HTTP response.type response struct {    conn             *conn

它有个Field就是conn,再转成TCPConn就可以Peek。

这样做的风险就是,不同的GOLANG版本,可能会对底层实现进行变更,在升级时会有风险。

Reflect方式始终不是最好的。

另外,还有一种方式,就是用http hijack方式,这种方式虽然是http库提供的接口,但是很多地方注释都说hijack需要特殊处理,因此也不是最好的方式。参考When to use hijack。

Close Notifier

在GO1.1提供了http.CloseNotifier接口,参考Close Notifier,但是也注意会有一些问题,参考net/http: CloseNotifier fails to fire when underlying connection is gone。用法如下:

var incoming chan []bytehttp.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {    select {    case <- w.(http.CloseNotifier).CloseNotify():        fmt.Println("connection closed")    }})

实际上,超时机制始终是需要的,加上之前的逻辑,考虑context.Context取消事件,http-long polling的完整实现应该是:

func polling(ctx context.Context, incoming chan []byte) {    http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {        select {        case <- ctx.Done():            fmt.Println("system quit")        case b := <- incoming:            w.Write(b)        case <-time.After(5 * time.Second):            w.Write("keepalive")        case <- w.(http.CloseNotifier).CloseNotify():            fmt.Println("connection closed")        }    })}
原创粉丝点击