go embed 编译加入静态文件
什么是 go embed
背景: go 编译,默认只会将 go 文件打包出一个可执行的二进制文件,但是实际使用中,可能我们还会用到一些配置环境变量的文件或者是静态资源文件,而 go embed 的功能就是将这些静态文件打包进入二进制文件中
功能
go embed 的功能1
- 对于单个的文件,支持嵌入为字符串和 byte slice
- 对于多个文件和文件夹,支持嵌入为新的文件系统 FS
- 比如导入 “embed"包,即使无显式的使用
go:embed
指令用来嵌入,必须紧跟着嵌入后的变量名- 只支持嵌入为 string, byte slice 和 embed. FS 三种类型,这三种类型的别名 (alias) 和命名类型 (如 type S string) 都不可以
关于第三点的解释
“比如导入 ’embed’包,即使无显式的使用” 是指在 Go 语言中,在代码中导入一个包(如 import "embed"
)但没有直接使用这个包中的任何函数、类型或变量时的情况。
在 Go 语言中,通常情况下,如果我们导入了一个包但没有使用它,编译器会报错。这是 Go 的一个特性,用来避免不必要的依赖。
然而,有些包(如 embed
包)是用于特殊目的的。embed
包是 Go 1.16 引入的,它允许在编译时将文件嵌入到 Go 程序中。即使我们没有显式地调用 embed
包中的函数,仅仅通过导入它,并使用特殊的注释(如 //go:embed
)就能触发其功能。
例如:
|
|
这种情况下,虽然看起来没有显式使用 embed
包,但实际上通过特殊注释触发了它的功能,所以需要导入。
关于第二点,当多个文件是可以通过一个 embed. FS 定义, 然后多次读入这个变量,获取每个文件的内容
相对路径解析
当使用 Go 的 embed
包嵌入文件时,相对路径的根路径 (基准目录) 是包含当前 Go 源文件的目录,而不是工作目录或项目根目录。这一点非常重要,因为它决定了嵌入文件的查找方式。
以下是一个例子展示文件结构和相对路径的使用:
|
|
在 app.go
中嵌入文件:
|
|
处理特殊字符文件名
对于包含空格、特殊字符或非 ASCII 字符的文件或目录名,Go 的 embed 包允许使用双引号 "
或反引号 `
来包围文件路径或模式。这在处理实际项目中可能遇到的复杂文件名时非常有用。
使用双引号
|
|
使用反引号 (推荐)
反引号更适合处理包含特殊字符的路径,因为它们不需要转义:
|
|
模式匹配中使用引号
在使用通配符模式时也可以使用引号:
|
|
读取文件的方式, 传统方式与新方式对比
传统方式:使用本地文件系统
在 Go 的 net/http
包中,传统上我们通过指向物理路径的方式提供静态文件服务:
|
|
这种方式直接从服务器的文件系统读取文件,将 /tmp
目录下的内容暴露给 HTTP 请求者。
新方式:使用嵌入文件系统
Go 1.16 引入的 embed
包和 io/fs
接口使得可以将静态文件嵌入到二进制文件中,并通过 HTTP 提供服务:
|
|
这里的关键变化是 http.FS()
函数,它是一个适配器,将任何实现了 fs.FS
接口的文件系统包装成 http.FileSystem
接口,使其可以被 http.FileServer
使用。
三个个点
embed
实现了fs. FS
的接口http.FileSystem
- HTTP 包使用的文件系统接口http.FS()
- 适配器函数,将fs.FS
转换为http.FileSystem
嵌入文件服务的优势
单一二进制文件部署 - 所有静态资源都嵌入到单个可执行文件中,无需额外的文件传输或管理
跨平台一致性 - 不依赖于运行环境的文件系统结构,确保在不同平台上行为一致
安全性 - 嵌入文件内容不能被外部修改,防止文件篡改
性能 - 嵌入文件加载速度快,不需要从磁盘读取
简化部署 - 减少了部署时需要管理的文件,特别是在云环境或容器中部署时非常有价值
小总结
针对场景
- 字符
- 单文件 embed. FS
- 多文件 embed. FS
- 支持定义 exported 或者 unexported 变量
- 编译之初就将值嵌入,后续不变,读取同一个文件初始化两个变量,两个变量属于不同地址的字段
- 线程安全,FS 文件系统只提供打开和读取的方法,多个 goroutine 可以并发使用
- 支持直接读取文件夹
- 读取特殊文件名的文件,使用反引号作为转义字符
- 使用 embed 时默认根路径在使用 embed 字符的文件的路径
- embed. FS 实现了一个 io/fs. FS 接口