更新 @ 2019-12-13: 由 Go 1.13 開始,請使用內置的 Go Module 來管理套件。
Echo 是 Golang 裡用於構建 RESTful API 的輕型又完整的 Web 框架。它速度很快並且包含了不少中介軟體來處理整個 HTTP 請求與回應過程。在 Echo 框架裡您可以使用於任何樣板引擎來渲染 HTML,為了簡單起見這篇文章將會利用 Go 標準函式庫所提供的 html/template 套件。而在本文的最後,您可以找到一個演示了嵌套樣板的 Echo 示例項目。
如果您已經了解 Echo 的基本操作原理,您可以直接地跳到使用嵌套樣板的章節 。
一個基本的 Echo 項目設置
在 $GOPATH 下創建項目文件夾
完整的項目代碼已放在 GitLab 上作參考。現在請先創建項目文件夾 _$GOPATH/src/gitlab.com/ykyuen/golang-echo-template-example_。
創建 main.go
在新創建的文件夾中,我們先從 Echo 官方網站複製 hello world 示例並創建 main.go 。
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main () {
e := echo.New ()
e.GET ("/" , func (c echo.Context) error {
return c.String (http.StatusOK, "Hello, World!" )
})
e.Logger.Fatal (e.Start (":1323" ))
}
使用 dep 下載 Echo 套件
如果已經安裝了 dep ,只需運行 dep init 。有關如何使用 dep ,請參閱以下文章。
如果不想使用 dep ,那麼您也可以執行 go github.com/labstack/echo 把 Echo 套件下載到 $GOPATH 裡。
執行 hello world
透過 go run main.go 指令啟動應用程序,然後可以通過瀏覽器或 curl 指令來訪問 http://localhost:1323。
返回一個 JSON 回應
在現實生活中構建RESTful API時,客戶端通常希望接收的是一個 JSON 回應而不是一堆字串。試試在 main.go 中修改一些 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
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main () {
e := echo.New ()
e.GET ("/" , func (c echo.Context) error {
return c.String (http.StatusOK, "Hello, World!" )
})
e.GET ("/json" , func (c echo.Context) error {
return c.JSONBlob (
http.StatusOK,
[]byte (`{ "id": "1", "msg": "Hello, Boatswain!" }` ),
)
})
e.Logger.Fatal (e.Start (":1323" ))
}
返回一個 HTML
與返回 JSON 物件類似,我們只需要在 return 語句中呼叫另一個方法。
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
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main () {
e := echo.New ()
e.GET ("/" , func (c echo.Context) error {
return c.String (http.StatusOK, "Hello, World!" )
})
e.GET ("/json" , func (c echo.Context) error {
return c.JSONBlob (
http.StatusOK,
[]byte (`{ "id": "1", "msg": "Hello, Boatswain!" }` ),
)
})
e.GET ("/html" , func (c echo.Context) error {
return c.HTML (
http.StatusOK,
"<h1>Hello, Boatswain!</h1>" ,
)
})
e.Logger.Fatal (e.Start (":1323" ))
}
以上只是兩個簡單的例子,Echo 有一些更方便的方法來返回 JSON 和 HTML。有興趣的話可參閱文檔 。
使用樣板引擎渲染 HTML
正如本文開首提到,我們可以在返回 HTTP 回應時使用樣板引擎來渲染 HTML,但在此之前,讓我們重新規劃這個示例項目的結構。
1
2
3
4
5
6
7
8
9
10
11
golang-echo-template-example/
├── handler/ # folder of request handlers
│ └── home_handler.go
├── vendor/ # dependencies managed by dep
│ ├── github.com/*
│ └── golang.org/*
├── view/ # folder of html templates
│ └── home.html
├── Gopkg.lock # dep config file
├── Gopkg.toml # dep config file
└── main.go # programme entrypoint
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
package main
import (
"html/template"
"io"
"github.com/labstack/echo"
"gitlab.com/ykyuen/golang-echo-template-example/handler"
)
// Define the template registry struct
type TemplateRegistry struct {
templates *template.Template
}
// Implement e.Renderer interface
func (t *TemplateRegistry) Render (w io.Writer, name string , data interface {}, c echo.Context) error {
return t.templates.ExecuteTemplate (w, name, data)
}
func main () {
// Echo instance
e := echo.New ()
// Instantiate a template registry and register all html files inside the view folder
e.Renderer = &TemplateRegistry{
templates: template.Must (template.ParseGlob ("view/*.html" )),
}
// Route => handler
e.GET ("/" , handler.HomeHandler)
// Start the Echo server
e.Logger.Fatal (e.Start (":1323" ))
}
在這個新 main.go 中,我們定義了一個名為 TemplateRegistry 的型別並實現了 Renderer 介面。Renderer 是一個包裝 Render() 函數的簡單接口。之後在 main() 函數中實例化一個 TemplateRegistry 並加入所需的樣板。
另一方面,我們定義 HomeHandler 以便將邏輯保存在單獨的文件中。
handler/home_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package handler
import (
"net/http"
"github.com/labstack/echo"
)
func HomeHandler (c echo.Context) error {
// Please note the the second parameter "home.html" is the template name and should
// be equal to the value stated in the {{ define }} statement in "view/home.html"
return c.Render (http.StatusOK, "home.html" , map [string ]interface {}{
"name" : "HOME" ,
"msg" : "Hello, Boatswain!" ,
})
}
當 c.Render() 被呼叫時,它會執行在 TemplateRegistry 實例中所配置的一個的樣板。這三個參數分別是
HTTP 狀態碼
樣板名稱
以及可以在樣板中使用的 data 物件
view/home.html
1
2
3
4
5
6
7
8
9
10
11
{{define "home.html"}}
<!DOCTYPE html>
<html >
<head >
<title >Boatswain Blog | {{index . "name"}}</title >
</head >
<body >
<h1 >{{index . "msg"}}</h1 >
</body >
</html >
{{end}}
如上面的 define 語句中所述,樣板名稱命名為 home.html ,它可以從 c.Render() 的 data 物件讀取 name 和 msg 並放到 <title> 和 <h1> 元素中。
使用嵌套樣板
在以上的設置中,每個 HTML 樣板都有一整套 HTML 代碼,其中許多代碼都是重複的。若果使用嵌套樣板便可避免重複的代碼,可以更輕鬆地維護項目。
目前的 TemplateRegistry 中 templates 的欄位包含所有樣板文件。而在新設置中,我們將其設置為映射(map) ,每組鍵/值都是一套特定的 HTML 嵌套樣板文件。
我們在項目中添加了一些文件,新的項目結構如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
golang-echo-template-example/
├── handler/ # folder of request handlers
│ ├── home_handler.go # handler for home page
│ └── about_handler.go # handler for about page
├── vendor/ # dependencies managed by dep
│ ├── github.com/*
│ └── golang.org/*
├── view/ # folder of html templates
│ ├── base.html # base layout template
│ ├── home.html # home page template
│ └── about.html # about page template
├── Gopkg.lock # dep config file
├── Gopkg.toml # dep config file
└── main.go # programme entrypoint
下面的代碼參考了由 rand99 的創建的 gist 。
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
45
46
47
package main
import (
"errors"
"html/template"
"io"
"github.com/labstack/echo"
"gitlab.com/ykyuen/golang-echo-template-example/handler"
)
// Define the template registry struct
type TemplateRegistry struct {
templates map [string ]*template.Template
}
// Implement e.Renderer interface
func (t *TemplateRegistry) Render (w io.Writer, name string , data interface {}, c echo.Context) error {
tmpl, ok := t.templates[name]
if !ok {
err := errors.New ("Template not found -> " + name)
return err
}
return tmpl.ExecuteTemplate (w, "base.html" , data)
}
func main () {
// Echo instance
e := echo.New ()
// Instantiate a template registry with an array of template set
// Ref: https://gist.github.com/rand99/808e6e9702c00ce64803d94abff65678
templates := make (map [string ]*template.Template)
templates["home.html" ] = template.Must (template.ParseFiles ("view/home.html" , "view/base.html" ))
templates["about.html" ] = template.Must (template.ParseFiles ("view/about.html" , "view/base.html" ))
e.Renderer = &TemplateRegistry{
templates: templates,
}
// Route => handler
e.GET ("/" , handler.HomeHandler)
e.GET ("/about" , handler.AboutHandler)
// Start the Echo server
e.Logger.Fatal (e.Start (":1323" ))
}
我們添加了一個由 AboutHandler 處理的 /about 新路徑,從上面加亮顯示的行中可以看到 templates 映射包含不同 HTML 頁面的樣板集,Render() 將 name 參數作為樣板映射 儲存鍵,以便它可以執行正確的樣板集。
view/base.html
1
2
3
4
5
6
7
8
9
10
11
{{define "base.html"}}
<!DOCTYPE html>
<html >
<head >
<title >{{template "title" .}}</title >
</head >
<body >
{{template "body" .}}
</body >
</html >
{{end}}
template 語句告訴樣板引擎它應該在樣板集中尋找 {{title}} 和 {{body}} 的定義,在 home.html 和 about.html 中可以找到。
view/about.html
1
2
3
4
5
6
7
8
{{define "title"}}
Boatswain Blog | {{index . "name"}}
{{end}}
{{define "body"}}
<h1 >{{index . "msg"}}</h1 >
<h2 >This is the about page.</h2 >
{{end}}
以下就是 AboutHanlder ,它與 HomeHandler 沒有甚麼大分別。
handler/about_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package handler
import (
"net/http"
"github.com/labstack/echo"
)
func AboutHandler (c echo.Context) error {
// Please note the the second parameter "about.html" is the template name and should
// be equal to one of the keys in the TemplateRegistry array defined in main.go
return c.Render (http.StatusOK, "about.html" , map [string ]interface {}{
"name" : "About" ,
"msg" : "All about Boatswain!" ,
})
}
總結
以上就是如何在 Echo 框架下利用 Go 標準函式庫所提供的 html/template 套件來實現嵌套樣板的基本示例。通過適當的設置,我們可以為 Echo 開發更加個性化和方便的嵌套樣板,甚至可以使用其它不同的樣板引擎。
完整的例子可以在此 gitlab.com 代碼庫 上找到。