Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 735
rmonjo Avatar asked Oct 10 '13 09:10

rmonjo


2 Answers

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.

like image 127
Denys Séguret Avatar answered Sep 21 '22 19:09

Denys Séguret


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

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

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.

like image 24
Intermernet Avatar answered Sep 21 '22 19:09

Intermernet