はじめに
前回の記事の続きです。
前回の記事はこちら
Goのhttpパッケージ詳細
前回は、Go
がWebサーバを立てている裏で何をしているのかを見ていきました。
今回は、http
パッケージの中身を見ていくようです。Conn
と ServerMux
というのがコアの機能と書いてあります。
Connのgoroutine
goroutineを使うと、ノンブロッキングで効率的にレスポンスができるみたいです。
コードが書いてありますね、これは前回も出てきたものです、これでリクエストを待ち受けるようです。
クライアントの各リクエストはどれもConnを一つ作成しているのがわかるかと思います。
このConnには今回のリクエストの情報が保存されています。
これは目的のhandlerに渡され、このhandlerで目的のhandler情報を読み取ることができます。
このように各リクエストの独立性を保証します。
なるほど。
リクエストごとにConn
を張っているようですね、これで独立性を担保しているようです。
(1つ1つにConn
を作成すると、そうでない場合に比べリソースを喰いそうな気がしますがどうなんでしょうか。)
ServeMuxのカスタム定義
前の節でconn.serverについてご説明した際、拾は内部ではhttpパッケージのデフォルトのルートをコールしていました。
確かに!そうでした。
ルータを通して今回のリクエストのデータをバックエンドの処理関数に渡します。ではこのルータはどのように実現されているのでしょうか?
type ServeMux struct {
mu sync.RWMutex //ミューテックス、リクエストがマルチスレッド処理に及んだことでミューテックス機構が必要になります。
m map[string]muxEntry // ルーティングルール、一つのstringがひとつのmuxエンティティに対応します。ここではstringは登録されるルーティングを表現しています。
hosts bool // 任意のルールにhost情報が含まれているか
}
type muxEntry struct {
explicit bool // 精確にマッチするか否か
h Handler // このルーティング式はどのhandlerに対応するか
pattern string //マッチング文字列
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // ルーティング実現器
}
あーなんとなく構造が見えてきました。
Handlerはインターフェースですが、
前の節の中のsayhelloName関数では特にServeHTTPというインターフェースを実装してはいませんでした。どうして追加できるのでしょうか?
そうね!
もともとhttpパッケージの中ではHandlerFuncという型が定義されています。
私達が定義したsayhelloName関数はまさにこのHandlerFuncがコールされた結果であり、
この型はデフォルトでServeHTTPインターフェースを実装していることになります。
どれどれ、HandlerFunc
を見てみます。
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
見たことありますねぇ!
つまり、HandlerFunc(f)をコールして強制的にfをHandlerFunc型に型変換しているのです。
このようにしてfはServeHTTPメソッドを持つようになります。
ServeHTTP
というのが実装されているわけですね。
ルータでは対応するルーティングルールを保存した後、具体的にはどのようにリクエストを振り分けているのでしょうか?以下のコードをご覧ください。
muxEntry
型として保存されてイますが、たしかにこれを実行するのはどうしているのでしょう。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
w.Header().Set("Connection", "close")
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
上に示す通りルータはリクエストを受け取った後、*であれば接続を切断し、
そうでなければmux.handler(r).ServeHTTP(w, r)をコールして対応する設定された処理Handlerを返し、h.ServeHTTP(w, r)を実行します。
ほうほう、パターンを検索して対応する関数をもらってきて実行するみたいですね。
つまり、目的のルーティングのhandlerのServerHTTPインターフェースへのコールです。ではmux.Handler(r)はどのように処理するのでしょうか?
更に中身ですね、パターンを検索する部分
のところですね。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method != "CONNECT" {
if p := cleanPath(r.URL.Path); p != r.URL.Path {
_, pattern = mux.handler(r.Host, p)
return RedirectHandler(p, StatusMovedPermanently), pattern
}
}
return mux.handler(r.Host, r.URL.Path)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
長いですが、
もともとこれはユーザのリクエストしたURLとルータの中に保存されているmapに従ってマッチングしています。マッチングによって保存されているhandlerが返されるにあたり、このhandlerのServeHTTPインターフェースがコールされ、目的の関数を実行することができます。
if mux.hosts {
h, pattern = mux.match(host + path)
}
ここの部分ですね。
上の紹介を通して、私達はルーティングの全体プロセスを理解しました。Goは実は外部で実装されたルータをサポートしています。ListenAndServeの第2引数が外部のルータを設定する為に用いられます。これはHandlerインターフェースのひとつで、外部ルータでHandlerインターフェースを実装し、そのServeHTTPにカスタム定義のルーティング機能を実装することができます。
多少なりとも理解できました、さてコードがあるので早速実行してみます。
Not Found
のパターンです。 (ルートに当てはまらない場合)
長いですが実行プロセスを追っていきます。
Goのコードの実行プロセス
http
パッケージへの分析を通して、全体のコードの実行プロセスを整理してみましょう。
まずHttp.HandleFunc
をコールします。
順序にしたがっていくつかの事を行います:
1DefaultServeMux
のHandlerFunc
をコールする。
2DefaultServeMux
のHandle
をコールする。
3DefaultServeMux
のmap[string]muxEntry
で目的のhandler
とルーティングルールを追加する。
次にhttp.ListenAndServe(":9090", nil)
をコールする。
順序にしたがっていくつかの事を行う:
1Server
のエンティティ化
2Server
のListenAndServe()
をコールする
3 net.Listen(“tcp”, addr)をコールし、ポートを監視する
4 forループを起動し、ループの中でリクエストをAcceptする
5 各リクエストに対してConnを一つエンティティ化し、このリクエストに対しgoroutineを一つ開いてgo c.serve()
のサービスを行う。
6 各リクエストの内容を読み込むw, err := c.readRequest()
7 handlerが空でないか判断する。もしhandlerが設定されていなければ(この例ではhandlerは設定していません)、handler
はDefaultServeMux
に設定されます。
8handler
のServeHttp
をコールする
9 この例の中では、この後DefaultServeMux.ServeHttp
の中に入ります
10 requestに従ってhandlerを選択し、このhandlerのServeHTTPに入りますmux.handler(r).ServeHTTP(w, r)
11 handlerを選択します:
A ルータがこのrequestを満足したか判断します(ループによってServerMuxのmuxEntryを走査します。)
B もしルーティングされれば、このルーティングhandlerのServeHttpをコールします。
C ルーティングされなければ、NotFoundHandlerのServeHttpをコールします
A, B, C が キャプチャのパターンに対応していますね。
前章と合わせると中身がだいぶわかってきたような気がします。
おわりに
次回はまとめですが、こちらは飛ばして フォーム
をやっていきます。まとめ
は裏で読んでおきます・・・!