どうもてぃです。
前回の記事がこちら。
今回はhandlerを作成して、html要素を返してみようと思います。
環境
- Linux Mint 19.3 Cinnamon
- go version go1.14.4 linux/amd64
前回の振り返り
前回と少し違いますが、http.HandlerFunc
を用いた方法です。
package main import ( "fmt" "net/http" ) func ServeHTTP(rw http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/dog": fmt.Fprintf(rw, "dog dog dog") case "/cat": fmt.Fprintf(rw, "cat cat cat") default: fmt.Fprintf(rw, "hello world") } } func main() { http.ListenAndServe(":8080", http.HandlerFunc(ServeHTTP)) }
http.ListenAndServe
の第二引数にHandlerFuncを渡し、リクエストパスに応じて表示するものを変更していました。
http.HandleFuncを使う
今回はhttp.HandleFunc
を使います。前回はhttp.HandlerFunc
です。見間違えないように気をつけてね。
まず/hello
にHandleFuncを設定してみます。
package main import ( "net/http" ) // 使わないので一旦コメントアウト // func ServeHTTP(rw http.ResponseWriter, req *http.Request) { // switch req.URL.Path { // case "/dog": // fmt.Fprintf(rw, "dog dog dog") // case "/cat": // fmt.Fprintf(rw, "cat cat cat") // default: // fmt.Fprintf(rw, "hello world") // } // } func helloHandler(rw http.ResponseWriter, req *http.Request) { } func main() { http.HandleFunc("/hello", helloHandler) http.ListenAndServe(":8080", nil) }
http.HandleFunc
のgodocを見てみるとわかりますが、第一引数はパス、第二引数はhandlerをわたします。
一応参考に引っ張ってくると
// HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. // The documentation for ServeMux explains how patterns are matched. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
http.ResponseWriter
と*http.Request
を引数にもった関数のhandlerを渡せば良いので、helloHandler
のような形で今後も関数を作成していきます。
この状態で前回設定したairを起動し、localhost:8080/hello
アクセスすると何も表示されていないページが出てくると思います。
それ以外のパス(例えばアプリケーションルート)にアクセスすると、404 page not found
が返ってきます。
これは前回の記事にも書いた、http.ListenAndServe
の第二引数にnil
を渡しておくと、設定してないパスにアクセスした際、内部的にNotFound
の関数が返るようになっているためです。
実際にFprintfで表示する
helloHandler
で実際に文字を表示してみます。
前回の記事をやってればだいたいわかります。
package main import ( "fmt" "net/http" ) func helloHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, "<h1>hello golang</h1>") } func main() { http.HandleFunc("/hello", helloHandler) http.ListenAndServe(":8080", nil) }
はい、簡単。
ちなみに、http.HandleFunc("/hello/", helloHandler)
としておくと、localhost:8080/hello/asdfasd/qwerqw/asdfas
とかにアクセスしても<h1>hello golang</h1>
が表示されます。
こんな感じ。
html要素を複数渡す
まずはhelloHandler
に直接書いてみます。
func helloHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>golang practice</title> </head> <body> <h1>hello golang</h1> </body> </html> `) }
文字列が複数行に渡る場合は、バッククォートでかけばおkです。
他にもhandlerを追加してみます。
package main import ( "fmt" "net/http" ) func helloHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>golang practice</title> </head> <body> <h1>hello golang</h1> </body> </html> `) } func dogHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>golang practice</title> </head> <body> <h1>dogs</h1> <h2>dog dog dog</h2> </body> </html> `) } func main() { http.HandleFunc("/hello", helloHandler) http.HandleFunc("/dog", dogHandler) http.ListenAndServe(":8080", nil) }
こんな感じで要素を直接渡すことで、bodyを書き換えたり、titleを書き換えたり出来ます。
html要素を関数化
上記2つのhandlerの場合、bodyしか変更がなく、html要素を毎回書くのはさすがに冗長すぎるので関数化します。
func htmlTemplate(title, body string) string { template := fmt.Sprintf(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>%s</title> </head> <body> %s </body> </html>`, title, body) return template }
関数をもうちょい良い感じに使うと
func htmlTemplate(title, body string) (template string) { template = fmt.Sprintf(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>%s</title> </head> <body> %s </body> </html>`, title, body) return }
こんな感じにかけます。
作成した関数を適応
実際に適応して整理したものがこちら。
package main import ( "fmt" "net/http" ) func helloHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, htmlTemplate("golang practice", "<h1>hello golang</h1>")) } func dogHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, htmlTemplate("golang practice", "<h1>dogs</h1><h2>dog dog dog</h2>")) } func htmlTemplate(title, body string) (template string) { template = fmt.Sprintf(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>%s</title> </head> <body> %s </body> </html>`, title, body) return } func main() { http.HandleFunc("/hello", helloHandler) http.HandleFunc("/dog", dogHandler) http.ListenAndServe(":8080", nil) }
templateファイルを読み込む形へ変更
html要素を関数化しましたが、実際はテンプレートを読み込んで値をわたしてhandlerごとに表示するものを変更するほうがいいですよね。
てなわけで、htmlTemplate
部分を別ファイルにして読み込むにします。
まずはmain.goと同じディレクトリにtemplate.html
を作成して、以下の形で準備します。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{.Title}}</title> </head> <body> {{.Body}} </body> </html>
これをgo側で読み込みます。
とりあえず、読み込み部分だけ書くと
var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("template.html")) }
func init()
はfunc main()
の前に動く初期化処理。
読み込んでキャッシュしたtpl
に対して操作をしていきます。
template.html
に書いてある.Body
と.Title
はstructをtemplate.html
に渡しているので、structを作成する前提で進めます(structについても今度まとめる予定)。
package main import ( "fmt" "html/template" "log" "net/http" ) 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) (tmpl template.HTML) { tmpl = 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()) } } func dogHandler(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, "<h1>dogs</h1><h2>dog dog dog</h2>") } func main() { http.HandleFunc("/hello", helloHandler) http.HandleFunc("/dog", dogHandler) http.ListenAndServe(":3000", nil) }
とりあえずコードだけ。
ちょっと記事が長くなってしまったので、helloHandler
だけ適応してます。
解説はまた次回その3
をお楽しみに。
合わせてjsonの返し方も解説出来たらと思います。
ではでは。