Go の http.Handler と http.HandlerFunc の違いを整理する
Go の net/http パッケージには http.Handler と http.HandlerFunc という似た名前の 2 つが存在します。混同しやすいですが、役割がはっきり違うので整理しておきましょう。
http.Handler はインターフェース
http.Handler はインターフェースであり、ServeHTTP メソッドを持つ型ならすべて http.Handler として扱えます。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}つまり、構造体に ServeHTTP メソッドを実装すれば、それがそのままハンドラになります。
type HelloHandler struct{}
func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from Handler!")
}
func main() {
http.Handle("/hello", HelloHandler{})
http.ListenAndServe(":8080", nil)
}http.Handle は第 2 引数に http.Handler インターフェースを受け取ります。HelloHandler は ServeHTTP を実装しているので、問題なく渡せるわけです。
構造体を使うメリットは、フィールドに状態を持たせられることです。たとえばデータベースの接続情報やログ設定をフィールドに入れておけば、ハンドラ内でアクセスできます。
http.HandlerFunc は型変換
一方、http.HandlerFunc は関数型です。定義を見ると、func(ResponseWriter, *Request) というシグネチャの関数を http.Handler インターフェースに変換するための仕組みであることがわかります。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}HandlerFunc 自体が ServeHTTP メソッドを持っているため、http.Handler インターフェースを自動的に満たします。普通の関数を書くだけでハンドラとして使えるのは、この型変換のおかげです。
func helloFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from HandlerFunc!")
}
func main() {
http.Handle("/hello", http.HandlerFunc(helloFunc))
http.ListenAndServe(":8080", nil)
}http.HandlerFunc(helloFunc) というキャストで、ただの関数が http.Handler に変わります。
HandleFunc はショートカット
実は http.HandleFunc という関数もあります。これは上記のキャストを自動でやってくれるショートカットです。
// この 2 つは同じ意味
http.Handle("/hello", http.HandlerFunc(helloFunc))
http.HandleFunc("/hello", helloFunc)http.HandleFunc を使えば、わざわざ http.HandlerFunc でキャストする手間が省けます。Go のチュートリアルでよく見かけるのはこちらの書き方でしょう。
構造体に ServeHTTP を実装する。状態を持てるので、DB 接続やロガーなどを構造体フィールドに格納できる。
普通の関数をハンドラに変換する。シンプルなエンドポイントに向いており、記述量が少ない。
実践的な使い分け
どちらを使うべきかは、ハンドラが状態を持つかどうかで判断できます。
// 状態が必要なケース → http.Handler(構造体)
type UserHandler struct {
DB *sql.DB
}
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// h.DB を使ってデータベースにアクセス
users, err := h.DB.Query("SELECT name FROM users")
// ...
}データベース接続やキャッシュなど、リクエストをまたいで共有したいリソースがある場合は構造体にまとめるのが自然です。
// 状態が不要なケース → http.HandlerFunc(関数)
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
})ヘルスチェックのような単純なエンドポイントなら、関数で書くほうが簡潔になります。
実際のプロジェクトでは両方が混在するのが普通です。Handler と HandlerFunc は競合する概念ではなく、場面に応じて使い分ける補完的なツールだと考えてください。Go のインターフェースの仕組みがわかると、この 2 つの関係はとても自然に感じられるはずです。