Golang でのストリーミング レスポンス: バッファリングされた ResponseWriter ヒッチ
Golang で Web アプリケーションを作成する場合、http の動作を理解することが不可欠です。レスポンスライター。デフォルトでは、応答はバッファリングされます。つまり、リクエストが完全に処理されると、データが収集され、ブロック単位で送信されます。ただし、クライアントに 1 行ずつ応答をストリーミングしたり、バッファリング容量を超える大きな出力を処理したりするシナリオでは、この動作が障害になります。
次の例を考えてみましょう。
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") }
クライアントの観点からは、「データの 1 行目を送信中」メッセージと「データの 2 行目送信」メッセージは別々に受信される必要があります。ただし、バッファリングにより、両方の行が集約されて同時に送信されます。
この問題を解決するには、各書き込み操作の後に ResponseWriter を手動でフラッシュします。これは、次に示すように、Flusher インターフェイスを使用して実現できます。
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") }
この変更により、応答は必要に応じてクライアントに徐々にストリーミングされます。
高度なシナリオ: 外部コマンドのパイプ
ただし、特定の状況では、手動のフラッシュでは不十分な場合があります。外部コマンドの出力をクライアントにパイプするシナリオを考えてみましょう。このコマンドは、バッファリング容量を超える大量のデータを生成します。
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 } } }
この場合、データが遅延なくクライアントにストリーミングされるように、ResponseWriter を「自動フラッシュ」する必要があります。これは、提供されているコード スニペットを使用して実現できます。
代替ソリューション
外部コマンドの出力を直接パイプする代わりに、チャネルベースのアプローチを使用できます。
// 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) } } } }
このアプローチでは、ゴルーチンはコマンド出力を非同期的に処理し、それをチャンネル。次に、handleCmdOutput 関数は出力を ResponseWriter に遅延ストリーミングし、書き込み操作のたびにフラッシュします。
Flusher インターフェイスを活用し、代替アプローチを検討することで、データを効果的にクライアントにストリーミングし、Golang の ResponseWriter のバッファリング制限を克服できます。
以上がGolang で応答をストリーミングし、`http.ResponseWriter` のバッファリング制限を克服するにはどうすればよいですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。