Goの準々標準ライブラリの探索

[フューチャーアドベントカレンダー21日目](https://qiita.com/advent-calendar/2020/future)のエントリーです。全25話中、神回が25回あったウルトラマンZが終わってしまいましたね。全日本人のうちの1億2000万人ほどがウルトラマンZロスでうちひしがれているころかと思います。

Goでは、Goをインストールしたときに一緒にインストールされるライブラリを「標準ライブラリ」と呼びます。また、golang.org/xなパッケージ群は準標準ライブラリと呼ばれます。準標準ライブラリには多彩なライブラリが含まれます。今ではもう本家に入ってしまって、後方互換性のためだけに残っているパッケージも一部ありますが・・・

これ以外にも https://github.com/golang/ にはたくさんパッケージがあります。一部はgolang/xのものも入っているのですが、それ以外のものをここでは仮に準々標準ライブラリと呼ぶことにします。

golang.org/xのものは、だいたいリポジトリの説明に``[mirror]``と入っているので、それを見れば識別しやすいかと思います。

![スクリーンショット 2020-12-18 22.42.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6105/8f35945e-589e-e5da-af79-f347d00eaeaf.png)

本エントリーではおすすめの準々標準ライブラリを紹介するとともにそのコードを読んでみます。ちなみに、[go.chromium.org](https://pkg.go.dev/search?q=go.chromium.org)も面白そうなライブラリがいっぱいあって、これの[errors](https://pkg.go.dev/go.chromium.org/luci/common/errors)とか[logging](https://pkg.go.dev/go.chromium.org/luci/common/logging)とか[モック可能なmath/rand](https://pkg.go.dev/go.chromium.org/luci/common/data/rand/mathrand)とか、標準ライブラリをとことん強くしてみた感が興味深いのですが、キリがないのでやめておきます。

あとは、pkg.go.devのコードのリポジトリにも、[TeeProxy](https://github.com/golang/pkgsite/blob/master/internal/teeproxy/teeproxy.go)とか面白げなものがいろいろあったんですが、internalなのでそのままimportできずにコピーせざるを得ないという。やはりinternalは悪い文明！

# github.com/golang/groupcache/consistenthash

github.com/golang/groupcacheはmemcachedのリプレースを狙った、分散キャッシュシステムとのこと。単独のアプリケーションのサーバーと、クライアントライブラリの組み合わせです。で、これは、分散キャッシュでよく利用されるアルゴリズムのコンシステントハッシュです。ワクワクしますね。

引数のkeysは一部peersの方が分かりやすいのではないか、というか、入力が全部keyなのは何かの間違い感があります。これを[利用しているコード](https://github.com/golang/groupcache/blob/8c9f03a8e57eb486e42badaed3fb287da51807ba/http.go#L119-L140)をみる方が分かりやすいですね。

```go
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)
	}
}
```

https://play.golang.org/p/GzEtCydb0Ij

これを利用している親パッケージのコードをみるだけだと、ノードが落ちた時の復旧とかどうするのか、そのあたりはよくわからなかったです。1つでも落ちたらイミュータブルにクラスタ丸ごと再起動とかやっているんですかね？ デフォルトのレプリカ数50というのが強い。斜め読みしかしてないですが、[これを紹介するスライド](https://talks.golang.org/2013/oscon-dl.slide#43)がありました。

# github.com/golang/groupcache/lru

上記のキャッシュサーバー内部で使われているLRUキャッシュです。[hashicorp](https://github.com/hashicorp/golang-lru)の方が強そうですが、まあシンプルで良いですね。わかりやすいです。コードリーディングにもおすすめ。

```go
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)
}
```

https://play.golang.org/p/QonPQi3UwzJ

# github.com/golang/gddo/httputil

Webサービスであるgodoc.orgのリポジトリの中のパッケージです。リアルワールドなアプリケーション向けに作られた生々しいライブラリです。これが紹介したくてこのエントリーを書いたようなものです。サーバーとクライアントの間のコンテンツネゴシエーションを行う関数とか、あるいはバッファリングを行うhttp.ResponseWriterなどがあります。

コンテンツネゴシエーションは、ブラウザが欲しい（理解可能）なフォーマットと、サーバーが提供可能なフォーマットの仲介をする仕組みです。もちろん、みなさん、Real World HTTPですでに学習済みですよね。

きちんとq値を見ての選択もしてくれます。最近は画像フォーマットがいろいろ増えてきていています。お互いにより効率の良い形式で返したい、みたいなことが簡単に実現できます。標準ライブラリのhttputilに入れて欲しい。他にも[NegotiateContentEncoding](https://pkg.go.dev/github.com/golang/gddo/httputil#NegotiateContentEncoding)もあります。

```go
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)
}
```

https://play.golang.org/p/g08pPFJUNM5

[StaticServer](https://pkg.go.dev/github.com/golang/gddo/httputil#StaticServer)は静的ファイルを返すサーバーです。標準ライブラリにも[ServeFile](https://golang.org/pkg/net/http/#ServeFile)あるじゃんと思われた方もいると思いますが、こちらはより積極的なキャッシュ戦略が選択できます。

余談ですが、標準ライブラリのhttp.ServeFileには[脆弱性](https://shogo82148.github.io/blog/2016/04/13/serving-static-files-in-golang/)があるんですね。知りませんでした。ServeFileのドキュメントにも、将来的には..はリジェクトされるよ、と書いてありますね。

こちらのStaticServerはディレクトリごと公開もできますし、特定のファイル(単数or複数)のみの公開もできるAPIも別に用意されています。ホワイトリストでできるのは安心ですね。

[ResponseBuffer](https://pkg.go.dev/github.com/golang/gddo/httputil#ResponseBuffer)は、http.ResponseWriterを満たす構造体です。ミドルウェアでwをこいつに差し替えて、レスポンスの内容をログに出力する、みたいなことが簡単にできますね。

[CacheBusters](https://pkg.go.dev/github.com/golang/gddo/httputil#CacheBusters)はその名の通り、コンテンツが変わった時にキャッシュを使わせないようにクエリーパラメータを付与するライブラリです。ETagとかLast-Modifiedヘッダーを元に生成します。

```go
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製ウェブサイト用の汎用タスクのスクリプトで利用されていて、こちらも興味深いですね。

* https://github.com/golang/pkgsite/blob/master/all.bash

## 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相当のものを[雑ですが自前実装](https://github.com/pyspa/brbundle/blob/105df2a6ff7ec0529cd420651a19b90600576df6/websupport/websupport.go#L16-L30)していたので、先に知っておけば！！！！！みたいに思った悔しさをエントリーにしました。

それ以外にも、有名どころだと github.com/golang/snappy とか、 github.com/golang/freetype とかありますが、そのあたりは今更説明するまでもないでしょう。