net/http パッケージの ListenAndServe って何をしているの?
はじめに
Go の学習を始めた時にビックリしたのは、サーバを立てることの簡単さでした。
例えば以下の手順でサーバを立てることが出来ます。
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
http.ListenAndServe(":8080", nil)
}
$ go run main.go
$ curl http://localhost:8080/
Hello, World!
めちゃくちゃ簡単な反面、 http.ListenAndServer って何をしているのか気になったので net/http パッケージを軽くコードリーディングをしてみました。
結論
Server は Listener を実行してコネクションを獲得して、 Handler を実行して HTTP リクエストにレスポンスを行なっている。
http.ListenAndServe はこれを楽に実現してくれる関数である。
この結論に辿り着くためにコードリーディングしていきます。
コードリーディング
http.ListenAndServe
Server 構造体にアドレスと handler を渡して構造体を生成し、 ListenAndServe メソッドを呼んでいるだけです。
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
先ほどの例では第二引数である handler には nil を渡していましたが、コードコメントを見るとこうあり、 nil の時には http.DefaultServeMux が使われます。
type Server struct {
// 他のフィールドは省略
Handler Handler // handler to invoke, http.DefaultServeMux if nil
}
ちなみに DefaultServeMux の Mux は multiplexer のことです。
参考: https://wa3.i-3-i.info/word13228.html
DefaultServeMux は ServeMux の空の構造体のことですが、コードコメントを見るとこうあります。
大雑把に理解をすれば、 URL のマッチングを行なって Handler を呼び出すものと考えて良さそうです。
// 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.
// 省略
type ServeMux struct {
mu sync.RWMutex
tree routingNode
index routingIndex
patterns []*pattern // TODO(jba): remove if possible
mux121 serveMux121 // used only when GODEBUG=httpmuxgo121=1
}
なお冒頭のコード例では http.HandleFunc でパスと handler のパターンの紐付けを行なっていますが、中を読むと DefaultServeMux にパターンの登録を行なっていることが分かります。
DefaultServeMux はグローバルな変数として定義されているので、第二引数で nil を渡していないのにコールしても意味がありません。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if use121 {
DefaultServeMux.mux121.handleFunc(pattern, handler)
} else {
DefaultServeMux.register(pattern, HandlerFunc(handler))
}
}
server.ListenAndServe
Listener を生成して、それを Serve メソッドに渡しています。
http.ListenAndServe はアドレスを受け取っていて、それは Server 構造体のフィールドに入っていましたが、 Listener の生成のためだったことが分かります。
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
server.Serve
長いので省略していますが、重要なところはこのようになっています。
ループで Listener の Accept を呼び出してコネクションを確立し、ゴルーチンを立ち上げています。
ちなみにsrv.newConn は何をしているのか分からなかったのですが、 rw が interface 型なのに対して newConn の戻り値は struct だったので何かしら具体化しているようです。
今回のコードリーディングでは気にしないことにします。
func (srv *Server) Serve(l net.Listener) error {
// 省略
for {
rw, err := l.Accept()
// 省略
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
c.serve
ここまでで Server は Listener を実行してコネクションを獲得しているところまで確認できました。
残りは Handler を実行して HTTP リクエストにレスポンスを行なっている箇所を確認しましょう。
ちなみに先ほどループでゴルーチンを立てていることが説明されていて、 http は同時にリクエストを捌けないためにゴルーチンを立てて対策しているようです。
serverHandler.ServeHTTP メソッドが呼ばれていますがもう少し中を見てみましょう。
func (c *conn) serve(ctx context.Context) {
// 省略
for {
// 省略
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
inFlightResponse = w
serverHandler{c.server}.ServeHTTP(w, w.req)
}
}
なおこれから ServeHTTP メソッドが複数回呼ばれることになりますが、以下のような interface で定義されています。
ServeHTTP はその名前のとおり http リクエストを受け取ってレスポンスを書き込む関数ですが、色々工程を分けていくため複数回呼ばれているようです。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
serverHandler.ServeHTTP
注目したいのは srv の Handler が nil だった場合は DefaultServeMux がセットされます。
http.ListenAndServe の第二引数が nil だったらようやくここでセットされるわけです。
そして handler の ServeHTTP がコールされており、 HTTP リクエストにレスポンスを行なっている箇所を確認しましょう。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if !sh.srv.DisableGeneralOptionsHandler && req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
ServeMux.ServeHTTP
ここも Handler を取り出しているだけなので元ネタを探しましょう。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// 省略
var h Handler
if use121 {
h, _ = mux.mux121.findHandler(r)
} else {
h, _, r.pat, r.matches = mux.findHandler(r)
}
h.ServeHTTP(w, r)
}
ServerMux.findHandler
処理が長いですが、要するに ServeMux の routineNode の Handler を返していることが分かります。
ServeMux の routineNode の Handler は冒頭でお話した HandleFunc で登録されています。
func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *pattern, matches []string) {
var n *routingNode
host := r.URL.Host
escapedPath := r.URL.EscapedPath()
path := escapedPath
// CONNECT requests are not canonicalized.
if r.Method == "CONNECT" {
// 省略
} else {
// 省略
n, matches, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
if u != nil {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
}
// 省略
}
// 省略
return n.handler, n.pattern.String(), n.pattern, matches
}
HandleFunc
冒頭でこのように登録をしました。
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
// 省略
}
HandleFunc の実装を見るとこのようになっています。
先ほど登録した handler は func(w http.ResponseWriter, r *http.Request)
という型でしたが、 HandlerFunc 関数によって ServeHTTP をレシーバメソッドとして持つようになります。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if use121 {
DefaultServeMux.mux121.handleFunc(pattern, handler)
} else {
DefaultServeMux.register(pattern, HandlerFunc(handler))
}
}
この ServeHTTP が呼ばれることによって、先ほど登録した処理が呼びだされるわけですね。
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
おわりに
Go は気になったところを気軽にコードリーディングできるのが良いところですね。
Discussion