フューチャー技術ブログ

Goのおすすめのフレームワークはnet/http

僕としてはGoのおすすめのフレームワークを聞かれたら、標準ライブラリのnet/httpと答えるようにしています。というよりも、Goの他のフレームワークと呼ばれているものは、このnet/httpのラッパーでしかないからです。

Goでアプリケーションを作成する場合のイメージは次の通り。battery includedなアプローチは他の言語でもたまにありますが、ついてくる機能が今時のものが多くて、標準ライブラリで済むことが多いです。ウェブ開発についてもそんな感じです。

PythonとかRubyとかもそうですが、言語組み込みのウェブサーバー機能はテスト用で本番運用には機能が足りない、性能が足りない、ということから「プロダクションに耐えうるフレームワークを別に入れないと」と思う人も多いんじゃないかな、と思いますが、Goの場合は組み込みのサーバーで問題なかったりします。Node.jsに近いかも?世間にはテスト用のはずだったのにやたら性能が高いPHPの内蔵サーバー(ケンオールで有名な会社の社長の作らしい)なんてものもあったりもしますが・・・

アプリケーションにおけるコードの比率

Goのウェブを語る上で重要な2つの型

Goのnet/httpでは2つのインタフェースを定義しています。

  • http.HandlerFunc
  • http.Handler

前者は、こういうやつ。

func Hello(w http.ResponseWriter, *http.Request) {
}

開発者(フレームワークユーザー)がイベントハンドラを実装するときにイベントハンドラが持つべき型ですね。わかりやすいですね。

後者は構造体でも関数でもなんでもいいが、次のメソッドを持っているやつはレスポンスを受け取れるよ、というやつです。

func (r Receiver) ServeHTTP(http.ResponseWriter, *http.Request) {
}

実は前者の関数も、http.HandlerFunc(Hello)とラップしてあげれば、上記のメソッドが生えてhttp.Handlerになる、というのはあるんですが、Goになじみがある人でも、この感覚は持ちにくいところかな、とは思います。今回はこの話は忘れてしまってもいいです。

このServeHTTP()メソッドが誰が持つかと言うと、標準ライブラリではhttp.ServeMux、いわゆる「Router」というやつです。Goの標準ライブラリのサーバーhttp.Serverは、このServeHTTP()を持つもの(ようするにhttp.Handler)を1つ受け取って、受け取ったリクエストの実際の処理をこいつに委譲します。

通常はhttp.ServeMuxなどのRouterを渡します。これにhttp.HandlerFuncの実際のロジックを登録して、パスごとのロジックを書く、というのがシンプルな状態ですね。

HandlerFuncの動作イメージ

リクエストを受け取ってヘッダーを解析したり、HTTP/2対応だったり、TLSだったりの下回り部分は標準ライブラリで用がすみます。

このServeMuxは他のhttp.Handlerも子供にできるのでネストできます。一部のパスを別のRouterに渡せます。このインタフェースを提供している静的ファイル配信のhttp.FileServerとかhttp.RedirectHandlerとか柔軟に組み合わせられます。

ネストしたルーター

最近のウェブのフレームワークは、ミドルウェアという機構を用意していたりします。リクエストを事前に解釈し、エラー処理をまとめて行ったり、認証チェックをしたり・・・図には書きにくいのですが、これも、http.Handlerとして振る舞い、受け取ったリクエストの処理結果を次のhttp.Handlerに渡すラッパーという実装になります。標準ライブラリのhttp.TimeoutHandlerもこれですね。

ミドルウェア

net/http/httptestといったテスト用パッケージも、http.Handlerを受け取るローカルテスト用サーバーがいたりします。 猫も杓子もhttp.Handlerです。

他のフレームワークはどうか?

まず、Gorillachiは、http.ServeMuxの置き換えて使うRouterを提供しています。置き換えなので、http.Handlerを実装していますし、http.HandlerFunchttp.Handlerも登録できます。サンプルを見てみるとお分かりのように、http.Serverを使って、各ライブラリのRouterを起動するコードになっています。

ハンドラの形式がちょっと特殊っぽいechoはというと、内部ではhttp.Serverを使っています。また、http.Handlerインタフェースは実装しているし、標準ライブラリのhttp.Handlerラップしてechoの中に持ち込むこともできるので、やろうと思えば標準のサーバーの下の一部だけをEchoにしたり、他のライブラリのハンドラをぶら下げることもできます。

Ginも同様に、http.Serverの上に構築されていますし、それ自身がhttp.Handlerインタフェースを満たしていますしhttp.HandlerFuncなどのラッパーで標準形式のハンドラーもかけます。

このhttp.Handlerは他の言語でいうWSGI/Rackとかのような、重要なインタフェースであることがお分かりいただけると思いますし、その上で標準のサーバーが利用上もスタンダードとなっていることがわかるでしょう。

一方で、違いとなっているのが、パスパラメータの切り出しだったり(標準ライブラリでは面倒)、各種ミドルウェアが最初からたくさんついてくるとかの差だったりします。echoは独自のインタフェースのミドルウェアですが、Gorillaはミドルウェアも他のフレームワークから利用できます。

例外はあるのか?

おそらく、fasthttpはnet/httpをラップしてないサーバーなんじゃないかと思います。

fasthttpはnet/httpではなし得ないパフォーマンスを発揮するためにつくられたサーバーです。http.Requestはハンドラーに渡ってくるときには、ヘッダーは完全にパースされてmap[string][]stringに格納されます。一方、fasthttpは不要なパースは避けるために、内部は文字列ですらなく、バイト列で情報を持っています。ヘッダー周りもこのありさまです。

なお、最近Tech Empowerでブイブイ言わせている次のライブラリはみんなこのfasthttpの上に構築されているようです。

こんな感じで特別な事情があれば別実装はありえます。が、ちょっとエクストリームな選択肢ではあると思います。

まとめ

Goでウェブのフレームワークを学ぶ場合、多少の機能差はあれど、どれも言語標準の共通の基盤の上に作られており、自由に組み合わせができることがわかります。net/httpがある意味メタフレームワークとなっています。

net/httpを単独でそのまま使おうとすると、標準のhttp.ServeMuxではちょっとRouterとして機能が少ないな、とかあります。HTML生成をhtml/templateでやろうとしても、他の言語で慣れた記法とちょっと違って面食らって手が動きにくいということもあります。その場合にはchiとか、Go版mustacheなど、いくつかを好きなサードパーティのライブラリに置き換える方法もあります。

echoやGinでもGorillaでもなんでも使っても問題ありません。どれもnet/httpの兄弟です。こいつらの方が、ミドルウェアなどはたくさんリリースしているので、他の言語ユーザーには親しみやすいかもしれません。これをフレームワークと呼ぶかどうか、便利なライブラリ集と見るかはあなた次第です。