Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作るを読み進める中で、
第3章にある 3.10 ハンドラとハンドラ関数のチェイン の項で最初理解不能だったけど向き合ったら理解できたのでその過程を書いていきます。
↓(詰まった)問題となるコード
gowebprog/ch03/10chain_handlerfunc/server.go
package main
import (
"fmt"
"net/http"
"reflect"
"runtime"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}
func log(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
fmt.Println("ハンドラ関数が呼び出されました - " + name)
h(w, r)
}
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", log(hello))
server.ListenAndServe()
}
詰まった(1回目)
log()でreturnしている匿名関数が、log()内に有りもしない値を引数としてセットしている点。
HandleFunc()の中身を見てみる。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
第2引数にあるhandler func(ResponseWriter, *Request)
に着目すると、
log()内の匿名関数と形が一致していることがわかる -> 高階関数
↓処理のフロー的なもの
HandleFunc(~, log(hello))
↓ 中身を展開
log(hello) {
return func(w, r) {
name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()
fmt.Println("ハンドラ関数が呼び出されました - " + name)
hello(w, r)
}
}
↓ log実行
func (w, r) {
name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()
fmt.Println("ハンドラ関数が呼び出されました - " + name)
hello(w, r)
}
↓ funcがhandlerとして扱われる
name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()
fmt.Println("ハンドラ関数が呼び出されました - " + name)
hello(w, r) // ←最終的にhelloが実行
詰まった(2回目)
log()でreturnしている匿名関数ではh()に引数を書くのに、main()で書くhelloは引数が不要な点
HandleFuncの引数のhelloは関数オブジェクトとして扱われていて、
log()内匿名関数のhは関数呼び出しをしていることの違い。
まとめ
個人的にはこう書いたほうがわかりやすそう
package main
import (
"fmt"
"net/http"
"reflect"
"runtime"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}
func log(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := runtime.FuncForPC(reflect.ValueOf(next).Pointer()).Name()
fmt.Println("ハンドラ関数が呼び出されました - " + name)
next(w, r)
}
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", log(hello))
server.ListenAndServe()
}
追記
log()のreturnよりも上で書いた処理がgo run時点で走ってしまうのは関数呼び出しとして扱われているからか
— shotaro ozawa (@tk_zawa) March 13, 2021
それとHandleFunc()の第2引数はあくまで匿名関数(handler)の中身しか見てないからエンドポイントにアクセスしてものlog()のreturnより上の処理が走らんのね