Nie buforowany http.ResponseWritter in Golang

[4]} piszę prostą aplikację internetową W Go i chcę, aby moje odpowiedzi były przesyłane strumieniowo do klienta (tzn. nie buforowane i wysyłane w blokach po pełnym przetworzeniu żądania):

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")
}

Z punktu widzenia klienta, dwie linie zostaną wysłane w tym samym czasie. Wszelkie sugestie są mile widziane:)

Edit after @ dystroy odpowiedz

Możliwe jest spłukanie po każdym wpisie, który osobiście wykonuję, ale w moim przypadku użycia to za mało:

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

Chcę aby wyjście mojego cmd być spłukane. W każdym razie, aby "automatycznie" ResponseWritter ?

Rozwiązanie

Znalazłem pomoc na liście mailingowej golanga. Istnieje 2 sposoby, aby to osiągnąć: użycie hijacker, które pozwala przejąć podstawowe połączenie TCP HTTP, lub Orurowanie stdout i stderr komendy w rutynę go, która będzie pisać i 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
    }
  } 
}

Ostatnia aktualizacja

Jeszcze ładniej: http://play.golang.org/p/PpbPyXbtEs

Author: brian d foy, 2013-10-10

2 answers

Jak sugerowano w dokumentacji , niektóre ResponseWriter mogą zaimplementować interfejs Flusher.

Oznacza to, że możesz zrobić coś takiego:

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")
}

Należy uważać, aby buforowanie mogło wystąpić w wielu innych miejscach w sieci lub po stronie klienta.

 23
Author: Denys Séguret,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-10 09:48:43

Przepraszam, jeśli źle zrozumiałem twoje pytanie, ale czy coś takiego jak poniżej zadziała?

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

Zwraca:

Param1 = value1&param2=value2

Możesz zawsze dołączyć dowolne dane body lub odczytać więcej bajtów do bufora z innego miejsca przed ponownym zapisaniem.

 1
Author: Intermernet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-10 13:01:46