Go 1.16 推出 Embedding Files

golang logo

Go 語言官方維護團隊 rsc 之前在 GitHub Issue 上面提出要在 go command line 直接支援 Embedding Files,沒想到過沒幾個月,就直接實現出來了,並且預計在 2021 的 go 1.16 版本直接支援 embed 套件。有了這個功能,就可以將靜態檔案或專案設定檔直接包起來,這樣部署就更方便了。底下來看看官方怎麼使用。

影片教學

如果對於課程內容有興趣,可以參考底下課程。

如果需要搭配購買請直接透過 FB 聯絡我,直接匯款(價格再減 100

embed package

直接看官方給的例子:

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

import (
	"embed"
)

//go:embed hello.txt
var s string

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

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

func main() {
	print(s)
	print(string(b))
	data, _ := f.ReadFile("hello.txt")
	print(string(data))
}

可以看到關鍵字: go:embed,透過註解就可以將靜態檔案直接使用在開發上面,另外也可以引用多個檔案或多個目錄:

1
2
3
4
5
6
7
8
package server

import "embed"

// content holds our static web server content.
//go:embed image/* template/*
//go:embed html/index.html
var content embed.FS

可以看到 go:embed 支援多個目錄,單一檔案或多個檔案都可以,假如沒有用到 embed.FS,請在 import 時加上 _,範例如下:

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

import _ "embed"

//go:embed hello.txt
var s string

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

func main() {
    print(s)
    print(string(b))
}

有了這個 Package 後,再也不需要第三方套件 Resource Embedding 了,底下來看看如何將 embed 套件整合進 Gin

整合 Gin Framework

先假設 Gin 需要包含靜態圖片及 Template,底下是目錄結構:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
├── assets
│   ├── favicon.ico
│   └── images
│       └── example.png
├── go.mod
├── go.sum
├── main.go
└── templates
    ├── foo
    │   └── bar.tmpl
    └── index.tmpl

該如何將 Template 跟 assets 目錄直接打包進 Go 呢?直接看 main.go

 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

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

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

//go:embed assets/* templates/*
var f embed.FS

func main() {
    router := gin.Default()
    templ := template.Must(template.New("").ParseFS(f, "templates/*.tmpl", "templates/foo/*.tmpl"))
    router.SetHTMLTemplate(templ)

    // example: /public/assets/images/example.png
    router.StaticFS("/public", http.FS(f))

    router.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "Main website",
        })
    })

    router.GET("/foo", func(c *gin.Context) {
        c.HTML(http.StatusOK, "bar.tmpl", gin.H{
            "title": "Foo website",
        })
    })

    router.GET("favicon.ico", func(c *gin.Context) {
        file, _ := f.ReadFile("assets/favicon.ico")
        c.Data(
            http.StatusOK,
            "image/x-icon",
            file,
        )
    })

    router.Run(":8080")
}

開發者可以很簡單用兩行就將靜態檔案直接包進來:

1
2
//go:embed assets/* templates/*
var f embed.FS

靜態檔案的 route 可以直接透過底下設定:

1
2
// example: /public/assets/images/example.png
router.StaticFS("/public", http.FS(f))

也可以透過 ReadFile 讀取單一檔案:

1
2
3
4
5
6
7
8
router.GET("favicon.ico", func(c *gin.Context) {
    file, _ := f.ReadFile("assets/favicon.ico")
    c.Data(
        http.StatusOK,
        "image/x-icon",
        file,
    )
})

程式範例可以直接在這邊找到

心得

Go 團隊真的蠻用心的,會比一些常用核心的功能納入官方維護,以保持後續的更新,有了這項功能,在 Go 的部署流程,直接可以略過靜態檔案加入 Docker 內了。未來專案有些保密檔案也可以透過此方式直接在 CI 流程內先換掉,再進行 go build 了。


See also