[ja] apply patch
This commit is contained in:
182
ja/ebook/03.3.md
182
ja/ebook/03.3.md
@@ -1,95 +1,87 @@
|
||||
# 3.3 GoはどのようにしてWeb作業を行うか
|
||||
前の節でどのようにGoを通じてWebサービスを立てるかご紹介しました。net/httpパッケージを簡単に応用して便利に立てることができたかと思います。では、Goは低レイヤーで一体何を行なっているのでしょうか?万物は姿を変えてもその元は同じであります。GoのWebサービス作業も第一章でご紹介したWebの作業方法に関係しています。
|
||||
|
||||
## webの作業方法のいくつかの概念
|
||||
|
||||
以下はどれもサーバの概念のいくつかです
|
||||
|
||||
Request:ユーザが要求するデータ。ユーザのリクエスト情報を解析します。post、get、cookie、url等の情報を含みます。
|
||||
|
||||
Response:サーバがクライアントにデータをフィードバックする必要があります。
|
||||
|
||||
Conn:ユーザの毎回のリクエストリンクです。
|
||||
|
||||
Handler:リクエストを処理し、返すデータを生成する処理ロジック。
|
||||
|
||||
## httpパッケージが実行する機能を分析する
|
||||
|
||||
下の図はGoが実現するWebサービスの作業モードのプロセス図です
|
||||
|
||||

|
||||
|
||||
図3.9 httpパッケージの実行フロー
|
||||
|
||||
1. Listen Socketを作成し、指定したポートを関しします。クライアントのリクエストを待ちます。
|
||||
|
||||
2. Listen Socketはクライアントのリクエストを受け付けます。Client Socketを得ると、Client Socketを通じてクライアントと通信を行います。
|
||||
|
||||
3. クライアントのリクエストを処理します。まず、Client SocketからHTTPリクエストのプロトコルヘッダを読み取り、もしPOSTメソッドであれば、クライアントが入力するデータをさらに読み取るかもしれません。その後対応するhandlerがリクエストを処理します。handlerがクライアントの要求するデータを準備し終えたら、Client Socketを通じてクライアントに書き出します。
|
||||
|
||||
この全体のプロセスでは3つの問題についてだけ理解しておけば構いません。これはまたGoがいかにしてWebを実行するのかということを知るという意味です。
|
||||
|
||||
- どのようにポートを監視するか?
|
||||
- クライアントのリクエストをどのように受け付けるか?
|
||||
- handlerにどのように受け渡すか?
|
||||
|
||||
前の節のコードではGoは関数`ListenAndServe`を通してこれらの事を処理していました。ここでは実はこのように処理しています:serverオブジェクトを初期化します。その後`net.Listen("tcp", addr)`をコールします。つまり、低レイヤでTCPプロトコルを用いてサービスを立ち上げます。その後我々が設定したポートを監視します。
|
||||
|
||||
下のコードはGoのhttpパッケージのソースコードから引用したものです。下のコードで全体のHTTP処理プロセスを見ることができます。
|
||||
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
defer l.Close()
|
||||
var tempDelay time.Duration // how long to sleep on accept failure
|
||||
for {
|
||||
rw, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return e
|
||||
}
|
||||
tempDelay = 0
|
||||
if srv.ReadTimeout != 0 {
|
||||
rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout))
|
||||
}
|
||||
if srv.WriteTimeout != 0 {
|
||||
rw.SetWriteDeadline(time.Now().Add(srv.WriteTimeout))
|
||||
}
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
}
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
監視した後どのようにしてクライアントのリクエストを受け取るのでしょうか?上のコードではポートの監視を実行後、`srv.Serve(net.Listener)`関数をコールしています。この関数はクライアントのリクエスト情報を処理しています。この関数では`for{}`が置かれており、まずListenerを通じてリクエストを受け取った後、Connを作成します。最後に単独のgoroutineを開きます。このリクエストのデータを引数としてこのconnに渡します。:`go c.serve()`。これはマルチスレッドを行なっています。ユーザが行うリクエストはすべて真新しいgoroutineの上で行われ、互いに影響しません。
|
||||
|
||||
ではいかにして具体的に目的の関数でリクエストを処理するように振り分けるのでしょうか?connはまずrequestを解析します:`c.readRequest()`、その後目的のhandlerを取得します:`handler := c.server.Handler`、つまり、我々がさきほど`ListenAndServe`をコールした時、その2つ目の引数です。前の例でnilを渡したのですが、これは空ということです。デフォルトで`handler = DefaultServeMux`を取得します。この変数は一体何に使われるのでしょうか?そうです。この変数はルータです。これはマッチするurlを対応するhandler関数にリダイレクトするために用いられます。我々はこれを設定したでしょうか?ええ。我々がコールしたコードのいの一番で`http.HandleFunc("/", sayhelloName)`をコールしたじゃないですか。これは`/`をリクエストするルートのルールを登録します。urlが"/"をリクエストした場合、ルートは関数sayhelloNameにリダイレクトします。DefaultServeMuxはServeHTTPメソッドをコールします。このメソッド内では実はsayhelloName本体をコールしています。最後にresponseの情報を入力することで、クライアントにフィードバックを返します。
|
||||
|
||||
|
||||
全体のフローの詳細は以下の図の通りです:
|
||||
|
||||

|
||||
|
||||
図3.10 http接続の処理フロー
|
||||
|
||||
ここに来て我々は3つの問題に対して全て解答を得ました。Goが如何にWebを走らせるか、すでに基本的なことは理解されたのではないでしょうか?
|
||||
|
||||
|
||||
## links
|
||||
* [目次](<preface.md>)
|
||||
* 前へ: [GOで簡単なwebサービスを立ち上げる](<03.2.md>)
|
||||
* 次へ: [Goのhttpパッケージ詳細](<03.4.md>)
|
||||
|
||||
# 3.3 GoはどのようにしてWeb作業を行うか
|
||||
前の節でどのようにGoを通じてWebサービスを立てるかご紹介しました。net/httpパッケージを簡単に応用して便利に立てることができたかと思います。では、Goは低レイヤーで一体何を行なっているのでしょうか?万物は姿を変えてもその元は同じであります。GoのWebサービス作業も第一章でご紹介したWebの作業方法に関係しています。
|
||||
|
||||
## webの作業方法のいくつかの概念
|
||||
|
||||
以下はどれもサーバの概念のいくつかです
|
||||
|
||||
Request:ユーザが要求するデータ。ユーザのリクエスト情報を解析します。post、get、cookie、url等の情報を含みます。
|
||||
|
||||
Response:サーバがクライアントにデータをフィードバックする必要があります。
|
||||
|
||||
Conn:ユーザの毎回のリクエストリンクです。
|
||||
|
||||
Handler:リクエストを処理し、返すデータを生成する処理ロジック。
|
||||
|
||||
## httpパッケージが実行する機能を分析する
|
||||
|
||||
下の図はGoが実現するWebサービスの作業モードのプロセス図です
|
||||
|
||||

|
||||
|
||||
図3.9 httpパッケージの実行フロー
|
||||
|
||||
1. Listen Socketを作成し、指定したポートを関しします。クライアントのリクエストを待ちます。
|
||||
|
||||
2. Listen Socketはクライアントのリクエストを受け付けます。Client Socketを得ると、Client Socketを通じてクライアントと通信を行います。
|
||||
|
||||
3. クライアントのリクエストを処理します。まず、Client SocketからHTTPリクエストのプロトコルヘッダを読み取り、もしPOSTメソッドであれば、クライアントが入力するデータをさらに読み取るかもしれません。その後対応するhandlerがリクエストを処理します。handlerがクライアントの要求するデータを準備し終えたら、Client Socketを通じてクライアントに書き出します。
|
||||
|
||||
この全体のプロセスでは3つの問題についてだけ理解しておけば構いません。これはまたGoがいかにしてWebを実行するのかということを知るという意味です。
|
||||
|
||||
- どのようにポートを監視するか?
|
||||
- クライアントのリクエストをどのように受け付けるか?
|
||||
- handlerにどのように受け渡すか?
|
||||
|
||||
前の節のコードではGoは関数`ListenAndServe`を通してこれらの事を処理していました。ここでは実はこのように処理しています:serverオブジェクトを初期化します。その後`net.Listen("tcp", addr)`をコールします。つまり、低レイヤでTCPプロトコルを用いてサービスを立ち上げます。その後我々が設定したポートを監視します。
|
||||
|
||||
下のコードはGoのhttpパッケージのソースコードから引用したものです。下のコードで全体のHTTP処理プロセスを見ることができます。
|
||||
|
||||
func (srv *Server) Serve(l net.Listener) error {
|
||||
defer l.Close()
|
||||
var tempDelay time.Duration // how long to sleep on accept failure
|
||||
for {
|
||||
rw, e := l.Accept()
|
||||
if e != nil {
|
||||
if ne, ok := e.(net.Error); ok && ne.Temporary() {
|
||||
if tempDelay == 0 {
|
||||
tempDelay = 5 * time.Millisecond
|
||||
} else {
|
||||
tempDelay *= 2
|
||||
}
|
||||
if max := 1 * time.Second; tempDelay > max {
|
||||
tempDelay = max
|
||||
}
|
||||
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
|
||||
time.Sleep(tempDelay)
|
||||
continue
|
||||
}
|
||||
return e
|
||||
}
|
||||
tempDelay = 0
|
||||
c, err := srv.newConn(rw)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
go c.serve()
|
||||
}
|
||||
}
|
||||
|
||||
監視した後どのようにしてクライアントのリクエストを受け取るのでしょうか?上のコードではポートの監視を実行後、`srv.Serve(net.Listener)`関数をコールしています。この関数はクライアントのリクエスト情報を処理しています。この関数では`for{}`が置かれており、まずListenerを通じてリクエストを受け取った後、Connを作成します。最後に単独のgoroutineを開きます。このリクエストのデータを引数としてこのconnに渡します。:`go c.serve()`。これはマルチスレッドを行なっています。ユーザが行うリクエストはすべて真新しいgoroutineの上で行われ、互いに影響しません。
|
||||
|
||||
ではいかにして具体的に目的の関数でリクエストを処理するように振り分けるのでしょうか?connはまずrequestを解析します:`c.readRequest()`、その後目的のhandlerを取得します:`handler := c.server.Handler`、つまり、我々がさきほど`ListenAndServe`をコールした時、その2つ目の引数です。前の例でnilを渡したのですが、これは空ということです。デフォルトで`handler = DefaultServeMux`を取得します。この変数は一体何に使われるのでしょうか?そうです。この変数はルータです。これはマッチするurlを対応するhandler関数にリダイレクトするために用いられます。我々はこれを設定したでしょうか?ええ。我々がコールしたコードのいの一番で`http.HandleFunc("/", sayhelloName)`をコールしたじゃないですか。これは`/`をリクエストするルートのルールを登録します。urlが"/"をリクエストした場合、ルートは関数sayhelloNameにリダイレクトします。DefaultServeMuxはServeHTTPメソッドをコールします。このメソッド内では実はsayhelloName本体をコールしています。最後にresponseの情報を入力することで、クライアントにフィードバックを返します。
|
||||
|
||||
|
||||
全体のフローの詳細は以下の図の通りです:
|
||||
|
||||

|
||||
|
||||
図3.10 http接続の処理フロー
|
||||
|
||||
ここに来て我々は3つの問題に対して全て解答を得ました。Goが如何にWebを走らせるか、すでに基本的なことは理解されたのではないでしょうか?
|
||||
|
||||
|
||||
## links
|
||||
* [目次](<preface.md>)
|
||||
* 前へ: [GOで簡単なwebサービスを立ち上げる](<03.2.md>)
|
||||
* 次へ: [Goのhttpパッケージ詳細](<03.4.md>)
|
||||
|
||||
339
ja/ebook/03.4.md
339
ja/ebook/03.4.md
@@ -1,158 +1,181 @@
|
||||
# 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
|
||||
* [目次](<preface.md>)
|
||||
* 前へ: [Goはどのようにしてweb作業を行うか](<03.3.md>)
|
||||
* 次へ: [概要](<03.5.md>)
|
||||
|
||||
# 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は登録されるルーティングを表現しています。
|
||||
hosts bool // 任意のルールにhost情報が含まれているか
|
||||
}
|
||||
|
||||
以下でmuxEntryを見てみましょう
|
||||
|
||||
type muxEntry struct {
|
||||
explicit bool // 精確にマッチするか否か
|
||||
h Handler // このルーティング式はどのhandlerに対応するか
|
||||
pattern string //マッチング文字列
|
||||
}
|
||||
|
||||
次に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)
|
||||
}
|
||||
|
||||
ルータでは対応するルーティングルールを保存した後、具体的にはどのようにリクエストを振り分けているのでしょうか?以下のコードをご覧ください。デフォルトのルータは`ServerHTTP`を実装します:
|
||||
|
||||
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).ServerHTTP(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の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
|
||||
* [目次](<preface.md>)
|
||||
* 前へ: [Goはどのようにしてweb作業を行うか](<03.3.md>)
|
||||
* 次へ: [概要](<03.5.md>)
|
||||
|
||||
Reference in New Issue
Block a user