Hateburo: kazeburo hatenablog

Operations Engineer / 運用系小姑 / Perl Monger

Go net/httpで任意のタイミングでchunked レスポンスを返す

調査のため、chunked レスポンスを行っている合間に遅延をいれる必要があったので、Go net/httpでの実現方法を調べました。

こちらにあった Flusher.Flush() を使うことになります。

stackoverflow.com

golang.org

FizzBuzzっぽいものを遅延をいれつつ書き出すにはこんな感じ

func fizzBuzz(w http.ResponseWriter, r *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        w.WriteHeader(500)
        w.Write([]byte("expected http.ResponseWriter to be an http.Flusher"))
        return
    }
    for i := 1; i <= 15; i++ {
        p := fmt.Sprintf("#%03d ", i)
        if i%3 == 0 {
            p += "Fizz"
        }
        if i%5 == 0 {
            p += "Buzz"
        }
        p = strings.TrimSpace(p)
        p += "\n"
        w.Write([]byte(p))
        flusher.Flush()
        time.Sleep(300 * time.Millisecond)
    }
}

telnetで試すと意図した通り chunk に分かれてレスポンスされてくるのがわかる

]% telnet localhost 3000
Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /demo/fizzbuzz_stream HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
Vary: Accept-Encoding
Date: Wed, 10 Mar 2021 01:46:27 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked

5
#001

5
#002

a
#003 Fizz

5
#004

a
#005 Buzz

a
#006 Fizz

5
#007

5
#008

このFlushは github.com/NYTimes/gziphandler にて圧縮をかけていても有効でした。

github.com

gziphandlerの簡単な使い方

// fizzbuzzのためMinSizeを小さくしておく。デフォルト 1400
gz, _ := gziphandler.NewGzipLevelAndMinSize(gzip.DefaultCompression, 5)
http.Handle("/", gz(http.HandlerFunc(fizzBuzz)))

テスト・検証用で作っている http-dump-request というサーバにこのFizzBuzzを返すエンドポイントを追加しています。このサーバを今後は監視にも利用していくつもりです。

github.com