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

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

Not buffered http.ResponseWritter in Golang

问题

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

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

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

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

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

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

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

pipeReader, pipeWriter := io.Pipe()
cmd.Stdout = pipeWriter
cmd.Stderr = pipeWriter
go writeCmdOutput(res, pipeReader)
err := cmd.Run()
pipeWriter.Close()

//---------------------
func writeCmdOutput(res http.ResponseWriter, pipeReader *io.PipeReader) {
  buffer := make([]byte, BUF_LEN)
  for {
    n, err := pipeReader.Read(buffer)
    if err != nil {
      pipeReader.Close()
      break
    }

    data := buffer[0:n]
    res.Write(data)
    if f, ok := res.(http.Flusher); ok {
      f.Flush()
    }
    //reset buffer
    for i := 0; i < n; i++ {
      buffer[i] = 0
    }
  } 
}

###最后更新
更好的方法: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) :

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

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:

cmd := exec.Command(&quot;a long command that outputs lots of lines&quot;)
cmd.Stdout = res //where res is a http.ResponseWritter
cmd.Stderr = res
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 :

pipeReader, pipeWriter := io.Pipe()
cmd.Stdout = pipeWriter
cmd.Stderr = pipeWriter
go writeCmdOutput(res, pipeReader)
err := cmd.Run()
pipeWriter.Close()

//---------------------
func writeCmdOutput(res http.ResponseWriter, pipeReader *io.PipeReader) {
  buffer := make([]byte, BUF_LEN)
  for {
    n, err := pipeReader.Read(buffer)
    if err != nil {
      pipeReader.Close()
      break
    }

    data := buffer[0:n]
    res.Write(data)
    if f, ok := res.(http.Flusher); ok {
      f.Flush()
    }
    //reset buffer
    for i := 0; i &lt; n; i++ {
      buffer[i] = 0
    }
  } 
}

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

答案1

得分: 38

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

这意味着你可以这样做:

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

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

英文:

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

This means you can do something like this :

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

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

答案2

得分: 1

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

package main

import (
	"bytes"
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	body := make([]byte, int(r.ContentLength))
	b := bytes.NewBuffer(body)
	if _, err := b.ReadFrom(r.Body); err != nil {
		fmt.Fprintf(w, "%s", err)
	}
	if _, err := b.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}
}

func main() {
	http.HandleFunc("/", handler)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		panic(err)
	}
}

$ 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?

package main

import (
	&quot;bytes&quot;
	&quot;fmt&quot;
	&quot;net/http&quot;
)

func handler(w http.ResponseWriter, r *http.Request) {
	body := make([]byte, int(r.ContentLength))
	b := bytes.NewBuffer(body)
	if _, err := b.ReadFrom(r.Body); err != nil {
		fmt.Fprintf(w, &quot;%s&quot;, err)
	}
	if _, err := b.WriteTo(w); err != nil {
		fmt.Fprintf(w, &quot;%s&quot;, err)
	}
}

func main() {
	http.HandleFunc(&quot;/&quot;, handler)
	if err := http.ListenAndServe(&quot;:8080&quot;, nil); err != nil {
		panic(err)
	}
}

$ 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来刷新响应。

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

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

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

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:

确定