【徹底整理】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を満たすハンドラに変換されているのか理解できます。
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.Handle+http.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
Handleはhttp.Handlerを満たす型を受け取るため、構造体が登録可能。
Discussion