Go の http.Handler と http.HandlerFunc の違いを整理する

Go の net/http パッケージには http.Handlerhttp.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 インターフェースを受け取ります。HelloHandlerServeHTTP を実装しているので、問題なく渡せるわけです。

構造体を使うメリットは、フィールドに状態を持たせられることです。たとえばデータベースの接続情報やログ設定をフィールドに入れておけば、ハンドラ内でアクセスできます。

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 のチュートリアルでよく見かけるのはこちらの書き方でしょう。

http.Handler(インターフェース)

構造体に ServeHTTP を実装する。状態を持てるので、DB 接続やロガーなどを構造体フィールドに格納できる。

http.HandlerFunc(関数型)

普通の関数をハンドラに変換する。シンプルなエンドポイントに向いており、記述量が少ない。

実践的な使い分け

どちらを使うべきかは、ハンドラが状態を持つかどうかで判断できます。

// 状態が必要なケース → 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 つの関係はとても自然に感じられるはずです。