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

[インデックス 11750] ファイルの概要

このコミットは、Go言語の標準ライブラリである net/http パッケージに ServeContent 関数を追加するものです。この関数は、HTTPレスポンスとしてファイルやその他の io.ReadSeeker インターフェースを実装するコンテンツを効率的かつ適切に提供するための汎用的なメカニズムを提供します。特に、HTTPの Range リクエストや If-Modified-Since ヘッダーの処理、MIMEタイプの自動検出といった重要な機能が統合されています。

コミット

commit 4539d1f307d0f8f110367bc61d11e0888feb071d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Fri Feb 10 10:02:06 2012 +1100

    net/http: add ServeContent
    
    Fixes #2039
    
    R=r, rsc, n13m3y3r, r, rogpeppe
    CC=golang-dev
    https://golang.org/cl/5643067

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4539d1f307d0f8f110367bc61d11e0888feb071d

元コミット内容

net/http: add ServeContent

このコミットは、net/http パッケージに ServeContent 関数を追加します。 Issue #2039 を修正します。

変更の背景

この変更の背景には、HTTPサーバーが静的ファイルや動的に生成されるコンテンツを効率的かつ標準的な方法で提供する必要性がありました。特に、以下の課題に対応するために ServeContent が導入されました。

  1. HTTP Range リクエストの適切な処理: 大容量のファイル(動画や音声など)をストリーミングする際、クライアントはファイルの特定の部分のみを要求することがあります(例: Range: bytes=0-499)。これまでの実装では、このようなリクエストを適切に処理するための汎用的なメカニズムが不足しており、開発者が個別に実装する必要がありました。ServeContentContent-Range ヘッダーの生成と部分的なコンテンツの送信を自動的に行います。
  2. If-Modified-Since ヘッダーによるキャッシュ制御: クライアントが以前に取得したコンテンツのキャッシュを持っている場合、If-Modified-Since ヘッダーを送信して、サーバーにコンテンツが更新されているかどうかを問い合わせます。サーバーがコンテンツが変更されていないと判断した場合、304 Not Modified ステータスコードを返すことで、帯域幅の節約とパフォーマンスの向上が図れます。ServeContent はこのロジックを組み込みます。
  3. MIMEタイプの自動検出と設定: 提供するコンテンツのMIMEタイプ(例: text/html, image/jpeg)を正確に設定することは、ブラウザがコンテンツを正しく解釈するために不可欠です。ServeContent はファイル名拡張子からのMIMEタイプ推測と、コンテンツの最初のブロックを読み取ってMIMEタイプを検出するフォールバックメカニズムを提供します。
  4. コードの重複排除と汎用化: serveFile のような既存の関数が持っていたコンテンツ提供ロジックの一部を ServeContent に集約することで、コードの重複を減らし、より汎用的なコンテンツ提供APIを提供します。これにより、開発者はファイルだけでなく、メモリ上のデータやデータベースから取得したデータなど、io.ReadSeeker インターフェースを満たすあらゆるコンテンツを容易に提供できるようになります。

これらの機能は、堅牢で効率的なHTTPサーバーを構築する上で不可欠であり、ServeContent の導入によって net/http パッケージの機能が大幅に強化されました。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念を理解しておく必要があります。

  1. HTTPプロトコル:
    • HTTPヘッダー: クライアントとサーバー間で送受信されるメタデータ。特に Content-Type, Content-Length, Content-Range, Accept-Ranges, Last-Modified, If-Modified-Since, Range ヘッダーが重要です。
    • HTTPステータスコード: リクエストの結果を示す3桁の数値コード。200 OK, 206 Partial Content, 304 Not Modified, 416 Requested Range Not Satisfiable, 500 Internal Server Error などが関連します。
    • GET/HEADメソッド: GET はリソースの取得、HEAD はリソースのヘッダーのみの取得に使用されます。ServeContentHEAD リクエストの場合にボディを送信しないように処理します。
  2. MIMEタイプ (Multipurpose Internet Mail Extensions):
    • インターネット上で送受信されるデータの種類を示す標準的な識別子(例: text/html, application/json, image/png)。ブラウザはMIMEタイプに基づいてコンテンツの表示方法を決定します。
    • mime.TypeByExtension: ファイルの拡張子からMIMEタイプを推測するGoの関数。
    • DetectContentType: コンテンツの最初の数バイトを調べてMIMEタイプを検出するGoの関数。
  3. Go言語のI/Oインターフェース:
    • io.Reader: データを読み取るためのインターフェース。
    • io.Seeker: データの読み取り位置を移動するためのインターフェース。
    • io.ReadSeeker: io.Readerio.Seeker の両方を組み合わせたインターフェース。ファイルのように、読み取りとシーク(位置移動)が可能なデータソースを表します。*os.File はこのインターフェースを実装しています。
    • io.Copy / io.CopyN: io.Reader から io.Writer へデータをコピーするためのGoのユーティリティ関数。io.CopyN は指定されたバイト数だけコピーします。
  4. ファイルシステム操作:
    • os.SEEK_END, os.SEEK_SET: Seek メソッドで使用される定数で、それぞれファイルの末尾、ファイルの先頭からの相対位置を示します。ServeContent はコンテンツのサイズを決定するために Seek(0, os.SEEK_END) を使用します。
  5. 時間と日付のフォーマット:
    • time.Time: Go言語における時刻を表す型。
    • time.Format(TimeFormat): 特定のフォーマットで時刻を文字列に変換するメソッド。HTTPの Last-ModifiedIf-Modified-Since ヘッダーで使用される日付フォーマット(RFC1123)に準拠する必要があります。

これらの概念を理解することで、ServeContent がどのようにHTTPの仕様に準拠し、効率的なコンテンツ提供を実現しているかを深く把握できます。

技術的詳細

ServeContent 関数は、HTTPレスポンスライター (http.ResponseWriter)、HTTPリクエスト (*http.Request)、コンテンツ名 (name string)、最終更新時刻 (modtime time.Time)、およびコンテンツ自体 (content io.ReadSeeker) を引数として受け取ります。

その内部動作は以下のステップで構成されます。

  1. コンテンツサイズの取得とシーク位置のリセット:

    • まず、content.Seek(0, os.SEEK_END) を呼び出してコンテンツの末尾にシークし、その戻り値からコンテンツの合計サイズを取得します。これにより、Content-Length ヘッダーや Content-Range ヘッダーを設定するために必要な情報が得られます。
    • 次に、content.Seek(0, os.SEEK_SET) を呼び出してコンテンツの読み取り位置を先頭に戻します。これは、後続の読み取り操作がコンテンツの最初から開始されるようにするためです。
    • シーク操作でエラーが発生した場合、500 Internal Server Error を返します。
  2. If-Modified-Since ヘッダーの処理とキャッシュ制御:

    • checkLastModified ヘルパー関数が呼び出されます。
    • リクエストに If-Modified-Since ヘッダーが含まれており、かつ modtime がそのヘッダーで指定された時刻よりも新しくない場合(つまり、コンテンツが変更されていない場合)、304 Not Modified ステータスコードを返して処理を終了します。これにより、クライアントはキャッシュされたコンテンツを使用できます。
    • コンテンツが変更されている場合、または If-Modified-Since ヘッダーがない場合は、Last-Modified ヘッダーに modtime を設定してレスポンスに含めます。
  3. Content-Type ヘッダーの設定:

    • レスポンスの Content-Type ヘッダーがまだ設定されていない場合、以下のロジックでMIMEタイプを決定します。
      • まず、name 引数(通常はファイル名)の拡張子に基づいて mime.TypeByExtension を使用してMIMEタイプを推測します。
      • 推測できなかった場合、コンテンツの最初の1024バイトを読み取り、http.DetectContentType を使用してMIMEタイプを検出します。この際、コンテンツの読み取り位置は再度先頭に戻されます。
    • 決定されたMIMEタイプがレスポンスの Content-Type ヘッダーに設定されます。
  4. Range リクエストの処理:

    • リクエストに Range ヘッダーが含まれている場合、parseRange 関数(このコミットの差分には含まれていませんが、既存のヘルパー関数)を使用して、要求されたバイト範囲を解析します。
    • 現時点では、単一のバイト範囲のみがサポートされており、複数の範囲が要求された場合はエラー (416 Requested Range Not Satisfiable) を返します。
    • 単一の範囲が有効な場合、content.Seek を使用してその範囲の開始位置にシークし、Content-Range ヘッダーと 206 Partial Content ステータスコードを設定します。送信するデータのサイズも、要求された範囲の長さに調整されます。
    • Accept-Ranges: bytes ヘッダーが常に設定され、サーバーがバイト範囲リクエストをサポートしていることを示します。
    • Content-Length ヘッダーは、送信されるデータの実際のサイズ(全体または部分)に設定されます。
  5. レスポンスの書き込み:

    • 決定されたステータスコード (200 OK または 206 Partial Content) で w.WriteHeader が呼び出されます。
    • リクエストメソッドが HEAD でない場合(つまり GET の場合)、io.CopyN または io.Copy を使用して、content から ResponseWriter へデータがコピーされます。Range リクエストが処理された場合は、io.CopyN で指定されたバイト数のみがコピーされます。

この一連の処理により、ServeContent はHTTPの仕様に厳密に準拠し、キャッシュ、部分コンテンツの取得、MIMEタイプ検出といった複雑な要件を自動的に処理する、堅牢なコンテンツ提供メカニズムを提供します。

コアとなるコードの変更箇所

このコミットにおける主要なコードの変更は、src/pkg/net/http/fs.go ファイルに集中しています。

  1. isText 関数の削除: fs.go から isText 関数が削除されました。この関数は、バイトスライスがUTF-8テキストであるかどうかをヒューリスティックに判断するためのものでしたが、ServeContent の導入により、より汎用的な http.DetectContentType が使用されるようになったため、不要になりました。

    -func isText(b []byte) bool {
    -	for len(b) > 0 && utf8.FullRune(b) {
    -		rune, size := utf8.DecodeRune(b)
    -		if size == 1 && rune == utf8.RuneError {
    -			// decoding error
    -			return false
    -		}
    -		if 0x7F <= rune && rune <= 0x9F {
    -			return false
    -		}
    -		if rune < ' ' {
    -			switch rune {
    -			case '\n', '\r', '\t':
    -				// okay
    -			default:
    -				// binary garbage
    -				return false
    -			}
    -		}
    -		b = b[size:]
    -	}
    -	return true
    -}
    
  2. ServeContent 関数の追加: HTTPレスポンスとして io.ReadSeeker を実装するコンテンツを提供する新しい公開関数 ServeContent が追加されました。

    // ServeContent replies to the request using the content in the
    // provided ReadSeeker.  The main benefit of ServeContent over io.Copy
    // is that it handles Range requests properly, sets the MIME type, and
    // handles If-Modified-Since requests.
    //
    // If the response's Content-Type header is not set, ServeContent
    // first tries to deduce the type from name's file extension and,
    // if that fails, falls back to reading the first block of the content
    // and passing it to DetectContentType.
    // The name is otherwise unused; in particular it can be empty and is
    // never sent in the response.
    //
    // If modtime is not the zero time, ServeContent includes it in a
    // Last-Modified header in the response.  If the request includes an
    // If-Modified-Since header, ServeContent uses modtime to decide
    // whether the content needs to be sent at all.
    //
    // The content's Seek method must work: ServeContent uses
    // a seek to the end of the content to determine its size.
    //
    // Note that *os.File implements the io.ReadSeeker interface.
    func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
        size, err := content.Seek(0, os.SEEK_END)
        if err != nil {
            Error(w, "seeker can't seek", StatusInternalServerError)
            return
        }
        _, err = content.Seek(0, os.SEEK_SET)
        if err != nil {
            Error(w, "seeker can't seek", StatusInternalServerError)
            return
        }
        serveContent(w, req, name, modtime, size, content)
    }
    
  3. serveContent ヘルパー関数の追加: ServeContent から呼び出される内部ヘルパー関数 serveContent が追加されました。この関数が実際のコンテンツ提供ロジックの大部分を担います。

    // if name is empty, filename is unknown. (used for mime type, before sniffing)
    // if modtime.IsZero(), modtime is unknown.
    // content must be seeked to the beginning of the file.
    func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) {
        // ... (詳細なロジックは「技術的詳細」セクションを参照) ...
    }
    
  4. checkLastModified ヘルパー関数の追加: If-Modified-Since ヘッダーと Last-Modified ヘッダーの処理をカプセル化するためのヘルパー関数 checkLastModified が追加されました。

    // modtime is the modification time of the resource to be served, or IsZero().
    // return value is whether this request is now complete.
    func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool {
        if modtime.IsZero() {
            return false
        }
        if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.After(t) {
            w.WriteHeader(StatusNotModified)
            return true
        }
        w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
        return false
    }
    
  5. serveFile 関数のリファクタリング: 既存の serveFile 関数が、新しく追加された serveContentcheckLastModified を利用するように変更されました。これにより、serveFile 内の重複するロジックが削除され、コードが簡潔になりました。

    --- a/src/pkg/net/http/fs.go
    +++ b/src/pkg/net/http/fs.go
    @@ -148,14 +238,11 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
     		}
     	}
     
    -	if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && !d.ModTime().After(t) {
    -		w.WriteHeader(StatusNotModified)
    -		return
    -	}
    -	w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
    -
      	// use contents of index.html for directory, if present
      	if d.IsDir() {
    +		if checkLastModified(w, r, d.ModTime()) {
    +			return
    +		}
      		index := name + indexPage
      		ff, err := fs.Open(index)
      		if err == nil {
    @@ -174,60 +261,7 @@ func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirec
      		return
      	}
      
    -	// serve file
    -	size := d.Size()
    -	code := StatusOK
    -
    -	// If Content-Type isn't set, use the file's extension to find it.
    -	if w.Header().Get("Content-Type") == "" {
    -		ctype := mime.TypeByExtension(filepath.Ext(name))
    -		if ctype == "" {
    -			// read a chunk to decide between utf-8 text and binary
    -			var buf [1024]byte
    -			n, _ := io.ReadFull(f, buf[:])
    -			b := buf[:n]
    -			if isText(b) {
    -				ctype = "text/plain; charset=utf-8"
    -			} else {
    -				// generic binary
    -				ctype = "application/octet-stream"
    -			}
    -			f.Seek(0, os.SEEK_SET) // rewind to output whole file
    -		}
    -		w.Header().Set("Content-Type", ctype)
    -	}
    -
    -	// handle Content-Range header.
    -	// TODO(adg): handle multiple ranges
    -	ranges, err := parseRange(r.Header.Get("Range"), size)
    -	if err == nil && len(ranges) > 1 {
    -		err = errors.New("multiple ranges not supported")
    -	}
    -	if err != nil {
    -		Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
    -		return
    -	}
    -	if len(ranges) == 1 {
    -		ra := ranges[0]
    -		if _, err := f.Seek(ra.start, os.SEEK_SET); err != nil {
    -			Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
    -			return
    -		}
    -		size = ra.length
    -		code = StatusPartialContent
    -		w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, d.Size()))
    -	}
    -
    -	w.Header().Set("Accept-Ranges", "bytes")
    -	if w.Header().Get("Content-Encoding") == "" {
    -		w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
    -	}
    -
    -	w.WriteHeader(code)
    -
    -	if r.Method != "HEAD" {
    -		io.CopyN(w, f, size)
    -	}
    +	serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f)
      }
    
  6. テストファイルの追加と修正: src/pkg/net/http/fs_test.goTestServeContent が追加され、新しい ServeContent 関数の動作が検証されています。また、既存のテストヘルパー関数 getBody も、テスト名引数を追加するように修正されています。

    --- a/src/pkg/net/http/fs_test.go
    +++ b/src/pkg/net/http/fs_test.go
    @@ -306,17 +307,66 @@ func TestServeIndexHtml(t *testing.T) {
     	}
     }
     
    +func TestServeContent(t *testing.T) {
    +	type req struct {
    +		name    string
    +		modtime time.Time
    +		content io.ReadSeeker
    +	}
    +	ch := make(chan req, 1)
    +	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
    +		p := <-ch
    +		ServeContent(w, r, p.name, p.modtime, p.content)
    +	}))
    +	defer ts.Close()
    +
    +	css, err := os.Open("testdata/style.css")
    +	if err != nil {
    +		t.Fatal(err)
    +	}
    +	defer css.Close()
    +
    +	ch <- req{"style.css", time.Time{}, css}
    +	res, err := Get(ts.URL)
    +	if err != nil {
    +		t.Fatal(err)
    +	}
    +	if g, e := res.Header.Get("Content-Type"), "text/css; charset=utf-8"; g != e {
    +		t.Errorf("style.css: content type = %q, want %q", g, e)
    +	}
    +	if g := res.Header.Get("Last-Modified"); g != "" {
    +		t.Errorf("want empty Last-Modified; got %q", g)
    +	}
    +
    +	fi, err := css.Stat()
    +	if err != nil {
    +		t.Fatal(err)
    +	}
    +	ch <- req{"style.html", fi.ModTime(), css}
    +	res, err = Get(ts.URL)
    +	if err != nil {
    +		t.Fatal(err)
    +	}
    +	if g, e := res.Header.Get("Content-Type"), "text/html; charset=utf-8"; g != e {
    +		t.Errorf("style.html: content type = %q, want %q", g, e)
    +	}
    +	if g := res.Header.Get("Last-Modified"); g == "" {
    +		t.Errorf("want non-empty last-modified")
    +	}
    +}
    +
    -func getBody(t *testing.T, req Request) (*Response, []byte) {
    +func getBody(t *testing.T, testName string, req Request) (*Response, []byte) {
     	r, err := DefaultClient.Do(&req)
     	if err != nil {
    -		t.Fatal(req.URL.String(), "send:", err)
    +		t.Fatalf("%s: for URL %q, send error: %v", testName, req.URL.String(), err)
     	}
     	b, err := ioutil.ReadAll(r.Body)
     	if err != nil {
    -		t.Fatal("reading Body:", err)
    +		t.Fatalf("%s: for URL %q, reading body: %v", testName, req.URL.String(), err)
     	}
     	return r, b
     }
    

これらの変更により、net/http パッケージはより強力で柔軟なコンテンツ提供機能を持つようになりました。

コアとなるコードの解説

このコミットのコアとなるコードは、ServeContent 関数とその内部で呼び出される serveContent ヘルパー関数です。

ServeContent 関数

func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
    // 1. コンテンツの合計サイズを取得するために、末尾にシーク
    size, err := content.Seek(0, os.SEEK_END)
    if err != nil {
        Error(w, "seeker can't seek", StatusInternalServerError)
        return
    }
    // 2. 読み取り位置を先頭に戻す
    _, err = content.Seek(0, os.SEEK_SET)
    if err != nil {
        Error(w, "seeker can't seek", StatusInternalServerError)
        return
    }
    // 3. 実際のコンテンツ提供ロジックを serveContent ヘルパー関数に委譲
    serveContent(w, req, name, modtime, size, content)
}
  • 目的: ServeContent は、外部から呼び出される主要なAPIです。io.ReadSeeker インターフェースを実装する任意のコンテンツ(例: *os.File)をHTTPレスポンスとして提供します。
  • io.ReadSeeker の要件: この関数は、content 引数が Seek メソッドを正しく実装していることを前提としています。これは、コンテンツの合計サイズを決定するため(Seek(0, os.SEEK_END))と、部分的なコンテンツ提供のために読み取り位置を移動するため(Seek(offset, os.SEEK_SET))に不可欠です。
  • エラーハンドリング: Seek 操作が失敗した場合、500 Internal Server Error をクライアントに返します。
  • 委譲: 実際の複雑なロジックは、内部ヘルパー関数である serveContent に委譲されています。これにより、APIの公開インターフェースをシンプルに保ちつつ、内部実装の柔軟性を高めています。

serveContent 関数

func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) {
    // 1. If-Modified-Since ヘッダーのチェックとキャッシュ制御
    if checkLastModified(w, r, modtime) {
        return // コンテンツが変更されていない場合、304を返して終了
    }

    code := StatusOK // デフォルトのステータスコードは200 OK

    // 2. Content-Type ヘッダーの設定
    if w.Header().Get("Content-Type") == "" {
        ctype := mime.TypeByExtension(filepath.Ext(name)) // 拡張子から推測
        if ctype == "" {
            // 推測できなかった場合、コンテンツの最初のブロックを読み取り DetectContentType で検出
            var buf [1024]byte
            n, _ := io.ReadFull(content, buf[:])
            b := buf[:n]
            ctype = DetectContentType(b)
            _, err := content.Seek(0, os.SEEK_SET) // 読み取り位置を先頭に戻す
            if err != nil {
                Error(w, "seeker can't seek", StatusInternalServerError)
                return
            }
        }
        w.Header().Set("Content-Type", ctype)
    }

    // 3. Range リクエストの処理
    sendSize := size // 送信するデータの初期サイズはコンテンツ全体
    if size >= 0 { // サイズが不明でない場合のみRange処理を行う
        ranges, err := parseRange(r.Header.Get("Range"), size)
        if err == nil && len(ranges) > 1 {
            err = errors.New("multiple ranges not supported") // 複数範囲は未サポート
        }
        if err != nil {
            Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) // Rangeヘッダーが無効な場合
            return
        }
        if len(ranges) == 1 { // 単一のRangeリクエストの場合
            ra := ranges[0]
            if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { // 要求された開始位置にシーク
                Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
                return
            }
            sendSize = ra.length // 送信するサイズをRangeの長さに設定
            code = StatusPartialContent // ステータスコードを206 Partial Content に変更
            w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, size))
        }

        w.Header().Set("Accept-Ranges", "bytes") // バイト範囲リクエストをサポートすることを示す
        if w.Header().Get("Content-Encoding") == "" {
            w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) // 送信するデータの長さを設定
        }
    }

    // 4. ヘッダーを書き込み、レスポンスボディをコピー
    w.WriteHeader(code) // ステータスコードを書き込む

    if r.Method != "HEAD" { // HEADリクエストの場合はボディを送信しない
        if sendSize == -1 { // サイズが不明な場合(通常は発生しないが念のため)
            io.Copy(w, content)
        } else {
            io.CopyN(w, content, sendSize) // 指定されたバイト数だけコピー
        }
    }
}
  • checkLastModified の利用: キャッシュ制御ロジックを checkLastModified に委譲し、コードの重複を避けています。
  • MIMEタイプ検出の優先順位:
    1. 既存の Content-Type ヘッダーが設定されていればそれを使用。
    2. name の拡張子から mime.TypeByExtension で推測。
    3. それでも不明な場合、コンテンツの最初の1024バイトを DetectContentType で分析。
  • Range リクエストの堅牢な処理:
    • parseRange を使用して Range ヘッダーを解析。
    • 現時点では単一の範囲のみをサポートし、複数範囲はエラーとする。
    • 要求された範囲に基づいて Content-Range ヘッダーを設定し、206 Partial Content ステータスコードを返す。
    • Accept-Ranges: bytes を設定し、クライアントにバイト範囲リクエストのサポートを通知。
    • Content-Length を送信するデータの実際の長さに設定。
  • HEAD メソッドのサポート: HEAD リクエストの場合、ボディは送信せず、ヘッダーのみを送信します。これは、リソースのメタデータのみを取得したい場合に効率的です。
  • io.CopyN による効率的なデータ転送: Range リクエストや Content-Length が設定されている場合、io.CopyN を使用して必要なバイト数だけを効率的にコピーします。これにより、不要なデータ転送を防ぎます。

これらの関数は連携して、HTTPの複雑な仕様に準拠しつつ、Go言語のシンプルで効率的なI/Oインターフェースを活用して、汎用的なコンテンツ提供機能を実現しています。

関連リンク

参考にした情報源リンク