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
ではcontext
やnet
パッケージを使った処理が行われているが、
この部分は今回のスコープ外のため割愛する。
(単に勉強不足のため別途学習した内容をまとめる予定)
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
の実装を
ソースコードからピックアップして辿ってきた。
やり残しの課題は下記となる。
-
context
やnet
パッケージを使った処理 -
http.ServeMux.ServeHTTP
によるhttp.ResponseWriter
,http.Request
の処理
cf. net/http
パッケージのソースコード
→ 下記コマンドなどと打って確認
$ godoc -src net/http | less
- Web上の最新版(4/24更新)は下記
https://golang.org/src/net/http/server.go