Go

Go_Gin搭配模板

在實務上, 也不會只有做WebApi專案.
也會有做WebServe的專案, 差別在那 ??
最顯著的差別就是有沒有View.
Gin有提供載入View並且把參數給填入template中再渲染的功能.

View

來玩看看.
建立一個view資料夾, 在加入一個index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Gin Hello</title>
</head>
<body>
<h1>Hi IT Home</h1>
</body>
</html>

好…但要告訴Gin, 靜態網頁在哪裡吧?
不然它不會聰明到知道頁面在這資料夾內.

LoadHTMLGlob

這隻API就是載入指定的文件夾下所有的靜態頁面.
之後我們在透過Context.HTML直接渲染網頁返回給瀏覽器.

1
2
3
// LoadHTMLGlob loads HTML files identified by glob pattern
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLGlob(pattern string) {...}

回來修改SetupRouter.go, 使用這API來載入, 把之前的mux也給搬進來

1
2
3
4
5
func SetupRouter() *gin.Engine {
router := gin.Default()
router.LoadHTMLGlob("view/*")
...
}

再來新增一個handler處理首頁的請求
indexHandler.go

1
2
3
4
5
6
7
8
9
10
11
package handler

import (
"net/http"

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

func GetIndex(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "index.html", nil)
}

一樣註冊進去routing table

1
2
3
4
indexRouting := router.Group("/")
{
indexRouting.GET("", handler.GetIndex)
}

執行看看

1
go run main.go

棒!! 記得補上測試XD

透過資料來渲染模板

很多時候的頁面內容都不可能是靜態資訊…
是會隨時隨地因人而看到的內容略有不同.
Gin支援tmpl的渲染跟資料綁定.
來玩看看

把index.html改成index.tmpl
內容改成使用

}}```, 這樣的方式表示要由gin來動態動入的內容.
1
2
```htmlmixed=
<h1>Hi {{.title}}</h1>

修改indexHandler.go
傳入一組map[string]interface{}
string要是tmpl的屬性名稱.

1
2
3
4
5
6
func GetIndex(ctx *gin.Context) {
// ctx.HTML(http.StatusOK, "index.html", nil)
ctx.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "IT Home again",
})
}

重新執行, 刷新頁面

來寫index的測試

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
package test

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/tedmax100/gin-angular/router"
)

var engine *gin.Engine

func init() {
gin.SetMode(gin.TestMode)
engine = router.SetupRouter()
}
func TestIndexGetRouter(t *testing.T) {

w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/", nil)

engine.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
}
// panic: html/template: pattern matches no files: `view/*`

疑? 找不到符合這路徑的html/template檔案.
原因在於該路徑是相對路徑, 是相對於現在執行的入口檔案所在的位置.
我們平常執行的是main.go, 該同層目錄下就有view/這資料夾.

單元測試跑得是/test/xxx_test.go
它們同層下可沒這目錄可載入.

所以解法有

  1. 寫絕對路徑; 我不想用這個XD
  2. 如果我們知道現在運行的是什麼Mode, 取不同Mode的相對路徑.

Gin Running Man(x) Mode(o)

在我們每次執行go run main.go時, 都會出現這段訊息.

1
2
3
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)

Gin定義了3種Mode

1
2
3
4
5
6
7
8
const (
// DebugMode indicates gin mode is debug.
DebugMode = "debug"
// ReleaseMode indicates gin mode is release.
ReleaseMode = "release"
// TestMode indicates gin mode is test.
TestMode = "test"
)

清楚的告訴我們能用env或是SetMode去定義.

把SetupRouter.go()改成這樣
判斷mode, 去載入相對於自己不同位置的view
test的上一層才能看得到veiw所以要先往上爬一層,再去載入.

1
2
3
4
5
6
7
8
9
10
func SetupRouter() *gin.Engine {
router := gin.Default()

if mode := gin.Mode(); mode == gin.TestMode {
router.LoadHTMLGlob("./../view/*")
} else {
router.LoadHTMLGlob("view/*")
}
...
}
1
go run test ./...

繼續, 有了tmpl了.
那js、css、image這類的靜態資源總要吧?

Static Assets 靜態資源

先來下載Bootstrap
Bootstrap內剛好有js+css, 來美化我們的網頁

在專案根目錄開個asset資料夾, 內有js、css、img這三個資料夾.
把bootstrap解壓縮到對應的資料夾內.

老樣子要告訴Gin這些asset在哪裡.
一樣修改SetupRouter(), 這裡新增呼叫Static()這隻API.

1
2
3
4
5
6
7
8
9
10
11
12
func SetupRouter() *gin.Engine {
router := gin.Default()

if mode := gin.Mode(); mode == gin.TestMode {
router.LoadHTMLGlob("./../view/*")
} else {
router.LoadHTMLGlob("view/*")
}

router.Static("/assetPath", "./asset")
...
}
1
2
3
4
5
6
7
8
9
// Static serves files from the given file system root.
// Internally a http.FileServer is used, therefore http.NotFound is used instead
// of the Router's NotFound handler.
// To use the operating system's file system implementation,
// use :
// router.Static("/static", "/var/www")
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
return group.StaticFS(relativePath, Dir(root, false))
}

第一個參數是說我們對著gin註冊一個靜態資源目錄.
第二個參數才是這些靜態資源的真實位置
舉例: 也許最後這些資源都會被bundle放到dist/內.
那我們第二個參數就會改成dist/…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">

<link rel="stylesheet" href="/assetPath/css/bootstrap.min.css">
<link rel="stylesheet" href="/assetPath/css/bootstrap-grid.min.css">
<link rel="stylesheet" href="/assetPath/css/bootstrap-reboot.min.css">
<script rel="script" src="/assetPath/js/bootstrap.bundle.js"></script>

<title>Gin Hello</title>
</head>
<body>
<h1>Hi {{.title}}</h1>
</body>
</html>

執行看看!

水, 正常.

因為index.tmpl 就只是個template.
隨時改存檔都會隨時變更, 不必重新編譯.

鐵人賽連結

分享到