フューチャーアドベントカレンダー21日目のエントリーです。全25話中、神回が25回あったウルトラマンZが終わってしまいましたね。全日本人のうちの1億2000万人ほどがウルトラマンZロスでうちひしがれているころかと思います。
Goでは、Goをインストールしたときに一緒にインストールされるライブラリを「標準ライブラリ」と呼びます。また、golang.org/xなパッケージ群は準標準ライブラリと呼ばれます。準標準ライブラリには多彩なライブラリが含まれます。今ではもう本家に入ってしまって、後方互換性のためだけに残っているパッケージも一部ありますが・・・
これ以外にも https://github.com/golang/ にはたくさんパッケージがあります。一部はgolang/xのものも入っているのですが、それ以外のものをここでは仮に準々標準ライブラリと呼ぶことにします。
golang.org/xのものは、だいたいリポジトリの説明に[mirror]
と入っているので、それを見れば識別しやすいかと思います。
本エントリーではおすすめの準々標準ライブラリを紹介するとともにそのコードを読んでみます。ちなみに、go.chromium.orgも面白そうなライブラリがいっぱいあって、これのerrorsとかloggingとかモック可能なmath/randとか、標準ライブラリをとことん強くしてみた感が興味深いのですが、キリがないのでやめておきます。
あとは、pkg.go.devのコードのリポジトリにも、TeeProxyとか面白げなものがいろいろあったんですが、internalなのでそのままimportできずにコピーせざるを得ないという。やはりinternalは悪い文明!
github.com/golang/groupcache/consistenthash
github.com/golang/groupcacheはmemcachedのリプレースを狙った、分散キャッシュシステムとのこと。単独のアプリケーションのサーバーと、クライアントライブラリの組み合わせです。で、これは、分散キャッシュでよく利用されるアルゴリズムのコンシステントハッシュです。ワクワクしますね。
引数のkeysは一部peersの方が分かりやすいのではないか、というか、入力が全部keyなのは何かの間違い感があります。これを利用しているコードをみる方が分かりやすいですね。
package main
import (
"fmt"
"github.com/golang/groupcache/consistenthash"
)
func main() {
h := consistenthash.New(3, nil)
h.Add("host1", "host2", "host3", "host4", "host5")
self := "host1" // 自分
peer := h.Get("content's key")
if self == peer {
fmt.Println("このファイルはこのノードにあります。")
} else {
fmt.Printf("このファイルは%sにあります\n", peer)
}
}
これを利用している親パッケージのコードをみるだけだと、ノードが落ちた時の復旧とかどうするのか、そのあたりはよくわからなかったです。1つでも落ちたらイミュータブルにクラスタ丸ごと再起動とかやっているんですかね? デフォルトのレプリカ数50というのが強い。斜め読みしかしてないですが、これを紹介するスライドがありました。
github.com/golang/groupcache/lru
上記のキャッシュサーバー内部で使われているLRUキャッシュです。hashicorpの方が強そうですが、まあシンプルで良いですね。わかりやすいです。コードリーディングにもおすすめ。
package main
import (
"fmt"
"github.com/golang/groupcache/lru"
)
func main() {
c := lru.New(10) // 0だと無限
// 計算結果をキャッシュ
c.Add(2, 2 * 2)
c.Add(3, 3 * 3)
c.Add(4, 4 * 4)
result, found := c.Get(4)
fmt.Println(result, found)
}
github.com/golang/gddo/httputil
Webサービスであるgodoc.orgのリポジトリの中のパッケージです。リアルワールドなアプリケーション向けに作られた生々しいライブラリです。これが紹介したくてこのエントリーを書いたようなものです。サーバーとクライアントの間のコンテンツネゴシエーションを行う関数とか、あるいはバッファリングを行うhttp.ResponseWriterなどがあります。
コンテンツネゴシエーションは、ブラウザが欲しい(理解可能)なフォーマットと、サーバーが提供可能なフォーマットの仲介をする仕組みです。もちろん、みなさん、Real World HTTPですでに学習済みですよね。
きちんとq値を見ての選択もしてくれます。最近は画像フォーマットがいろいろ増えてきていています。お互いにより効率の良い形式で返したい、みたいなことが簡単に実現できます。標準ライブラリのhttputilに入れて欲しい。他にもNegotiateContentEncodingもあります。
package main
import (
"fmt"
"net/http/httptest"
"github.com/golang/gddo/httputil"
)
func main() {
req := httptest.NewRequest("GET", "http://localhost:8080/item", nil)
req.Header.Set("Accept", "image/avif, image/webp;q=0.9, */*;q=0.8")
t := httputil.NegotiateContentType(req, []string{"image/avif", "mage/heif", "image/heic", "image/webp", "image/png"}, "image/png")
fmt.Printf("acceptable content-type: %s\n", t)
}
StaticServerは静的ファイルを返すサーバーです。標準ライブラリにもServeFileあるじゃんと思われた方もいると思いますが、こちらはより積極的なキャッシュ戦略が選択できます。
余談ですが、標準ライブラリのhttp.ServeFileには脆弱性があるんですね。知りませんでした。ServeFileのドキュメントにも、将来的には..はリジェクトされるよ、と書いてありますね。
こちらのStaticServerはディレクトリごと公開もできますし、特定のファイル(単数or複数)のみの公開もできるAPIも別に用意されています。ホワイトリストでできるのは安心ですね。
ResponseBufferは、http.ResponseWriterを満たす構造体です。ミドルウェアでwをこいつに差し替えて、レスポンスの内容をログに出力する、みたいなことが簡単にできますね。
CacheBustersはその名の通り、コンテンツが変わった時にキャッシュを使わせないようにクエリーパラメータを付与するライブラリです。ETagとかLast-Modifiedヘッダーを元に生成します。
package main
import (
"fmt"
"net/http"
"github.com/golang/gddo/httputil"
)
func main() {
cbs := &httputil.CacheBusters{Handler: http.FileServer(http.Dir("."))}
path := cbs.AppendQueryParam("main.go", "cache")
fmt.Printf("path: %s\n", path)
// path: main.go?cache=Fri-18Dec202022-45-40GMT
}
github.com/golang/gddo/log
ロガーをcontext.Contextに差し込むミドルウェアです。contextの使用例として無駄がない。
その他の公式サービスが使っている気になったサードパーティライブラリ/ツール
公式が使っているのだからきっと質が高いものも多かろう、ということで。自分が知らなかったものをピックアップ。
github.com/client9/misspell/cmd/misspell
github.com/golang/blogやgithub.com/golang/pkgsiteで使われていたものです。名前からしてスペルチェックですよね。
sourcegraph.com/sourcegraph/go-template-lint
github.com/golang/blogやgithub.com/golang/pkgsiteで使われていたものです。テンプレートのLinterのようです。上のツールと同様、にGo製ウェブサイト用の汎用タスクのスクリプトで利用されていて、こちらも興味深いですね。
github.com/inconshreveable/log15
github.com/golang/gddo/logで使われていたライブラリです。シンプルな構造化ログとのこと。logfmt形式でデフォルトで出力されるが、JSONでも出力できるとのこと。
なお、github.com/golang/gddo/logはcontext.Contextにロガーを格納していました。context.Contextにはロガーなどは入れないみたいな説明が日本では見かけたことがありますが、公式がやっているんで、入れちゃってもいいってことですね。
github.com/yuin/goldmark
これも同じく、github.com/golang/websiteで使われていたMarkdownパーサーですね。
github.com/magiconair/properties
Javaのpropertiesファイルを読み込んで構造体にマッピングするライブラリ。エンタープライズな香りがする!
まとめ
最近、github.com/golangを覗いてみたらいろいろ見つけたのでその紹介でした。brotliをサポートしていたら、内部で持っているbrotli形式のデータをダイレクトに返すアセットバンドラー&http.FileServerもどき(インタフェースは満たしてないので別物)を実装したときに、httputil.NegotiateContentEncoding相当のものを雑ですが自前実装していたので、先に知っておけば!!!!!みたいに思った悔しさをエントリーにしました。
それ以外にも、有名どころだと github.com/golang/snappy とか、 github.com/golang/freetype とかありますが、そのあたりは今更説明するまでもないでしょう。