[ja] apply patch

This commit is contained in:
Shin Kojima
2013-12-25 03:56:26 +09:00
parent 7e2d5ddc3a
commit 2d4e45d2a6
2 changed files with 268 additions and 253 deletions

View File

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

View File

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