rmonjo rmonjo - 4 months ago 64
HTTP Question

Not buffered http.ResponseWritter in Golang

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, "sending first line of data")
sleep(10) //not real code
fmt.Fprintf(res, "sending second line of data")
}


From the client point of view, the two lines will be sent at the same time. Any suggestions are appreciated :)

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("a long command that outputs lots of lines")
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)[http://golang.org/pkg/net/http/#example_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 < n; i++ {
buffer[i] = 0
}
}
}


Last update



Even nicer: http://play.golang.org/p/PpbPyXbtEs

Answer

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, "sending first line of data")
  if f, ok := res.(http.Flusher); ok {
     f.Flush()
  } else {
     log.Println("Damn, no flush");
  }
  sleep(10) //not real code
  fmt.Fprintf(res, "sending second line of data")
}

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

Comments