「golang」Go內嵌靜態資源go-bindata的安裝及使用

「golang」Go內嵌靜態資源go-bindata的安裝及使用

引言

使用 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 裡釋放靜態文件的代碼:

「golang」Go內嵌靜態資源go-bindata的安裝及使用

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))
}
}
「golang」Go內嵌靜態資源go-bindata的安裝及使用

asset.go 內的靜態內容還是根據實際的目錄位置索引。所以我們可以直接通過目錄或者文件地址去操作。


-debug 開發模式

go-bindata 支持開發模式,即不嵌入靜態內容,只生成操作方法到輸出的 go 代碼中,如:

go-bindata -debug -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/... 

-debug 參數開啟開發模式。生成的代碼會直接去讀取靜態文件到內存,而不是編碼到代碼中。代碼文件更小,你更快速的編寫業務邏輯。

「golang」Go內嵌靜態資源go-bindata的安裝及使用

// -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)
「golang」Go內嵌靜態資源go-bindata的安裝及使用

http.FileSystem

http.FileSystem 是定義 HTTP 靜態文件服務的接口。go-bindata 的第三方包 go-bindata-assetfs 實現了這個接口,支持 HTTP 訪問靜態文件目錄的行為。以我們上面編譯好的 asset.go 為例:

「golang」Go內嵌靜態資源go-bindata的安裝及使用

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)
}
「golang」Go內嵌靜態資源go-bindata的安裝及使用

訪問 http://localhost:12345,就可以看到嵌入的 source,theme,doc 的目錄列表頁面,和 Nginx 查看靜態文件目錄一樣的。

回到頂部(go to top)

go.rice

go.rice 也支持打包靜態文件到 go 文件中,但是行為和 go-bindata 很不相同。從使用角度,go.rice 其實是更便捷的靜態文件操作庫。打包靜態文件反而是順帶的功能。

安裝和 go-bindata 一樣,注意 三個點

go get github.com/GeertJohan/go.rice/...

go.rice 把一個目錄認為是一個 rice.Box 操作:

「golang」Go內嵌靜態資源go-bindata的安裝及使用

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)
}
「golang」Go內嵌靜態資源go-bindata的安裝及使用


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)
}

回到頂部(go to top)

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:

「golang」Go內嵌靜態資源go-bindata的安裝及使用

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,但是沒有做展示目錄的支持
}
「golang」Go內嵌靜態資源go-bindata的安裝及使用

esc 有個較大的問題是隻能一個一個文件操作,不能文件夾操作,沒有類似go-bindata 的 asset.RestoreDir() 方法。並且沒有方法可以列出嵌入的文件的列表,導致也無法一個一個文件操作,除非自己寫死。這是我不使用他的最大原因。

回到頂部(go to top)

go generate

嵌入靜態資源的工具推薦配合 go generate 使用。例如 pugo 的入口文件就有:

「golang」Go內嵌靜態資源go-bindata的安裝及使用

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/...
// ......
「golang」Go內嵌靜態資源go-bindata的安裝及使用

在編譯的時候執行:

go generate && go build

這個是 go generate 的基本用法。更詳細的瞭解可以看 官方博文。

回到頂部(go to top)

總結

我在開發 pugo 的時候對這幾款嵌入靜態資源的程序進行了測試。go.rice 並不是我想要的模式,就沒有考慮。esc 提供的操作方法太少,無法滿足程序開發的需要。最後選擇 go-bindata。但是 go-bindata 和 go.rice 都是將純字符數據或 []byte 字符數據寫入 go 文件,內容非常大。esc 是寫入 gzip 壓縮流的 Base64 編碼。經過壓縮後 go 代碼的大小明顯更少(我嵌入的都是模板等文本文件)。可見庫類都有各自的優缺點。倘若有 go-bindata 那樣豐富的 API,又有 esc 那樣嵌入壓縮過的字符數據,那該多好。


分享到:


相關文章: