web-dev-qa-db-fra.com

Http.ResponseWritter non tamponné à Golang

J'écris une application Web simple dans Go et je veux que mes réponses soient diffusées au client (c'est-à-dire non mises en mémoire tampon et envoyées en blocs une fois la demande entièrement traitée):

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

Du point de vue client, les deux lignes seront envoyées en même temps. Toutes les suggestions sont appréciées :)

Modifier après la réponse @dystroy

Il est possible de vider après chaque écriture que je fais personnellement, mais dans mon cas d'utilisation, ce n'est pas suffisant:

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

Je veux que la sortie de mon cmd soit également vidée. Quoi qu'il en soit pour "vider automatiquement" le ResponseWritter?

Solution

J'ai trouvé de l'aide sur la liste de diffusion de Golang. Il y a 2 façons d'y parvenir: utiliser pirate de l'air qui permet de reprendre la connexion sous-jacente TCP connexion de HTTP, ou canaliser la stdout et la stderr de la commande d'un coup routine qui va écrire et vider:

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

Dernière mise à jour

Encore plus agréable: http://play.golang.org/p/PpbPyXbtEs

36
rmonjo

Comme implicite dans la documentation , certains ResponseWriter peuvent implémenter l'interface Flusher.

Cela signifie que vous pouvez faire quelque chose comme ceci:

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

Veillez à ce que la mise en mémoire tampon puisse se produire à de nombreux autres endroits du côté réseau ou client.

29
Denys Séguret

Désolé si j'ai mal compris votre question, mais est-ce que quelque chose comme ci-dessous ferait l'affaire?

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

retour:

param1 = valeur1 & param2 = valeur2

Vous pouvez toujours ajouter toutes les données que vous vouliez body, ou lire plus d'octets dans le tampon d'ailleurs avant de tout réécrire.

1
Intermernet