Golang如何实现类似php缓冲区刷新输出的功能?

阅读 4.9k
2 个回答

为了方便,我直接贴代码吧。

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // 这一步很重要,要text类型的context-type才支持flush
        w.Header().Set("Content-Type", "text/html")

        f, _ := w.(http.Flusher)

        for i := 0; i < 10; i++ {
            fmt.Fprintf(w, "time.now(): %v \n\r", time.Now())
            f.Flush()
            time.Sleep(time.Second)
        }

    })

    log.Fatal(http.ListenAndServe(":8888", nil))
}

说实话,在这点上,go写的确实很模糊,谁会想到要把http.ResponseWriter转为http.Flusher呢?我也是看源码才知道

没办法实现。

如果楼主熟悉 HTTP 协议的话应该知道,协议中消息头(header)在前,消息体(body)在后,消息头与消息体之间有一个空行,并且服务端响应的消息头中包含了 Content-Length 一项,用来告诉客户端:“你接收到空行之后再接收这么多个字节就可以停止接收了,这么多个字节就是我发给你的完整的 HTTP body 了,保证你不会因为缺少或存在多余字节而造成解析出错。”

那么,这种消息头和消息体的前后顺序关系也就意味着服务端必须要先知道总体 body 有多大,才能确定 header 中的 content-type 的值。所以服务端没办法先将请求头发送,再不断地发送请求体,因为请求头中已经昭示了未来的行为。另外,从客户端的角度来讲,它必须知道 HTTP 消息的长度,并在接收到这么多长度的完整消息之后,才可以进行解析操作。

那为什么在 Go 代码中可以接连不断地进行 Write 呢,其实 http.ResponseWriter.Write() 也仅仅是将 bytes 写进了 underlying buffer,等待 handler 函数 return 之后再进行 body 长度的计算,写进 header,然后将整个 HTTP 消息发送给客户端。

其实这个问题很好验证,在 handler 中连续两次 Write,中间设置时间间隔:

mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    _, _ = w.Write([]byte("hello "))
    time.Sleep(3 * time.Second)
    _, _ = w.Write([]byte("world"))
})
_ = http.ListenAndServe(":8888", mux)

然后,在命令行中进行 curl:

$ curl localhost:8888

你会发现,3 秒之后 helloworld 一同被响应。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏