👌

【徹底整理】GoのHTTP「ハンドラ」全部入り図解!Handler/HandlerFunc/Handle/HandleFunc徹底整理

に公開

GoでWebサーバを作成しているときに、http.Handler(ハンドラ)周辺の理解に苦しんだので体系的に整理します。

ハンドラ(http.Handler)とは:

まず前提としてハンドラ(http.Handler)とは何かを押えましょう
ハンドラ(http.Handler)とは:
GoのHTTPサーバにおいて、リクエストを受け取った時に“実際に処理を担当する関数や構造体”のことです。

http.Handler

  • 種類:インターフェース(interface)

これがまさに、ハンドラと呼ばれるものですが、ハンドラとして扱われるには条件があります。

↓こんな感じで作成すれば、ハンドラとして使用できます。

type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}

http.HandlerFunc

  • 種類:型(type)

では通常の関数(func(http.ResponseWriter, *http.Request))をハンドラにするときはどうしたらいいの?
※ServeHTTPが無いけど
というときに、使用するのがハンドラーファンク(http.HandlerFunc)です。

使用例
//MyFuncは func(http.ResponseWriter, *http.Request)のシグネチャを持つ通常の関数
func MyFunc(w http.ResponseWriter, r *http.Request) { ... }

//上記MyFuncをhttp.HandlerFunc型に変換
var h http.Handler = http.HandlerFunc(MyFunc)

↑こんな感じで、func(http.ResponseWriter, *http.Request)のシグネチャを持つ関数をhttp.HandlerFunc型に変換すると、ServeHTTPを満たすハンドラーに変換してくれます。

ハンドラーファンクhttp.HandlerFunc()の中で何が起こっているのか

ここは少し蛇足です、不要であれば読み飛ばしてください。
Goの標準ライブラリ(net/http)で定義されいるハンドラーファンクhttp.HandlerFunc()の処理を見ると、なんでServeHTTPを満たすハンドラに変換されているのか理解できます。

標準ライブラリ(net/http)
type HandlerFunc func(http.ResponseWriter, *http.Request)

func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f(w, r)
}

このGoの標準ライブラリで書かれていること

①新たにHandlerFuncという型を作るよ(この型はfunc(http.ResponseWriter, http.Request)と同じシグネチャを持つ方として宣言するよ)
②上記、HandlerFuncという型にはServeHTTPというメソッドを持たせるよ

これにより、

使用例(キャスト部分抽出)
//上記MyFuncをhttp.HandlerFunc型に変換
var h http.Handler = http.HandlerFunc(MyFunc)

上記のようにキャストされた"h"はServeHTTPメソッドを持つこととなる

ここまでで、”ハンドラの要件とは”とか”ハンドラに変換するには?”についての説明を終わります

ルーティング登録

ここからは、実際にどうやってハンドラを使うの?
という事を説明していきます。

GoでWebサーバを作成するとき、ルーティング処理のタイミングが使いどころです。

ルーティング処理で使用する関数は下の2種類

  • http.HandleFunc()
  • http.Handle()

簡単に仕分けすると、大きく下の3パターンの使い方があると思う

使うべきもの 第一引数 第二引数
ハンドルファンクhttp.HandleFunc http.HandleFunc("/a", myFunc) URL シンプルな関数(func(http.ResponseWriter, *http.Request) 型)
ハンドルhttp.Handle http.Handle("/b", &MyHandlerStruct{}) URL http.Handlerインターフェースを満たす構造体
ハンドルhttp.Handlehttp.HandlerFunc http.Handle("/c", http.HandlerFunc(myFunc)) URL http.Handlerインターフェースを満たす関数(HandlerFunc)

ルーティング登録の具体例

もうすこし、具体的なコードで、ルーティングの説明をしますね、、、

1. http.HandleFunc

  • 種類:関数

  • 内容・役割
    ルーティングの登録時に使用する

  • 第一引数にURLパス、第二引数に func(http.ResponseWriter, *http.Request) 型の関数を取り、内部でこれを http.HandlerFuncに変換して登録する
    ※「関数を直接登録したい」時に手軽に使える

使い方例

func Hello(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
}

http.HandleFunc("/hello", Hello)

2. http.Handle

  • 種類:関数

  • 内容・役割
    ルーティングの登録時に使用する

  • 第一引数にURLパス、第二引数にhttp.Handlerインターフェースを満たす型(構造体や HandlerFunc)を登録する

  • すでにハンドラ(**ServeHTTP**を持つ)を用意している場合に使う

使い方例

type GreetHandler struct {
    Name string
}

func (h *GreetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", h.Name)
}

h := &GreetHandler{Name: "Go"}
http.Handle("/greet", h)

おおよそ上記のような感じです。

まとめ

最後にまとめです

名前 種類 役割・特徴
http.Handler インターフェース ServeHTTPメソッドを持つ型なら何でもハンドラになる共通仕様
http.HandlerFunc 関数をラップし、ServeHTTPを持たせることでhttp.Handlerを満たすアダプター
http.HandleFunc 関数 関数をURLパスに直接登録。内部でHandlerFuncに変換し、http.Handlerとして登録
http.Handle 関数 すでにServeHTTPを持つハンドラ型をURLパスに登録

使い分けポイント・実例

こうしたいこと 使うべきもの
シンプルに関数でハンドラ登録 http.HandleFunc http.HandleFunc("/a", myFunc)
状態や複雑なロジックを持つ構造体を使いたい http.Handle http.Handle("/b", &MyHandlerStruct{})
関数を明示的にhttp.Handler化したい http.HandlerFunc http.Handle("/c", http.HandlerFunc(myFunc))

よくある間違い

  • http.HandleFunc("/path", &MyHandlerStruct{}) → NG!
    HandleFuncは関数しか受け付けず、構造体は登録できない。

  • http.Handle("/path", &MyHandlerStruct{}) → OK
    Handlehttp.Handlerを満たす型を受け取るため、構造体が登録可能。

Discussion