为什么Golang http.ResponseWriter执行被延迟? [英] Why is Golang http.ResponseWriter execution being delayed?

查看:206
本文介绍了为什么Golang http.ResponseWriter执行被延迟?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在收到请求后立即发送页面响应,然后进行处理,但是我发现响应即使是代码序列中的第一个也不会第一个"发送出去.在现实生活中,我有一个用于上传excel工作表的页面,该工作表已保存到数据库中,这需要花费时间(50000+行),并且需要更新用户进度.这是一个简化的示例; (取决于您拥有多少RAM,您可能需要添加几个零来计数才能看到结果)

I am trying to send a page response as soon as request is received, then process something, but I found the response does not get sent out "first" even though it is first in code sequence.In real life I have a page for uploading a excel sheet which gets saved into the database which takes time (50,0000+ rows) and would like to update to user progress. Here is a simplified example; (depending how much RAM you have you may need to add a couple zeros to counter to see result)

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)

}

推荐答案

HTTP协议的原始概念是一个简单的请求-响应服务器-客户端计算模型.没有流或连续"客户端更新支持.总是(总是)首先联系服务器的客户需要它的某种信息.

The original concept of the HTTP protocol is a simple request-response server-client computation model. There was no streaming or "continuous" client update support. It is (was) always the client who first contacted the server should it needed some kind of information.

此外,由于大多数Web服务器都会缓存响应,直到响应完全就绪(或达到一定的限制,通常是缓冲区大小)为止,因此您写入(发送)到客户端的数据将不会立即传输.

Also since most web servers cache the response until it is fully ready (or a certain limit is reached–which is typically the buffer size), data you write (send) to the client won't be transmitted immediately.

为了避免这种局限性",已经开发出了多种技术,以便服务器能够通知客户端有关更改或进度的信息,例如HTTP长轮询,HTTP流,HTTP/2服务器推送或Websocket.您可以在以下答案中详细了解这些内容:真正的服务器通过http推送?

Several techniques were "developed" to get around this "limitation" so that the server is able to notify the client about changes or progress, such as HTTP Long polling, HTTP Streaming, HTTP/2 Server Push or Websockets. You can read more about these in this answer: Is there a real server push over http?

因此,要实现您想要的目标,您必须绕过HTTP协议的原始边界".

So to achieve what you want, you have to step around the original "borders" of the HTTP protocol.

如果要定期发送数据或将数据流传输到客户端,则必须将此信息告知服务器.最简单的方法是检查交给您的 http.ResponseWriter 是否实现了 http.Flusher 界面(使用

If you want to send data periodically, or stream data to the client, you have to tell this to the server. The easiest way is to check if the http.ResponseWriter handed to you implements the http.Flusher interface (using a type assertion), and if it does, calling its Flusher.Flush() method will send any buffered data to the client.

使用http.Flusher仅是解决方案的一半.由于这是HTTP协议的非标准用法,因此通常也需要客户端支持才能正确处理.

Using http.Flusher is only half of the solution. Since this is a non-standard usage of the HTTP protocol, usually client support is also needed to handle this properly.

首先,您必须通过设置ContentType=text/event-stream响应标头,让客户端了解响应的流"性质.

First, you have to let the client know about the "streaming" nature of the response, by setting the ContentType=text/event-stream response header.

接下来,为避免客户端缓存响应,请确保还设置Cache-Control=no-cache.

Next, to avoid clients caching the response, be sure to also set Cache-Control=no-cache.

最后,要让客户端知道您可能不会以单个单元(而是以定期更新或流的形式)发送响应,以便客户端应保持连接处于活动状态并等待更多数据,请设置Connection=keep-alive响应标头.

And last, to let the client know that you might not send the response as a single unit (but rather as periodic updates or as a stream) and so that the client should keep the connection alive and wait for further data, set the Connection=keep-alive response header.

一旦将响应标头设置为上述值,就可以开始漫长的工作,并且每当要向客户端更新进度时,都可以编写一些数据并调用Flusher.Flush().

Once the response headers are set as the above, you may start your long work, and whenever you want to update the client about the progress, write some data and call Flusher.Flush().

让我们看一个简单的示例,该示例正确"地完成所有操作:

Let's see a simple example that does everything "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,您将看到输出每秒增长":

Now if you open http://localhost:8080/long in your browser, you will see an output "growing" by every second:

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时,应将更新打包"到SSE框架中,即应以"data:"前缀开始,并以2个换行符结束每个框架:"\n\n".

Also note that when using SSE, you should "pack" updates into SSE frames, that is you should start them with "data:" prefix, and end each frame with 2 newline chars: "\n\n".

详细了解 Wikipedia上的服务器发送事件.

请参见 Golang HTML5 SSE示例.

请参见带有客户端代码的Golang SSE服务器示例.

服务器发送的事件-单向消息传递上查看w3school.com的使用说明.

See w3school.com's turorial on Server-Sent Events - One Way Messaging.

这篇关于为什么Golang http.ResponseWriter执行被延迟?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆