[golang]Go內嵌靜態資源go-bindata的安裝及使用
- 2019 年 10 月 7 日
- 筆記
使用 Go 開發應用的時候,有時會遇到需要讀取靜態資源的情況。比如開發 Web 應用,程式需要載入模板文件生成輸出的 HTML。在程式部署的時候,除了發布應用可執行文件外,還需要發布依賴的靜態資源文件。這給發布過程添加了一些麻煩。既然發布單獨一個可執行文件是非常簡單的操作,就有人會想辦法把靜態資源文件打包進 Go 的程式文件中。下面就來看一些解決方案:
go-bindata
go-bindata 是目前我的程式 pugo
在用的嵌入靜態資源的工具。它可以把靜態文件嵌入到一個 go 文件中,並提供一些操作方法。
安裝
go-bindata
: go get -u github.com/jteeuwen/go-bindata/…
注意 go get 地址最後的三個點 ...
。這樣會分析所有子目錄並下載依賴編譯子目錄內容。go-bindata
的命令工具在子目錄中。(還要記得把 $GOPATH/bin
加入系統 PATH
)。
使用命令工具 go-bindata
( pugo 的例子):
go-bindata -o=app/asset/asset.go -pkg=asset source/… theme/… doc/source/… doc/theme/…
-o
輸出文件到 app/asset/asset.go
,包名 -pkg=asset
,然後是需要打包的目錄,三個點包括所有子目錄。這樣就可以把所有相關文件打包到 asset.go
且開頭是 package asset
保持和目錄一致。
pugo 里釋放靜態文件的程式碼:
dirs := []string{"source", "theme", "doc"} // 設置需要釋放的目錄 for _, dir := range dirs { // 解壓dir目錄到當前目錄 if err := asset.RestoreAssets("./", dir); err != nil { isSuccess = false break } } if !isSuccess { for _, dir := range dirs { os.RemoveAll(filepath.Join("./", dir)) } }
asset.go
內的靜態內容還是根據實際的目錄位置索引。所以我們可以直接通過目錄或者文件地址去操作。
-debug 開發模式
go-bindata
支援開發模式,即不嵌入靜態內容,只生成操作方法到輸出的 go 程式碼中,如:
go-bindata -debug -o=app/asset/asset.go -pkg=asset source/… theme/… doc/source/… doc/theme/…
-debug
參數開啟開發模式。生成的程式碼會直接去讀取靜態文件到記憶體,而不是編碼到程式碼中。程式碼文件更小,你更快速的編寫業務邏輯。
// -pkg=asset, 打包的包名是 asset bytes, err := asset.Asset("theme/default/post.html") // 根據地址獲取對應內容 if err != nil { fmt.Println(err) return } t, err := template.New("tpl").Parse(string(bytes)) // 比如用於模板處理 fmt.Println(t, err)
http.FileSystem
http.FileSystem
是定義 HTTP 靜態文件服務的介面。go-bindata
的第三方包 go-bindata-assetfs 實現了這個介面,支援 HTTP 訪問靜態文件目錄的行為。以我們上面編譯好的 asset.go
為例:
import ( "net/http" "github.com/elazarl/go-bindata-assetfs" "github.com/go-xiaohei/pugo/app/asset" // 用 pugo 的asset.go進行測試 ) func main() { fs := assetfs.AssetFS{ Asset: asset.Asset, AssetDir: asset.AssetDir, AssetInfo: asset.AssetInfo, } http.Handle("/", http.FileServer(&fs)) http.ListenAndServe(":12345", nil) }
訪問 http://localhost:12345
,就可以看到嵌入的 source
,theme
,doc
的目錄列表頁面,和 Nginx 查看靜態文件目錄一樣的。
go.rice
go.rice 也支援打包靜態文件到 go 文件中,但是行為和 go-bindata
很不相同。從使用角度,go.rice
其實是更便捷的靜態文件操作庫。打包靜態文件反而是順帶的功能。
安裝和 go-bindata
一樣,注意 三個點:
go get github.com/GeertJohan/go.rice/…
go.rice
把一個目錄認為是一個 rice.Box
操作:
import ( "fmt" "html/template" "github.com/GeertJohan/go.rice" ) func main() { // 這裡寫相對於的執行文件的地址 box, err := rice.FindBox("theme/default") if err != nil { println(err.Error()) return } // 從目錄 Box 讀取文件 str, err := box.String("post.html") if err != nil { println(err.Error()) return } t, err := template.New("tpl").Parse(str) fmt.Println(t, err) }
rice 命令
go.rice
的打包命令是 rice
。用起來非常直接:在有使用 go.rice 操作的 go 程式碼目錄,直接執行 rice embed-go
:
rice embed-go rice -i "github.com/fuxiaohei/xyz" embed-go // -i 處理指定包里的 go.rice 操作
他就會生成當前包名下的、嵌入了文件的程式碼 rice-box.go
。但是,它不遞歸處理 import。他會分析當前目錄下的 go 程式碼中 go.rice
的使用,找到對應需要嵌入的文件夾。但是子目錄下的和 import
的裡面的 go.rice
使用不會分析,需要你手動 cd 過去或者 -i
指定要處理的包執行命令。這點來說非常的不友好。
http.FileSystem
go.rice
是直接支援 http.FileSystem
介面:
func main() { // MustFindBox 出錯直接 panic http.Handle("/", http.FileServer(rice.MustFindBox("theme").HTTPBox())) http.ListenAndServe(":12345", nil) }
有點略繁瑣的是 rice.FindBox(dir)
只能載入一個目錄。因此需要多個目錄的場景,會有程式碼:
func main() { http.Handle("/img", http.FileServer(rice.MustFindBox("static/img").HTTPBox())) http.Handle("/css", http.FileServer(rice.MustFindBox("static/css").HTTPBox())) http.Handle("/js", http.FileServer(rice.MustFindBox("static/js").HTTPBox())) http.ListenAndServe(":12345", nil) }
esc
esc 的作者在研究幾款嵌入靜態資源的工具後,發覺都不好用,就自己寫出了 esc
。它的需求很簡單,就是嵌入靜態資源 和 支援 http.FileSystem
。esc
工具也這兩個主要功能。
安裝 esc
:
go get github.com/mjibson/esc
使用方法和 go-bindata
類似:
// 注意 esc 不支援 source/… 三個點表示所有子目錄 go-bindata -o=asset/asset.go -pkg=asset source theme doc/source doc/theme
直接支援 http.FileSystem
:
import ( "net/http" "asset" // esc 生成 asset/asset.go ) func main() { fmt.Println(asset.FSString(false, "/theme/default/post.html")) // 讀取單個文件 http.ListenAndServe(":12345", http.FileServer(asset.FS(false))) // 支援 http.FileSystem,但是沒有做展示目錄的支援 }
esc
有個較大的問題是只能一個一個文件操作,不能文件夾操作,沒有類似go-bindata
的 asset.RestoreDir()
方法。並且沒有方法可以列出嵌入的文件的列表,導致也無法一個一個文件操作,除非自己寫死。這是我不使用他的最大原因。
go generate
嵌入靜態資源的工具推薦配合 go generate 使用。例如 pugo
的入口文件就有:
package main import ( "os" "time" "github.com/go-xiaohei/pugo/app/command" "github.com/go-xiaohei/pugo/app/vars" "github.com/urfave/cli" ) //go:generate go-bindata -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/... // ......
在編譯的時候執行:
go generate && go build
這個是 go generate
的基本用法。更詳細的了解可以看 官方博文。
總結
我在開發 pugo
的時候對這幾款嵌入靜態資源的程式進行了測試。go.rice
並不是我想要的模式,就沒有考慮。esc
提供的操作方法太少,無法滿足程式開發的需要。最後選擇 go-bindata
。但是 go-bindata
和 go.rice
都是將純字元數據或 []byte 字元數據寫入 go 文件,內容非常大。esc
是寫入 gzip 壓縮流的 Base64 編碼。經過壓縮後 go 程式碼的大小明顯更少(我嵌入的都是模板等文本文件)。可見庫類都有各自的優缺點。倘若有 go-bindata
那樣豐富的 API,又有 esc
那樣嵌入壓縮過的字元數據,那該多好。