rakyll/statik でシングルバイナリにまとめる

【2021-03-20 追記】 Go 1.16 で embed 標準パッケージおよび //go:embed ディレクティブが追加されたため,この記事は不要になった。 一応,過去の記録として残しておく。

以前紹介した『改訂2版 みんなのGo言語』を読んで知ったのだが,これまたずいぶん前に紹介した jessevdk/go-assets はもうメンテされてないらしい。 確かにリポジトリを見ると3年前(2016年)から更新されてないな。

というわけで『改訂2版 みんなのGo言語』を見ながら rakyll/statik を試してみるとしよう。

rakyll/statik のダウンロードとビルド

rakyll/statik のダウンロードとビルドは以下の通り。

$ go get github.com/rakyll/statik@latest
go: finding github.com/rakyll/statik v0.1.6
go: downloading github.com/rakyll/statik v0.1.6
go: extracting github.com/rakyll/statik v0.1.6

おー。 外部パッケージは使わないのか。 善き哉。

ビルドが成功すれば $GOPATH/bin ディレクトリに statik コマンドがインストールされる。

$ statik -h
Usage of statik:
  -Z	Do not use compression to shrink the files.
  -c string
    	The package comment. An empty value disables this comment.
    	 (default "Package statik contains static assets.")
  -dest string
    	The destination path of the generated package. (default ".")
  -f	Overwrite destination file if it already exists.
  -m	Ignore modification times on files.
  -p string
    	Name of the generated package (default "statik")
  -src string
    	The path of the source directory. (default "public")
  -tags string
    	Write build constraint tags

rakyll/statik によるソース・コードの生成と組み込み

今回の構成は以下の通り(ちなみにモジュール名は sample で)。

$ tree .
.
├── go.mod
├── html
│   └── index.html
└── sample.go

このうち html ディレクトリ以下のファイルを実行バイナリに組み込む。

$ statik -src html

これで statik/statik.go が生成される。 中身はこんな感じ。

// Code generated by statik. DO NOT EDIT.

// Package statik contains static assets.
package statik

import (
	"github.com/rakyll/statik/fs"
)

func init() {
	data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x92=/O\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\x00	\x00index.htmlUT\x05\x00\x01\xe5\xeb}]\\\x8e1\xcb\xc2@\x0c@\xe7\xaf\xbf\xe2z\xf3W\x8a\x9bC\xae\x8b\nn:\x08\xe2\x18\xef\"\x0d\xa4w\xa5\x8d-\xfe{i\x0f\x1d\x9cB^\x1e\xe1A\xb9?\xed.\xb7\xf3\xc1\xb4\xdaIS\xc0g\x10\x86\xa6\xf8\x83\x8e\x14\x8doq\x18I\x9d}\xea\xa3\xda\xda/\x8f\xd8\x91\xb3\x13\xd3\xdc\xa7A\xad\xf1)*Euv\xe6\xa0\xad\x0b4\xb1\xa7j]\xfe\x0dGVF\xa9F\x8fBn\xb3~QV\xa1\xe6H\"\xc9\\\xd3 \xa1\x84:\xb3\x02\xea\x9c\x00\xf7\x14^\x8b\xdb\xffx\xfd\xe2\xe4#\xd4k\xf5;\x00\x00\xff\xffPK\x07\x08\x13\xf25U\x97\x00\x00\x00\xcc\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x08\x00\x92=/O\x13\xf25U\x97\x00\x00\x00\xcc\x00\x00\x00\n\x00	\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x00index.htmlUT\x05\x00\x01\xe5\xeb}]PK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00A\x00\x00\x00\xd8\x00\x00\x00\x00\x00"
	fs.Register(data)
}

html/index.html ファイルの中身は圧縮されて格納されるようだ。 -Z オプションで圧縮を解除できるが,まぁ普通は圧縮するだろう(笑)

生成された statik サブパッケージを使って以下のように HTTP サーバを構成してみる。

package main

import (
	"fmt"
	"net/http"
	"os"

	_ "sample/statik"

	"github.com/rakyll/statik/fs"
)

func main() {
	fmt.Println("Open http://localhost:3000/")
	fmt.Println("Press ctrl+c to stop")

	statikFs, err := fs.New()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	http.Handle("/", http.FileServer(statikFs))
	if err := http.ListenAndServe(":3000", nil); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}

これで

$ go run sample.go 
Open http://localhost:3000/
Press ctrl+c to stop

としてサーバを起動しブラウザで http://localhost:3000/ にアクセスすれば html/index.html ファイルの内容が表示される。

statik/fs.New() 関数は http.FileSystem インスタンスを返す。 http.FileSystem は interface 型で,以下のように定義されている。

// A FileSystem implements access to a collection of named files.
// The elements in a file path are separated by slash ('/', U+002F)
// characters, regardless of host operating system convention.
type FileSystem interface {
	Open(name string) (File, error)
}

http.File も interface 型で,以下のように定義されている。

// A File is returned by a FileSystem's Open method and can be
// served by the FileServer implementation.
//
// The methods should behave the same as those on an *os.File.
type File interface {
	io.Closer
	io.Reader
	io.Seeker
	Readdir(count int) ([]os.FileInfo, error)
	Stat() (os.FileInfo, error)
}

つまり rakyll/statik パッケージで生成されるファイルシステムを使えばこれだけの操作ができるということだ。 上手く使っていきたい。

go generate コマンドによるソース・コードの生成

// コメントに go:generate マーカを使うことにより go generate コマンドで statik コマンドを呼び出せる。

package main

import (
	"fmt"
	"net/http"
	"os"

	_ "sample/statik"

	"github.com/rakyll/statik/fs"
)

//go:generate statik -src html

func main() {
	fmt.Println("Open http://localhost:3000/")
	fmt.Println("Press ctrl+c to stop")

	statikFs, err := fs.New()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	http.Handle("/", http.FileServer(statikFs))
	if err := http.ListenAndServe(":3000", nil); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
}
$ go generate

$ go run sample.go 
Open http://localhost:3000/
Press ctrl+c to stop

go generate コマンドは明示的に行う必要があるので注意。

ブックマーク

参考図書

photo
改訂2版 みんなのGo言語
松木 雅幸 (著), mattn (著), 藤原 俊一郎 (著), 中島 大一 (著), 上田 拓也 (著), 牧 大輔 (著), 鈴木 健太 (著)
技術評論社 2019-08-01 (Release 2019-08-01)
Kindle版
B07VPSXF6N (ASIN)
評価     

改訂2版の目玉は7章の「データベースの扱い方」が追加されたことだろう。他の章では,大まかな構成は1版と同じだが細かい部分が変わっていて Go 1.12 への言及まであるのには驚いた。

reviewed by Spiegel on 2019-08-12 (powered by PA-APIv5)

photo
プログラミング言語Go
アラン・ドノバン (著), ブライアン・カーニハン (著), 柴田芳樹 (著)
丸善出版 2016-06-20 (Release 2021-07-13)
Kindle版
B099928SJD (ASIN)
評価     

Kindle 版出た! 一部内容が古びてしまったが,この本は Go 言語の教科書と言ってもいいだろう。感想はこちら

reviewed by Spiegel on 2021-05-22 (powered by PA-APIv5)