Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

Go プログラミング実践入門を読みながら Go での Web App「テンプレート」

$
0
0

Go 言語って標準ライブラリにテンプレートまであるのか。便利。

ということで使ってみましょう。

使い方は簡単。template.Must(template.ParseFiles("templateFilePath1", "templateFilePath2", ...) みたいにしてテンプレートをパースする。パースしたら ExecuteTemplate メソッドで出力先とテンプレート名とテンプレートに渡す値を指定して完成。

package main

import (
    "html/template""net/http"
)

type pageData struct {
    Title   string
    Message string
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{Title: "Template sample title", Message: "Template sample message"})
    })

    http.ListenAndServe("localhost:8080", mux)
}

テンプレートは以下のような感じになります。

まずは templates/index.html

{{ define "index" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p></body></html>
{{ end }}

templates/test.html はこんな感じ。

{{ define "test" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Test</title></head><bodystyle="background-color: yellow"><h1>{{ .Title }}</h1><p>{{ .Message }}</p></body></html>
{{ end }}

基本的には {{ }}でくくった中に何か書く。先頭は define でレイアウト名(ExecuteTemplate で指定する名前)を指定して最後に end でおしまい。

{{ .プロパティ名 }} とかで ExecuteTemplate で渡されたデータにアクセスできる感じっぽい。

動かしてみるとちゃんと動いた。

f:id:okazuki:20181122165325p:plain

今回のプログラムは URL のパラメータでテンプレート名指定するからこんな感じでも動く。

f:id:okazuki:20181122165403p:plain

{{ range .xxx }} でループも行ける。テンプレートをいじって

{{ define "index" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p><hr/><ul>
            {{ range .Records }}
            <li>{{ .Name }}</li>
            {{ end }}
        </ul></body></html>
{{ end }}

main.go もそれにあわせて変更。

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message string
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: "Template sample message",
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

いい感じに動く。

f:id:okazuki:20181122171033p:plain

さらに if やカスタムの関数とかも定義出来るみたい。今回は偶数番目のデータと奇数番目のデータで色を変えたかったので、mod という関数を追加したうえでパースして実行してみました。

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message string
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.New("").Funcs(template.FuncMap{
        "mod": func(x int, y int) int {
            return x % y
        },
    }).ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: "Template sample message",
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

テンプレートはこんな感じになりました。

{{ define "index" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p><hr/><ul>
            {{ range $index, $record := .Records }}
            {{ $isEvenRow := eq (mod $index 2) 0 }}
            <listyle="color: {{ if $isEvenRow}} red {{ else }} black {{ end }}">{{ $index }}{{ $record.Name }}</li>
            {{ end }}
        </ul></body></html>
{{ end }}

if や range のインデックスの取得方法と関数呼び出しと盛りだくさん。実行するとこんな感じです。

f:id:okazuki:20181122173525p:plain

あとはサニタイズとかもあるみたいですね。普通に Go 側とこういう風に書くと…

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message string
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.New("").Funcs(template.FuncMap{
        "mod": func(x int, y int) int {
            return x % y
        },
    }).ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: "<script type='text/javascript`>alert('Template sample message')</script>", // !?
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

素晴らしい。

f:id:okazuki:20181122173712p:plain

あえてエスケープしたくないときは template.HTML を使う。まぁレアケースだけどマークダウンエディターみたいなものを作りたいときとかは必要。

こんな感じで

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message template.HTML
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.New("").Funcs(template.FuncMap{
        "mod": func(x int, y int) int {
            return x % y
        },
    }).ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: template.HTML("<script type='text/javascript'>alert('Template sample message')</script>"),
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

実行するとこうなる。ばっちり。

f:id:okazuki:20181122174547p:plain


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>