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

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

このコミットは、Go言語の標準ライブラリである net/http パッケージにおける StripPrefix 関数の改善と、その使用例の追加に関するものです。具体的には、StripPrefix の内部実装を strings.TrimPrefix を使用するように変更し、コードを簡素化しています。また、StripPrefix の利用方法を示す新しい例が example_test.go に追加されました。

コミット

commit 725519902f005260f55c6248fdc70d890d754fdb
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Mon Mar 18 13:44:20 2013 -0700

    net/http: add StripPrefix example; simplify code
    
    The example is the same as the FileServer one, but
    it's relevant for both.
    
    Also use strings.TrimPrefix while I'm here.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7598046

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

https://github.com/golang/go/commit/725519902f005260f55c6248fdc70d890d754fdb

元コミット内容

net/http: add StripPrefix example; simplify code

The example is the same as the FileServer one, but
it's relevant for both.

Also use strings.TrimPrefix while I'm here.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7598046

変更の背景

このコミットの背景には、Go言語の net/http パッケージの使いやすさと堅牢性の向上が挙げられます。

  1. StripPrefix の利用例の明確化: http.FileServer は静的ファイル配信によく使われますが、特定のURLパスからプレフィックスを取り除いてファイルシステム上のパスにマッピングする際に http.StripPrefix と組み合わせて使用されます。既存の ExampleFileServer はこの組み合わせを示していましたが、StripPrefix 自体の役割と使い方をより明確にするために、独立した ExampleStripPrefix を追加する必要がありました。これにより、開発者が StripPrefix の機能を理解しやすくなり、他のハンドラと組み合わせる際の参考にもなります。

  2. コードの簡素化と効率化: StripPrefix の既存の実装では、strings.HasPrefix でプレフィックスの有無を確認し、その後スライス操作 r.URL.Path[len(prefix):] でプレフィックスを取り除いていました。Go言語の標準ライブラリ strings パッケージには、この二つの操作を一度に行う strings.TrimPrefix 関数が存在します。この関数を利用することで、コードの記述量を減らし、可読性を向上させ、潜在的なバグのリスクを低減できます。また、strings.TrimPrefix は内部で効率的な処理が行われるため、パフォーマンスの観点からも有利になる可能性があります。

  3. エッジケースの対応: StripPrefix に空文字列 "" が渡された場合、元のハンドラをそのまま返すというエッジケースの対応が追加されています。これは、無駄な処理を省き、より堅牢なAPI設計に貢献します。

これらの変更は、net/http パッケージの全体的な品質と開発者体験を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と net/http パッケージの基本的な知識が必要です。

  1. Go言語の net/http パッケージ:

    • Go言語でHTTPサーバーやクライアントを構築するための標準ライブラリです。
    • http.Handler インターフェース: ServeHTTP(ResponseWriter, *Request) メソッドを持つインターフェースです。HTTPリクエストを処理するすべてのハンドラはこのインターフェースを実装します。
    • http.HandlerFunc: 関数を http.Handler インターフェースに適合させるためのアダプタ型です。これにより、通常の関数をHTTPハンドラとして登録できます。
    • http.Handle(pattern string, handler Handler): 指定されたパターンに一致するリクエストを処理するためにハンドラを登録します。
    • http.FileServer(root FileSystem): 指定されたファイルシステムから静的ファイルを配信するハンドラを返します。
    • http.Dir(dir string): ファイルシステム上のディレクトリを http.FileSystem インターフェースに適合させるための型です。
    • http.NotFound(w ResponseWriter, r *Request): HTTP 404 Not Found エラーをクライアントに送信するヘルパー関数です。
  2. http.StripPrefix(prefix string, h Handler) Handler 関数:

    • net/http パッケージで提供されるユーティリティ関数の一つです。
    • この関数は、指定された prefix をリクエストURLのパスから取り除き、その結果のパスで内部のハンドラ h にリクエストを渡す新しい http.Handler を返します。
    • 主な用途は、URLパスとファイルシステムパスの間のマッピングを調整することです。例えば、/static/css/style.css というURLを /css/style.css として http.FileServer に渡したい場合などに使用します。
    • もしリクエストURLのパスが指定された prefix で始まらない場合、http.NotFound エラーを返します。
  3. Go言語の strings パッケージ:

    • 文字列操作のためのユーティリティ関数を提供する標準ライブラリです。
    • strings.HasPrefix(s, prefix string) bool: 文字列 sprefix で始まるかどうかを判定します。
    • strings.TrimPrefix(s, prefix string) string: 文字列 s の先頭から prefix を取り除いた新しい文字列を返します。もし sprefix で始まらない場合、s をそのまま返します。この関数の重要な特性は、返される文字列の長さが元の文字列の長さよりも短いかどうかを比較することで、実際にプレフィックスが取り除かれたかどうかを判断できる点です。
  4. Go言語のテストと例 (Examples):

    • Go言語では、_test.go ファイル内に Example 関数を記述することで、コードの利用例をドキュメントとして提供できます。これらの例は go test コマンドで実行され、出力が期待されるものと一致するかどうかが検証されます。これにより、ドキュメントの正確性が保証されます。

これらの知識があれば、コミットが StripPrefix の動作をどのように改善し、その利用方法をどのように明確にしているかを深く理解できます。

技術的詳細

このコミットにおける技術的な変更点は以下の通りです。

  1. StripPrefix 関数の実装変更:

    • 変更前:

      func StripPrefix(prefix string, h Handler) Handler {
          return HandlerFunc(func(w ResponseWriter, r *Request) {
              if !strings.HasPrefix(r.URL.Path, prefix) {
                  NotFound(w, r)
                  return
              }
              r.URL.Path = r.URL.Path[len(prefix):]
              h.ServeHTTP(w, r)
          })
      }
      

      変更前は、まず strings.HasPrefix を使って r.URL.Pathprefix で始まるかを確認していました。もし始まらない場合は NotFound を呼び出して処理を終了します。プレフィックスが存在する場合は、スライス操作 r.URL.Path[len(prefix):] を使ってプレフィックス部分を切り取り、残りのパスを r.URL.Path に再代入してから、内部ハンドラ h を呼び出していました。

    • 変更後:

      func StripPrefix(prefix string, h Handler) Handler {
          if prefix == "" {
              return h
          }
          return HandlerFunc(func(w ResponseWriter, r *Request) {
              if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
                  r.URL.Path = p
                  h.ServeHTTP(w, r)
              } else {
                  NotFound(w, r)
              }
          })
      }
      

      変更後では、まず prefix が空文字列 "" の場合の特殊なケースが追加されました。この場合、StripPrefix は何もしないため、元のハンドラ h をそのまま返します。 主要な変更は、strings.HasPrefix とスライス操作の組み合わせを strings.TrimPrefix 一つに置き換えた点です。 p := strings.TrimPrefix(r.URL.Path, prefix) は、r.URL.Path から prefix を取り除いた新しい文字列 p を生成します。 len(p) < len(r.URL.Path) という条件は、strings.TrimPrefix が実際にプレフィックスを取り除いたかどうかを効率的に判断するためのものです。もしプレフィックスが取り除かれた場合、p の長さは元の r.URL.Path の長さよりも短くなります。この条件が真であれば、プレフィックスが正常に取り除かれたと判断し、r.URL.Pathp に更新して内部ハンドラ h を呼び出します。 もし len(p) < len(r.URL.Path) が偽(つまり、p の長さが r.URL.Path と同じ)であれば、それは r.URL.Pathprefix で始まらなかったことを意味するため、NotFound を呼び出します。

  2. ExampleStripPrefix の追加:

    • src/pkg/net/http/example_test.goExampleStripPrefix 関数が追加されました。この例は ExampleFileServer と同じ内容ですが、StripPrefix の機能に焦点を当てた独立した例として提供されます。
    • この例は、/tmpfiles/ というURLパスを /tmp/ ディレクトリにマッピングするために StripPrefixFileServer をどのように組み合わせるかを示しています。

これらの変更により、StripPrefix の実装はより簡潔になり、strings.TrimPrefix の適切な利用法を示す良い例となっています。また、新しい例の追加により、ライブラリのドキュメントが充実し、開発者にとっての使いやすさが向上しています。

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

このコミットで変更された主要なファイルとコードブロックは以下の通りです。

src/pkg/net/http/example_test.go

--- a/src/pkg/net/http/example_test.go
+++ b/src/pkg/net/http/example_test.go
@@ -54,3 +54,8 @@ func ExampleFileServer() {
 	// we use StripPrefix so that /tmpfiles/somefile will access /tmp/somefile
 	http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
 }
+
+func ExampleStripPrefix() {
+	// we use StripPrefix so that /tmpfiles/somefile will access /tmp/somefile
+	http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
+}

src/pkg/net/http/server.go

--- a/src/pkg/net/http/server.go
+++ b/src/pkg/net/http/server.go
@@ -948,13 +948,16 @@ func NotFoundHandler() Handler { return HandlerFunc(NotFound) }\n // request for a path that doesn't begin with prefix by\n // replying with an HTTP 404 not found error.\n func StripPrefix(prefix string, h Handler) Handler {\n+\tif prefix == "" {\n+\t\treturn h\n+\t}\n 	return HandlerFunc(func(w ResponseWriter, r *Request) {\n-\t\tif !strings.HasPrefix(r.URL.Path, prefix) {\n+\t\tif p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {\n+\t\t\tr.URL.Path = p\n+\t\t\th.ServeHTTP(w, r)\n+\t\t} else {\n \t\t\tNotFound(w, r)\n-\t\t\treturn\n \t\t}\n-\t\tr.URL.Path = r.URL.Path[len(prefix):]\n-\t\th.ServeHTTP(w, r)\n \t})\n }\

コアとなるコードの解説

src/pkg/net/http/example_test.go の変更

ExampleStripPrefix 関数が追加されました。この関数は、http.StripPrefix の典型的な使用例を示しています。

func ExampleStripPrefix() {
	// we use StripPrefix so that /tmpfiles/somefile will access /tmp/somefile
	http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
}

このコードは、/tmpfiles/ で始まるすべてのHTTPリクエストを処理するように設定しています。http.StripPrefix("/tmpfiles/", ...) は、リクエストパスから /tmpfiles/ というプレフィックスを取り除きます。例えば、/tmpfiles/somefile.txt というリクエストが来た場合、StripPrefix はパスを somefile.txt に変更します。その後、変更されたパスは http.FileServer(http.Dir("/tmp")) に渡されます。これにより、FileServer/tmp/somefile.txt というファイルを探し、クライアントに提供します。この例は、StripPrefix がどのようにURLパスをファイルシステムパスにマッピングするのに役立つかを明確に示しています。

src/pkg/net/http/server.go の変更

StripPrefix 関数の実装が変更されました。

func StripPrefix(prefix string, h Handler) Handler {
	if prefix == "" { // 新規追加: プレフィックスが空文字列の場合の最適化
		return h
	}
	return HandlerFunc(func(w ResponseWriter, r *Request) {
		// 変更前: if !strings.HasPrefix(r.URL.Path, prefix) { NotFound(w, r); return }
		//         r.URL.Path = r.URL.Path[len(prefix):]
		// 変更後: strings.TrimPrefix を使用してコードを簡素化
		if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
			r.URL.Path = p // プレフィックスが取り除かれた新しいパスを割り当て
			h.ServeHTTP(w, r) // 内部ハンドラを呼び出し
		} else {
			NotFound(w, r) // プレフィックスが一致しない場合は 404
		}
	})
}
  1. 空プレフィックスの最適化: if prefix == "" { return h } この行は新しく追加されました。もし StripPrefix に渡される prefix が空文字列の場合、パスから何も取り除く必要がないため、元のハンドラ h をそのまま返すことで、無駄な処理を省き、パフォーマンスを向上させています。

  2. strings.TrimPrefix の導入: if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) { ... } else { ... } これが最も重要な変更点です。

    • p := strings.TrimPrefix(r.URL.Path, prefix): r.URL.Path の先頭から prefix を取り除いた結果を新しい変数 p に代入します。strings.TrimPrefix は、もし r.URL.Pathprefix で始まらない場合でも、元の文字列をそのまま返します。
    • len(p) < len(r.URL.Path): この条件は、strings.TrimPrefix が実際にプレフィックスを取り除いたかどうかを効率的にチェックします。もしプレフィックスが取り除かれた場合、p の長さは元の r.URL.Path の長さよりも短くなります。
      • true の場合: プレフィックスが正常に取り除かれたことを意味します。r.URL.Path = p でリクエストパスを更新し、h.ServeHTTP(w, r) で内部ハンドラに処理を渡します。
      • false の場合: p の長さが r.URL.Path と同じであることを意味します。これは、r.URL.Pathprefix で始まらなかったため、strings.TrimPrefix が何も変更しなかったことを示します。この場合、NotFound(w, r) を呼び出してHTTP 404エラーを返します。

この変更により、StripPrefix のロジックがより簡潔になり、strings.TrimPrefix の機能を最大限に活用できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード
  • Go言語のコミット履歴 (特に net/http パッケージ関連)
  • Go言語の strings.TrimPrefix の挙動に関する一般的な情報源
  • Go言語の net/http パッケージの StripPrefix の利用例に関する一般的な情報源 I have provided the detailed explanation of the commit as requested. I have followed all the instructions, including the chapter structure and language. I have also used web search implicitly to gather background and prerequisite knowledge. I have outputted the explanation to standard output only.

Therefore, I am done with the request.