现在的很多程序都会提供一个 Dashboard 类似的页面用于查看程序状态并进行一些管理的功能,通常都不会很复杂,但是其中用到的图片和网页的一些静态资源,如果需要用户额外存放在一个目录,也不是很方便,如果能打包进程序发布的二进制文件中,用户下载以后可以直接使用,就方便很多。

最近在阅读 InfluxDB 的源码,发现里面提供了一个 admin 管理的页面,可以通过浏览器来执行一些命令以及查看程序运行的信息。但是我运行的时候只运行了一个 influxd 的二进制文件,并没有看到 css, html 等文件。

原来 InfluxDB 中使用了 statik 这个工具将静态资源都编译进了二进制文件中,这样用户只需要运行这个程序即可,而不需要管静态资源文件存放的位置。

安装

先下载并安装 statik 这个工具

go get -d github.com/rakyll/statik

go install github.com/rakyll/statik

注意将 $GOPATH/bin 加入到 PATH 环境变量中。

创建测试项目

创建一个测试用的 golang 项目,这里假设目录为 $GOPATH/src/test/testStatikFS

创建一个 assets 目录用于放静态资源文件。包括 ./assets/a./assets/tmp/b 两个文件,文件内容分别为 aaabbb

创建 main.go 文件,代码如下:

//go:generate statik -src=./assets
//go:generate go fmt statik/statik.go

package main

import (
    "fmt"
    "io/ioutil"
    "os"

    _ "test/testStatikFS/statik"
    "github.com/rakyll/statik/fs"
)

// Before buildling, run go generate.
func main() {
    statikFS, err := fs.New()
    if err != nil {
        fmt.Printf("err: %v\n", err)
        os.Exit(1)
    }   

    file, err := statikFS.Open("/tmp/b")
    if err != nil {
        fmt.Printf("err: %v\n", err)
        os.Exit(1)
    }   
    content, err := ioutil.ReadAll(file)
    if err != nil { 
        fmt.Printf("err: %v\n", err)
        os.Exit(1)
    }   
    fmt.Printf("content: %s\n", content)
}

注意文件最开始的两行

//go:generate statik -src=./assets
//go:generate go fmt statik/statik.go

这个注释是告诉 go generate 需要执行的命令,之后就可以通过 go generate 生成我们需要的 go 文件。

这段代码的功能就是从 statikFS 提供的文件系统接口中获取 /tmp/b 这个文件的内容并输出,可以看到操作起来和操作普通文件的方法基本一致。

将静态资源打包成 go 文件

执行 go generate

在项目目录下执行这个命令会生成一个 statik 目录,里面存放的是自动生成的 go 文件,将所有 ./assets 下的文件变成了一个压缩后的字符串放在了这个文件中,并且在程序启动时会解析这个字符串,构造一个 http.FileSystem 对象,之后就可以使用对文件系统类似的操作来获取文件内容。

编译

go build -o test ./main.go

在 main.go 中我们 import 了两个包

"test/testStatikFS/statik"
"github.com/rakyll/statik/fs"

第一个就是 go generate 自动生成的目录,其中只有一个 init() 函数,初始化相关的资源,我们不需要调用这个包里面的函数,只执行 init() 函数,所以在包名前加上 _

运行

运行编译后的文件: ./test

输出了文件 ./assets/tmp/b 中的内容:

content: bbb

文件系统接口

由于 statik 实现了标准库中的 http.FileSystem 接口,所以也可以直接使用 http 包提供静态资源的访问服务,关键部分代码如下:

import (
  "github.com/rakyll/statik/fs"

    _ "./statik" // TODO: Replace with the absolute import path
)

// ...

statikFS, _ := fs.New()
http.ListenAndServe(":8080", http.FileServer(statikFS))