Chapter 06

GoでのWebサーバー起動の全体図

さき(H.Saki)
さき(H.Saki)
2021.09.07に更新

この章について

前章までは、実際にコード内で呼ばれている関数・メソッドを網羅する形で処理の流れを追っていきました。
そこで作った図は「正確」ではあるのですが、インターフェースや具体型が入り混じっており、その分大枠は掴みづらいものになっています。

そのためここでは、上で紹介した2つのフェーズの重要ポイントだけに絞る形で、処理の大枠をまとめ直してみます。

2つのフェーズ

GoでWebサーバーを起動させるときの処理は、大きく2つのフェーズに分けることができます。

  1. http.Server型やnet.Connインターフェースの作成といった、サーバーの起動処理
  2. 実際に受信したリクエストをハンドラに処理させる、リクエストハンドリング

処理の大枠

ここでは、上で紹介した2つのフェーズの大枠を述べていきます。

「インターフェース」で見る

処理の重要ポイントだけ抽出するには、メソッドセットの形である程度の抽象化がなされているインターフェースに着目するのがいいです。
すると、処理の大枠は下図のようにまとめることができます。

1. サーバー起動

サーバーの起動部分で、最初に呼び出されるハンドラを内部に持つhttp.Server型と、http通信をするためのnet.Connインターフェースを作成しています。
net.Connhttp.Server型の外にあるのは、おそらく依存性注入の観点での設計です。

  • http.Server型が持つルーティング情報は、どの環境で動かしたとしても不変なもの
  • net.Connが持つURLホストやポートといったネットワーク環境情報は、状況によって変わる

これを踏まえて、もしURLやコネクションが変わったとしてもhttp.Server型を作り直さなくてもいいようにしているのです。

2. リクエストハンドリング

実際にリクエストを受けて、レスポンスを返す段階になると、http.Server型はServeHTTPメソッドがあるhttp.serverHandler型にキャストされた上で、そのServeHTTPメソッドを呼び出すことでリクエストを捌いていきます。
serverHandler型から最初に呼び出されるhttp.Handlerは、http.ListenAndServeの第二引数に渡されたルーティングハンドラです(=デフォルトだとDefaultServeMux)。

リクエストを受け取ったhttp.Handlerは、リクエストパスを見て、他のhttp.Handlerに処理を委譲するか、自身でレスポンス作成をするかのどちらかの処理を行います。

具体型で見る

インターフェースで見た場合、リクエストをハンドルする部品は全てhttp.Handlerでした。
「他のhttp.Handlerに処理を移譲するハンドラ」と「自身でリクエストを処理するハンドラ」の違いは一体なんなのでしょうか。

それをわかりやすくするために、上記の図をhttp.Handlerインターフェースを満たしうる実体型で書き換えました。

http.Handlerインターフェース部分の具体型として使われているのは、大きく分けて二種類です。

  • http.ServeMux型: ルーティングハンドラ。リクエストパスをみて、他のハンドラに処理を振り分ける役割を担う。
  • http.HandlerFunc型: ユーザーが書いたhttpハンドラ。実際にレスポンス内容を作成し、net.Connに書き込む役割を担う。

http.serverHandler型もhttp.Handlerインターフェースを満たす型であるので、

  • 処理の起点となる初めのhttp.serverHandlerから別のhttp.serverHandlerにハンドリング
  • http.ServeMux型からhttp.serverHandlerにハンドリング

ということも理論上は可能です。
ただし「あるサーバーから別のサーバーにハンドリング」というユースケースが現実的にありうるかどうかは疑問です(少なくとも筆者は思いつきません)。

http.ServeMux型にするか、http.HandlerFunc型にするか」の選択イメージについては、以下の図のように「パス/users以降は別のハンドラに任せる」というようなハンドリングをする場合を思い浮かべてもらえればわかりやすいかと思います。