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 := sh.srv.Handler、つまり、我々がさきほどListenAndServeをコールした時、その2つ目の引数です。前の例でnilを渡したのですが、これは空ということです。デフォルトでhandler = DefaultServeMuxを取得します。この変数は一体何に使われるのでしょうか?そうです。この変数はルータです。これはマッチするurlを対応するhandler関数にリダイレクトするために用いられます。我々はこれを設定したでしょうか?ええ。我々がコールしたコードのいの一番でhttp.HandleFunc("/", sayhelloName)をコールしたじゃないですか。これは/をリクエストするルートのルールを登録します。urlが"/"をリクエストした場合、ルートは関数sayhelloNameにリダイレクトします。DefaultServeMuxはServeHTTPメソッドをコールします。このメソッド内では実はsayhelloName本体をコールしています。最後にresponseの情報を入力することで、クライアントにフィードバックを返します。

全体のフローの詳細は以下の図の通りです:

図3.10 http接続の処理フロー

ここに来て我々は3つの問題に対して全て解答を得ました。Goが如何にWebを走らせるか、すでに基本的なことは理解されたのではないでしょうか?

results matching ""

    No results matching ""