LoginSignup
1
2

More than 5 years have passed since last update.

http.HandleFunc, http.ListenAndServeの実装を辿ってみたメモ

Posted at

GoでWebサーバを書く

GoでWebサーバを書く場合、標準のnet/httpパッケージを使用することが多い。
便利な反面、中身を知らずに使ってしまう危険があるように思う。

net/httpパッケージの利用

サーバを起動する最小限のコードは下記のようになるかと思う。

func homeHandle(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the home page!")
}

func main() {
    http.HandleFunc("/", homeHandle)
    http.ListenAndServe(":8080", nil)
}

以下、http.HandleFunc,http.ListenAndServeの実装について、
ドキュメントとソースコードからピックアップして辿ってみた内容をメモしておく。

goバージョンは下記を使用。

$ go version
go version go1.9.2 darwin/amd64

http.HandleFunc

まずはmain()の一行目http.HandleFuncから見ていく。

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

DefaultServeMux.HandleFuncが呼ばれている。
DefaultServeMuxとは下記に定義されるように、デフォルトのServemuxである。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
type ServeMux struct {
    ...
}

つまりDefaultServeMux.HandleFuncとはhttp.ServeMux.HandleFunc
定義されていることになる。

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

上記ではhttp.ServeMux.Handleが呼ばれる。

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    ...
}

上記でpatternごとのhandler登録が行われるようである。
主要な処理部分は下記の一行である。

    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

muxEntryとしてmux.mへの登録が行われる。
Servemux, muxEntryの定義は下記にある。

type ServeMux struct {
    ...
    m     map[string]muxEntry
    ...
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

また、explicitを使用して、重複したpatternが登録されないよう
チェックをおこなっている。

    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

一度登録された後はmux.m[pattern].explicit == trueとなり、
同じpatternに二度目に登録しようとするとpanicが呼ばれることになる。

http.Servemux.Handleのコメント行にも下記とある。

// If a handler already exists for pattern, Handle panics.

以上がhttp.HandleFunc部分となる。

http.ListenAndServe

次にhttp.ListenAndServeを見ていく。
定義は下記となる。

// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests
// on incoming connections.
// ...
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

http.Serverにaddr, handlerが格納され、そのメソッドである、
http.Server.ListenAndServeが呼ばれるのが分かる。
その定義部分は下記となる。

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
func (srv *Server) ListenAndServe() error {
    ...
    ln, err := net.Listen("tcp", addr)
    ...
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

指定したaddrがnet.Listenに渡される。
その後はnet.Listenerを使用し、下記http.Server.Serveが呼ばれる。

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
// ...
func (srv *Server) Serve(l net.Listener) error {
    ...
    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept()
        ...
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

http.Server.Serveではcontextnetパッケージを使った処理が行われているが、
この部分は今回のスコープ外のため割愛する。
(単に勉強不足のため別途学習した内容をまとめる予定)

The service goroutines read requests and then call srv.Handler to reply to them.

ここで上記コメント内の記述にもある通り、c.serveメソッドがgoroutineとして起動される。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    ...
    for {
        w, err := c.readRequest(ctx)
        ...
        // Until the server replies to this request, it can't read another,
        // so we might as well run the handler in this goroutine.
        // ...
        serverHandler{c.server}.ServeHTTP(w, w.req)
        ...
    }
}

上記でhttp.Serverに保持していた情報はserverHandlerに格納され、
そのメソッドであるServeHTTPが呼ばれる。定義は下記にある。

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    ...
    handler.ServeHTTP(rw, req)
}

ここで初めて、http.ListenAndServeに指定したhandlerが取り出される。

    if handler == nil {
        handler = DefaultServeMux
    }

今回はnilを指定していたので、上記の処理でhttp.DefaultServeMuxに置き換えられる。

    handler.ServeHTTP(rw, req)

したがって上記http.DefaultServeMux.ServeHTTPの呼び出しによって、
http.ResponseWriter, http.Requestが処理されるというのが分かった。
(ここの処理部分についても学習した後にまとめたいと思う)

まとめ

以上でhttp.HandleFunc,http.ListenAndServeの実装を
ソースコードからピックアップして辿ってきた。

やり残しの課題は下記となる。

  • contextnetパッケージを使った処理
  • http.ServeMux.ServeHTTPによるhttp.ResponseWriter,http.Requestの処理

cf. net/httpパッケージのソースコード
→ 下記コマンドなどと打って確認

$ godoc -src net/http | less
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2