# 3.4 Goのhttpパッケージ詳細 前の節でGoが如何にWebの作業モードを実現するかフローをご紹介しました。この節では、httpパッケージを詳しく解剖していきます。これはどのように全体のプロセスを実現しているのでしょうか。 Goのhttpには2つのコアとなる機能があります:Conn、ServeMux ## Connのgoroutine 我々が普段書くhttpサーバとは異なり、Goはマルチスレッドと高い性能を実現するため、goroutinesを使ってConnのイベント読み書きを処理します。これによって各リクエストは独立性を保持することができます。互いにブロックせず、効率よくネットワークイベントにレスポンスすることができます。これがGoに高い効率を保証します。 Goがクライアントのリクエストを待ち受けるには以下のように書きます: c, err := srv.newConn(rw) if err != nil { continue } go c.serve() クライアントの各リクエストはどれもConnを一つ作成しているのがわかるかと思います。このConnには今回のリクエストの情報が保存されています。これは目的のhandlerに渡され、このhandlerで目的のhandler情報を読み取ることができます。このように各リクエストの独立性を保証します。 ## ServeMuxのカスタム定義 前の節でconn.serverについてご説明した際、拾は内部ではhttpパッケージのデフォルトのルートをコールしていました。ルータを通して今回のリクエストのデータをバックエンドの処理関数に渡します。ではこのルータはどのように実現されているのでしょうか? 構造は以下のとおりです: type ServeMux struct { mu sync.RWMutex //ミューテックス、リクエストがマルチスレッド処理に及んだことでミューテックス機構が必要になります。 m map[string]muxEntry // ルーティングルール、一つのstringがひとつのmuxエンティティに対応します。ここではstringは登録されるルーティングを表現しています。 } 以下でmuxEntryを見てみましょう type muxEntry struct { explicit bool // 精確にマッチするか否か h Handler // このルーティング式はどのhandlerに対応するか } 次にHandlerの定義を見てみましょう。 type Handler interface { ServeHTTP(ResponseWriter, *Request) // ルーティング実現器 } Handlerはインターフェースですが、前の節の中で`sayhelloName`関数が特にServerHTTPというインターフェースを実装してはいませんでした。どうして追加できるのでしょうか?もともとhttpパッケージの中では`HandlerFunc`という型を定義しています。この型はデフォルトでServerHTTPインターフェースを実装しています。つまり、HandlerFunc(f)をコールして強制的にfをHandlerFunc型に型変換しているのです。このようにしてfはServerHTTPメソッドを持つことになります。 type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } ルータでは対応するルーティングルールを保存した後、具体的にはどのようにリクエストを振り分けているのでしょうか? ルータはリクエストを受け取った後、`mux.handler(r).ServerHTTP(w, r)`をコールします。 言い換えれば、目的のルーティングのhandlerのServerHTTPインターフェースをコールすると、mux.handler(r)はどのように処理するのでしょうか? func (mux *ServeMux) handler(r *Request) Handler { mux.mu.RLock() defer mux.mu.RUnlock() // Host-specific pattern takes precedence over generic ones h := mux.match(r.Host + r.URL.Path) if h == nil { h = mux.match(r.URL.Path) } if h == nil { h = NotFoundHandler() } return h } もともとこれはユーザのリクエストしたURLとルータの中に保存されているmapのマッチングに従って、このhandlerのServHTTPインターフェースをコールすることで目的の関数を実行することができます。 上の紹介を通じて、ルーティングの全体プロセスを理解しました。これはHandlerインターフェースです。つまり外部のルータはHandlerインターフェースを実装するだけで良く、自分自身で実装したルータのServHTTPの中でカスタムに定義されたルータ機能を実現することができます。 下のコードを通して、自分自身で簡単なルータを実装してみます。 package main import ( "fmt" "net/http" ) type MyMux struct { } func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sayhelloName(w, r) return } http.NotFound(w, r) return } func sayhelloName(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello myroute!") } func main() { mux := &MyMux{} http.ListenAndServe(":9090", mux) } ## Goのコードの実行プロセス httpパッケージへの分析を通して、全体のコードの実行プロセスを整理してみましょう。 - まずHttp.HandleFuncをコールします。 順序にしたがっていくつかの事を行います: 1 DefaultServerMuxのHandlerFuncをコールする。 2 DefaultServerMuxのHandleをコールする。 3 DefaultServerMuxのmap[string]muxEntryで目的のhandlerとルーティングルールを追加する。 - 次にhttp.ListenAndServe(":9090", nil)をコールする。 順序にしたがっていくつかの事を行う: 1 Serverのエンティティ化 2 Serverの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に設定されます。 8 handlerのServeHttpをコールする 9 この例の中では、この後DefaultServerMux.ServeHttpの中に入ります 10 requestに従ってhandlerを選択し、このhandlerのServeHTTPに入ります mux.handler(r).ServeHTTP(w, r) 11 handlerを選択します: A ルータがこのrequestを満足したか判断します(ループによってServerMuxのmuxEntryを走査します。) B もしルーティングされれば、このルーティングhandlerのServeHttpをコールします。 C ルーティングされなければ、NotFoundHandlerのServeHttpをコールします ## links * [目次]() * 前へ: [Goはどのようにしてweb作業を行うか](<03.3.md>) * 次へ: [概要](<03.5.md>)