[インデックス 11741] ファイルの概要
このコミットは、Go言語の標準ライブラリ strings パッケージ内の Reader 型に Seek メソッドを追加するものです。これにより、strings.Reader が io.Seeker インターフェースを満たすようになり、特に http.ServeContent のような、io.ReadSeeker を要求する関数との互換性が向上します。
コミット
commit 396170da9b622e1a4866d9f28552aee008274ed5
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Feb 9 17:28:41 2012 +1100
strings: add Seek method to Reader
strings.Reader is already stateful and read-only.
This permits a *Reader with http.ServeContent.
R=golang-dev, r, rsc, rsc
CC=golang-dev
https://golang.org/cl/5639068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/396170da9b622e1a4866d9f28552aee008274ed5
元コミット内容
strings パッケージの Reader 型に Seek メソッドを追加します。strings.Reader は既に状態を持ち、読み取り専用です。この変更により、*Reader を http.ServeContent と共に使用できるようになります。
変更の背景
この変更の主な背景は、strings.Reader を net/http パッケージの http.ServeContent 関数と互換性を持たせることです。http.ServeContent は、HTTPレスポンスとしてコンテンツを効率的に提供するための関数であり、そのコンテンツソースとして io.ReadSeeker インターフェースを実装した型を要求します。
従来の strings.Reader は io.Reader と io.ByteScanner、io.RuneScanner インターフェースを実装していましたが、io.Seeker インターフェースは実装していませんでした。そのため、文字列データを http.ServeContent で直接提供することができませんでした。
Seek メソッドを追加することで、strings.Reader は io.Seeker インターフェースも満たすようになり、結果として io.ReadSeeker インターフェースも満たすことになります。これにより、メモリ上の文字列データをファイルのようにシーク可能な形で http.ServeContent に渡すことが可能になり、HTTPのRangeリクエストへの対応や、適切なヘッダー(Content-Type, Content-Length, Last-Modifiedなど)の自動設定、キャッシュの効率的な利用といった http.ServeContent の利点を享受できるようになります。
前提知識の解説
io.Reader インターフェース
io.Reader はGo言語の io パッケージで定義されている最も基本的なインターフェースの一つです。データを読み取るための単一のメソッド Read を持ちます。
type Reader interface {
Read(p []byte) (n int, err error)
}
Read メソッドは、データを p に読み込み、読み込んだバイト数 n とエラー err を返します。ストリームからの順次読み取りを抽象化します。
io.Seeker インターフェース
io.Seeker は、データストリーム内の現在の読み書き位置を変更する機能を提供するインターフェースです。ファイルやその他のランダムアクセス可能なデータソースを操作する際に特に有用です。
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
offset(int64): 移動する距離を指定します。whence(int):offsetの計算基準となる参照点を指定します。osパッケージで定義されている以下の定数を使用します。os.SEEK_SET: ファイルの先頭からの絶対位置。os.SEEK_CUR: 現在位置からの相対位置。os.SEEK_END: ファイルの末尾からの相対位置(通常は負のオフセットで使用)。
- 戻り値: 新しいオフセット(ファイルの先頭からの絶対位置)とエラーを返します。
io.Seeker を実装することで、データストリーム内の任意の位置に移動し、そこから読み取りや書き込みを開始できるようになります。
io.ReadSeeker インターフェース
io.ReadSeeker は io.Reader と io.Seeker の両方の機能を組み合わせたインターフェースです。これにより、データの読み取りと、読み取り位置のシークの両方が可能になります。
type ReadSeeker interface {
Reader
Seeker
}
strings.Reader 型
strings.Reader は、Go言語の strings パッケージで提供される型で、文字列からデータを読み取るための io.Reader の実装です。内部的には文字列と現在の読み取り位置を保持しており、文字列の内容をバイトストリームとして扱うことができます。このコミット以前は、順次読み取りのみが可能でした。
http.ServeContent 関数
net/http パッケージの http.ServeContent 関数は、HTTPレスポンスとしてコンテンツを効率的に提供するためのユーティリティ関数です。この関数は、io.ReadSeeker インターフェースを実装したコンテンツソースを受け取ります。
http.ServeContent の主な利点は以下の通りです。
- Rangeリクエストの処理: HTTPの
Rangeヘッダーを適切に解釈し、クライアントがコンテンツの一部のみを要求した場合に、その部分だけを返します。これにより、大きなファイルのダウンロード再開や、動画のストリーミングなどが効率的に行えます。 - ヘッダーの自動設定:
Content-Type、Content-Length、Last-Modifiedなどの適切なHTTPヘッダーを自動的に設定します。 - キャッシュのサポート:
If-Modified-Sinceなどの条件付きリクエストを処理し、コンテンツが変更されていない場合は304 Not Modifiedステータスを返すことで、クライアント側のキャッシュを効率的に利用します。
技術的詳細
このコミットでは、strings.Reader 型に Seek メソッドが追加され、io.Seeker インターフェースが実装されました。
Seek メソッドの実装
Seek メソッドは、offset と whence の値に基づいて、strings.Reader の内部的な読み取り位置 r.i を更新します。
-
whenceの処理:os.SEEK_SET(0):abs = offset。オフセットがファイルの先頭からの絶対位置として扱われます。os.SEEK_CUR(1):abs = int64(r.i) + offset。オフセットが現在の読み取り位置r.iからの相対位置として扱われます。os.SEEK_END(2):abs = int64(len(r.s)) + offset。オフセットが文字列の末尾からの相対位置として扱われます。len(r.s)は文字列全体の長さです。- 上記以外の
whenceの値が指定された場合は、"strings: invalid whence"というエラーを返します。
-
位置の検証:
- 計算された絶対位置
absが負の場合 (abs < 0) は、"strings: negative position"というエラーを返します。 - 計算された絶対位置
absが1<<31(2GB) 以上の場合 (abs >= 1<<31) は、"strings: position out of range"というエラーを返します。これは、r.iがint型であるため、その最大値を超える位置へのシークを防ぐためのチェックです。
- 計算された絶対位置
-
読み取り位置の更新:
- 検証を通過した場合、
r.i = int(abs)として、strings.Readerの内部的な読み取り位置r.iを更新します。 - 更新された絶対位置
absを返します。
- 検証を通過した場合、
Len() メソッドの修正
Len() メソッドは、未読部分のバイト数を返します。このコミットでは、r.i >= len(r.s) の場合に 0 を返すガード句が追加されました。これにより、既に文字列の末尾を超えて読み取り位置がある場合に、負の値ではなく 0 が返されるようになり、より堅牢になりました。
Read() メソッドの修正
Read() メソッドは、バイトスライス b にデータを読み込みます。このコミットでは、len(b) == 0 の場合に 0, nil を返すガード句が追加されました。これは、空のスライスに読み込もうとした場合に、すぐに 0 バイト読み込み、エラーなしで終了することを保証します。
テストの追加
src/pkg/strings/reader_test.go という新しいテストファイルが追加され、Seek メソッドの動作が詳細にテストされています。これには、様々な offset と whence の組み合わせ、エラーケース(負の位置、範囲外の位置)、そしてシーク後の Read 動作の検証が含まれています。
コアとなるコードの変更箇所
--- a/src/pkg/strings/reader.go
+++ b/src/pkg/strings/reader.go
@@ -10,7 +10,7 @@ import (
"unicode/utf8"
)
-// A Reader implements the io.Reader, io.ByteScanner, and
+// A Reader implements the io.Reader, io.Seeker, io.ByteScanner, and
// io.RuneScanner interfaces by reading from a string.
type Reader struct {
s string
@@ -21,10 +21,16 @@ type Reader struct {
// Len returns the number of bytes of the unread portion of the
// string.
func (r *Reader) Len() int {
+ if r.i >= len(r.s) {
+ return 0
+ }
return len(r.s) - r.i
}
func (r *Reader) Read(b []byte) (n int, err error) {
+ if len(b) == 0 {
+ return 0, nil
+ }
if r.i >= len(r.s) {
return 0, io.EOF
}
@@ -87,6 +93,29 @@ func (r *Reader) UnreadRune() error {
return nil
}
+// Seek implements the io.Seeker interface.
+func (r *Reader) Seek(offset int64, whence int) (int64, error) {
+ var abs int64
+ switch whence {
+ case 0:
+ abs = offset
+ case 1:
+ abs = int64(r.i) + offset
+ case 2:
+ abs = int64(len(r.s)) + offset
+ default:
+ return 0, errors.New("strings: invalid whence")
+ }
+ if abs < 0 {
+ return 0, errors.New("strings: negative position")
+ }
+ if abs >= 1<<31 {
+ return 0, errors.New("strings: position out of range")
+ }
+ r.i = int(abs)
+ return abs, nil
+}
+
// NewReader returns a new Reader reading from s.
// It is similar to bytes.NewBufferString but more efficient and read-only.
func NewReader(s string) *Reader { return &Reader{s, 0, -1} }
--- /dev/null
+++ b/src/pkg/strings/reader_test.go
@@ -0,0 +1,58 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package strings_test
+
+import (
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestReader(t *testing.T) {
+ r := strings.NewReader("0123456789")
+ tests := []struct {
+ off int64
+ seek int
+ n int
+ want string
+ wantpos int64
+ seekerr string
+ }{
+ {seek: os.SEEK_SET, off: 0, n: 20, want: "0123456789"},
+ {seek: os.SEEK_SET, off: 1, n: 1, want: "1"},
+ {seek: os.SEEK_CUR, off: 1, wantpos: 3, n: 2, want: "34"},
+ {seek: os.SEEK_SET, off: -1, seekerr: "strings: negative position"},
+ {seek: os.SEEK_SET, off: 1<<31 - 1},
+ {seek: os.SEEK_CUR, off: 1, seekerr: "strings: position out of range"},
+ {seek: os.SEEK_SET, n: 5, want: "01234"},
+ {seek: os.SEEK_CUR, n: 5, want: "56789"},
+ {seek: os.SEEK_END, off: -1, n: 1, wantpos: 9, want: "9"},
+ }
+
+ for i, tt := range tests {
+ pos, err := r.Seek(tt.off, tt.seek)
+ if err == nil && tt.seekerr != "" {
+ t.Errorf("%d. want seek error %q", i, tt.seekerr)
+ continue
+ }
+ if err != nil && err.Error() != tt.seekerr {
+ t.Errorf("%d. seek error = %q; want %q", i, err.Error(), tt.seekerr)
+ continue
+ }
+ if tt.wantpos != 0 && tt.wantpos != pos {
+ t.Errorf("%d. pos = %d, want %d", i, pos, tt.wantpos)
+ }
+ buf := make([]byte, tt.n)
+ n, err := r.Read(buf)
+ if err != nil {
+ t.Errorf("%d. read = %v", i, err)
+ continue
+ }
+ got := string(buf[:n])
+ if got != tt.want {
+ t.Errorf("%d. got %q; want %q", i, got, tt.want)
+ }
+ }
+}
コアとなるコードの解説
src/pkg/strings/reader.go
-
type Reader structのコメント変更:// A Reader implements the io.Reader, io.ByteScanner, andから// A Reader implements the io.Reader, io.Seeker, io.ByteScanner, andに変更され、io.Seekerインターフェースを実装したことが明示されています。 -
func (r *Reader) Len() intの変更:if r.i >= len(r.s) { return 0 }が追加されました。これにより、読み取り位置r.iが文字列の長さを超えている場合、未読バイト数は0であると正確に報告されます。以前はlen(r.s) - r.iが負の値になる可能性がありました。 -
func (r *Reader) Read(b []byte) (n int, err error)の変更:if len(b) == 0 { return 0, nil }が追加されました。これは、読み込み先のバイトスライスbが空の場合、何も読み込まずに0バイトとnilエラーを返すという一般的なio.Readerの振る舞いに準拠するためのものです。 -
func (r *Reader) Seek(offset int64, whence int) (int64, error)の追加: これがこのコミットの主要な変更点です。whenceの値に応じて、新しい絶対位置absを計算します。os.SEEK_SET(0):abs = offsetos.SEEK_CUR(1):abs = 現在の読み取り位置 + offsetos.SEEK_END(2):abs = 文字列の長さ + offset
- 無効な
whenceの値が与えられた場合、"strings: invalid whence"エラーを返します。 - 計算された
absが負の場合、"strings: negative position"エラーを返します。 - 計算された
absが1<<31(約2GB) 以上の場合、"strings: position out of range"エラーを返します。これは、r.iがint型であり、その範囲を超える位置へのシークを防ぐためです。 - すべてのチェックを通過した場合、
r.iをint(abs)に設定し、新しい絶対位置absを返します。
src/pkg/strings/reader_test.go
- 新規ファイルの追加:
strings.ReaderのSeekメソッドの動作を検証するためのテストケースが多数含まれています。 TestReader関数:strings.NewReader("0123456789")でReaderインスタンスを作成します。testsスライスには、様々なoffset、whence、期待される読み取りバイト数n、期待される読み取り結果want、期待される最終位置wantpos、そして期待されるエラーメッセージseekerrの組み合わせが定義されています。- 各テストケースでは、
r.Seekを呼び出し、返された位置とエラーが期待通りであるかを検証します。 - その後、
r.Readを呼び出し、読み取られたデータが期待通りであるかを検証します。 - 特に、負のオフセットや範囲外のオフセットに対するエラーハンドリングがテストされています。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/396170da9b622e1a4866d9f28552aee008274ed5
- Go CL (Change List) リンク:
https://golang.org/cl/5639068(ただし、Web検索ではこのCL番号は確認できませんでした。GoのCL番号は通常もっと短いです。)
参考にした情報源リンク
- Go
http.ServeContentの解説:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH055hLtGeLmJk8kJRV_pUyQ8_zH5JM9-f_7tJqvhXXXaMRdUVFgV0cHcAZNuUre91i26_Jpz7iwJlGTpwkiagjBnP3h2xHmYgoSUg0f3KUvL_7YoNGYKOaxGjKUVzQebU9wvPG-P2DzUAMG1nM3pKfLfIzx0G-ba06D5ckNn25WW4iuA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF4SyoqHjhQ98v_ln-hrnZk1Q5aQiEPCVSMtat_DBANgXe9L6Vs-BljJyBjjKzL8RqzAFVv3bbCzb3OVO-LpZbBWAMmD4dekmqyzThyHVb26jje0oyqHfRo5K3LbW-58rwlSMbFMn6QTw==
- https://vertexaisearch.cloud.google.google.com/grounding-api-redirect/AUZIYQHTg20iwTbxWFSxP-8lK-VnZ_pLZiU49Wsv0cTZAuLtL6qsqQuDPtzjzXpDtJeaLxTUZzEXDkLddgBvHIliQKllB4LbZrfZWnHgBV7dayp6mejNMP67upaECdLylKK1hzhgXiKrJAPmVyOQhQ==
- Go
io.Seekerインターフェースの解説:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEyYABSyAOuWIVm1EmH9BOL6p17psKVTifl-z_dq_XcEpk78exOBXnN_pKk-e-YaC34I0HiYJntyANGN_MqaMxOetIpBQF76gPt1tf0VdiQKrrGp09tzmEXULW_IWEpxh1gPJsAjbq2w-67_Mr5sX0BQrHc-EA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGxZyrelfomP1SeysQ9Vmm0O1s21McmiLpckCZJHJbD4548gbP7Amqta2-ILabOcKUmd7b30QaAO-72OhKXxk-5XdsyNLahKr9BVSLWRh7pOzXCNUJ2Gb808pX3oyN7InL2D0GUZg==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQETzaUGa-X6cj9NALa189MmIvActDgGRzc0wWbJX2iaz3Yt7x_QPCKt0GRicdjtkSX-Ag4WFOOPatljrUYFPEMPJ6AFY54Z3P-KdfLcpg8pzto6mk3bpGIaD_s2FOquaj-ZU-m_isV9jDS3GtCztht3tolvD_HUuzVxkaAKqVIttg==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHYclaks7U9PbfSCzgNfCQVDeOwlhW_QtkpOaz-I4_oHazqQmf3rAOMvJM92wopQC6jRZdhUGEDeXSGDXumVTYd4QMsy4Es6Bz2mYpHAjhSbP-Pow5GsL99DLrfZL2uozttqNQpFVwltEMWK4AvuXhKnWaKmMmROwA89sO2Drw0_zV9sb0EMGja1tbLbaD92D5sNHtlTIByd0fsRlSu58BcjXI2qXYZQgQ69VfvBKQRU1qxA==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGnHhQs4k15883udFKTyXPI0oBo1NtwI-m6eIXHRmPHCn6tOvACB4YtxqAqBl66yjYY9Ol8BfksQJnZnYoPcVxfkgnt_R88MkDmRnmp2uvI7OcFFBY3JioPVtGM1Cs09_5k66DXwBK2NvnyJRUjt3tX3tD9pjCj-DxcVHUctMVIa0K4JDiHBcG9aAa1oiIOR6vh0I0U1Bo=