为什么 Golang http.ResponseWriter 执行被延迟?
Why is Golang http.ResponseWriter execution being delayed?
我试图在收到请求后立即发送页面响应,然后进行处理,但我发现响应没有发送出去 "first",即使它在代码中排在第一位 sequence.In 现实生活中我有一个用于上传 excel sheet 的页面,它被保存到数据库中,这需要时间(50,0000 多行)并且想更新用户进度。这是一个简化的例子; (取决于你有多少 RAM,你可能需要添加几个零来计数器才能看到结果)
package main
import (
"fmt"
"net/http"
)
func writeAndCount(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Starting to count"))
for i := 0; i < 1000000; i++ {
if i%1000 == 0 {
fmt.Println(i)
}
}
w.Write([]byte("Finished counting"))
}
func main() {
http.HandleFunc("/", writeAndCount)
http.ListenAndServe(":8080", nil)
}
您可以检查 ResponseWriter
是否是 http.Flusher
,如果是,则强制刷新到网络:
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
但是,请记住这是一个非常非常规的 HTTP 处理程序。将进度消息像终端一样流式传输到响应会带来一些问题,尤其是当客户端是 Web 浏览器时。
您可能需要考虑一些更符合 HTTP 性质的东西,例如立即返回 202 Accepted
响应,并带有一个唯一标识符,客户端可以使用它来检查使用后续调用的处理状态你的 API.
HTTP protocol 的原始概念是一个简单的 request-response server-client 计算模型。没有流媒体或 "continuous" 客户端更新支持。如果需要某种信息,首先联系服务器的始终是客户端。
此外,由于大多数网络服务器缓存响应直到它完全准备好(或达到某个限制——通常是缓冲区大小),您写入(发送)到客户端的数据不会立即传输。
有几种技术 "developed" 可以解决这个问题 "limitation" 以便服务器能够通知客户端有关更改或进度的信息,例如 HTTP 长轮询、HTTP 流、HTTP/2 服务器推送或 Websockets。您可以在这个答案中阅读更多关于这些的信息:Is there a real server push over http?
所以要实现你想要的,你必须绕过HTTP协议的原始"borders"。
如果你想定期发送数据,或者流式传输数据到客户端,你必须告诉服务器。最简单的方法是检查 http.ResponseWriter
handed to you implements the http.Flusher
interface (using a type assertion) 是否存在,如果存在,则调用其 Flusher.Flush()
方法会将任何缓冲数据发送到客户端。
使用http.Flusher
只是解决方案的一半。由于这是 HTTP 协议的 non-standard 用法,通常还需要客户端支持才能正确处理。
首先,您必须通过设置 ContentType=text/event-stream
响应 header.
让客户知道响应的 "streaming" 性质
接下来,为避免客户端缓存响应,请务必同时设置 Cache-Control=no-cache
。
最后,让客户端知道您可能不会将响应作为单个单元发送(而是作为定期更新或作为流),这样客户端应该保持连接并等待进一步的数据, 设置 Connection=keep-alive
响应 header.
如上设置响应header后,您就可以开始漫长的工作了,每当您想向客户端更新进度时,写入一些数据并调用Flusher.Flush()
。
让我们看一个可以完成所有操作的简单示例 "right":
func longHandler(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Server does not support Flusher!",
http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
start := time.Now()
for rows, max := 0, 50*1000; rows < max; {
time.Sleep(time.Second) // Simulating work...
rows += 10 * 1000
fmt.Fprintf(w, "Rows done: %d (%d%%), elapsed: %v\n",
rows, rows*100/max, time.Since(start).Truncate(time.Millisecond))
flusher.Flush()
}
}
func main() {
http.HandleFunc("/long", longHandler)
panic(http.ListenAndServe("localhost:8080", nil))
}
现在,如果您在浏览器中打开 http://localhost:8080/long
,您将每秒看到一个输出 "growing":
Rows done: 10000 (20%), elapsed: 1s
Rows done: 20000 (40%), elapsed: 2s
Rows done: 30000 (60%), elapsed: 3s
Rows done: 40000 (80%), elapsed: 4.001s
Rows done: 50000 (100%), elapsed: 5.001s
另请注意,在使用 SSE 时,您应该 "pack" 更新到 SSE 帧中,也就是说您应该以 "data:"
前缀开始它们,并以 2 个换行符结束每个帧:"\n\n"
.
"Literature" 和进一步阅读/教程
详细了解 Server-sent events on Wikipedia。
参见 Golang SSE server example with client codes using it。
在 Server-Sent Events - One Way Messaging 上查看 w3school.com 的教程。
我试图在收到请求后立即发送页面响应,然后进行处理,但我发现响应没有发送出去 "first",即使它在代码中排在第一位 sequence.In 现实生活中我有一个用于上传 excel sheet 的页面,它被保存到数据库中,这需要时间(50,0000 多行)并且想更新用户进度。这是一个简化的例子; (取决于你有多少 RAM,你可能需要添加几个零来计数器才能看到结果)
package main
import (
"fmt"
"net/http"
)
func writeAndCount(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Starting to count"))
for i := 0; i < 1000000; i++ {
if i%1000 == 0 {
fmt.Println(i)
}
}
w.Write([]byte("Finished counting"))
}
func main() {
http.HandleFunc("/", writeAndCount)
http.ListenAndServe(":8080", nil)
}
您可以检查 ResponseWriter
是否是 http.Flusher
,如果是,则强制刷新到网络:
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
但是,请记住这是一个非常非常规的 HTTP 处理程序。将进度消息像终端一样流式传输到响应会带来一些问题,尤其是当客户端是 Web 浏览器时。
您可能需要考虑一些更符合 HTTP 性质的东西,例如立即返回 202 Accepted
响应,并带有一个唯一标识符,客户端可以使用它来检查使用后续调用的处理状态你的 API.
HTTP protocol 的原始概念是一个简单的 request-response server-client 计算模型。没有流媒体或 "continuous" 客户端更新支持。如果需要某种信息,首先联系服务器的始终是客户端。
此外,由于大多数网络服务器缓存响应直到它完全准备好(或达到某个限制——通常是缓冲区大小),您写入(发送)到客户端的数据不会立即传输。
有几种技术 "developed" 可以解决这个问题 "limitation" 以便服务器能够通知客户端有关更改或进度的信息,例如 HTTP 长轮询、HTTP 流、HTTP/2 服务器推送或 Websockets。您可以在这个答案中阅读更多关于这些的信息:Is there a real server push over http?
所以要实现你想要的,你必须绕过HTTP协议的原始"borders"。
如果你想定期发送数据,或者流式传输数据到客户端,你必须告诉服务器。最简单的方法是检查 http.ResponseWriter
handed to you implements the http.Flusher
interface (using a type assertion) 是否存在,如果存在,则调用其 Flusher.Flush()
方法会将任何缓冲数据发送到客户端。
使用http.Flusher
只是解决方案的一半。由于这是 HTTP 协议的 non-standard 用法,通常还需要客户端支持才能正确处理。
首先,您必须通过设置 ContentType=text/event-stream
响应 header.
接下来,为避免客户端缓存响应,请务必同时设置 Cache-Control=no-cache
。
最后,让客户端知道您可能不会将响应作为单个单元发送(而是作为定期更新或作为流),这样客户端应该保持连接并等待进一步的数据, 设置 Connection=keep-alive
响应 header.
如上设置响应header后,您就可以开始漫长的工作了,每当您想向客户端更新进度时,写入一些数据并调用Flusher.Flush()
。
让我们看一个可以完成所有操作的简单示例 "right":
func longHandler(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Server does not support Flusher!",
http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
start := time.Now()
for rows, max := 0, 50*1000; rows < max; {
time.Sleep(time.Second) // Simulating work...
rows += 10 * 1000
fmt.Fprintf(w, "Rows done: %d (%d%%), elapsed: %v\n",
rows, rows*100/max, time.Since(start).Truncate(time.Millisecond))
flusher.Flush()
}
}
func main() {
http.HandleFunc("/long", longHandler)
panic(http.ListenAndServe("localhost:8080", nil))
}
现在,如果您在浏览器中打开 http://localhost:8080/long
,您将每秒看到一个输出 "growing":
Rows done: 10000 (20%), elapsed: 1s
Rows done: 20000 (40%), elapsed: 2s
Rows done: 30000 (60%), elapsed: 3s
Rows done: 40000 (80%), elapsed: 4.001s
Rows done: 50000 (100%), elapsed: 5.001s
另请注意,在使用 SSE 时,您应该 "pack" 更新到 SSE 帧中,也就是说您应该以 "data:"
前缀开始它们,并以 2 个换行符结束每个帧:"\n\n"
.
"Literature" 和进一步阅读/教程
详细了解 Server-sent events on Wikipedia。
参见 Golang SSE server example with client codes using it。
在 Server-Sent Events - One Way Messaging 上查看 w3school.com 的教程。