[インデックス 16047] ファイルの概要
このコミットは、Go言語の標準ライブラリであるcompress/gzip
パッケージに、Writer
型のためのFlush
メソッドを追加するものです。これにより、gzip圧縮されたデータを書き込む際に、バッファリングされたデータを強制的に下層のライターに書き出す機能が提供されます。特に、ネットワークプロトコルなど、リアルタイム性やパケット単位でのデータ転送が求められるシナリオにおいて重要な機能強化となります。
コミット
commit 4432be3b288976fd3f8a398801803020ec78c7f5
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Apr 2 09:07:43 2013 -0700
compress/gzip: add Writer.Flush to call flate.Writer's Flush
From a discussion on golang-nuts.
R=golang-dev, dsymonds, nigeltao, coocood, adg
CC=golang-dev
https://golang.org/cl/8251043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4432be3b288976fd3f8a398801803020ec78c7f5
元コミット内容
このコミットは、compress/gzip
パッケージのWriter
型にFlush
メソッドを追加し、その内部で利用しているflate.Writer
のFlush
メソッドを呼び出すように変更します。この変更は、golang-nuts
メーリングリストでの議論から生まれたものです。
変更されたファイルは以下の通りです。
doc/go1.1.html
: Go 1.1のリリースノートにcompress/gzip
パッケージのWriter.Flush
メソッドの追加を記述。src/pkg/compress/gzip/gzip.go
:gzip.Writer
構造体にFlush
メソッドを実装し、内部のcompressor
フィールドの型をio.WriteCloser
から*flate.Writer
に変更。src/pkg/compress/gzip/gzip_test.go
:Writer.Flush
メソッドの動作を検証するためのテストケースTestWriterFlush
を追加。
変更の背景
Go言語のcompress/gzip
パッケージは、gzip形式でのデータ圧縮と伸長を提供します。しかし、このコミット以前のgzip.Writer
には、バッファリングされた圧縮データを強制的に下層のライターに書き出すためのFlush
メソッドがありませんでした。
一般的なデータストリーム処理において、特にネットワーク通信のようなリアルタイム性が求められるシナリオでは、書き込み操作が即座に下層のI/Oに反映されることが重要です。データが内部バッファに留まったままだと、受信側がデータを完全に受け取ることができず、プロトコルのタイムアウトや不完全なパケットの再構築といった問題が発生する可能性があります。
この問題はgolang-nuts
メーリングリストで議論され、gzip.Writer
にもFlush
機能が必要であるという結論に至りました。これにより、ユーザーは必要に応じて圧縮データを明示的にフラッシュし、下層のストリームに確実に書き出すことができるようになります。これは、例えばHTTPのチャンク転送エンコーディングやWebSocketのようなプロトコルで、圧縮されたデータをリアルタイムに送信する際に不可欠な機能です。
前提知識の解説
Gzip圧縮
Gzip (GNU zip) は、主に単一ファイルの圧縮に使用されるファイルフォーマットおよびデータ圧縮プログラムです。内部的にはDEFLATEアルゴリズム(LZ77とハフマン符号化の組み合わせ)を使用しており、これはRFC 1951で定義されています。Gzipは、HTTP通信におけるコンテンツエンコーディング(Content-Encoding: gzip
)としても広く利用されており、ウェブページの転送量を削減し、表示速度を向上させるのに貢献しています。
ZlibライブラリとZ_SYNC_FLUSH
Zlibは、DEFLATE圧縮アルゴリズムを実装したオープンソースのデータ圧縮ライブラリです。多くのプログラミング言語やアプリケーションで利用されており、gzipもzlibの機能を利用しています。 Zlibには、圧縮ストリームのフラッシュに関するいくつかのモードがあります。
Z_NO_FLUSH
: デフォルトの動作。可能な限り多くのデータをバッファリングし、最適な圧縮率を目指します。Z_SYNC_FLUSH
: 現在バッファリングされているすべての出力データを強制的に下層のストリームに書き出します。この際、圧縮状態は維持されるため、後続のデータも同じ圧縮ストリームで処理を続けることができます。これは、ネットワークプロトコルでパケットの境界をマークしたり、部分的なデータを送信したりするのに役立ちます。Z_FULL_FLUSH
:Z_SYNC_FLUSH
と同様にすべてのデータをフラッシュしますが、さらに圧縮状態をリセットします。これにより、後続のデータは独立した新しいブロックとして圧縮されます。これは、エラー回復やランダムアクセスが必要な場合に有用ですが、圧縮効率は低下します。
このコミットで追加されたgzip.Writer.Flush
は、zlibのZ_SYNC_FLUSH
に相当する機能を提供します。
Go言語のio
パッケージ
Go言語のio
パッケージは、I/Oプリミティブを提供します。
io.Writer
インターフェース:Write([]byte) (n int, err error)
メソッドを持つ型が実装するインターフェースで、バイトスライスを書き込む機能を提供します。io.WriteCloser
インターフェース:io.Writer
とio.Closer
(Close() error
メソッドを持つ)の両方を実装するインターフェースです。
Go言語のcompress/flate
パッケージ
Go言語のcompress/flate
パッケージは、DEFLATE圧縮アルゴリズムの実装を提供します。gzip.Writer
は内部的にflate.Writer
を使用して実際のDEFLATE圧縮を行っています。flate.Writer
は、Flush()
メソッドを持っており、これが今回のgzip.Writer.Flush
の実装の基盤となります。
ネットワークプロトコルとバッファリング
ネットワーク通信では、効率のためにデータは通常、内部バッファに蓄積されてからまとめて送信されます。しかし、一部のプロトコルでは、特定のイベントやタイムアウト時に、バッファリングされたデータを強制的に送信する必要があります。例えば、HTTP/1.1のチャンク転送エンコーディングでは、サーバーはレスポンスボディを小さなチャンクに分割して送信できます。この際、各チャンクの送信後にバッファをフラッシュすることで、クライアントはデータを受信次第処理を開始できます。Flush
メソッドは、このようなシナリオで、圧縮されたデータがバッファに留まることなく、即座にネットワークに送られることを保証するために不可欠です。
技術的詳細
このコミットの技術的な核心は、compress/gzip
パッケージのWriter
型にFlush
メソッドを追加し、その内部実装をcompress/flate
パッケージのflate.Writer
のFlush
メソッドに委譲することです。
-
gzip.Writer
構造体の変更: 以前のgzip.Writer
構造体では、内部の圧縮器を保持するフィールドcompressor
がio.WriteCloser
型として宣言されていました。type Writer struct { // ... compressor io.WriteCloser // ... }
しかし、
io.WriteCloser
インターフェースにはFlush()
メソッドが定義されていません。flate.Writer
はFlush()
メソッドを持っていますが、インターフェース型ではそのメソッドを直接呼び出すことができません。 このコミットでは、compressor
フィールドの型を具体的な*flate.Writer
に変更しました。type Writer struct { // ... compressor *flate.Writer // 変更点: io.WriteCloser から *flate.Writer へ // ... }
この変更により、
gzip.Writer
は内部のflate.Writer
インスタンスに直接アクセスし、そのFlush()
メソッドを呼び出すことが可能になります。 -
Writer.Flush()
メソッドの実装: 新しく追加されたFlush()
メソッドは以下のロジックで実装されています。// Flush flushes any pending compressed data to the underlying writer. // // It is useful mainly in compressed network protocols, to ensure that // a remote reader has enough data to reconstruct a packet. Flush does // not return until the data has been written. If the underlying // writer returns an error, Flush returns that error. // // In the terminology of the zlib library, Flush is equivalent to Z_SYNC_FLUSH. func (z *Writer) Flush() error { if z.err != nil { return z.err } if z.closed { return nil } if z.compressor == nil { // If no data has been written yet, the compressor might not be initialized. // Writing nil bytes will initialize it without actually writing data. z.Write(nil) } z.err = z.compressor.Flush() // 内部のflate.WriterのFlushを呼び出す return z.err }
- まず、既存のエラー(
z.err
)がある場合はそれを返します。 Writer
が既に閉じられている(z.closed
がtrue)場合は、何もしません。z.compressor
がnil
の場合(つまり、まだデータが書き込まれておらず、圧縮器が初期化されていない場合)、z.Write(nil)
を呼び出して圧縮器を初期化します。Write(nil)
はデータを書き込みませんが、内部状態を適切に設定します。- 最後に、内部の
z.compressor
(*flate.Writer
型)のFlush()
メソッドを呼び出し、その結果をz.err
に格納して返します。
- まず、既存のエラー(
-
ドキュメントの更新:
doc/go1.1.html
に、Go 1.1の変更点としてcompress/gzip
パッケージにWriter.Flush
メソッドが追加されたことが明記されました。これにより、ユーザーは新しい機能の存在を認識できます。 -
テストケースの追加:
src/pkg/compress/gzip/gzip_test.go
にTestWriterFlush
という新しいテスト関数が追加されました。このテストは、Flush
メソッドが正しく動作することを確認します。具体的には、Flush
を呼び出す前後のバッファサイズを比較し、データが正しくフラッシュされていること、そしてFlush
が複数回呼び出されても期待通りの動作をすること(例えば、データが書き込まれていない状態でFlush
を呼び出してもエラーにならないこと)を検証しています。
これらの変更により、gzip.Writer
はより柔軟になり、特にストリーミング圧縮が必要なアプリケーションにおいて、データの制御性が向上しました。
コアとなるコードの変更箇所
src/pkg/compress/gzip/gzip.go
--- a/src/pkg/compress/gzip/gzip.go
+++ b/src/pkg/compress/gzip/gzip.go
@@ -28,7 +28,7 @@ type Writer struct {
Header
w io.Writer
level int
- compressor io.WriteCloser
+ compressor *flate.Writer // 変更点: 型がio.WriteCloserから*flate.Writerへ
digest hash.Hash32
size uint32
closed bool
@@ -191,6 +191,28 @@ func (z *Writer) Write(p []byte) (int, error) {
return n, z.err
}
+// Flush flushes any pending compressed data to the underlying writer.
+//
+// It is useful mainly in compressed network protocols, to ensure that
+// a remote reader has enough data to reconstruct a packet. Flush does
+// not return until the data has been written. If the underlying
+// writer returns an error, Flush returns that error.
+//
+// In the terminology of the zlib library, Flush is equivalent to Z_SYNC_FLUSH.
+func (z *Writer) Flush() error {
+ if z.err != nil {
+ return z.err
+ }
+ if z.closed {
+ return nil
+ }
+ if z.compressor == nil {
+ z.Write(nil)
+ }
+ z.err = z.compressor.Flush() // 新しいFlushメソッドの実装
+ return z.err
+}
+
// Close closes the Writer. It does not close the underlying io.Writer.
func (z *Writer) Close() error {
if z.err != nil {
src/pkg/compress/gzip/gzip_test.go
--- a/src/pkg/compress/gzip/gzip_test.go
+++ b/src/pkg/compress/gzip/gzip_test.go
@@ -157,3 +157,43 @@ func TestLatin1RoundTrip(t *testing.T) {
}
}\n
}\n
+\n+func TestWriterFlush(t *testing.T) { // 新しいテストケース
+\tbuf := new(bytes.Buffer)\n
+\n+\tw := NewWriter(buf)\n+\tw.Comment = \"comment\"\n+\tw.Extra = []byte(\"extra\")\n+\tw.ModTime = time.Unix(1e8, 0)\n+\tw.Name = \"name\"\n+\n+\tn0 := buf.Len()\n+\tif n0 != 0 {\n+\t\tt.Fatalf(\"buffer size = %d before writes; want 0\", n0)\n+\t}\n+\n+\tif err := w.Flush(); err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\n+\tn1 := buf.Len()\n+\tif n1 == 0 {\n+\t\tt.Fatal(\"no data after first flush\")\n+\t}\n+\n+\tw.Write([]byte(\"x\"))\n+\n+\tn2 := buf.Len()\n+\tif n1 != n2 {\n+\t\tt.Fatalf(\"after writing a single byte, size changed from %d to %d; want no change\", n1, n2)\n+\t}\n+\n+\tif err := w.Flush(); err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\n+\tn3 := buf.Len()\n+\tif n2 == n3 {\n+\t\tt.Fatal(\"Flush didn\'t flush any data\")\n+\t}\n+}\n```
## コアとなるコードの解説
### `gzip.go`における変更
1. **`compressor`フィールドの型変更**:
`Writer`構造体の`compressor`フィールドの型が`io.WriteCloser`から`*flate.Writer`に変更されました。これは、`io.WriteCloser`インターフェースには`Flush()`メソッドが定義されていないため、`flate.Writer`が持つ`Flush()`メソッドを直接呼び出すために必要な変更です。これにより、`gzip.Writer`は内部のDEFLATE圧縮器のフラッシュ機能を直接制御できるようになります。
2. **`Flush()`メソッドの追加**:
- `func (z *Writer) Flush() error`: このメソッドが`gzip.Writer`に追加されました。
- `if z.err != nil { return z.err }`: 以前の操作でエラーが発生している場合、そのエラーをすぐに返します。
- `if z.closed { return nil }`: `Writer`が既に`Close()`されている場合、それ以上の操作は不要なので`nil`を返します。
- `if z.compressor == nil { z.Write(nil) }`: これは重要な初期化ロジックです。`gzip.Writer`は、最初の`Write`呼び出しが行われるまで内部の`flate.Writer`(`z.compressor`)を遅延初期化する場合があります。`Flush`が`Write`より先に呼び出された場合、`z.compressor`が`nil`のままになる可能性があります。`z.Write(nil)`を呼び出すことで、実際にデータを書き込むことなく`z.compressor`が確実に初期化されるようにします。
- `z.err = z.compressor.Flush()`: 内部の`flate.Writer`の`Flush()`メソッドを呼び出します。この`flate.Writer.Flush()`が、バッファリングされた圧縮データを下層の`io.Writer`に強制的に書き出す実際の処理を行います。その結果発生したエラーは`z.err`に格納され、メソッドの戻り値として返されます。
### `gzip_test.go`における変更
1. **`TestWriterFlush`関数の追加**:
このテスト関数は、`Writer.Flush()`メソッドの動作を検証するために追加されました。
- `buf := new(bytes.Buffer)`: 圧縮されたデータが書き込まれるメモリバッファを作成します。
- `w := NewWriter(buf)`: `gzip.Writer`の新しいインスタンスを作成し、`buf`を書き込み先として設定します。
- 初期状態でのバッファサイズが0であることを確認します。
- `w.Flush()`を呼び出し、エラーがないことを確認します。この時点ではまだデータが書き込まれていないため、`Flush`は圧縮器を初期化するだけで、バッファにはヘッダ情報などが書き込まれる可能性があります。
- `n1 := buf.Len()`: 最初の`Flush`後のバッファサイズを記録します。これが0でないことを確認し、`Flush`が何らかのデータを(例えばgzipヘッダ)書き出したことを検証します。
- `w.Write([]byte("x"))`: 1バイトのデータを書き込みます。この時点では、データは内部バッファに保持され、すぐにフラッシュされないことが期待されます。
- `n2 := buf.Len()`: 1バイト書き込んだ後のバッファサイズを記録します。`n1`と`n2`が同じであることを確認し、`Write`がすぐにフラッシュしないことを検証します。
- `if err := w.Flush(); err != nil { ... }`: 再度`Flush()`を呼び出します。
- `n3 := buf.Len()`: 2回目の`Flush`後のバッファサイズを記録します。
- `if n2 == n3 { t.Fatal("Flush didn't flush any data") }`: `n2`と`n3`が異なることを確認し、2回目の`Flush`が実際にバッファリングされていた「x」の圧縮データなどをフラッシュしたことを検証します。
このテストは、`Flush`メソッドが期待通りに動作し、バッファリングされたデータを強制的に書き出す機能を提供していることを保証します。
## 関連リンク
- Go言語の`compress/gzip`パッケージのドキュメント: [https://pkg.go.dev/compress/gzip](https://pkg.go.dev/compress/gzip)
- Go言語の`compress/flate`パッケージのドキュメント: [https://pkg.go.dev/compress/flate](https://pkg.go.dev/compress/flate)
- Go言語の`io`パッケージのドキュメント: [https://pkg.go.dev/io](https://pkg.go.dev/io)
- RFC 1951 - DEFLATE Compressed Data Format Specification version 1.3: [https://www.rfc-editor.org/rfc/rfc1951](https://www.rfc-editor.org/rfc/rfc1951)
- RFC 1952 - GZIP File Format Specification version 4.3: [https://www.rfc-editor.org/rfc/rfc1952](https://www.rfc-editor.org/rfc/rfc1952)
## 参考にした情報源リンク
- golang-nutsメーリングリストの議論 (コミットメッセージに記載の`https://golang.org/cl/8251043`はGoのChange Listへのリンクであり、議論の直接的なリンクではないが、関連する情報源として参照)
- Zlib Manual: [https://www.zlib.net/manual.html](https://www.zlib.net/manual.html) (特に`Z_SYNC_FLUSH`に関する説明)
- Go言語のソースコード(`src/pkg/compress/gzip/gzip.go`および`src/pkg/compress/gzip/gzip_test.go`)