骑驴找蚂蚁

全干工程师

Golang 使用embed打包静态资源文件

在使用golang撰写一些需要模板技术或使用cssjavascripthtml等文件的程序时。这篇文章将告诉你如何使用embed库将这些静态文件嵌入到二进制文件中。

使用embed你的golang版本必须是v1.6.0及以上.

如何使用

embed是通过注释指令的方式来告诉编译器那些包文件或子目录文件需要被嵌入在二进制中。

import _ "embed"

//go:embed hello.txt
var s string
print(s)

上面的//go:embed 就是它的指令, 你只能在string[]byteFS 类型的变量上使用embed指令。在go build时编译器会把hello.txt内容直接附给变量s

http静态服务

在使用http服务时需要处理很多静态文件,下面将使用embed来处理这些静态文件。

下面是程序的目录结构,statis目录是静态资源目录,templates是模板目录,main.go是主程序。

- static
	- js
		- bootstrap.min.js
	- css 
		- bootstrap.min.css
- templates
	- layout.html
	- dashboard.html
- go.mod
- main.go
  • 实现最简单的http服务
package main  
  
import (  
    "net/http"  
 "os")  
  
func main() {  
    mux := http.NewServeMux()  
    err := http.ListenAndServe("127.0.0.1:8080", mux)  
    if err != nil {  
       os.Exit(255)  
       return  
  }  
}

这个http服务没有任何的路由监听操作。将/static作为静态文件的路由前缀.比如访问这个: /static/js/bootstrap.min.js就需要指向对应的目录。

  • 添加静态服务

以传统的方工http.Dir来作来静态服务的文件系统目录器。


fileServer := http.FileServer(http.Dir("/Users/meshell/Projects/go/lwfz-sign"))  
mux.Handle("/static/", fileServer)

运行测试下效果: go run main.go

static server

然而通过这种方式绑定的文件服务器,你需要把静态文件目录也打包一份放在对应的指定目录比如上面的/Users/meshell/Projects/go/lwfz-sign

  • 使用embedFS对象实现文件服务

上面说过embed可以对embed.FS实现嵌入。

var (  
  //go:embed all:static  
  assets embed.FS  
)

上面的代码指令//go:embed all:static 是告诉编译器嵌入static目录的所有文件.

  • 嵌入指定文件//go:embed static/js/test.js
  • 嵌入所有JS文件//go:embed static/js/*.js
  • 嵌入不同的目录文件//go:embed static/css/*.css static/js/*.js

定义的变量只是告诉编译器嵌入文件,该如何使用变量实现文件服务器呢。

var (  
  //go:embed all:static  
  assets embed.FS  
)
// 为静态资源目录生成一前缀目录`static`
func Assets() (fs.FS, error) {  
    return fs.Sub(assets, "static")  
}
func main() {
	mux := http.NewServeMux()
	// 更改此处的实现
	sfs,  err := Assets()  
	if err != nil {  
	    panic(err)  
	}  
	fileServer := http.FileServer(http.FS(sfs))
	// Assets为静态目录生成前缀目录,所以这里需要加上前缀
	// 如果你不想加入前缀你可以直接使用http.FileServer(http.FS(assets)) 
	mux.Handle("/static/", http.StripPrefix("/static", fileServer))
	err = http.ListenAndServe("127.0.0.1:8080", mux)  
	if err != nil {  
	    os.Exit(255)  
	    return  
	}
}

运行之后的效果和上面的方式是一样的效果,但是静态资源已经打包在二进制文件中,但打包之后的包是比之前的大。这也取决于你的资源文件的大小。

  • 模板文件嵌入渲染

监听一个首页/的路由绑定在dashboard函数上,渲染模板文件中的dashboard.html文件。

mux.HandleFunc("/", dashboard)

func dashboard(writer http.ResponseWriter, request *http.Request) {  
    err := tpl.ExecuteTemplate(writer, "layout", nil)  
    if err != nil {  
       http.Error(writer, err.Error(), http.StatusInternalServerError)  
       return  
  }  
}

定义嵌入的模板文件的变量和加载模板的函数.

var (
	//go:embed all:templates  
	templates embed.FS  
  
	tpl *template.Template
)

func loadTemplate() {  
    var err error  
	tpl, err = template.ParseFS(templates, "templates/layout.html", "templates/dashboard.html")  
    if err != nil {  
       panic(err)  
    }  
}

运行之后的效果: go run main.go

static server

完整代码

以下是main.go的完整内容,其它的静态文件内容自己可以随便填充。

package main  
  
import (  
    "embed"  
 "html/template" "io/fs" "net/http" "os")  
  
var (  
    //go:embed all:static  
  assets embed.FS  
  //go:embed all:templates  
  templates embed.FS  
  
  tpl *template.Template  
)  
  
func Assets() (fs.FS, error) {  
    return fs.Sub(assets, "static")  
}  
  
func loadTemplate() {  
    var err error  
  tpl, err = template.ParseFS(templates, "templates/layout.html", "templates/dashboard.html")  
    if err != nil {  
       panic(err)  
    }  
}  
  
func main() {  
    mux := http.NewServeMux()  
    loadTemplate()  
    sfs, err := Assets()  
    if err != nil {  
       panic(err)  
    }  
    fileServer := http.FileServer(http.FS(sfs))  
    mux.Handle("/static/", http.StripPrefix("/static", fileServer))  
    mux.HandleFunc("/", dashboard)  
  
    err = http.ListenAndServe("127.0.0.1:8080", mux)  
    if err != nil {  
       os.Exit(255)  
       return  
  }  
}  
  
func dashboard(writer http.ResponseWriter, request *http.Request) {  
    err := tpl.ExecuteTemplate(writer, "layout", struct {  
    }{})  
    if err != nil {  
       http.Error(writer, err.Error(), http.StatusInternalServerError)  
       return  
  }  
}

相信你通过这篇文章对golang的embed库有个大概了解的用法。

什么时候不使用embed

  • 没有嵌入需求
  • 文件更改非常平凡(更改后需要重新打包发布)
  • 文件非常多且非常大
  • 有专门的静态资源服务

扩展阅读

  1. embed官方文档
  2. embed jetbrains

留言