在 Go Echo Web 框架中設置 HTML 嵌套樣板
更新 @ 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
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
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
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,但在此之前,讓我們重新規劃這個示例項目的結構。
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
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
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
{{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 嵌套樣板文件。
我們在項目中添加了一些文件,新的項目結構如下。
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
main.go
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
{{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
{{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
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 代碼庫上找到。