いわむぶろぐ

Webエンジニア@スタートアップ@のんびり綴ってます。

Golang + statik + gin で静的ファイルをシングルバイナリにまとめる

Golangはビルドするとシングルバイナリにまとめられ、各環境に配布するときなどはそのシングルバイナリを渡せばデプロイができるのでとても便利です。

しかし、アプリケーションの要件によっては、必要なファイルの形式が増えてきます。例えばWebアプリケーションを作る場合だと画像ファイルやHTMLファイルも不可欠になってきますよね。

今回はそういった静的ファイルをgolangのソース化 => シングルバイナリにまとめてビルドする方法を紹介します。

利用したパッケージ

静的ファイルをgolangのソース化するために rakyll/statik を利用しました。

github.com

他にもパッケージは(go-bindata, go-assets etc...)はあるのですが、 一見人気が高そうに見えたり、参考書に載っていても、すでメンテナンスされていないものもあるのでお気をつけください。

サンプルコード

記事で紹介するコードはこちらにまとめてあります。 AWS API Gateway + AWS Lambda の利用を想定したパターンです。 (開発途中のアプリから抜粋したものです。🙇‍♂️)

github.com

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を生成しないといけないところが簡略化されると嬉しいなあ。

参考

gin,golang,How does golang's static file packaging output combine with gin to output packr as a template

Goで静的ファイルのバイナリ埋め込み - AllIsHackedOff