当前位置: 首页 > 面试题库 >

为什么Golang http.ResponseWriter执行被延迟?

籍星汉
2023-03-14
问题内容

我试图在收到请求后立即发送页面响应,然后进行处理,但是我发现响应即使按代码顺序排列也没有“首先”发送。在现实生活中,我有一个页面可供上传一个Excel工作表,该工作表保存到数据库中需要花费时间(50000+行),并且希望更新用户进度。这是一个简化的示例;(取决于您有多少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)

}

问题答案:

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

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

为了避免这种“局限性”,已经开发出了几种技术,以便服务器能够通知客户端有关更改或进度的信息,例如HTTP Long轮询,HTTP流,HTTP / 2
Server
Push或Websockets。

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

如果要定期发送数据或将数据流传输到客户端,则必须将此信息告知服务器。最简单的方法是检查http.ResponseWriter传递给您的http.Flusher接口是否实现了接口(使用类型断言),如果接口实现了,则调用其Flusher.Flush()方法会将所有缓冲的数据发送到客户端。

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

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

接下来,为避免客户端缓存响应,请务必同时设置Cache-Control=no-cache

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

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

让我们看一个简单的示例,它“正确”地执行所有操作:

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在浏览器中打开,您将看到输出每秒“增长”:

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"

“文学”和进一步阅读/教程

在Wikipedia上了解有关服务器发送事件的更多信息。

请参阅Golang HTML5 SSE示例。

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

请参阅w3school.com关于服务器发送事件-
单向消息传递的资料。



 类似资料:
  • 我在玩Java的可选代码,认为它的工作原理就像一个if else块。但是在下面的代码中,即使变量不是,也会执行块的内容。有什么解释吗? 产出:

  • 我的建议是正确执行并预先准备正确的操作,除了执行两次。我希望它只执行一次。应该触发通知的方法只执行一次,因为startTestSuite标题只在日志中打印一次。bean和上下文是在TestNG类中生成的。我尝试在initSpring()方法上使用@beforeClass和@beforeSuite标记运行它,结果相同。 进一步的上下文:这样做的目的是获取测试套件何时开始和结束的时间戳,以及各个测试何

  • 我有一个C++实验室,问题是:用户应该为X输入一个值(X是所持有的测试数)。如果x<15,程序不计算任何东西。如果X在16和30之间,程序应计算C=1.0/10.0*(24a);如果X>30,程序应计算C=0.15(24*a)。我的multiple if代码可以工作,但是当我输入X的值时,方程没有解出。有人知道吗??

  • 问题内容: 我将这段代码与从文中)获得的声明一起使用,它的工作原理绝对不错: 但是,如果我将其更改为以下内容,则会中断,因为除了执行所有其他情况外: 当我通过时会打印出来。我尝试将生成器更改为缓冲,但是得到了相同的响应…这可能是错误,还是我犯了一些错误? 问题答案: 这是您代码中的错误。您忘了每次输入:

  • 问题内容: 我正在学习Go,并且想尝试goroutine和频道。 这是我的代码: 结果如下: 我不明白为什么我的goroutine永远不会执行。没有输入“进入goroutine”,并且没有任何错误消息。 问题答案: 事实是您的goroutine开始执行,但是在执行任何操作之前就结束了,因为您的程序在打印后立即停止:goroutine的执行与主程序无关,但是将在与程序相同的位置处停止。因此,基本上,

  • 问题内容: 我在MacOSX上使用Docker(带有Boot2Docker)。 我可以从Docker Hub运行映像。 但是,当我尝试像这样运行自己的映像之一时: 要么 要么 我得到: 我猜它找不到在容器中执行的bash二进制文件,但是为什么呢? 基本图像是 谢谢你的帮助。 阿什莉 问题答案: 您的图片基于不带bash外壳的busybox。它的确有外壳。 所以这不起作用: 但这确实是: 由于您的入