はじめに
GoでHTTP Serverを作ろうとすると、標準ライブラリを使う場合以下のようなコードをよく書くと思う。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.Handle("/hello", http.HandlerFunc(hello))
log.Fatal(http.ListenAndServe(":8080", mux))
}
func hello(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello World")
}
このコードの登場人物としては以下になるが、それぞれなんだっけ?というのをいっつも忘れてしまうのでメモしておく。
- http.ListenAndServe
- http.Handler
- http.HandleFunc
- http.ServeMux
- http.ServeMux.Handle
- http.ServeMux.HandleFunc
http.ListenAndServe はその名前の通りサーバーを起動するものである。
- 第1引数にTCPのアドレス
- 第2引数にhttp.Handler
を渡す。この第2引数のhttp.Handlerってなに?というのを次のセクションで説明する。
http.HandlerはServeHTTP
というメソッドを定義しているinterface。以下がそのメソッドの定義。このServeHTTPメソッドはHTTPリクエストを受けてHTTPレスポンスを返す処理が記述されるものである。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
後述するhttp.ServeMux
のHandleメソッドの引数がこのinterface型になっている。
func (mux *ServeMux) Handle(pattern string, handler Handler) {
....
}
http.ServeMuxには、HTTPリクエストのURLとそれに対応するハンドラを登録する。HTTPリクエストが来た時に、そのURLにマッチしたハンドラを呼び出すマルチプレクサである。pkg.go.devからの引用。
ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.
ServeMux.Handleメソッドを呼び出して以下のようにURLのPathとHandlerを登録しておく。例えばHTTPリクエストのURLが /echo
の場合、echoHandler
のServeHTTPメソッドが呼び出される。
func main() {
mux := http.NewServeMux()
mux.Handle("/echo", echoHandler)
...
}
type echoHandler struct{}
func (h *echoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello World")
}
なお、http.ServeMuxはServeHTTPメソッドを実装しているので、http.ListenAndServeの第2引数に渡すことができる。
func(ResponseWriter, *Request)
を別の型として定義したもの
- このHandlerFunc型には
ServeHTTP(ResponseWriter, *Request)
メソッドが実装されている。func(http.ResponseWriter, *http.Request)
のシグネチャの関数を自分で定義しておき、このHandlerFuncにキャストするだけで構造体を宣言することなく http.Handler を実装することができる
- godocには
The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
と書かれている。
実際のコードはこんな感じ。HandlerFunc自体にServeHTTPがあり、その中では元の関数であるf
を呼び出しているだけ。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
いまいちイメージしづらいので、使う側のコードを書いてみる。
func hello(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello World")
}
func main() {
mux := http.NewServeMux()
mux.Handle("/hello", http.HandlerFunc(hello))
}
- helloをhttp.HandlerFuncにキャスト
- http.HandlerFuncは ServeHTTP を実装しているので、ServeMux.Handleの第2引数に指定することが可能。
という仕組みになっている。先程のServeHTTPのサンプルコードだと、echoHandlerというstructを定義してそこにServeHTTPメソッドを定義する必要があったが、このコードではそのような構造体は必要ないことがわかると思う。
ServeMuxにはHandleメソッド以外にもHandleFunc
というメソッドが定義されている。コードとしてはこんな感じの実装になっている。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
この実装を見ればわかるように、第2引数のhandler
をHandlerFuncにキャストして、自身のHandle
を呼び出しているだけである。
呼び出し側のコードはこんな感じになる。
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello World")
})
何が嬉しいのかというと、func(http.ResponseWriter, *http.Request)
を自分でhttp.HandlerFuncにキャストしなくてもよいということ。自分はServeMux.HandleとServeMux.HandleFuncのどちらを使えばいいんだっけ?といつも混乱しているけど、HandleFuncはHandleのショートカットだと思えば良い。
http.Handleもまたショートカットである。
- httpパッケージ内に
DefaultServeMux
というグローバル変数が定義されている
- http.Handleを呼び出すとこのDefaultServeMux.Handleが呼び出される
という仕組み。http.HandleFuncも同様に定義されていて、これもDefaultServeMux.HandleFuncが呼び出されるだけだ。
まとめ
似たような名前のものがあってややこしいけど
- http.HandlerはServeHTTPメソッドを定義するinterface
- http.HandlerFuncを使えばServeHTTPを実装するstructを作らなくて良い
- ServeMux.HandleメソッドでPathとhttp.Handlerのマッピングを登録する
というのだけ最低限覚えておけば良いのかなと思う。あとは応用。なお、例として挙げたサンプルコードはここにあがってる。
参考リンク