- @ohtsuchi
- 経歴: フリーランス の Java サーバサイド 開発者
- Go言語の実務経験は無し...
- Go 詳しい方、教えて下さい...
- Go言語の実務経験は無し...
本をお読みでない方もダウンロードしてお試しいただけます
任意のディレクトリに clone
git clone https://github.com/mushahiroyuki/gowebprog.git
移動
cd gowebprog
go mod init
go mod init github.com/mushahiroyuki/gowebprog
以下、箇条書き... (詳細は書籍を参照)
- Goのテンプレートエンジン (
text/template
,html/template
)
- アクション
{{ . }}
- 2段階
- 解析
- 実行
- 解析
- この章の例では
error
(2番目の戻り値) は無視 (_
)
- この章の例では
- 実行
"Hello World!"
→{{ . }}
cd ch05/02trigger_template
go run server.go
確認: http://127.0.0.1:8080/process
- (P127) 複数のファイル の 解析
- (P128)
Glob
ワイルドカード 指定 - (P128) 文字列 の 解析
(追記) Must
参考サイト(1): Writing Web Applications - The Go Programming Language
A better approach would be to call
ParseFiles
once at program initialization,
parsing all templates into a single*Template
.より良い方法は、 プログラムの初期化時に
ParseFiles
を一度呼び出して
全ての template を単一の*Template
に Parse することです
var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
The function
template.Must
is a convenience wrapper
that panics when passed a non-nilerror
value,
and otherwise returns the*Template
unaltered.
template.Must
関数 は 便利なラッパーです
nil
以外のerror
値が渡されると panic が発生 します
そうでなければ*Template
(=template.ParseFiles
の 1個目 の 戻り値) を変更せずに 返しますA panic is appropriate here;
if the templates can't be loaded the only sensible thing to do is exit the program.ここでは panic が適切 です
template を読み込めない場合 に 唯一賢明なことはプログラムを終了すること です
(追記) Must
参考サイト(2): Learn and Use Templates in Go - Level Up Coding
It is preferred to do the parsing job inside init function and execution at required places.
init
関数内 で Parse ジョブ を実行し、必要な場所で Execute することをお勧めします
func init() {
// Must is a helper that wraps a function returning
// (*Template, error) and panics if the error is non-nil.
tpl = template.Must(template.ParseGlob("templates/*"))
}
Execute
func (*Template) Execute
template - The Go Programming Language
func (t *Template) Execute(wr io.Writer, data interface{}) error
ExecuteTemplate
func (*Template) ExecuteTemplate
template - The Go Programming Language
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
→ 5.3 は一旦飛ばして、 以下を軽く予習
- (P136) 5.3.4 インクルードアクション
- (P137) リスト5.9
- ch05/11include/server.go
t, _ := template.ParseFiles("t1.html", "t2.html")
t.Execute(w, "Hello World!")
- ch05/11include/t1.html
{{ template "t2.html" }}
- ch05/11include/server.go
- (P137) リスト5.9
- (P151) 5.7 テンプレートの入れ子
- (P153) リスト5.23 リスト5.24
- ch05/25nested1/layout.html
{{ define "layout" }}
{{ template "content" }}
- ch05/25nested1/server.go
t, _ := template.ParseFiles("layout.html")
t.ExecuteTemplate(w, "layout", "")
- ch05/25nested1/layout.html
- (P153) リスト5.23 リスト5.24
ソース: ch05/03random_number
cd ch05/03random_number
go run server.go
{{ if . }}
template - The Go Programming Language
eq
Returns the boolean truth of arg1 == arg2
ne
Returns the boolean truth of arg1 != arg2
lt
Returns the boolean truth of arg1 < arg2
le
Returns the boolean truth of arg1 <= arg2
gt
Returns the boolean truth of arg1 > arg2
ge
Returns the boolean truth of arg1 >= arg2
- (❌)
{{ if 1 == 1 }}
- (⭕)
{{ if eq 1 1 }}
(注意:{{ if 1 eq 1 }}
では駄目)
template - The Go Programming Language
{{if pipeline}} T1 {{end}} If the value of the pipeline is empty, no output is generated; otherwise, T1 is executed. The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero. Dot is unaffected.
- 以下は全て false と同じ扱い
{{ if "" }}
{{ if 0 }}
t.Execute(w, []int{})
→{{ if . }}
var i interface{}
→t.Execute(w, i)
→{{ if . }}
- 以下は全て true と同じ扱い
{{ if "a" }}
{{ if 1 }}
t.Execute(w, []int{1})
→{{ if . }}
var i interface{} = 1
→t.Execute(w, i)
→{{ if . }}
ソース: ch05/05iterator
cd ch05/05iterator
go run server.go
go run server2.go
{{ range . }}
ソース: ch05/07set_dot
cd ch05/07set_dot
go run server.go
go run server2.go
{{ with "world"}}
ソース: ch05/11include
cd ch05/11include
go run server.go
{{ template "t2.html" }}
ソース: ch05/125include2
cd ch05/125include2
go run server.go
{{ template "t2.html" . }}
- 変数(
$
で始まる名前) - パイプライン(
|
)
ソース: ch05/13pipeline
cd ch05/13pipeline
go run server.go
{{ 12.3456 | printf "%.2f" }}
以下のように変更しても同じ結果
{{ printf "%.2f" 12.3456 }}
cd ch05/14custom_function
go run server.go
func formatDate(t time.Time) string {
layout := "2006-01-02"
return t.Format(layout)
}
// 中略
funcMap := template.FuncMap{"fdate": formatDate}
t := template.New("tmpl.html").Funcs(funcMap)
("2006-01-02"
の意味は Goの日付フォーマット 〜<2006年1月2日>の謎〜 - Hack Your Design! 参照 )
<div>日付は {{ . | fdate }} です</div>
以下のように変更しても同じ結果
<div>日付は {{ fdate . }} です</div>
<div>日付は {{ .Format "2006-01-02" }} です</div>
注意: 書籍には以下のように記述されていますが誤植です → マーリンアームズ株式会社 正誤表 参照
<div>日付は {{ . | fdate . }} です</div>
更に注意: インプレスブックス 正誤表 の方は 正誤表の内容自体が間違って ます
143ページ リスト5.16の下から3行目(p.143の末尾)
[誤]日付は{{ fdate . }}です[正]日付は{{ fdate }}です
引数2個の関数 を新たに定義
func formatDate2(t time.Time, t2 time.Time) string {
layout := "2006-01-02"
layout2 := "2006-01-02 15:04:05"
return t.Format(layout) + " : " + t2.Format(layout2)
}
funcMap := template.FuncMap{"fdate": formatDate}
↓
funcMap := template.FuncMap{"fdate": formatDate, "fdate2": formatDate2}
に変更
テンプレートに
<div>日付は {{ . | fdate2 . }} です</div>
を追記
↓
日付は 2020-03-19 : 2020-03-19 17:41:53 です
と表示される
↑
fdate2
関数 (= func formatDate2(t time.Time, t2 time.Time) string
)
は 正しく動作
つまり【 {{ . | fdate . }}
が表示されない原因 】は、
fdate
関数 (= func formatDate(t time.Time) string
) に対して
【 2個の引数を渡して 実行(= {{ fdate . . }}
) しようとして、エラーになっているため】と思われます
ソース: ch05/17context_aware
cd ch05/17context_aware
go run server.go
例) :
,
(半角空白), <
, >
, "
, '
の結果
: (半角空白) < > " '
: (半角空白) < > " ' (html)
: %20 %3c %3e %22 %27 (urlのパス)
%3a %20 %3c %3e %22 %27 (urlのクエリパラメータ)
: (半角空白) \x3c \x3e \x22 \x27 (javascript)
HTML5入門 - HTML - エンティティ(特殊文字)の一覧表 - Webkaru
HTML Codes - Table of ascii characters and symbols
テキスト処理 - JavaScript | MDN
ソース: ch05/25nested1
cd ch05/25nested1
go run server.go
{{ define "layout" }}
<!-- 中略 -->
<body>
{{ template "content" }}
</body>
<!-- 中略 -->
{{ end }}
{{ define "content" }}
Hello World!
{{ end }}
t, _ := template.ParseFiles("layout.html")
t.ExecuteTemplate(w, "layout", "")
ソース: ch05/28nested2
ソース: ch05/29nested3
以下、質問されて試してみた内容。要注意!!
-
5.3.4 インクルードアクション の ch05/11include/t2.html の中で
{{ template "t1.html" }}
を追加
-
5.7 テンプレートの入れ子 ch05/25nested1/layout.html の
{{ define "content" }}
内で{{ template "layout" }}
を追加
結果:
私の環境(go version go1.13.4 darwin/amd64
) では
- 5.3.4 インクルードアクション の例では
- レスポンス は status code = 200 で応答
- 無限ループで永遠に応答が続くような状態 となりました...
- 応答が終了する前に PC が悲鳴...
- レスポンス は status code = 200 で応答
- 5.7 テンプレートの入れ子 の例では
ExecuteTemplate
実行時 に 「exceeded maximum template depth (100000)
」という error が発生ParseFiles
実行時 は error は発生しない
- レスポンス は status code = 200 で応答
- 応答は終了
- 大量の
Hello World!
が 表示 - → 恐らく
100000
回 繰り返し 表示 になっている
server側 検証コード:
- 5.3.4 インクルードアクション の ch05/11include/server.go の差分
- t, _ := template.ParseFiles("t1.html", "t2.html")
+ if err != nil {
+ fmt.Printf("ParseFiles: %v\n", err);
+ return
+ }
- t.Execute(w, "Hello World!")
+ err = t.Execute(w, "Hello World!")
+ if err != nil {
+ fmt.Printf("Execute: %v\n", err);
+ }
- 5.7 テンプレートの入れ子 ch05/25nested1/server.go の差分
- t, _ := template.ParseFiles("layout.html")
+ t, err := template.ParseFiles("layout.html")
+ if err != nil {
+ fmt.Printf("ParseFiles: %v\n", err);
+ return
+ }
- t.ExecuteTemplate(w, "layout", "")
+ err = t.ExecuteTemplate(w, "layout", "")
+ if err != nil {
+ fmt.Printf("ExecuteTemplate: %v\n", err);
+ }
text/template
の実装内容を確認:
-> src/text/template/exec.go - The Go Programming Language
var maxExecDepth = initMaxExecDepth()
// maxExecDepth specifies the maximum stack depth of templates within
// templates. This limit is only practically reached by accidentally
// recursive template invocations. This limit allows us to return
// an error instead of triggering a stack overflow.
func initMaxExecDepth() int {
if runtime.GOARCH == "wasm" {
return 1000
}
return 100000
}
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
// 中略
if s.depth == maxExecDepth {
s.errorf("exceeded maximum template depth (%v)", maxExecDepth)
}
// 中略
}
100000
が ハードコードされているので 設定変更は 無理 ??
書籍には載っていませんが template - GoDoc の Text and spaces
■ template の内容が 以下の場合
<p>
{{- "Hello World!" }}
</p>
<p>
{{ "Hello World!" -}}
</p>
<p>
{{- "Hello World!" -}}
</p>
↓ 結果
<p>Hello World!
</p>
<p>
Hello World!</p>
<p>Hello World!</p>
■ 条件構文で 「改行 + インデント」で記述した場合
<p>
{{ if eq 1 1 }}
{{ "Hello World!" }}
{{ end }}
</p>
<p>
{{- if eq 1 1 -}}
{{ "Hello World!" }}
{{- end -}}
</p>
↓ 結果
<p>
Hello World!
</p>
<p>Hello World!</p>
<title>{{.Title}}</title>
{{range .Items}}<div>{{ . }}</div>{{else}}<div><strong>no rows</strong></div>{{end}}
data := struct {
Title string
Items []string
}{
Title: "My page",
Items: []string{
"My photos",
"My blog",
},
}
err = t.Execute(os.Stdout, data)
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have been pwned')</script>")
type Todo struct {
Title string
Done bool
}
type TodoPageData struct {
PageTitle string
Todos []Todo
}
data := TodoPageData{
PageTitle: "My TODO list",
Todos: []Todo{
{Title: "Task 1", Done: false},
{Title: "Task 2", Done: true},
{Title: "Task 3", Done: true},
},
}
tmpl.Execute(w, data)
<h1>{{.PageTitle}}</h1>
<ul>
{{range .Todos}}
{{if .Done}}
<li class="done">{{.Title}}</li>
{{else}}
<li>{{.Title}}</li>
{{end}}
{{end}}
</ul>
type Page struct {
Title string
Body []byte
}
<form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
func editHandler(w http.ResponseWriter, r *http.Request) {
// 中略
t, _ := template.ParseFiles("edit.html")
t.Execute(w, p)
}
The
printf "%s" .Body
instruction is a function call that outputs.Body
as a string instead of a stream of bytes, the same as a call tofmt.Printf
.
printf "%s" .Body
命令 は byte ストリーム ではなく string として.Body
を出力する 関数呼び出しです。
(正:fmt.Printf
fmt.Sprintf
) の呼び出しと同じように