はじめに
他の言語でweb開発の経験がある人 (筆者) がgolangに入門する際に勉強したことを一連の記事でまとめていきます。
やること
記事においてはweb開発において必要な機能をはじめとした、ある程度実践的な範囲を対象とします。基本的には対象とする機能を実装したコードを記載します。またコードだけではなく、ドキュメントを踏まえた概念的な説明を挟んでいきます。
やらないこと
開発環境の構築方法や基本文法の説明は省略します。
今回のテーマ
http.Handleを用いてHello World!を表示させるまでの流れを説明します。
概念編
以下は[net/httpのdocument] (https://godoc.org/net/http)から抜粋しています。
golangでは他の言語と同様にHTTP requestに応じてresponseを生成します。ざっくりとした流れは以下の通りです (簡単のため、今回はURLにのみ注目します) 。
- HTTP requestの中身を確認する
- URLをparseし、事前に登録しておいたpatternとmatchするものを探す
- matchするpatternがあれば、そのpatternに応じたhandlerを元にresponseを生成 (なければerror処理)
上記の流れで必要なpatternとのmatchを行なったり、patternとhandlerの対応を登録したりするものをマルチプレクサと言います。net/httpにおいてはfunc Handleを呼び出した際に、引数として渡したhandlerが[type ServeMux] (https://godoc.org/net/http#ServeMux)に登録されています。そして、2や3においてServeMuxの情報を用いて処理が実行されます。
コード編
[github] (https://github.com/ryuji0123/go-samples/blob/master/mux.go)
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct{}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
func main() {
hello := HelloHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.Handle("/", &hello)
server.ListenAndServe()
}
コードの実行・確認
ターミナルから実行できます。
go run mux.go
ブラウザ上でlocalohost:8080と入力すれば結果が見れます。また、
curl localhost:8080/
としても確認できます。
コードの解説
上記のコードのうち、以下の部分がやや奇妙な挙動をしているように感じます。
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
fmt.Fprintfという部分からprint関数を呼び出していることがわかります。print関数といえば、ターミナルに出力を返すものとして使用することが多いと思います。例えばお馴染みのpython3では以下のように書きます。
print('Hello World!')
両者の違いは何でしょうか?ポイントはFprintf関数の第1引数として指定されているhttp.ResponseWriterです。
fmt.Fprintfのdocumentによると以下のように説明がなされています。
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
Fprintf formats according to a format specifier and writes to w. It returns the number of bytes written and any write error encountered.
第1引数で指定したinput/output writerに出力先を指定していることが分かります。また、[実際のコード] (https://golang.org/src/fmt/print.go?s=5516:5593#L192)では以下のように記述されています。
// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
全てのコードを掲載するのは冗長なので流れを説明します。Fprintfで行なっている操作は以下の通りです。
- printer (p) の生成
- doPrintfを実行し、printerのbufferに情報を格納。この時、入力されたformatの型がstringからbyteに変換される。
- printerのbufferから情報を読み取りi/o writerが示す出力先に出力
- printerの解放
このように、i/oなどにおいて一時的に情報を格納する領域をbufferと言います。fmtにおいてはprinterをtype pp structという構造体で定義した上で上記のようなbufferを用いた出力を可能にしています。なお、golangと対比させる形で紹介したpythonでも省略されている第3引数fileに明示的に出力先を指定することができます。これにより、stdoutつまりターミナルから別の場所へと出力先を変更できます。
最後に
今後もライブラリのソースコードやdocumentの記載を用いる形で解説を行なっていきたいと思います。