Skip to content

Instantly share code, notes, and snippets.

@ohtsuchi
Last active May 5, 2020 01:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ohtsuchi/2820f9633ceca431e53fdc7ef129bf3e to your computer and use it in GitHub Desktop.
Save ohtsuchi/2820f9633ceca431e53fdc7ef129bf3e to your computer and use it in GitHub Desktop.
Goプログラミング実践入門 輪読会 第2回(2020/03/18) 第5章 発表メモ

Goプログラミング実践入門 輪読会 第2回(2020/03/18) 第5章 発表メモ

connpass

Goプログラミング実践入門 輪読会 (初心者歓迎!)

第5章 の発表者

  • @ohtsuchi
  • 経歴: フリーランス の Java サーバサイド 開発者
    • Go言語の実務経験は無し...
      • Go 詳しい方、教えて下さい...

書籍の公式サイト

正誤表

ソースコード

本をお読みでない方もダウンロードしてお試しいただけます

 


前準備

任意のディレクトリに clone

git clone https://github.com/mushahiroyuki/gowebprog.git

移動

cd gowebprog

go mod init

go mod init github.com/mushahiroyuki/gowebprog

以下、箇条書き... (詳細は書籍を参照)

(P123) 5.1 テンプレートとテンプレートエンジン

図5.1

  • Goのテンプレートエンジン (text/template, html/template)

(P124) 5.2 Goのテンプレートエンジン

  • アクション {{ . }}
  • 2段階
    • 解析
    • 実行

(P126) リスト5.2

ソース: ch05/02trigger_template

  • 解析
    • この章の例では error (2番目の戻り値) は無視 (_)
  • 実行
    • "Hello World!"{{ . }}
cd ch05/02trigger_template
go run server.go

確認: http://127.0.0.1:8080/process


(P127) 5.2.1 テンプレートの解析

  • (P127) 複数のファイル の 解析
  • (P128) Glob ワイルドカード 指定
  • (P128) 文字列 の 解析

(P129) Must

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-nil error 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/*"))
}

(P129) 5.2.2 テンプレートの実行

  • 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 は一旦飛ばして、 以下を軽く予習


(P129) 5.3 アクション

(P130) 5.3.1 条件アクション if

(P131) リスト5.3 リスト5.4

ソース: ch05/03random_number

cd ch05/03random_number
go run server.go
{{ if . }}

(追記) 補足: binary comparison operators

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 }} では駄目)

(追記) 補足: 引数 の true/false の扱い

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{} = 1t.Execute(w, i){{ if . }}

(P132) 5.3.2 イテレータアクション range

(P132) リスト5.5 (P133) リスト5.6

ソース: ch05/05iterator

cd ch05/05iterator
go run server.go
go run server2.go
{{ range . }}

(P134) 5.3.3 代入アクション with

(P134) リスト5.7 (P135) リスト5.8

ソース: ch05/07set_dot

cd ch05/07set_dot
go run server.go
go run server2.go
{{ with "world"}}

(P136) 5.3.4 インクルードアクション template

(P136) リスト5.9 (P137) リスト5.10 リスト5.11

ソース: ch05/11include

cd ch05/11include
go run server.go
{{ template "t2.html" }}

(P138) リスト5.12

ソース: ch05/125include2

cd ch05/125include2
go run server.go
{{ template "t2.html" . }}

(P139) 5.4 引数、変数($)、パイプライン(|)

  • 変数($で始まる名前)
  • パイプライン(|)

(P140) リスト5.13

ソース: ch05/13pipeline

cd ch05/13pipeline
go run server.go
{{ 12.3456 | printf "%.2f" }}

以下のように変更しても同じ結果

{{ printf "%.2f" 12.3456 }}

(P141) 5.5 関数 FuncMap, Funcs

(P141) リスト5.14 (P143) リスト5.15

ソース: ch05/14custom_function

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>

(追記) 補足: (P143) リスト5.15 errata

注意: 書籍には以下のように記述されていますが誤植です → マーリンアームズ株式会社 正誤表 参照

<div>日付は {{ . | fdate . }} です</div>

更に注意: インプレスブックス 正誤表 の方は 正誤表の内容自体が間違って ます

143ページ リスト5.16の下から3行目(p.143の末尾)
[誤]

日付は{{ fdate . }}です
[正]
日付は{{ 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 . . }}) しようとして、エラーになっているため】と思われます


(P144) 5.6 コンテキスト依存処理

(P144) リスト5.17 (P145) リスト5.18

ソース: ch05/17context_aware

cd ch05/17context_aware
go run server.go

例) :, (半角空白), <, >, ", ' の結果

:   (半角空白) <     >     "      '
:   (半角空白) &lt;  &gt;  &#34;  &#39;            (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


(P151) 5.7 テンプレートの入れ子

(P153) リスト5.24 (P154) リスト5.25

ソース: 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", "")

(P155) リスト5.26 リスト5.27 リスト5.28

ソース: ch05/28nested2


(P156) 5.8 ブロックアクションによるデフォルトテンプレートの定義

(P155) リスト5.29 (P157) リスト5.30

ソース: 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 が悲鳴...
  • 5.7 テンプレートの入れ子 の例では
    • ExecuteTemplate 実行時 に 「 exceeded maximum template depth (100000) 」という error が発生
      • ParseFiles 実行時 は error は発生しない
    • レスポンス は status code = 200 で応答
      • 応答は終了
      • 大量の Hello World! が 表示
      • → 恐らく 100000 回 繰り返し 表示 になっている

server側 検証コード:

-	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);
+	}
-	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 が ハードコードされているので 設定変更は 無理 ??


(追記) 補足: space の trim {{- , -}}

書籍には載っていませんが template - GoDocText 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 to fmt.Printf.

printf "%s" .Body 命令 は byte ストリーム ではなく string として .Body を出力する 関数呼び出しです。
fmt.Printf (正: fmt.Sprintf) の呼び出しと同じように

参考サイト その他


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment