Golang + statik + gin で静的ファイルをシングルバイナリにまとめる
Golangはビルドするとシングルバイナリにまとめられ、各環境に配布するときなどはそのシングルバイナリを渡せばデプロイができるのでとても便利です。
しかし、アプリケーションの要件によっては、必要なファイルの形式が増えてきます。例えばWebアプリケーションを作る場合だと画像ファイルやHTMLファイルも不可欠になってきますよね。
今回はそういった静的ファイルをgolangのソース化 => シングルバイナリにまとめてビルドする方法を紹介します。
利用したパッケージ
静的ファイルをgolangのソース化するために rakyll/statik を利用しました。
他にもパッケージは(go-bindata, go-assets etc...)はあるのですが、 一見人気が高そうに見えたり、参考書に載っていても、すでメンテナンスされていないものもあるのでお気をつけください。
サンプルコード
記事で紹介するコードはこちらにまとめてあります。 AWS API Gateway + AWS Lambda の利用を想定したパターンです。 (開発途中のアプリから抜粋したものです。🙇♂️)
webフレームワークの gin を利用しました。
ディレクトリ構造
必要なところを抜粋しています。
$ tree . . ├── Makefile ├── controller │ └── authorize.go ├── go.mod ├── go.sum ├── main.go └── templates └── authorize.html
statikコマンドを使ってgolangのソース化
templates/ ファイルにHTMLがまとまっている想定です。
└── templates └── authorize.html
以下のコマンドを打つと、templates/配下のファイルがgolangのソース化されます。
$ statik -src=templates/
statik/statik.goが生成されます。
├── statik │ └── statik.go
statik.go
// Code generated by statik. DO NOT EDIT. package statik import ( "github.com/rakyll/statik/fs" ) func init() { data := "PK\x03\x04\x14\x00\x08\x00\x08\x00\x87k\xe5P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00 \x00authorize.htmlUT\x05\x00\x01o\xd5\x01_\xccWO\xaf\x1b5\x10...." fs.Register(data) }
templates/配下の静的ファイルが zipdata["default"]の中に保存され、アプリからデータを利用できるようになりました。
利用方法
ここからはコードの中にコメントしていきます。
main.go
import ( "context" "io/ioutil" "html/template" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ginadapter "github.com/awslabs/aws-lambda-go-api-proxy/gin" "github.com/gin-gonic/gin" "github.com/koheiiwamura/golang-statik-gin-sample/functions/oauth/controller" // 生成した statik パッケージをimportする _ "github.com/koheiiwamura/golang-statik-gin-sample/functions/oauth/statik" "github.com/rakyll/statik/fs" ) func initTemplates() *template.Template { // zipdata["default"]の値("/templates/" 配下のデータ)を取得したhttp.FileSystemを生成。 statikFs, err := fs.New() if err != nil { panic(err) } // "/templates/authorize.html"のデータを取得 f, err := statikFs.Open("/authorize.html") if err != nil { panic(err) } // "authorize.html"を読み込む b, err := ioutil.ReadAll(f) if err != nil { panic(err) } // template.Template を生成 t, err := template.New("authorize.html").Parse(string(b)) if err != nil { panic(err) } return t } func init() { r := gin.Default() // ginのHTMLレンダラーに、テンプレートを関連付ける。 r.SetHTMLTemplate(initTemplates())
controller/authorize.go
package controller import ( "net/http" "strings" "github.com/gin-gonic/gin" ) // ReplceText replace texts for template.Execute type ReplceText struct { ClientID string RedirectURI string Scope []string } func Authorize(c *gin.Context) { clientID := c.Query("client_id") redirectURI := c.Query("redirect_uri") scope := c.Query("scope") responseType := c.Query("response_type") state := c.Query("state") replceText := ReplceText{ ClientID: clientID, RedirectURI: redirectURI, Scope: strings.Split(scope, " "), } // Content-Type を "text/html" に変換したり、template.Executeも行ってHTMLをレンダリングする c.HTML(http.StatusOK, "authorize.html", replceText) return }
まとめ
今回のコードはapi-gateway + lambda向けに書いたので、サーバーレスでアプリを開発したいときなどに参考になると嬉しいです。
ファイルからバイナリデータを取得して、html.Templateを生成しないといけないところが簡略化されると嬉しいなあ。