[インデックス 19600] ファイルの概要
このコミットは、Go言語のnet/http
パッケージ内のfs.go
ファイルから重複する範囲チェックロジックを削除するものです。具体的には、serveContent
関数内で、HTTPのRangeリクエストを処理する際に、すでにparseRange
関数によって行われている範囲の有効性チェックが再度行われていた部分を削除し、コードの冗長性を解消しています。
コミット
commit 71c9a4948a38d0bd9430b42b50f8e5312c8bc9f0
Author: Robert Obryk <robryk@gmail.com>
Date: Mon Jun 23 17:38:17 2014 -0700
net/http: remove a duplicated check
The previous call to parseRange already checks whether
all the ranges start before the end of file.
LGTM=robert.hencke, bradfitz
R=golang-codereviews, robert.hencke, gobot, bradfitz
CC=golang-codereviews
https://golang.org/cl/91880044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/71c9a4948a38d0bd9430b42b50f8e5312c8bc9f0
元コミット内容
このコミットの目的は、net/http
パッケージ内の重複したチェック処理を削除することです。コミットメッセージによると、parseRange
関数がすでにファイル終端より前にすべての範囲が開始しているかをチェックしているため、その後のコードで同じチェックを行う必要がないとされています。
変更の背景
HTTPのRangeリクエストは、クライアントがリソース全体ではなく、その一部(バイト範囲)のみを要求するために使用されます。Goのnet/http
パッケージでは、serveContent
関数がこのRangeリクエストの処理を担当しています。
serveContent
関数は、まずリクエストヘッダからRange情報を解析するためにparseRange
関数(またはそれに類する内部関数)を呼び出します。このparseRange
関数は、指定されたRangeがファイルのサイズに対して有効であるか(例えば、範囲の開始位置がファイルサイズを超えていないか、範囲がファイルと重なっているかなど)を検証する役割を担っています。
しかし、このコミット以前のコードでは、parseRange
が成功したにもかかわらず、その後に再度、各Rangeの開始位置がファイルサイズを超えていないかをループでチェックする冗長なコードが存在していました。この重複したチェックは、コードの可読性を低下させ、不必要な処理オーバーヘッドを発生させていました。
このコミットは、parseRange
がすでに必要な検証を行っているという認識に基づき、この重複したチェックを削除することで、コードをより簡潔で効率的にすることを目的としています。
前提知識の解説
HTTP Rangeリクエスト
HTTP/1.1では、クライアントがリソースの特定の部分のみを要求するためにRange
ヘッダを使用できます。これは、大きなファイルのダウンロードを中断・再開したり、動画や音声のストリーミング再生で特定の位置から再生を開始したりする際に利用されます。
Range
ヘッダ: クライアントが要求するバイト範囲を指定します。例:Range: bytes=0-499
(最初の500バイト)、Range: bytes=500-
(500バイト目から最後まで)、Range: bytes=-500
(最後の500バイト)。Content-Range
ヘッダ: サーバーが部分的なコンテンツを返す際に、そのコンテンツがリソース全体のどの部分であるかを示します。例:Content-Range: bytes 0-499/1234
。206 Partial Content
: Rangeリクエストが成功し、サーバーがリソースの一部を返した場合に返されるステータスコードです。416 Requested Range Not Satisfiable
: クライアントが要求したRangeがリソースの範囲外である場合(例: 存在しない範囲を要求した場合)に返されるステータスコードです。
net/http
パッケージ
Go言語の標準ライブラリであるnet/http
パッケージは、HTTPクライアントとサーバーの実装を提供します。ウェブアプリケーションの構築において中心的な役割を果たします。
http.ResponseWriter
: HTTPレスポンスを構築するためのインターフェースです。ヘッダの設定やボディへの書き込みを行います。http.Request
: 受信したHTTPリクエストの情報をカプセル化します。リクエストメソッド、URL、ヘッダ、ボディなどにアクセスできます。http.serveContent
関数: このコミットで変更された主要な関数です。ファイルやバイトスライスなどのコンテンツをHTTPレスポンスとして提供する際に使用されます。特に、Rangeリクエストの処理、If-Modified-Since
やIf-None-Match
などのキャッシュ関連ヘッダの処理、MIMEタイプの推測など、静的ファイル配信に必要な多くのロジックを内部で処理します。
parseRange
関数 (または類似の内部ロジック)
net/http
パッケージ内部には、HTTPのRange
ヘッダの文字列を解析し、有効なバイト範囲のリストに変換するロジックが存在します。このロジックは、単に文字列をパースするだけでなく、要求された範囲が提供されるコンテンツのサイズに対して有効であるかどうかの基本的な検証も行います。例えば、範囲の開始位置がコンテンツの総サイズを超えている場合や、範囲がコンテンツと全く重ならない場合は、エラーを返すか、無効な範囲として処理しないように設計されています。
Web検索で確認したnet/http.ParseRange
関数の情報によると、この関数はRange
ヘッダの文字列とコンテンツの総サイズを受け取り、有効なRange
構造体のスライスを返します。もし、指定された範囲がコンテンツサイズと全く重ならない場合(ErrNoOverlap
)、エラーを返します。これは、parseRange
が成功して範囲のリストが返された場合、それらの範囲が少なくともコンテンツのどこかと重なっている、つまりra.start > size
のような無効な状態ではないことを保証していることを意味します。
技術的詳細
serveContent
関数は、HTTPリクエストのRange
ヘッダを解析し、部分的なコンテンツを返すかどうかを決定します。
- Rangeヘッダの解析:
serveContent
は、まずリクエストからRange
ヘッダを取得し、内部のparseRange
関数(またはそれに相当するロジック)に渡して解析させます。このparseRange
は、Rangeヘッダの構文チェックと、要求された範囲がコンテンツの総サイズに対して有効であるかどうかの基本的な検証を行います。 - 単一Rangeと複数Rangeの処理:
- 単一の有効なRangeが要求された場合、
serveContent
はStatusPartialContent
(206)を返し、Content-Range
ヘッダを設定し、指定された範囲のコンテンツのみを書き込みます。 - 複数のRangeが要求された場合、
serveContent
はmultipart/byteranges
形式でレスポンスを構築します。これは、複数の部分的なコンテンツを一つのレスポンスボディに含めるためのMIMEタイプです。
- 単一の有効なRangeが要求された場合、
このコミットで削除されたコードは、複数のRangeが要求された場合の処理ブロック内にありました。具体的には、parseRange
が成功してranges
スライスが返された後、さらに以下のループで各ra
(Range)についてra.start > size
(範囲の開始位置がファイルサイズを超えているか)をチェックしていました。
for _, ra := range ranges {
if ra.start > size {
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
return
}
}
このチェックは、parseRange
関数がすでに同様の検証を行っているため、冗長でした。parseRange
が成功して有効なranges
スライスを返した場合、その中の各Rangeはすでにコンテンツのサイズに対して有効であることが保証されています。もし無効なRangeが含まれていれば、parseRange
の段階でエラーが返されるか、その無効なRangeは結果のリストに含まれないはずです。したがって、この追加のループは不要でした。
コアとなるコードの変更箇所
--- a/src/pkg/net/http/fs.go
+++ b/src/pkg/net/http/fs.go
@@ -212,12 +212,6 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
case len(ranges) == 1:
ra := ranges[0]
if ra.length == 0 {
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
return
}
sendSize = ra.length
code = StatusPartialContent
w.Header().Set("Content-Range", ra.contentRange(size))
case len(ranges) > 1:
- for _, ra := range ranges {
- if ra.start > size {
- Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
- return
- }
- }
sendSize = rangesMIMESize(ranges, ctype, size)
code = StatusPartialContent
コアとなるコードの解説
変更はsrc/pkg/net/http/fs.go
ファイルのserveContent
関数内で行われています。
削除されたコードブロックは、len(ranges) > 1
、つまり複数のバイト範囲がリクエストされた場合の処理パスにありました。
for _, ra := range ranges {
if ra.start > size {
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
return
}
}
このfor
ループは、解析された各ra
(Range)について、そのstart
(開始オフセット)がsize
(コンテンツの総サイズ)を超えていないかをチェックしていました。もし超えている場合、StatusRequestedRangeNotSatisfiable
(416)エラーを返していました。
しかし、前述の通り、serveContent
がRangeヘッダを解析するために使用するparseRange
関数は、すでにこの種の有効性チェックを行っています。parseRange
が成功してRangeのリストを返した場合、それはすべてのRangeがコンテンツのサイズに対して有効であり、少なくとも何らかの形でコンテンツと重なっていることを意味します。したがって、ra.start > size
のような無効なRangeは、parseRange
の時点でフィルタリングされるか、エラーとして処理されるべきです。
このコミットは、この冗長なチェックを認識し、削除することで、コードベースをクリーンアップし、不必要な処理を省いています。これにより、serveContent
関数はより効率的になり、コードの意図がより明確になりました。
関連リンク
- Go CL 91880044: https://golang.org/cl/91880044
参考にした情報源リンク
net/http.ParseRange
に関するGoのドキュメントやソースコードの解説(Web検索結果より)- HTTP/1.1 RFC 7233 (Range Requests): https://datatracker.ietf.org/doc/html/rfc7233 (一般的なHTTP Rangeリクエストの仕様について)
- Go
net/http
パッケージのソースコード (特にfs.go
): https://github.com/golang/go/blob/master/src/net/http/fs.go (コミット時点のコードとは異なる可能性がありますが、一般的な構造理解のため)