Utlizing go:embed to Wrap Files Into Binary

透過 go:embed 在編譯時將檔案包入

前言

紀錄使用到其中一項 Go 1.16 的 embed 功能,可以把任何檔案在編譯時就包進去,進而不用煩惱路徑與環境問題。

使用時機

在寫某項權限功能時會需要讀取本地的 JSON 設定,我大可以直接讀取檔案使用它:

if data, err := os.ReadFile("./rbac.json"); err != nil {
panic(err)
}

但可能會遇到:

  • 部署到 Docker 後忘了把檔案 COPY 進去
  • CI / 測試環境的工作目錄不同
  • binary 被丟到其他機器執行,卻找不到相對路徑
  • 想做成單一可執行檔發佈

單檔案

我自己在跑測試環境時就覺得動態抓檔案很麻煩。為了不讓程式依賴外部檔案設定,可以在編譯時就透過 embed 包進 binary:

package constants
import _ "embed"
//go:embed rbac.json
var RBACJSON []byte
if err := json.Unmarshal(constants.RBACJSON, &parsed); err != nil {
println("ERROR: Failed to unmarshal embedded rbac.json:", err.Error())
return
}

這個很像魔法的註解://go:embed 是一個編譯指令(compiler directive),編譯後,rbac.json 內容就已經存在 rbacData 變數中。

變數型別可以是:

  • string
  • []byte
  • embed.FS

多檔案

package main
import (
_ "embed"
"fmt"
)
//go:embed messages/*.txt
var messages embed.FS
func main() {
files, _ := messages.ReadDir("messages")
for _, file := range files {
data, _ := messages.ReadFile("messages/" + file.Name())
fmt.Printf("File: %snContent: %snn", file.Name(), data)
}
}

總結

比較項目os.ReadFileembed
檔案來源執行時讀取編譯時嵌入
是否依賴路徑
發佈方式需帶檔案單一 binary
適合場景動態變更檔案靜態設定檔

如果資料需要在 runtime 被修改,那就不適合使用 embed,但如果它是「版本的一部分」,那 embed 是更安全的選擇。

  • embed 是編譯時決定內容,修改檔案後必須重新編譯
  • 檔案會增加 binary 體積,不適合嵌入大型資源(例如影片)
  • 不能嵌入專案外的檔案

延伸閱讀