Golang 1.16新特性-embed包及其使用
embed 是什么
embed是在Go 1.16中新加入的包。它通过//go:embed指令,可以在编译阶段将静态资源文件打包进编译好的程序中,并提供访问这些文件的能力。
为什么需要 embed 包
在以前,很多从其他语言转过来Go语言的同学会问到,或者踩到一个坑。就是以为Go语言所打包的二进制文件中会包含配置文件的联同编译和打包。
结果往往一把二进制文件挪来挪去,就无法把应用程序运行起来了。因为无法读取到静态文件的资源。
无法将静态资源编译打包二进制文件的话,通常会有两种解决方法:
- 第一种是识别这类静态资源,是否需要跟着程序走。
- 第二种就是将其打包进二进制文件中。
第二种情况的话,Go以前是不支持的,大家就会借助各种花式的开源库,例如:go-bindata/go-bindata来实现。
但是在Go1.16起,Go语言自身正式支持了该项特性。
它有以下优点
- 能够将静态资源打包到二进制包中,部署过程更简单。传统部署要么需要将静态资源与已编译程序打包在一起上传,或者使用
docker和dockerfile自动化前者,这是很麻烦的。 - 确保程序的完整性。在运行过程中损坏或丢失静态资源通常会影响程序的正常运行。
- 静态资源访问没有io操作,速度会非常快。
embed 的常用场景
- Go模版:模版文件必须可用于二进制文件(模版文件需要对二进制文件可用)。对于Web服务器二进制文件或那些通过提供init命令的CLI应用程序,这是一个相当常见的用例。在没有嵌入的情况下,模版通常内联在代码中。
- 静态web服务:有时,静态文件(如index.html或其他HTML,JavaScript和CSS文件之类的静态文件)需要使用golang服务器二进制文件进行传输,以便用户可以运行服务器并访问这些文件。
- 数据库迁移:另一个使用场景是通过嵌入文件被用于数据库迁移脚本。
embed 的基本用法
embed包是golang 1.16中的新特性,所以,请确保你的golang环境已经升级到了1.16版本。
Go embed的使用非常简单,首先导入embed包,再通过//go:embed 文件名 将对应的文件或目录结构导入到对应的变量上。
特别注意:embed这个包一定要导入,如果导入不使用的话,使用 _ 导入即可。
嵌入的这个基本概念是通过在代码里添加一个特殊的注释实现的,Go会根据这个注释知道要引入哪个或哪几个文件。注释的格式是:
//go:embed FILENAME(S)
FILENAME可以是string类型也可以是[]byte类型,取决于你引入的是单个文件、还是embed.FS类型的一组文件。go:embed命令可以识别Go的文件格式,比如files/*.html这种文件格式也可以识别到(但要注意不要写成**/*.html这种递归的匹配规则)。
文件格式 https://pkg.go.dev/path#Match
可以看下官方文档的说明。https://golang.org/pkg/embed/
embed可以嵌入的静态资源文件支持三种数据类型:字符串、字节数组、embed.FS文件类型
| 数据类型 | 说明 |
|---|---|
| []byte | 表示数据存储为二进制格式,如果只使用[]byte和string需要以import (_ "embed")的形式引入embed标准库 |
| string | 表示数据被编码成utf8编码的字符串,因此不要用这个格式嵌入二进制文件比如图片,引入embed的规则同[]byte |
| embed.FS | 表示存储多个文件和目录的结构,[]byte和string只能存储单个文件 |
embed例子
例如:在当前目录下新建文件 version.txt,并在文件中输入内容:0.0.1
将文件内容嵌入到字符串变量中
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt
var version string
func main() {
fmt.Printf("version: %q\n", version)
}
当嵌入文件名的时候,如果文件名包含空格,则需要用引号将文件名括起来。如下,假设文件名是 "version info.txt",如下代码第8行所示:
package main
import (
_ "embed"
"fmt"
)
//go:embed "version info.txt"
var version string
func main() {
fmt.Printf("version: %q\n", version)
}
将文件内容嵌入到字符串或字节数组类型变量的时候,只能嵌入1个文件,不能嵌入多个文件,并且文件名不支持正则模式,否则运行代码会报错
如代码第8行所示:
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt info.txt
var version string
func main() {
fmt.Printf("version %q\n", version)
}
运行代码,得到错误提示:
sh-3.2# go run .
# demo
./main.go:8:5: invalid go:embed: multiple files for type string
软链接&硬链接
嵌入指令是否支持嵌入文件的软链接呢 ?如下:在当前目录下创建一个指向version.txt的软链接 v
ln -s version.txt v
package main
import (
_ "embed"
"fmt"
)
//go:embed v
var version string
func main() {
fmt.Printf("version %q\n", version)
}
运行程序,得到不能嵌入软链接文件的错误:
sh-3.2# go run .# demomain.go:8:12: pattern v: cannot embed irregular file vsh-3.2#
结论://go:embed指令不支持文件的软链接
让我们再来看看文件的硬链接,如下:
sh-3.2# rm v
sh-3.2# ln version.txt h
import (
_ "embed"
"fmt"
)
//go:embed v
var version string
func main() {
fmt.Printf("version %q\n", version)
}
运行程序,能够正常运行并输出,如下:
sh-3.2# go run .version 0.0.1
结论://go:embed指令支持文件的硬链接。因为硬链接本质上是源文件的一个拷贝。
我们能不能将嵌入指令用于 初始化的变量呢?如下:
package main
import (
_ "embed"
"fmt"
)
//go:embed v
var version string = ""
func main() {
fmt.Printf("version %q\n", version)
}
运行程序,得到error结果:
sh-3.2# go run ../main.go:12:3: go:embed cannot apply to var with initializersh-3.2#
结论:不能将嵌入指令用于已经初始化的变量上。
将文件内容嵌入到字节数组变量中
package main
import (
_ "embed"
"fmt"
)
//go:embed version.txt
var versionByte []byte
func main() {
fmt.Printf("version %q\n", string(versionByte))
}
将文件目录结构映射成embed.FS文件类型
使用embed.FS类型,可以读取一个嵌入到embed.FS类型变量中的目录和文件树,这个变量是只读的,所以是线程安全的。
embed.FS结构主要有3个对外方法,如下:
// Open 打开要读取的文件,并返回文件的fs.File结构.
func (f FS) Open(name string) (fs.File, error)
// ReadDir 读取并返回整个命名目录
func (f FS) ReadDir(name string) ([]fs.DirEntry, error)
// ReadFile 读取并返回name文件的内容.
func (f FS) ReadFile(name string) ([]byte, error)
读取单个文件
package main
import (
"embed"
"fmt"
"log"
)
//go:embed "version.txt"
var f embed.FS
func main() {
data, err := f.ReadFile("version.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
读取多个文件
首先,在项目根目录下建立 templates目录,以及在templates目录下建立多个文件,如下:
|-templates
|-—— t1.html
|——— t2.html
|——— t3.html
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*
var files embed.FS
func main() {
templates, _ := fs.ReadDir(files, "templates")
//打印出文件名称
for _, template := range templates {
fmt.Printf("%q\n", template.Name())
}
}
嵌入多个目录
通过使用多个//go:embed指令,可以在同一个变量中嵌入多个目录。我们在项目根目录下再创建一个cpp目录,在该目录下添加几个示例文件名。如下:
|-cpp
|——— cpp1.cpp
|——— cpp2.cpp
|——— cpp3.cpp
如下代码,第9、10行所示:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*
//go:embed cpp/*
var files embed.FS
func main() {
templates, _ := fs.ReadDir(files, "templates")
//打印出文件名称
for _, template := range templates {
fmt.Printf("%q\n", template.Name())
}
cppFiles, _ := fs.ReadDir(files, “cpp”)
for _, cppFile := range cppFiles {
fmt.Printf("%q\n", cppFile.Name())
}
}
按正则嵌入匹配目录或文件
只读取templates目录下的txt文件,如下代码第9行所示:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*.txt
var files embed.FS
func main() {
templates, _ := fs.ReadDir(files, "templates")
//打印出文件名称
for _, template := range templates {
fmt.Printf("%q\n", template.Name())
}
}
只读取templates目录下的t2.html和t3.html文件,如下代码第9行所示:
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/t[2-3].txt
var files embed.FS
func main() {
templates, _ := fs.ReadDir(files, "templates")
//打印出文件名称
for _, template := range templates {
fmt.Printf("%q\n", template.Name())
}
}
在http web中的使用
package main
import (
"embed"
"net/http"
)
//go:embed static
var static embed.FS
func main() {
http.ListenAndServe(":8080", http.FileServer(http.FS(static)))
}
http.FS这个函数,把embed.FS类型的static转换为http.FileServer函数可以识别的http.FileSystem类型。
在模板中的应用
package main
import (
"embed"
"html/template"
"net/http"
)
//go:embed templates
var tmpl embed.FS
func main() {
t, _ := template.ParseFS(tmpl, "templates/*.tmpl")
http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
t.ExecuteTemplate(rw,"index.tmpl",map[string]string{"title":"Golang Embed 测试"})
})
http.ListenAndServe(":8080",nil)
}
template包提供了ParseFS函数,可以直接从一个embed.FS中加载模板,然后用于HTTP Web中。模板文件夹的结构如下所示:
templates
└── index.tmpl
Gin静态文件服务
package main
import (
"embed"
"github.com/gin-gonic/gin"
"net/http"
)
//go:embed static
var static embed.FS
func main() {
r:=gin.Default()
r.StaticFS("/",http.FS(static))
r.Run(":8080")
}
在Gin中使用embed作为静态文件,也是用过http.FS函数转化的。
Gin HTML 模板
package main
import (
"embed"
"github.com/gin-gonic/gin"
"html/template"
)
//go:embed templates
var tmpl embed.FS
//go:embed static
var static embed.FS
func main() {
r:=gin.Default()
t, _ := template.ParseFS(tmpl, "templates/*.tmpl")
r.SetHTMLTemplate(t)
r.GET("/", func(ctx *gin.Context) {
ctx.HTML(200,"index.tmpl",gin.H{"title":"Golang Embed 测试"})
})
r.Run(":8080")
和前面的模板例子一样,也是通过template.ParseFS函数先加载embed中的模板,然后通过Gin的SetHTMLTemplate设置后就可以使用了。
http.FS函数是一个可以把embed.FS转为http.FileSystem的工具函数
embed的使用实例-一个简单的静态web服务
以下搭建一个简单的静态文件web服务为例。在项目根目录下建立如下静态资源目录结构
|-static
|---js
|------util.js
|---img
|------logo.jpg
|---index.html
package main
import (
"embed"
"io/fs"
"log"
"net/http"
"os"
)
func main() {
useOS := len(os.Args) > 1 && os.Args[1] == "live"
http.Handle("/", http.FileServer(getFileSystem(useOS)))
http.ListenAndServe(":8888", nil)
}
//go:embed static
var embededFiles embed.FS
func getFileSystem(useOS bool) http.FileSystem {
if useOS {
log.Print("using live mode")
return http.FS(os.DirFS("static"))
}
log.Print("using embed mode")
fsys, err := fs.Sub(embededFiles, "static")
if err != nil {
panic(err)
}
return http.FS(fsys)
}
以上代码,分别执行 go run . live 和 go run .
然后在浏览器中运行http://localhost:8888 默认显示static目录下的index.html文件内容。
当然,运行go run . live 和 go run . 的不同之处在于编译后的二进制程序文件在运行过程中是否依赖static目录中的静态文件资源。
以下为验证步骤:
首先,使用编译到二进制文件的方式。
若文件内容改变,输出依然是改变前的内容,说明embed嵌入的文件内容在编译后不再依赖于原有静态文件了。
- 运行
go run . - 修改
index.html文件内容为Hello China - 浏览器输入
http://localhost:8888查看输出。输出内容为修改之前的Hello World
其次,使用普通的文件方式。
若文件内容改变,输出的内容也改变,说明编译后依然依赖于原有静态文件。
go run . live- 修改
index.html文件内容为delete - 浏览器输入
http://localhost:8888查看输出。输出修改后的内容:Hello China
embed使用中注意事项
在使用//go:embed指令的文件都需要导入 embed包。
例如,以下例子 没有导入embed包,则不会正常运行 。
package main
import (
"fmt"
)
//go:embed file.txt
var s string
func main() {
fmt.Print(s)
}
//go:embed指令只能用在包一级的变量中,不能用在函数或方法级别,像以下程序将会报错,因为第10行的变量作用于属于函数级别:
package main
import (
_ "embed"
"fmt"
)
func main() {
//go:embed file.txt
var s string
fmt.Print(s)
}
当包含目录时,它不会包含以“.”或“_“开头的文件。
但是如果使用通配符,比如dir/*,它将包含所有匹配的文件,即使它们以“."或"_"开头。请记住,在您希望在Web服务器中嵌入文件但不允许用户查看所有文件的列表的情况下,包含Mac OS的.DS_Store文件可能是一个安全问题。出于安全原因,Go在嵌入时也不会包含符号链接或上一层目录。
Golang 1.16新特性-embed包及其使用的更多相关文章
- Java 16 新特性:record类
以前我们定义类都是用class关键词,但从Java 16开始,我们将多一个关键词record,它也可以用来定义类.record关键词的引入,主要是为了提供一种更为简洁.紧凑的final类的定义方式. ...
- golang1.16新特性速览
今天是假期最后一天,明天起大家也要陆续复工了.golang1.16也在今天正式发布了. 原定计划是2月1号年前发布的,不过迟到也是golang的老传统了,正好也趁着最后的假期快速预览一下golang1 ...
- Java 16 新特性:instanceof增强
instanceof这个关键词,主要用来判断某个对象是不是某个类的实例. 比如,有时候我们要处理一个类似这样的数据集: Map<String, Object> data = new Has ...
- MySQL8.0.16新特性:The Communication Protocol In Group Replication
MGR优雅升级到MySQL8.0.16 传统的升级手段之一,5.7 MGR集群与8.0 MGR集群进行数据传输,程序切换新集群后测试是否正常. 如果不正常,要么将新集群的新增数据同步回旧集群,要么就舍 ...
- 译文《全新首发JDK 16全部新特性》
封面:洛小汐 译者:潘潘 JDK 8 的新特性都还没摸透,JDK 16 的新特性就提着刀来了. 郑重申明: 第一次冒险翻译专业领域的文献,可想而知,效果特别糟糕.一般翻译文献特别是 技术专业领域 的内 ...
- JDK 16 正式发布,一次性发布 17 个新特性…不服不行!
上一篇:Java 15 正式发布, 14 个新特性 JDK 16 正式发布 牛逼啊,JDK 15 刚发布半年(2020/09/15),JDK 16 又如期而至(2021/03/16),老铁们,跟上. ...
- Java9至17的新特性总结
总览 讲讲Java 9-17 的一些语法糖和一些新发布的jeps, 重点讲讲JVM的垃圾回收器 时间线 SpringBoot 为什么选择Java17这个版本.我估计跟下面这个图有关系. Java 8 ...
- Atitit.go语言golang语言的新的特性 attilax总结
Atitit.go语言golang语言的新的特性 attilax总结 1. 继承树less 动态接口1 1.1. 按照书中说的,Go语言具有以下的特征,下面我们分别来进行介绍. q 自动垃圾回收 ...
- hadoop2.5发布:最新编译 32位、64位安装、源码包、API以及新特性
hadoop2.5发布:最新编译 32位.64位安装.源码包.API以及新特性 http://www.aboutyun.com/thread-8751-1-1.html (出处: about云开发) ...
- React 16.x 新特性思维导图
React 16版本相对于以前的版本做了很大的改动,下面是我整理的React 16.x 新特性的思维导图文件,欢迎围观和指导:
随机推荐
- Qt血的教训/细数Qt开发的各种坑/又爱又恨/欢迎围观留言评论
一.吐槽总结 搞Qt开发十几年了,最初从Qt4.6开始,一直追新到现在的6.7版本,中间经历过无数的血的教训,简直是又爱又恨.其实Qt挺好的,但是还是要忍不住吐槽一下,本人还是希望Qt发展的越来越好, ...
- Qt开源作品21-日志重定向输出类
一.前言 用qt开发商业程序已经十年了,陆陆续续开发过至少几十个程序,除了一些算不算项目的小工具外,大部分的程序都需要有个日志的输出功能,希望可以将程序的运行状态存储到文本文件或者数据库或者做其他处理 ...
- 学习破解一个Android程序
首先编写一个android测试程序 功能:校验用户名和注册码,成功则弹出注册成功提示 以下仅给出关键部分的代码 res/layout/activity_main.xml <?xml versio ...
- [转]ptp(precision time protocol)时钟同步
一.介绍1:什么是ptpPTP(Precision Time Protocol) 是一个通过网络同步时钟的一个协议.当硬件支持时,PTP 精度能达到亚微秒,比 NTP(Network Time Pro ...
- [转]vue项目中app.vue 、main.js和 index.html的关系
参考链接: 1.vue项目中app.vue .main.js和 index.html的关联 2.Vue中index.html.main.js.App.vue,之间关系 3.关于Vue中main.js, ...
- 10.3 - AM - 模拟赛 总结
复盘 T1 很水,一道异或求和,但是某两位仁兄因没打括号而死. T2 很水,一道字符串处理,但是我和某位仁兄因没特判而死(虽然没有 hack 掉我,所以我理论上还是满分). T3 不水,看了很久,没想 ...
- Kotlin:【针对空安全管理的操作】安全调用操作符、使用带let的安全调用、非空断言操作符(感叹号操作符)、使用if判断null值情况、使用空合并操作符(类似三元表达式)
具体使用:
- C笔记---01基础篇
一.C语言内存分区 1.程序代码区:存放 CPU 执行的机器指令. 2.数据区 2.1常量区:字符串.数字等常量存放在常量区,const修饰的全局变量存放在常量区:常量区的内存是只读的,程序结束后 ...
- mysql存储过程取得错误信息的方式。
GET DIAGNOSTICS CONDITION 1 @v_sqlstate=RETURNED_SQLSTATE,@v_message= MESSAGE_TEXT; SELECT @v_sqlsta ...
- 创新突破!天翼云荣膺CCF HPC China 2024高性能计算创新大奖
近日,第20届CCF全国高性能计算学术年会(CCF HPC China 2024)在武汉隆重召开.CCF HPC China是全球高性能计算领域三大标志性盛会之一,本届大会以"华章廿载 新质 ...