Streaming de réponses dans Golang : un accroc de ResponseWriter tamponné
Lors de la création d'applications Web dans Golang, il est essentiel de comprendre le comportement du http. RéponseWriter. Par défaut, les réponses sont mises en mémoire tampon, ce qui signifie que les données sont collectées et envoyées par blocs une fois la demande entièrement traitée. Cependant, dans les scénarios où vous souhaitez diffuser les réponses aux clients ligne par ligne ou gérer des sorties volumineuses qui dépassent la capacité de mise en mémoire tampon, ce comportement devient un obstacle.
Considérez l'exemple suivant :
func handle(res http.ResponseWriter, req *http.Request) { fmt.Fprintf(res, "sending first line of data") sleep(10) // Simulation of a long-running process fmt.Fprintf(res, "sending second line of data") }
Du point de vue du client, les messages « envoi de la première ligne de données » et « envoi de la deuxième ligne de données » doivent être reçus séparément. Cependant, en raison de la mise en mémoire tampon, les deux lignes seront agrégées et envoyées simultanément.
Pour résoudre ce problème, on peut vider manuellement le ResponseWriter après chaque opération d'écriture. Ceci peut être réalisé à l'aide de l'interface Flusher, comme démontré ci-dessous :
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() } sleep(10) // Simulation of a long-running process fmt.Fprintf(res, "sending second line of data") }
Avec cette modification, la réponse sera progressivement diffusée au client, comme souhaité.
Scénario avancé : Commandes externes de tuyauterie
Cependant, dans certaines situations, le rinçage manuel peut ne pas suffire. Considérez un scénario dans lequel vous souhaitez transmettre la sortie d’une commande externe au client. La commande génère une grande quantité de données, dépassant la capacité de mise en mémoire tampon.
pipeReader, pipeWriter := io.Pipe() cmd.Stdout = pipeWriter cmd.Stderr = pipeWriter go writeCmdOutput(res, pipeReader) err := cmd.Run() pipeWriter.Close() // Function to write command output to ResponseWriter 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 } } }
Dans ce cas, il devient nécessaire de « vider automatiquement » le ResponseWriter pour garantir que les données sont transmises au client sans délai. Ceci peut être réalisé en utilisant l'extrait de code fourni.
Solutions alternatives
Au lieu de rediriger directement la sortie de la commande externe, vous pouvez utiliser une approche basée sur les canaux :
// Create a channel to communicate with the goroutine outputChan := make(chan string) // Start a goroutine to process the command output go func() { scanner := bufio.NewScanner(cmd.Stdout) for scanner.Scan() { outputChan <- scanner.Text() } if err := scanner.Err(); err != nil { log.Fatal(err) } close(outputChan) // Notify that all output has been processed }() // Stream output to ResponseWriter lazily func handleCmdOutput(res http.ResponseWriter, req *http.Request) { if f, ok := res.(http.Flusher); ok { for { select { case output := <-outputChan: res.Write([]byte(output + "\n")) f.Flush() default: time.Sleep(10 * time.Millisecond) } } } }
Dans cette approche, la goroutine traite de manière asynchrone la sortie de la commande et l'envoie au canal. La fonction handleCmdOutput diffuse ensuite paresseusement la sortie vers ResponseWriter, en vidant après chaque opération d'écriture.
En tirant parti de l'interface Flusher et en explorant des approches alternatives, vous pouvez efficacement diffuser des données vers les clients et surmonter la limitation de mise en mémoire tampon dans ResponseWriter de Golang.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!