在Golang中,没有缓冲的http.ResponseWriter。

huangapple go评论130阅读模式
英文:

Not buffered http.ResponseWritter in Golang

问题

我正在使用Go编写一个简单的Web应用程序,并希望将响应流式传输到客户端(即不缓冲并在请求完全处理后以块的形式发送):

  1. func handle(res http.ResponseWriter, req *http.Request) {
  2. fmt.Fprintf(res, "sending first line of data")
  3. sleep(10) //not real code
  4. fmt.Fprintf(res, "sending second line of data")
  5. }

从客户端的角度来看,这两行将同时发送。欢迎任何建议 在Golang中,没有缓冲的http.ResponseWriter。

###在@dystroy回答后编辑
我可以在我个人编写的每个写入之后进行刷新,但在我的用例中这还不够:

  1. cmd := exec.Command("a long command that outputs lots of lines")
  2. cmd.Stdout = res //where res is a http.ResponseWritter
  3. cmd.Stderr = res
  4. err := cmd.Run()

我希望我的cmd的输出也能被刷新。有没有办法“自动刷新”ResponseWriter?

###解决方案
我在golang的邮件列表上找到了帮助。有两种方法可以实现这一点:使用hijacker来接管HTTP的底层TCP连接,或者将命令的stdout和stderr管道化到一个go例程中进行写入和刷新:

  1. pipeReader, pipeWriter := io.Pipe()
  2. cmd.Stdout = pipeWriter
  3. cmd.Stderr = pipeWriter
  4. go writeCmdOutput(res, pipeReader)
  5. err := cmd.Run()
  6. pipeWriter.Close()
  7. //---------------------
  8. func writeCmdOutput(res http.ResponseWriter, pipeReader *io.PipeReader) {
  9. buffer := make([]byte, BUF_LEN)
  10. for {
  11. n, err := pipeReader.Read(buffer)
  12. if err != nil {
  13. pipeReader.Close()
  14. break
  15. }
  16. data := buffer[0:n]
  17. res.Write(data)
  18. if f, ok := res.(http.Flusher); ok {
  19. f.Flush()
  20. }
  21. //reset buffer
  22. for i := 0; i < n; i++ {
  23. buffer[i] = 0
  24. }
  25. }
  26. }

###最后更新
更好的方法:http://play.golang.org/p/PpbPyXbtEs

英文:

I'm writing a simple web app in Go and I want my responses to be streamed to the client (i.e. not buffered and sent in blocks once the request is fully processed) :

  1. func handle(res http.ResponseWriter, req *http.Request) {
  2. fmt.Fprintf(res, &quot;sending first line of data&quot;)
  3. sleep(10) //not real code
  4. fmt.Fprintf(res, &quot;sending second line of data&quot;)
  5. }

From the client point of view, the two lines will be sent at the same time. Any suggestions are appreciated 在Golang中,没有缓冲的http.ResponseWriter。

###Edit after @dystroy answer
It's possible to flush after each write I personally make, but in my use case it's not enough:

  1. cmd := exec.Command(&quot;a long command that outputs lots of lines&quot;)
  2. cmd.Stdout = res //where res is a http.ResponseWritter
  3. cmd.Stderr = res
  4. err := cmd.Run()

I want the output of my cmd to be flushed as well. Anyway to "autoflush" the ResponseWritter ?

###Solution
I found help on golang's mailing list. There is 2 way to achieve this: using hijacker that allow to take over the underlying TCP connection of HTTP, or piping the stdout and stderr of the command in a go routine that will write and flush :

  1. pipeReader, pipeWriter := io.Pipe()
  2. cmd.Stdout = pipeWriter
  3. cmd.Stderr = pipeWriter
  4. go writeCmdOutput(res, pipeReader)
  5. err := cmd.Run()
  6. pipeWriter.Close()
  7. //---------------------
  8. func writeCmdOutput(res http.ResponseWriter, pipeReader *io.PipeReader) {
  9. buffer := make([]byte, BUF_LEN)
  10. for {
  11. n, err := pipeReader.Read(buffer)
  12. if err != nil {
  13. pipeReader.Close()
  14. break
  15. }
  16. data := buffer[0:n]
  17. res.Write(data)
  18. if f, ok := res.(http.Flusher); ok {
  19. f.Flush()
  20. }
  21. //reset buffer
  22. for i := 0; i &lt; n; i++ {
  23. buffer[i] = 0
  24. }
  25. }
  26. }

###Last update
Even nicer: http://play.golang.org/p/PpbPyXbtEs

答案1

得分: 38

根据文档的暗示,一些ResponseWriter可能实现了Flusher接口。

这意味着你可以这样做:

  1. func handle(res http.ResponseWriter, req *http.Request) {
  2. fmt.Fprintf(res, "发送第一行数据")
  3. if f, ok := res.(http.Flusher); ok {
  4. f.Flush()
  5. } else {
  6. log.Println("糟糕,无法刷新");
  7. }
  8. sleep(10) //不是真实的代码
  9. fmt.Fprintf(res, "发送第二行数据")
  10. }

请注意,在网络或客户端端的许多其他位置可能会发生缓冲。

英文:

As implied in the documentation, some ResponseWriter may implement the Flusher interface.

This means you can do something like this :

  1. func handle(res http.ResponseWriter, req *http.Request) {
  2. fmt.Fprintf(res, &quot;sending first line of data&quot;)
  3. if f, ok := res.(http.Flusher); ok {
  4. f.Flush()
  5. } else {
  6. log.Println(&quot;Damn, no flush&quot;);
  7. }
  8. sleep(10) //not real code
  9. fmt.Fprintf(res, &quot;sending second line of data&quot;)
  10. }

Be careful that buffering can occur in many other places in the network or client side.

答案2

得分: 1

抱歉,如果我误解了你的问题,但是下面的代码是否能满足你的需求?

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/http"
  6. )
  7. func handler(w http.ResponseWriter, r *http.Request) {
  8. body := make([]byte, int(r.ContentLength))
  9. b := bytes.NewBuffer(body)
  10. if _, err := b.ReadFrom(r.Body); err != nil {
  11. fmt.Fprintf(w, "%s", err)
  12. }
  13. if _, err := b.WriteTo(w); err != nil {
  14. fmt.Fprintf(w, "%s", err)
  15. }
  16. }
  17. func main() {
  18. http.HandleFunc("/", handler)
  19. if err := http.ListenAndServe(":8080", nil); err != nil {
  20. panic(err)
  21. }
  22. }

$ curl --data "param1=value1&param2=value2" http://localhost:8080

返回:

param1=value1&param2=value2

你可以在body中添加任何你想要的数据,或者从其他地方读取更多的字节到缓冲区中,然后再将其全部写出。

英文:

Sorry if I've misunderstood your question, but would something like the below do the trick?

  1. package main
  2. import (
  3. &quot;bytes&quot;
  4. &quot;fmt&quot;
  5. &quot;net/http&quot;
  6. )
  7. func handler(w http.ResponseWriter, r *http.Request) {
  8. body := make([]byte, int(r.ContentLength))
  9. b := bytes.NewBuffer(body)
  10. if _, err := b.ReadFrom(r.Body); err != nil {
  11. fmt.Fprintf(w, &quot;%s&quot;, err)
  12. }
  13. if _, err := b.WriteTo(w); err != nil {
  14. fmt.Fprintf(w, &quot;%s&quot;, err)
  15. }
  16. }
  17. func main() {
  18. http.HandleFunc(&quot;/&quot;, handler)
  19. if err := http.ListenAndServe(&quot;:8080&quot;, nil); err != nil {
  20. panic(err)
  21. }
  22. }

$ curl --data &quot;param1=value1&amp;param2=value2&quot; http://localhost:8080

returns:

>>param1=value1&param2=value2

You could always append whatever data you wanted to body, or read more bytes into the buffer from elsewhere before writing it all out again.

答案3

得分: 1

在Go 1.20或更高版本中,使用ResponseController.Flush来刷新响应。

  1. func handle(res http.ResponseWriter, req *http.Request) {
  2. rc := http.NewResponseController(res)
  3. fmt.Fprintf(res, "发送第一行数据")
  4. if err := rc.Flush(); err != nil {
  5. // 以某种方式处理错误。
  6. log.Println("哎呀,没有刷新")
  7. }
  8. sleep(10) //不是真正的代码
  9. fmt.Fprintf(res, "发送第二行数据")
  10. }
英文:

In Go 1.20 or later, use ResponseController.Flush to flush the response.

  1. func handle(res http.ResponseWriter, req *http.Request) {
  2. rc := http.NewResponseController(res)
  3. fmt.Fprintf(res, &quot;sending first line of data&quot;)
  4. if err := rc.Flush(); err != nil {
  5. // Handle error in some way.
  6. log.Println(&quot;Oops, no flush&quot;)
  7. }
  8. sleep(10) //not real code
  9. fmt.Fprintf(res, &quot;sending second line of data&quot;)
  10. }

huangapple
  • 本文由 发表于 2013年10月10日 17:34:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/19292113.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定