GolangでHTTP/1.1のチャンク受信をやってみた

HTTP/1.1では、データをチャンクと呼ばれる小さな塊に分割し送受信するルールを定めている。こうする事で、時間のかかるデータの転送を少しずつ行うことができる。チャンク方式を使えば、検索やライブ配信などで効率的な転送ができる。

まとめ

Goの標準パッケージが充実しているので、やることはそこまで多くない。

  1. TCPソケットを開き、リクエストをサーバーに投げる。
  2. コネクションからbufio.Readerを作成。
  3. チャンクのサイズを逐次取得し、サイズ分読み込む。

TCPソケットを開き、リクエストをサーバーに投げる

TCPソケットオープン

dialer := &net.Dialer{
    Timeout:   30 * time.Second,
    KeepAlive: 30 * time.Second,
}
conn, err := dialer.Dial("tcp", "localhost:8080")
if err != nil {
    panic(err)
}
defer conn.Close()

コネクションを通じてリクエストの送信

request, err := http.NewRequest("GET", "http://localhost:8080/chunked", nil)
err = request.Write(conn)
if err != nil {
    panic(err)
}

コネクションからbufio.Readerを作成

io.Reader作成。コネクションはio.Readerを実装しているので、bufio.Readerの作成が可能。

reader := bufio.NewReader(conn)

※レスポンスヘッダに「Transfer-Encoding: chunked」が含まれているかを確認

resp, err := http.ReadResponse(reader, request)
if err != nil {
    panic(err)
}
if resp.TransferEncoding[0] != "chunked" {
    panic("wront transfer encoding")
}

チャンクのサイズを逐次取得し、サイズ分読み込む

チャンク方式では、チャンクデータのサイズとデータ本体が逐次送信されてくる。これら二つはCRLFで区切られている。

チャンクのサイズをパースする時に、サイズを表すバイト配列の後ろ2個を読み込んでいない。これは、HTTPが区切り文字としてCRLFを使用しているため。

for {
    // サイズを取得
    sizeStr, err := reader.ReadBytes('\n')
    if err == io.EOF {
        break
    }

    // 16進数のサイズをパース。サイズが0ならクローズ
    size, err := strconv.ParseInt(string(sizeStr[:len(sizeStr)-2]), 16, 64)
    if size == 0 {
        break
    }
    if err != nil {
        panic(err)
    }
    // サイズ数分バッファを確保して読み込み
    line := make([]byte, int(size))
    reader.Read(line)
    reader.Discard(2)
    log.Println(" ", string(line))
}

全部

GolangでHTTP/1.1のチャンク受信

参考

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術