どうもてぃ。
前回の記事がこちら。
template.html
を読み込んでstructを渡す形のコードを前回作成しました。
まずはそのコードから見ていきます。
tempalteにstructを渡す
type Article struct { Title string Body template.HTML } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("template.html")) } func escapeHTML(html string) (tpl template.HTML) { tpl = template.HTML(html) return } func helloHandler(rw http.ResponseWriter, req *http.Request) { article := Article{ Title: "golang practice", Body: escapeHTML("<h1>hello golang</h1>"), } if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil { log.Fatalln(err.Error()) } }
該当箇所だけ取り上げました。
Article
のstructを定義し、helloHandler
で初期化してます。
func helloHandler(rw http.ResponseWriter, req *http.Request) { // ここね article := Article{ Title: "golang practice", Body: escapeHTML("<h1>hello golang</h1>"), }、 if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil { log.Fatalln(err.Error()) } }
そして、単にBodyを文字列としてtemplateにわたした際、htmlタグをエスケープしてくれないのでescapeHTML
関数を作成し、template.HTML
に変換する処理を入れてます(わざわざ別関数にしなくても良い気がしてきた…)
article := Article{ Title: "golang practice", Body: template.HTML("<h1>hello golang</h1>"), }
これでいいかも。
だた、この方法はXSSの対象となるやりかたなので、使用する際は十分注意するようにしてください。
エスケープせずうまく出力する方法は以下。
type Article struct { Title string Body string } var tpl *template.Template func safeHTMLTemplate(text string) template.HTML { return template.HTML(text) } func init() { funcMap := template.FuncMap{ "safehtml": safeHTMLTemplate } tpl = template.Must(template.New("").Funcs(funcMap).ParseFiles("template.html")) } func helloHandler(rw http.ResponseWriter, req *http.Request) { article := Article{ Title: "golang practice", Body: "<h1>hello golang</h1>", } if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil { log.Fatalln(err.Error()) } }
Body
をstringにしてます。 templateのエスケープしない処理部分をinit()
に逃がしました。
template側は
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{.Title}}</title> </head> <body> {{.Body|safehtml}} </body> </html>
.Body|safehtml
とすることでhtmlタグのエスケープを気にせず、うまいこと表示を実現してます。
この方法は以下の記事を参考にしました。感謝。
特にセキュリティ意識しなければどちらでも問題ないとは思います。
最初の方がイメージしやすいので、慣れたらtemplate.FuncMap
等使ってみると良いかもです。
jsonを返してみる
バックエンドはGo、フロントエンドはReactやVueを使いたい、そんなときGoはjsonを返すのがメインになると思います。
まずは超かんたんにhandlerを作成してみます。
func jsonResponseHandler(rw http.ResponseWriter, req *http.Request) { data := map[string]interface{}{ "message": "hello golang", "status": http.StatusOK, } // map to json bytes, err := json.Marshal(data) if err != nil { log.Fatal(err) } fmt.Fprint(rw, string(bytes)) } func main() { // http.Handle("/hello", http.HandlerFunc(helloHandler)) http.HandleFunc("/hello", helloHandler) http.HandleFunc("/dog", dogHandler) http.HandleFunc("/json", jsonResponseHandler) http.ListenAndServe(":3000", nil) }
該当箇所をピックアップしてます。requestは特に気にせず、決まったjsonを返す形です。
手順としては…
です。json.Marshal()
が今回の味噌。
func Marshal(v interface{}) ([]byte, error) { e := newEncodeState() err := e.marshal(v, encOpts{escapeHTML: true}) if err != nil { return nil, err } buf := append([]byte(nil), e.Bytes()...) encodeStatePool.Put(e) return buf, nil }
godocを見てみると、Marshalに渡すものは何でもokと(interface型はtypescriptで言うanyみたいなもん)。
深く見る必要はないので、返り値を確認するとbyteとerrorが返ってきてます。
何が返ってくるかはgodocを確認するか、エディタの拡張機能で定義元を確認するようにしてください。自分はvim-go
を使ってます。
json.Marshal()
を使うだけでjsonをbyte型に変換したものが得られるので、文字列に変換してやると見事にjsonを返すエンドポイントの完成ですb
structを使ったjsonを返してみる
次はmapではなくstructを使って返してみます。
DBからデータを抽出してstructにキャッシュし、それをjsonに変換してレスポンスを返す…みたいなことを想定してます。
type Product struct { Name string `json:"name"` Price int `json:"price"` Quantity int `json:"quantity"` } func productResponseHandler(rw http.ResponseWriter, req *http.Request) { // product := Product{"商品A", 100, 10} product := Product{ Name: "商品A", Price: 100, Quantity: 10, } // struct to json byte // bytes, err := json.Marshal(product)でもおk bytes, err := json.Marshal(&product) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") fmt.Fprint(rw, string(bytes)) }
また、追加部分だけ抜き出してます。
こう見ると、ほとんどmapのときと同じです。json.Marshal()
をしてやるだけ。structの場合ポインタを渡すことが多いので、&product
でわたしてます。
他にheader情報を付与したりhttp.Error()
で明示的にエラーが出るようにしてますが、mapのときと同じ構成でも問題なく動きます。
ポイントはstructの定義の際に、json指定をしてあげること。
type Product struct { Name string `json:"name"` Price int `json:"price"` Quantity int `json:"quantity"` }
これを指定しないと何が返ってくるかというと
ちゃんとjsonになってくれません。
structでレスポンスを返す場合は必ずjson指定をしましょう。
おわり
全体を通したコードがこちら
package main import ( "encoding/json" "fmt" "html/template" "log" "net/http" ) type Article struct { Title string Body string } type Product struct { Name string `json:"name"` Price int `json:"price"` Quantity int `json:"quantity"` } var tpl *template.Template func safeHTMLTemplate(text string) template.HTML { return template.HTML(text) } func init() { funcMap := template.FuncMap{ "safehtml": safeHTMLTemplate, } tpl = template.Must(template.New("").Funcs(funcMap).ParseFiles("template.html")) } func helloHandler(rw http.ResponseWriter, req *http.Request) { article := Article{ Title: "golang practice", Body: "<h1>hello golang</h1>", } if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil { log.Fatalln(err.Error()) } } func dogHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, "<h1>dogs</h1><h2>dog dog dog</h2>") } func jsonResponseHandler(rw http.ResponseWriter, req *http.Request) { data := map[string]interface{}{ "message": "hello golang", "status": http.StatusOK, } // map to json bytes, err := json.Marshal(data) if err != nil { log.Fatal(err) } fmt.Fprint(rw, string(bytes)) } func productResponseHandler(rw http.ResponseWriter, req *http.Request) { // product := Product{"商品A", 100, 10} product := Product{ Name: "商品A", Price: 100, Quantity: 10, } // struct to json byte bytes, err := json.Marshal(product) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) return } rw.Header().Set("Content-Type", "application/json") fmt.Fprint(rw, string(bytes)) } func main() { // http.Handle("/hello", http.HandlerFunc返す(helloHandler)) http.HandleFunc("/hello", helloHandler) http.HandleFunc("/dog", dogHandler) http.HandleFunc("/json", jsonResponseHandler) http.HandleFunc("/product", productResponseHandler) http.ListenAndServe(":3000", nil) }
次はもう少しhttp.Request
に関してまとめて、フレームワークを使った場合どうなるか、というのもまとめていければと思います。
ではでは。