最近自身初の Go 言語での Web アプリを書いており、Go ネタ続きます。
Go は標準ライブラリだけで十分な Web サーバ/アプリの実装が行えると言われています。外部/3rd パーティーのライブラリを使ってその依存性などに悩む必要もないと。確かに触っているとわかるのですが、必要なものが標準で用意されており、素の HTTP の仕組みを意識しながら実装できるので、とても直感的です。フレームワークなどは便利ですが、肝心な部分を隠してしまいますので、何か特別なことをやりたい時、逆にとても単純なことをやりたい時、問題に遭遇した場合、余計に時間を食ってしまうこともあり、本質のところではなく、そのお作法に悩まされることもあり、ストレスを感じることがあるのも事実だと思います。
便利なフレームワークも標準実装に被せて用意されていますので、標準的な仕組みをキチンと理解しておくことは実践的で、応用力も付けつけられるので、今のところ標準ライブラリだけで実装しています。その中で気付いた点を幾つか書いていこうと思います。
Go で HTTP Server の実装を見ているとリクエストの処理の書き方として、いろいろな書き方ができるように見え、当初、個人的に混乱してしまったのがここでした。そういう時は OSS の良いところでソースが読めますので、実際に実装を覗いてみました。
覗いてみるとわかりますが、その考え方は至ってシンプルでした。
- Go では、HTTP のリクエストの処理は、全てハンドラ(Handler)で処理されます。
Go では、リクエストの処理の最初の受け付けは特別に実装を入れない限り、デフォルトのマルチプレクサ(http.DefaultServeMux
)が処理します。マルチプレクサという言葉自体はこれまであまりWebアプリケーションの話の中では使ったことがありませんでしたが、いろいろなリクエストを受け付けてそのリクエストを正しい処理に受け渡してあげるルーティング処理を行う交通整理の担当者のようなもののようです。
実はこのマルチプレクサ自体もハンドラで、リクエストされたパスに基づいて自身に登録されたハンドラを呼び出す処理を行っています。
さて、ハンドラとは何のことでしょうか?
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
ハンドラ(Handler
) は ServeHTTP
の実装を求めるインターフェイスです。この要件を満たす型であればどの型でもハンドラになれます。
- つまるところ、この
ServeHTTP
を実装することが Go での HTTP リクエストの処理となります。
ただ、この ServeHTTP
を直接実装することはあまり多くないのではないでしょうか。
となると、この Handler
はどう扱われているのでしょうか。
実際にサーバが起動される時のソースで追っかけてみます。
Go では、最低以下の記述だけでも Web サーバ、Web アプリケーションの実装が行えます。
package main import ( "fmt" "log" "net/http" ) func hello(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintln(w, "Hello, World!") if err != nil { http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError) } } func main() { http.HandleFunc("/", hello) port := "8080" log.Printf("Listening on port %s", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) }
- ハンドラ関数の登録
http.HandleFunc
を使って、/
のパスにhello
という関数を割り当てています
- サーバの起動
http.ListenAndServe
を使ってサーバを起動します- 因みに第2引数のハンドラに
nil
を指定することで、デフォルトのマルチプレクサ(http.DefaultServeMux
)が使われることになります
これだけで Web サーバと Web アプリが実装できてしまうって不思議で面白いですよね。ちなみに、ここでは ServeHTTP
なんて実装していません。「つまるところ、この ServeHTTP
を実装することが Go での HTTP リクエストの処理となります。」は嘘だったのでしょうか。
最初から見てきます。
ハンドラ関数の登録のところですが、http.HandleFunc
の中身をみてみます。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
DefaultServeMux
の HandleFunc
メソッドに処理が委譲されています。このDefaultServeMux
はデフォルトのマルチプレクサなのですが、この正体は後ほど見ます。ここではこのまま処理を追っかけていきます。DefaultServeMux.HandleFunc
の処理の実体です。
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
更にmux.Handle
を追いかけます。mux.Handle
はDefaultServeMux
(ServeMux
)構造体のフィールド m
、つまり map[string]muxEntry
に pattern
(パス)をキーにハンドラを登録しています。先のサンプルアプリでいうと、/
のパスに hello
ハンドラ関数のハンドラが登録されるということになります。
// Handle registers the handler for the given pattern. // If a handler already exists for pattern, Handle panics. func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() defer mux.mu.Unlock() // ...(snip)... e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e if pattern[len(pattern)-1] == '/' { mux.es = appendSorted(mux.es, e) } // ...(snip)... }
「hello
ハンドラ関数のハンドラ」という紛らわしい書き方をしたのは意味があります。ここでは、「ハンドラ」と「ハンドラ関数」は別ものとして扱っています。mux.Handle
の2つ目の引数は Handler
(ハンドラ) です。hello
自体はハンドラ関数で、ハンドラではありませんでした(ServeHTTP
は実装していませんので) 。いつ Handler
(ハンドラ) になったのでしょうか。hello
ハンドラ関数をハンドラにしたのは、HandlerFunc(handler)
のところでした。
// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
HandlerFunc
は型です。でその型に、ServeHTTP
メソッドの実装が入っています。
これは何を意味しているかと言いますと、特定のシグネチャをもつ関数(ResponseWriter
と *Request
を引数にもつ)であれば、HandlerFunc
で型変換してあげると漏れなく ServerHTTP
メソッドがついてきて(そのメソッド内で関数は実行されます)、ハンドラになれるということです。
そうです。先のサンプルの Web アプリでは、処理を行う関数(ハンドラ関数)は定義していましたが、ServerHTTP
自体の実装はありませんでした。ですが、http.HandleFunc
でそのハンドラ関数を登録することで、ハンドラ関数は ServeHTTP
を持つハンドラに変換されていたのでした。
「つまるところ、この ServeHTTP
を実装することが Go での HTTP リクエストの処理となります」は真実で、その通り直接ハンドラを実装し、ServeHTTP
を実装することもできますし、ServeHTTP
の実装を行わずとも、ResponseWriter
と *Request
を引数にもつハンドラ関数を実装し、http.HandleFunc
を使って登録することで、ハンドラを実装したことになるんですね。
いや、すっきりしました。。また、インターフェイス、構造体、メソッドはこう使うのね、と勉強にもなりました。
さて、途中飛ばしておりましたDefaultServeMux
の正体を見ておきます。何者でしょうか。
Go では、リクエストの処理は通常、デフォルトのマルチプレクサ(http.DefaultServeMux
)が処理します。実体は以下のように定義されています。
// DefaultServeMux is the default ServeMux used by Serve. var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux
実体は ServeMux
型です。これは構造体になっています。
type ServeMux struct { mu sync.RWMutex m map[string]muxEntry es []muxEntry // slice of entries sorted from longest to shortest. hosts bool // whether any patterns contain hostnames }
で、この構造帯のメソッドを見てみますと、ServeHTTP
の実装があります。ServeMux
型は Handler
インターフェイスの要件を満たす型ですので、ハンドラになりえます。
// ServeHTTP dispatches the request to the handler whose // pattern most closely matches the request URL. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { if r.RequestURI == "*" { if r.ProtoAtLeast(1, 1) { w.Header().Set("Connection", "close") } w.WriteHeader(StatusBadRequest) return } h, _ := mux.Handler(r) h.ServeHTTP(w, r) }
まさにこの部分が先に記載しました
「このマルチプレクサ自体も実はハンドラで、リクエストされたパスに基づいて自身に登録されたハンドラ関数を呼び出す処理を行っています。」
の部分になります。
(mux.Handler(r)
がリクエストパスに対する適切なハンドラを返しています。)
ちなみに、デフォルトが DefaultServeMux
と決まる部分は以下の実装です。リクエストが実際に処理されるときには、serverHandler
構造体の ServeHTTP
がまず呼ばれています。
// serverHandler delegates to either the server's Handler or // DefaultServeMux and also handles "OPTIONS *" requests. type serverHandler struct { srv *Server } func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler if handler == nil { handler = DefaultServeMux } if req.RequestURI == "*" && req.Method == "OPTIONS" { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req) }
(上記は conn
構造体の serve(ctx context.Context)
メソッドの中で、serverHandler{c.server}.ServeHTTP(w, w.req)
と呼び出されます。)
こういうことは知らなくても実装はできますが、知っておくと何かと役立ちます。(個人的にはエラー処理を整理するのにこの構造を知っておくことが必要でした。)
The Go gopher was designed by Renée French.