[インデックス 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 = offset
os.SEEK_CUR
(1):abs = 現在の読み取り位置 + offset
os.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=