iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
1
Software Development

下班加減學點Golang與Docker系列 第 23

Gin框架搭配模板

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

View

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

<!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直接渲染網頁返回給瀏覽器.

// 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也給搬進來

func SetupRouter() *gin.Engine {
    router := gin.Default()
    router.LoadHTMLGlob("view/*")
    ...
}

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

package handler

import (
	"net/http"

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

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

一樣註冊進去routing table

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

執行看看

go run main.go

棒!! 記得補上測試XD

透過資料來渲染模板

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

把index.html改成index.tmpl
內容改成使用{{ .屬性名稱 }}, 這樣的方式表示要由gin來動態動入的內容.

<h1>Hi {{.title}}</h1>

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

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的測試

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時, 都會出現這段訊息.

[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

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所以要先往上爬一層,再去載入.

func SetupRouter() *gin.Engine {
	router := gin.Default()

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

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

Static Assets 靜態資源

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

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

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

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")
    ...
}
// 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/...

<!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.
隨時改存檔都會隨時變更, 不必重新編譯.


上一篇
Gin框架 with httptest and testify的第一次接觸
下一篇
Gin框架 檔案上傳 & 資料綁定和驗證
系列文
下班加減學點Golang與Docker30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言