什么是embed

在Golang 1.16版本中,新增了一个大家期待已久的特性//go:embed,它的作用就是可以在Go语言应用程序中包含任何文件、目录的内容,也就是说我们可以把文件以及目录中的内容都打包到生成的Go语言应用程序中了,部署的时候,直接扔一个二进制文件就可以了,不用再包含一些静态文件了,因为它们已经被打包到生成的应用程序中了。

基本用法

通过 官方文档 我们知道embed嵌入的三种方式:stringbytesFS(File Systems)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//go:embed 基本用法是:

package main

import "embed"

//go:embed hello.txt
var s string

//go:embed hello.txt
var b []byte

//go:embed hello.txt
//go:embed assets
var f embed.FS

func main() {
  print(s)

  print(string(b))

  data, _ := f.ReadFile("hello.txt")
  print(string(data))
}

1、导入 embed 包,如果没有使用 embed.FS 需要显示的导入:

1
import _ "embed"

2、匹配文件 //go:embed <匹配模式> <匹配模式>...,匹配模式符合 path.Match 方式。

(1)匹配模式是相对位置,如:

1
2
3
4
5
6
├── assets
│   ├── .gitkeep
│   ├── _home.html
│   └── index.html
├── hello.txt
└── main.go

匹配 index.html 则使用 //go:embed assets/index.html 即可,不能使用 ...(如./hello.txt)。

(2)可以匹配多个,以空格隔开,如 //go:embed hello.txt assets/index.html。也可以重复,避免匹配长度过长:

1
2
3
//go:embed hello.txt
//go:embed assets/index.html
var f embed.FS

(3)[]byte 和 string 只能匹配单个文件。如果文件名称有空格可使用双引号 " 或者反引号 ``。

(4)如果//go:embed assets匹配的是一个目录,那么该目录中所有文件都将递归的嵌入,除了以.或开头的文件。

(5)匹配目录中的所有内容,使用统配*,包括以.和` 开头的文件。

3、匹配的变量只能是全局变量

进阶用法

Go1.16 为了对 embed 的支持也添加了一个新包 io/fs。两者结合起来可以像之前操作普通文件一样。

常规文件操作

如通过 embed 进行常规的文件目录读取,文件递归遍历等:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//go:embed hello.txt
//go:embed hello.txt assets/*
var f embed.FS

...

entries, err := f.ReadDir(".")
if err != nil {
  panic(err)
}
for _, entry := range entries {
  info, err := entry.Info()
  if err != nil {
    panic(err)
  }
  fmt.Println(info.Name(), info.Size(), info.IsDir())
}

Web文件系统

通过原生go http服务,我们将静态资源文件嵌入到二进制中,做静态文件服务器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
  "embed"
  "net/http"
)

//go:embed hello.txt assets/*
var f embed.FS

func main() {
  http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(f))))

  http.ListenAndServe(":8080", nil)
}

以上代码的核心除了//go:embed指令外,还有通过http.FS这个函数,把embed.FS类型的static转换为http.FileServer函数可以识别的http.FileSystem类型。

通过常见的Web服务框架,提供文件的访问:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
  "embed"
  "net/http"

  "github.com/gin-gonic/gin"
)

//go:embed hello.txt assets/*
var f embed.FS

func main() {
  e := gin.Default()

  e.StaticFS("/static/", http.FS(f))
  e.Run(":8080")
}

其它web框架各自可以试试。

模版操作

通过 embed 方式嵌入模版,渲染模版:

1
2
3
4
├── main.go
└── tmpl
    ├── en.tmpl
    └── zh.tmpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
    "embed"
    "fmt"
    "html/template"
    "net/http"
)

//go:embed tmpl/*.tmpl
var f embed.FS

func main() {
  t, err := template.ParseFS(f, "tmpl/*.tmpl")
  if err != nil {
    panic(err)
  }

  // /hello?lang=xx.tmpl
  http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()

    t.ExecuteTemplate(w, r.FormValue("lang"), nil)
  })

  http.ListenAndServe(":8080", nil)
}

template包提供了ParseFS函数,可以直接从一个embed.FS中加载模板,然后用于HTTP Web中。