LoginSignup
1
2

Golang ハンドラ関数のチェインで詰まった過去の自分へ

Last updated at Posted at 2021-03-13

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()
}

追記

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2