[インデックス 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/template
が text/template
のエラーハンドリング機能を活用することで、コードの重複を避け、よりシンプルで一貫性のあるエラー処理を実現するという意図があります。Mike Samuel氏からの簡素化の提案がこの変更のきっかけとなりました。
前提知識の解説
Go言語のテンプレートパッケージ (text/template
と html/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/template
の t.text.Lookup(name)
(元のテキストテンプレート) の存在状態が一致しない場合に panic
を発生させるためのものです。これは、html/template
と text/template
の間でテンプレートの同期が取れていないという、内部的な整合性エラーを検出するためのアサーションとして機能します。
そして、テンプレートの実行は、常に t.text.ExecuteTemplate(wr, name, data)
に委ねられるようになりました。
// 変更後
return t.text.ExecuteTemplate(wr, name, data)
これにより、指定された name
のテンプレートが見つからない場合のエラーは、html/template
自身が生成するのではなく、text/template
の ExecuteTemplate
メソッドが生成するエラーがそのまま返されるようになります。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/template
は text/template
のエラーハンドリング機能をより直接的に利用するようになり、コードの簡素化と内部的な整合性の向上が図られています。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/5437147
参考にした情報源リンク
- Go html/template ExecuteTemplate error handling no template: