Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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-SinceIf-None-Matchなどのキャッシュ関連ヘッダの処理、MIMEタイプの推測など、静的ファイル配信に必要な多くのロジックを内部で処理します。

parseRange関数 (または類似の内部ロジック)

net/httpパッケージ内部には、HTTPのRangeヘッダの文字列を解析し、有効なバイト範囲のリストに変換するロジックが存在します。このロジックは、単に文字列をパースするだけでなく、要求された範囲が提供されるコンテンツのサイズに対して有効であるかどうかの基本的な検証も行います。例えば、範囲の開始位置がコンテンツの総サイズを超えている場合や、範囲がコンテンツと全く重ならない場合は、エラーを返すか、無効な範囲として処理しないように設計されています。

Web検索で確認したnet/http.ParseRange関数の情報によると、この関数はRangeヘッダの文字列とコンテンツの総サイズを受け取り、有効なRange構造体のスライスを返します。もし、指定された範囲がコンテンツサイズと全く重ならない場合(ErrNoOverlap)、エラーを返します。これは、parseRangeが成功して範囲のリストが返された場合、それらの範囲が少なくともコンテンツのどこかと重なっている、つまりra.start > sizeのような無効な状態ではないことを保証していることを意味します。

技術的詳細

serveContent関数は、HTTPリクエストのRangeヘッダを解析し、部分的なコンテンツを返すかどうかを決定します。

  1. Rangeヘッダの解析: serveContentは、まずリクエストからRangeヘッダを取得し、内部のparseRange関数(またはそれに相当するロジック)に渡して解析させます。このparseRangeは、Rangeヘッダの構文チェックと、要求された範囲がコンテンツの総サイズに対して有効であるかどうかの基本的な検証を行います。
  2. 単一Rangeと複数Rangeの処理:
    • 単一の有効なRangeが要求された場合、serveContentStatusPartialContent(206)を返し、Content-Rangeヘッダを設定し、指定された範囲のコンテンツのみを書き込みます。
    • 複数のRangeが要求された場合、serveContentmultipart/byteranges形式でレスポンスを構築します。これは、複数の部分的なコンテンツを一つのレスポンスボディに含めるためのMIMEタイプです。

このコミットで削除されたコードは、複数の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関数はより効率的になり、コードの意図がより明確になりました。

関連リンク

参考にした情報源リンク