[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作業を行うか
前の節でどのようにGoを通じてWebサービスを立てるかご紹介しました。net/httpパッケージを簡単に応用して便利に立てることができたかと思います。では、Goは低レイヤーで一体何を行なっているのでしょうか万物は姿を変えてもその元は同じであります。GoのWebサービス作業も第一章でご紹介したWebの作業方法に関係しています。
## webの作業方法のいくつかの概念
以下はどれもサーバの概念のいくつかです
Requestユーザが要求するデータ。ユーザのリクエスト情報を解析します。post、get、cookie、url等の情報を含みます。
Responseサーバがクライアントにデータをフィードバックする必要があります。
Connユーザの毎回のリクエストリンクです。
Handlerリクエストを処理し、返すデータを生成する処理ロジック。
## httpパッケージが実行する機能を分析する
下の図はGoが実現するWebサービスの作業モードのプロセス図です
![](images/3.3.http.png?raw=true)
図3.9 httpパッケージの実行フロー
1. Listen Socketを作成し、指定したポートを関しします。クライアントのリクエストを待ちます。
2. Listen Socketはクライアントのリクエストを受け付けます。Client Socketを得ると、Client Socketを通じてクライアントと通信を行います。 
3. クライアントのリクエストを処理します。まず、Client SocketからHTTPリクエストのプロトコルヘッダを読み取り、もしPOSTメソッドであれば、クライアントが入力するデータをさらに読み取るかもしれません。その後対応するhandlerがリクエストを処理します。handlerがクライアントの要求するデータを準備し終えたら、Client Socketを通じてクライアントに書き出します。
この全体のプロセスではつの問題についてだけ理解しておけば構いません。これはまた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`をコールした時、そのつ目の引数です。前の例でnilを渡したのですが、これは空ということです。デフォルトで`handler = DefaultServeMux`を取得します。この変数は一体何に使われるのでしょうかそうです。この変数はルータです。これはマッチするurlを対応するhandler関数にリダイレクトするために用いられます。我々はこれを設定したでしょうかええ。我々がコールしたコードのいの一番で`http.HandleFunc("/", sayhelloName)`をコールしたじゃないですか。これは`/`をリクエストするルートのルールを登録します。urlが"/"をリクエストした場合、ルートは関数sayhelloNameにリダイレクトします。DefaultServeMuxはServeHTTPメソッドをコールします。このメソッド内では実はsayhelloName本体をコールしています。最後にresponseの情報を入力することで、クライアントにフィードバックを返します。
全体のフローの詳細は以下の図の通りです:
![](images/3.3.illustrator.png?raw=true)
図3.10 http接続の処理フロー
ここに来て我々はつの問題に対して全て解答を得ました。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サービスの作業モードのプロセス図です
![](images/3.3.http.png?raw=true)
図3.9 httpパッケージの実行フロー
1. Listen Socketを作成し、指定したポートを関しします。クライアントのリクエストを待ちます。
2. Listen Socketはクライアントのリクエストを受け付けます。Client Socketを得ると、Client Socketを通じてクライアントと通信を行います。 
3. クライアントのリクエストを処理します。まず、Client SocketからHTTPリクエストのプロトコルヘッダを読み取り、もしPOSTメソッドであれば、クライアントが入力するデータをさらに読み取るかもしれません。その後対応するhandlerがリクエストを処理します。handlerがクライアントの要求するデータを準備し終えたら、Client Socketを通じてクライアントに書き出します。
この全体のプロセスではつの問題についてだけ理解しておけば構いません。これはまた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`をコールした時、そのつ目の引数です。前の例でnilを渡したのですが、これは空ということです。デフォルトで`handler = DefaultServeMux`を取得します。この変数は一体何に使われるのでしょうかそうです。この変数はルータです。これはマッチするurlを対応するhandler関数にリダイレクトするために用いられます。我々はこれを設定したでしょうかええ。我々がコールしたコードのいの一番で`http.HandleFunc("/", sayhelloName)`をコールしたじゃないですか。これは`/`をリクエストするルートのルールを登録します。urlが"/"をリクエストした場合、ルートは関数sayhelloNameにリダイレクトします。DefaultServeMuxはServeHTTPメソッドをコールします。このメソッド内では実はsayhelloName本体をコールしています。最後にresponseの情報を入力することで、クライアントにフィードバックを返します。
全体のフローの詳細は以下の図の通りです:
![](images/3.3.illustrator.png?raw=true)
図3.10 http接続の処理フロー
ここに来て我々はつの問題に対して全て解答を得ました。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パッケージ詳細
前の節でGoが如何にWebの作業モードを実現するかフローをご紹介しました。この節では、httpパッケージを詳しく解剖していきます。これはどのように全体のプロセスを実現しているのでしょうか。
Goのhttpにはつのコアとなる機能があります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にはつのコアとなる機能があります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>)