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

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

このコミットは、Go言語の標準ライブラリ html/template パッケージにおける ExecuteTemplate メソッドの内部的なエラーハンドリングロジックを簡素化するものです。具体的には、指定されたテンプレート名に対応するテンプレートが存在しない場合のエラー処理を、text/template パッケージの機能に委ねることで、コードの重複を排除し、より一貫性のある動作を目指しています。

コミット

  • コミットハッシュ: ee8b597b1f1ffa634189cdd8ab23f976f65dab7f
  • Author: Rob Pike r@golang.org
  • Date: Tue Dec 6 12:47:12 2011 -0800
  • コミットメッセージ:
    html/template: simplify ExecuteTemplate a little
    Allow the text template to handle the error case of no template
    with the given name.
    Simplification suggested by Mike Samuel.
    
    R=mikesamuel
    CC=golang-dev
    https://golang.org/cl/5437147
    

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

https://github.com/golang/go/commit/ee8b597b1f1ffa634189cdd8ab23f976f65dab7f

元コミット内容

html/template: simplify ExecuteTemplate a little
Allow the text template to handle the error case of no template
with the given name.
Simplification suggested by Mike Samuel.

R=mikesamuel
CC=golang-dev
https://golang.org/cl/5437147

変更の背景

html/template パッケージは、Go言語におけるHTMLテンプレートのレンダリングを安全に行うためのパッケージです。このパッケージは、クロスサイトスクリプティング(XSS)などの脆弱性からアプリケーションを保護するために、自動エスケープ機能を提供します。

ExecuteTemplate メソッドは、特定の名前を持つテンプレートをデータに適用し、その結果を io.Writer に書き出す役割を担っています。このコミット以前は、ExecuteTemplate メソッド内で、指定された名前のテンプレートが見つからない場合に fmt.Errorf を使って独自のエラーを生成していました。

しかし、html/template は内部的に text/template パッケージを利用しており、text/template も同様にテンプレートが見つからない場合のエラーハンドリング機能を持っています。このコミットの背景には、html/templatetext/template のエラーハンドリング機能を活用することで、コードの重複を避け、よりシンプルで一貫性のあるエラー処理を実現するという意図があります。Mike Samuel氏からの簡素化の提案がこの変更のきっかけとなりました。

前提知識の解説

Go言語のテンプレートパッケージ (text/templatehtml/template)

Go言語には、テキストベースのテンプレートを扱うための text/template パッケージと、HTMLコンテンツを安全に生成するための html/template パッケージがあります。

  • text/template: 任意のテキスト形式の出力を生成するための汎用的なテンプレートエンジンです。プレースホルダーや条件分岐、繰り返しなどの基本的なテンプレート機能を提供します。
  • html/template: text/template をベースにしており、HTMLコンテンツを生成する際に自動的にエスケープ処理を行うことで、XSS攻撃などのセキュリティ脆弱性を防ぎます。Webアプリケーションでユーザーからの入力を表示する際などに非常に重要です。

Template 構造体と ExecuteTemplate メソッド

html/template パッケージの Template 構造体は、一つまたは複数のテンプレートを保持します。ExecuteTemplate メソッドは、Template 構造体に関連付けられた、特定の名前を持つテンプレートを実行するために使用されます。

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

このメソッドは、name で指定されたテンプレートを data を使って実行し、結果を wr に書き込みます。テンプレートが見つからない場合や、実行中にエラーが発生した場合は error を返します。

エラーハンドリングの重要性

テンプレートエンジンにおいて、指定されたテンプレートが見つからないという状況は頻繁に発生し得ます。このような場合、適切なエラーメッセージを生成し、アプリケーションが予期せぬ動作をしないようにエラーを適切に処理することが重要です。以前の html/template では、この「テンプレートが見つからない」というエラーケースを独自に処理していましたが、このコミットにより、その処理が text/template に委譲されることになります。

技術的詳細

このコミットの主要な変更点は、html/template パッケージの Template 型の ExecuteTemplate メソッドにおけるテンプレートの存在チェックとエラーハンドリングロジックの変更です。

変更前は、t.set[name] でテンプレートをルックアップし、tmpl == nil であれば、その場で fmt.Errorf を使って「指定された名前のテンプレートが見つからない」というエラーを生成していました。

// 変更前
if tmpl == nil {
    t.nameSpace.mu.Unlock()
    return fmt.Errorf("template: no template %q associated with template %q", name, t.Name())
}

変更後は、この明示的なエラー生成が削除され、代わりに以下の条件式が追加されました。

// 変更後
if (tmpl == nil) != (t.text.Lookup(name) == nil) {
    panic("html/template internal error: template escaping out of sync")
}

この新しい条件式は、html/template 内部の tmpl (HTMLエスケープ処理済みのテンプレート) の存在状態と、基盤となる text/templatet.text.Lookup(name) (元のテキストテンプレート) の存在状態が一致しない場合に panic を発生させるためのものです。これは、html/templatetext/template の間でテンプレートの同期が取れていないという、内部的な整合性エラーを検出するためのアサーションとして機能します。

そして、テンプレートの実行は、常に t.text.ExecuteTemplate(wr, name, data) に委ねられるようになりました。

// 変更後
return t.text.ExecuteTemplate(wr, name, data)

これにより、指定された name のテンプレートが見つからない場合のエラーは、html/template 自身が生成するのではなく、text/templateExecuteTemplate メソッドが生成するエラーがそのまま返されるようになります。text/template は、テンプレートが見つからない場合に "template: no such template %q" のようなエラーを返すため、この変更によってエラーメッセージの形式が変わる可能性がありますが、機能的には一貫性が保たれます。

また、Clone メソッドの未実装エラーメッセージも "html/template: Add unimplemented" から "html/template: Clone unimplemented" に修正されています。これは直接的な機能変更ではありませんが、より正確なエラーメッセージを提供するための改善です。

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

--- a/src/pkg/html/template/template.go
+++ b/src/pkg/html/template/template.go
@@ -47,23 +47,22 @@ func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
 	return t.text.Execute(wr, data)
 }

-// ExecuteTemplate applies the template associated with t that has the given name
-// to the specified data object and writes the output to wr.
+// ExecuteTemplate applies the template associated with t that has the given
+// name to the specified data object and writes the output to wr.
 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) (err error) {
 	t.nameSpace.mu.Lock()
 	tmpl := t.set[name]
-	if tmpl == nil {
-		t.nameSpace.mu.Unlock()
-		return fmt.Errorf("template: no template %q associated with template %q", name, t.Name())
+	if (tmpl == nil) != (t.text.Lookup(name) == nil) {
+		panic("html/template internal error: template escaping out of sync")
 	}
-	if !tmpl.escaped {
+	if tmpl != nil && !tmpl.escaped {
 		err = escapeTemplates(tmpl, name)
 	}
 	t.nameSpace.mu.Unlock()
 	if err != nil {
 		return
 	}
-	return tmpl.text.ExecuteTemplate(wr, name, data)
+	return t.text.ExecuteTemplate(wr, name, data)
 }

 // Parse parses a string into a template. Nested template definitions
@@ -106,7 +105,7 @@ func (t *Template) AddParseTree(name string, tree *parse.Tree) error {

 // Clone is unimplemented.
 func (t *Template) Clone(name string) error {
-	return fmt.Errorf("html/template: Add unimplemented")
+	return fmt.Errorf("html/template: Clone unimplemented")
 }

 // New allocates a new HTML template with the given name.

コアとなるコードの解説

ExecuteTemplate メソッドの変更

  • 削除されたコード:

    if tmpl == nil {
        t.nameSpace.mu.Unlock()
        return fmt.Errorf("template: no template %q associated with template %q", name, t.Name())
    }
    

    このコードブロックは、指定された name のテンプレートが html/template の内部マップ t.set に存在しない場合に、独自のエラーメッセージを生成して返していました。このコミットにより、この明示的なエラーチェックと生成が削除されました。

  • 追加されたコード:

    if (tmpl == nil) != (t.text.Lookup(name) == nil) {
        panic("html/template internal error: template escaping out of sync")
    }
    

    この行は、html/template が管理するエスケープ済みテンプレート (tmpl) の存在状態と、基盤となる text/template が管理する元のテンプレート (t.text.Lookup(name)) の存在状態が一致しない場合に panic を発生させます。これは、html/template の内部的な整合性を保証するためのアサーションであり、通常の使用では発生しないはずの内部エラーを検出するためのものです。

  • 変更されたテンプレート実行の委譲:

    // 変更前: return tmpl.text.ExecuteTemplate(wr, name, data)
    // 変更後: return t.text.ExecuteTemplate(wr, name, data)
    

    以前は、tmpl (エスケープ済みテンプレート) の text フィールドを通じて ExecuteTemplate を呼び出していましたが、変更後は t (現在の html/template インスタンス) の text フィールドを通じて ExecuteTemplate を呼び出すようになりました。これにより、テンプレートが見つからない場合のエラー処理が text/template に完全に委譲され、html/template 側で特別なエラーハンドリングを行う必要がなくなりました。

Clone メソッドのエラーメッセージの変更

  • 変更前: return fmt.Errorf("html/template: Add unimplemented")
  • 変更後: return fmt.Errorf("html/template: Clone unimplemented") これは小さな修正ですが、Clone メソッドが未実装であることを示すエラーメッセージが、より正確にそのメソッド名 (Clone) を参照するように変更されました。

これらの変更により、html/templatetext/template のエラーハンドリング機能をより直接的に利用するようになり、コードの簡素化と内部的な整合性の向上が図られています。

関連リンク

参考にした情報源リンク