🐁

net/http パッケージの ListenAndServe って何をしているの?

2024/02/24に公開

はじめに

Go の学習を始めた時にビックリしたのは、サーバを立てることの簡単さでした。
例えば以下の手順でサーバを立てることが出来ます。

main.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 メソッドを呼んでいるだけです。

net/http/server.go
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

先ほどの例では第二引数である handler には nil を渡していましたが、コードコメントを見るとこうあり、 nil の時には http.DefaultServeMux が使われます。

net/http/server.go
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 を呼び出すものと考えて良さそうです。

net/http/server.go
// 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 を渡していないのにコールしても意味がありません。

net/http/server.go
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 の生成のためだったことが分かります。

net/http/server.go
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 だったので何かしら具体化しているようです。
今回のコードリーディングでは気にしないことにします。

net/http/server.go
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 メソッドが呼ばれていますがもう少し中を見てみましょう。

net/http/server.go
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 リクエストを受け取ってレスポンスを書き込む関数ですが、色々工程を分けていくため複数回呼ばれているようです。

net/http/server.go
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

serverHandler.ServeHTTP

注目したいのは srv の Handler が nil だった場合は DefaultServeMux がセットされます。
http.ListenAndServe の第二引数が nil だったらようやくここでセットされるわけです。

そして handler の ServeHTTP がコールされており、 HTTP リクエストにレスポンスを行なっている箇所を確認しましょう。

net/http/server.go
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 を取り出しているだけなので元ネタを探しましょう。

net/http/server.go
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

冒頭でこのように登録をしました。

main.go
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 をレシーバメソッドとして持つようになります。

net/http/server.go
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if use121 {
		DefaultServeMux.mux121.handleFunc(pattern, handler)
	} else {
		DefaultServeMux.register(pattern, HandlerFunc(handler))
	}
}

この ServeHTTP が呼ばれることによって、先ほど登録した処理が呼びだされるわけですね。

net/http/server.go
type HandlerFunc func(ResponseWriter, *Request)

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

おわりに

Go は気になったところを気軽にコードリーディングできるのが良いところですね。

GitHubで編集を提案

Discussion