1. 程式人生 > >Go語言web框架 gin

Go語言web框架 gin

Go語言web框架 GIN

gin是go語言環境下的一個web框架, 它類似於Martini, 官方聲稱它比Martini有更好的效能, 比Martini快40倍, Ohhhh….看著不錯的樣子, 所以就想記錄一下gin的學習. gin的github程式碼在這裡: gin原始碼. gin的效率獲得如此突飛猛進, 得益於另一個開源專案httprouter, 專案地址: httprouter原始碼. 下面主要記錄一下gin的使用.
  • 1

1. 安裝gin

  使用命令go get github.com/gin-gonic/gin就可以. 我們使用gin的時候引入相應的包就OKimport "github.com/gin-gonic/gin".
  • 1

2. 使用方法

<1> 一種最簡單的使用GET/POST方法

gin服務端程式碼是:

/ func1: 處理最基本的GET
func func1 (c *gin.Context)  {
    // 回覆一個200OK,在client的http-get的resp的body中獲取資料
    c.String(http.StatusOK, "test1 OK") } // func2: 處理最基本的POST func func2 (c *gin.Context) { // 回覆一個200 OK, 在client的http-post的resp的body中獲取資料 c.String(http.StatusOK, "test2 OK") } func main(){ // 註冊一個預設的路由器 router := gin.Default() // 最基本的用法 router.GET("/test1", func1) router.POST("/test2", func2) // 繫結埠是8888 router.Run(":8888") }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

客戶端程式碼是:

func main(){
    // 呼叫最基本的GET,並獲得返回值
    resp,_ := http.Get("http://0.0.0.0:8888/test1")
    helpRead(resp)

    // 呼叫最基本的POST,並獲得返回值
    resp,_ = http.Post("http://0.0.0.0:8888/test2", "",strings.NewReader("")) helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在服務端, 例項化了一個router, 然後使用GET和POST方法分別註冊了兩個服務, 當我們使用HTTP GET方法的時候會使用GET註冊的函式, 如果使用HTTP POST的方法, 那麼會使用POST註冊的函式. gin支援所有的HTTP的方法例如: GET, POST, PUT, PATCH, DELETE 和 OPTIONS等. 看客戶端中的程式碼, 當呼叫http.Get(“http://0.0.0.0:8888/test1“)的時候, 服務端接收到請求, 並根據/test1將請求路由到func1函式進行 處理. 同理, 呼叫http.Post(“http://0.0.0.0:8888/test2“, “”,strings.NewReader(“”))時候, 會使用func2函式處理. 在func1和func2中, 使用gin.Context填充了一個String的回覆. 當然也支援JSON, XML, HTML等其他一些格式資料. 當執行c.String或者c.JSON時, 相當於向http的回覆緩衝區寫入了 一些資料. 最後呼叫router.Run(“:8888”)開始進行監聽,Run的核心程式碼是:

func (engine *Engine) Run(addr string) (err error) {
    debugPrint("Listening and serving HTTP on %s\n", addr)
    defer func() { debugPrintError(err) }() // 核心程式碼 err = http.ListenAndServe(addr, engine) return }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其本質就是http.ListenAndServe(addr, engine). 
注意: helpRead函式是用於讀取response的Body的函式, 你可以自己定義, 本文中此函式定義為:

// 用於讀取resp的body
func helpRead(resp *http.Response)  {
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("ERROR2!: ", err) } fmt.Println(string(body)) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

<2> 傳遞引數

傳遞引數有幾種方法, 對應到gin使用幾種不同的方式來解析.

第一種: 使用gin.Context中的Param方法解析

對應的服務端程式碼為:

// func3: 處理帶引數的path-GET
func func3(c *gin.Context)  {
    // 回覆一個200 OK
    // 獲取傳入的引數
    name := c.Param("name")
    passwd := c.Param("passwd") c.String(http.StatusOK, "引數:%s %s test3 OK", name, passwd) } // func4: 處理帶引數的path-POST func func4(c *gin.Context) { // 回覆一個200 OK // 獲取傳入的引數 name := c.Param("name") passwd := c.Param("passwd") c.String(http.StatusOK, "引數:%s %s test4 OK", name, passwd) } // func5: 注意':'和'*'的區別 func func5(c *gin.Context) { // 回覆一個200 OK // 獲取傳入的引數 name := c.Param("name") passwd := c.Param("passwd") c.String(http.StatusOK, "引數:%s %s test5 OK", name, passwd) } func main(){ router := gin.Default() // TODO:注意':'必須要匹配,'*'選擇匹配,即存在就匹配,否則可以不考慮 router.GET("/test3/:name/:passwd", func3) router.POST("/test4/:name/:passwd", func4) router.GET("/test5/:name/*passwd", func5) router.Run(":8888") }
  • 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

客戶端測試程式碼是:

func main() {
    // GET傳引數,使用gin的Param解析格式: /test3/:name/:passwd
    resp,_ = http.Get("http://0.0.0.0:8888/test3/name=TAO/passwd=123")
    helpRead(resp)

    // POST傳引數,使用gin的Param解析格式: /test3/:name/:passwd
    resp,_ = http.Post("http://0.0.0.0:8888/test4/name=PT/passwd=456", "",strings.NewReader("")) helpRead(resp)  // 注意Param中':'和'*'的區別 resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/passwd=789") helpRead(resp) resp,_ = http.Get("http://0.0.0.0:8888/test5/name=TAO/") helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注意上面定義引數的方法有兩個輔助符號: ‘:’和’’. 如果使用’:’引數方法, 那麼這個引數是必須要匹配的, 例如上面的router.GET(“/test3/:name/:passwd”, func3), 當請求URL是 類似於http://0.0.0.0:8888/test3/name=TAO/passwd=123這樣的參會被匹配, 如果是http://0.0.0.0:8888/test3/name=TAO 或者http://0.0.0.0:8888/test3/passwd=123是不能匹配的. 但是如果使用’‘引數, 那麼這個引數是可選的. router.GET(“/test5/:name/*passwd”, func5) 可以匹配http://0.0.0.0:8888/test5/name=TAO/passwd=789, 也可以匹配http://0.0.0.0:8888/test5/name=TAO/. 需要注意的一點是, 下面這個URL是不是能夠 匹配呢? http://0.0.0.0:8888/test5/name=TAO, 注意TAO後面沒有’/’, 這個其實就要看有沒有一個路由是到http://0.0.0.0:8888/test5/name=TAO路徑的, 如果有, 那麼指定的那個函式進行處理, 如果沒有http://0.0.0.0:8888/test5/name=TAO會被重定向到http://0.0.0.0:8888/test5/name=TAO/, 然後被當前註冊的函式進行處理.

第二種: 使用gin.Context中的Query方法解析

這個類似於正常的URL中的引數傳遞, 先看服務端程式碼:

// 使用Query獲取引數
func func6(c *gin.Context)  {
    // 回覆一個200 OK
    // 獲取傳入的引數
    name := c.Query("name")
    passwd := c.Query("passwd") c.String(http.StatusOK, "引數:%s %s test6 OK", name, passwd) } // 使用Query獲取引數 func func7(c *gin.Context) { // 回覆一個200 OK // 獲取傳入的引數 name := c.Query("name") passwd := c.Query("passwd") c.String(http.StatusOK, "引數:%s %s test7 OK", name, passwd) } func main(){ router := gin.Default() // 使用gin的Query引數形式,/test6?firstname=Jane&lastname=Doe router.GET("/test6", func6) router.POST("/test7", func7) router.Run(":8888") }
  • 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

客戶端測試程式碼是:

func main() {
    // 使用Query獲取引數形式/test6?firstname=Jane&lastname=Doe
    resp,_ = http.Get("http://0.0.0.0:8888/test6?name=BBB&passwd=CCC")
    helpRead(resp)
    resp,_ = http.Post("http://0.0.0.0:8888/test7?name=DDD&passwd=EEE", "",strings.NewReader("")) helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這種方法的引數也是接在URL後面, 形如http://0.0.0.0:8888/test6?name=BBB&passwd=CCC伺服器可以使用name := c.Query(“name”)這種 方法來解析引數.

第三種: 使用gin.Context中的PostForm方法解析

我們需要將引數放在請求的Body中傳遞, 而不是URL中. 先看服務端程式碼:

// 引數是form中獲得,即從Body中獲得,忽略URL中的引數
func func8(c *gin.Context)  {
    message := c.PostForm("message")
    extra := c.PostForm("extra") nick := c.DefaultPostForm("nick", "anonymous") c.JSON(200, gin.H{ "status": "test8:posted", "message": message, "nick": nick, "extra": extra, }) } func main(){ router := gin.Default() // 使用post_form形式,注意必須要設定Post的type, // 同時此方法中忽略URL中帶的引數,所有的引數需要從Body中獲得 router.POST("/test8", func8) router.Run(":8888") }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

客戶端程式碼是:

func main() {
    // 使用post_form形式,注意必須要設定Post的type,同時此方法中忽略URL中帶的引數,所有的引數需要從Body中獲得
    resp,_ = http.Post("http://0.0.0.0:8888/test8", "application/x-www-form-urlencoded",strings.NewReader("message=8888888&extra=999999")) helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5

由於我們使用了request Body, 那麼就需要指定Body中資料的形式, 此處是form格式, 即application/x-www-form-urlencoded. 常見的幾種http提交資料方式有: application/x-www-form-urlencoded; multipart/form-data; application/json; text/xml. 具體使用請google. 
在服務端, 使用message := c.PostForm(“message”)方法解析引數, 然後進行處理.

<3> 傳輸檔案 
下面測試從client傳輸檔案到server. 傳輸檔案需要使用multipart/form-data格式的資料, 所有需要設定Post的型別是multipart/form-data. 
首先看服務端程式碼:

// 接收client上傳的檔案
// 從FormFile中獲取相關的檔案data!
// 然後寫入本地檔案
func func9(c *gin.Context) {
    // 注意此處的檔名和client處的應該是一樣的
    file, header , err := c.Request.FormFile("uploadFile")
    filename := header.Filename fmt.Println(header.Filename) // 建立臨時接收檔案 out, err := os.Create("copy_"+filename) if err != nil { log.Fatal(err) } defer out.Close() // Copy資料 _, err = io.Copy(out, file) if err != nil { log.Fatal(err) } c.String(http.StatusOK, "upload file success") } func main(){ router := gin.Default() // 接收上傳的檔案,需要使用 router.POST("/upload", func9) router.Run(":8888") }
  • 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

客戶端程式碼是:

func main() {
    // 上傳檔案POST
    // 下面構造一個檔案buf作為POST的BODY
    buf := new(bytes.Buffer)
    w := multipart.NewWriter(buf)
    fw,_ := w.CreateFormFile("uploadFile", "images.png") //這裡的uploadFile必須和伺服器端的FormFile-name一致 fd,_ := os.Open("images.png") defer fd.Close() io.Copy(fw, fd) w.Close() resp,_ = http.Post("http://0.0.0.0:8888/upload", w.FormDataContentType(), buf) helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

首先客戶端本地需要有一張”images.png”圖片, 同時需要建立一個Form, 並將field-name命名為”uploadFile”, file-name命名為”images.png”. 在服務端, 通過”uploadFile”可以得到檔案資訊. 客戶端繼續將圖片資料copy到建立好的Form中, 將資料資料Post出去, 注意資料的型別指定! 在服務端, 通過file, header , err := c.Request.FormFile(“uploadFile”)獲得檔案資訊, file中就是檔案資料, 將其拷貝到本地檔案, 完成檔案傳輸.

<4> binding資料

gin內建了幾種資料的繫結例如JSON, XML等. 簡單來說, 即根據Body資料型別, 將資料賦值到指定的結構體變數中. (類似於序列化和反序列化) 
看服務端程式碼:

// Binding資料
// 注意:後面的form:user表示在form中這個欄位是user,不是User, 同樣json:user也是
// 注意:binding:"required"要求這個欄位在client端傳送的時候必須存在,否則報錯!
type Login struct {
    User     string `form:"user" json:"user" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"` } // bind JSON資料 func funcBindJSON(c *gin.Context) { var json Login // binding JSON,本質是將request中的Body中的資料按照JSON格式解析到json變數中 if c.BindJSON(&json) == nil { if json.User == "TAO" && json.Password == "123" { c.JSON(http.StatusOK, gin.H{"JSON=== status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"JSON=== status": "unauthorized"}) } } else { c.JSON(404, gin.H{"JSON=== status": "binding JSON error!"}) } } // 下面測試bind FORM資料 func funcBindForm(c *gin.Context) { var form Login // 本質是將c中的request中的BODY資料解析到form中 // 方法一: 對於FORM資料直接使用Bind函式, 預設使用使用form格式解析,if c.Bind(&form) == nil // 方法二: 使用BindWith函式,如果你明確知道資料的型別 if c.BindWith(&form, binding.Form) == nil{ if form.User == "TAO" && form.Password == "123" { c.JSON(http.StatusOK, gin.H{"FORM=== status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"FORM=== status": "unauthorized"}) } } else { c.JSON(404, gin.H{"FORM=== status": "binding FORM error!"}) } } func main(){ router := gin.Default() // 下面測試bind JSON資料 router.POST("/bindJSON", funcBindJSON) // 下面測試bind FORM資料 router.POST("/bindForm", funcBindForm) // 下面測試JSON,XML等格式的rendering router.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey, budy", "status": http.StatusOK}) }) router.GET("/moreJSON", func(c *gin.Context) { // 注意:這裡定義了tag指示在json中顯示的是user不是User var msg struct { Name string `json:"user"` Message string Number int } msg.Name = "TAO" msg.Message = "hey, budy" msg.Number = 123 // 下面的在client的顯示是"user": "TAO",不是"User": "TAO" // 所以總體的顯示是:{"user": "TAO", "Message": "hey, budy", "Number": 123 c.JSON(http.StatusOK, msg) }) // 測試傳送XML資料 router.GET("/someXML", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{"name":"TAO", "message": "hey, budy", "status": http.StatusOK}) }) router.Run(":8888")
  • 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
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

客戶端程式碼:

func main() {
    // 下面測試binding資料
    // 首先測試binding-JSON,
    // 注意Body中的資料必須是JSON格式
    resp,_ = http.Post("http://0.0.0.0:8888/bindJSON", "application/json", strings.NewReader("{\"user\":\"TAO\", \"password\": \"123\"}")) helpRead(resp)  // 下面測試bind FORM資料 resp,_ = http.Post("http://0.0.0.0:8888/bindForm", "application/x-www-form-urlencoded", strings.NewReader("user=TAO&password=123")) helpRead(resp)  // 下面測試接收JSON和XML資料 resp,_ = http.Get("http://0.0.0.0:8888/someJSON") helpRead(resp) resp,_ = http.Get("http://0.0.0.0:8888/moreJSON") helpRead(resp) resp,_ = http.Get("http://0.0.0.0:8888/someXML") helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

客戶端傳送請求, 在服務端可以直接使用c.BindJSON繫結到Json結構體上. 或者使用BindWith函式也可以, 但是需要指定繫結的資料型別, 例如JSON, XML, HTML等. Bind*函式的本質是讀取request中的body資料, 拿BindJSON為例, 其核心程式碼是:

func (_ jsonBinding) Bind(req *http.Request, obj interface{}) error {
    // 核心程式碼: decode請求的body到obj中
    decoder := json.NewDecoder(req.Body)
    if err := decoder.Decode(obj); err != nil { return err } return validate(obj) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

<5> router group

router group是為了方便字首相同的URL的管理, 其基本用法如下. 
首先看服務端程式碼:

// router GROUP - GET測試
func func10(c *gin.Context)  {
    c.String(http.StatusOK, "test10 OK")
}
func func11(c *gin.Context) { c.String(http.StatusOK, "test11 OK") } // router GROUP - POST測試 func func12(c *gin.Context) { c.String(http.StatusOK, "test12 OK") } func func13(c *gin.Context) { c.String(http.StatusOK, "test13 OK") } func main(){ router := gin.Default() // router Group是為了將一些字首相同的URL請求放在一起管理 group1 := router.Group("/g1") group1.GET("/read1", func10) group1.GET("/read2", func11) group2 := router.Group("/g2") group2.POST("/write1", func12) group2.POST("/write2", func13) router.Run(":8888") }
  • 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

客戶端測試程式碼:

func main() {
    // 下面測試router 的GROUP
    resp,_ = http.Get("http://0.0.0.0:8888/g1/read1")
    helpRead(resp)
    resp,_ = http.Get("http://0.0.0.0:8888/g1/read2")
    helpRead(resp)
    resp,_ = http.Post("http://0.0.0.0:8888/g2/write1", "", strings.NewReader("")) helpRead(resp) resp,_ = http.Post("http://0.0.0.0:8888/g2/write2", "", strings.NewReader("")) helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在服務端程式碼中, 首先建立了一個組group1 := router.Group(“/g1”), 並在這個組下注冊了兩個服務, group1.GET(“/read1”, func10) 和group1.GET(“/read2”, func11), 那麼當使用http://0.0.0.0:8888/g1/read1http://0.0.0.0:8888/g1/read2訪問時, 是可以路由 到上面註冊的位置的. 同理對於group2 := router.Group(“/g2”)也是一樣的.

<6> 靜態檔案服務

可以向客戶端展示本地的一些檔案資訊, 例如顯示某路徑下地檔案. 服務端程式碼是:

func main(){
    router := gin.Default() // 下面測試靜態檔案服務 // 顯示當前資料夾下的所有檔案/或者指定檔案 router.StaticFS("/showDir", http.Dir(".")) router.Static("/files", "/bin") router.StaticFile("/image", "./assets/1.png") router.Run(":8888") }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

首先你需要在伺服器的路徑下建立一個assert資料夾, 並且放入1.png檔案. 如果已經存在, 請忽略. 
測試程式碼: 請在瀏覽器中輸入0.0.0.0:8888/showDir, 顯示的是伺服器當前路徑下地檔案資訊:

1這裡寫圖片描述

輸入0.0.0.0:8888/files, 顯示的是/bin目錄下地檔案資訊:

2這裡寫圖片描述

輸入0.0.0.0:8888/image, 顯示的是伺服器下地./assets/1.png圖片:

3這裡寫圖片描述

<7> 載入模板templates

gin支援載入HTML模板, 然後根據模板引數進行配置並返回相應的資料. 
看服務端程式碼

func main(){
    router := gin.Default()
    // 下面測試載入HTML: LoadHTMLTemplates
    // 載入templates資料夾下所有的檔案
    router.LoadHTMLGlob("templates/*")
    // 或者使用這種方法載入也是OK的: router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") router.GET("/index", func(c *gin.Context) { // 注意下面將gin.H引數傳入index.tmpl中!也就是使用的是index.tmpl模板 c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "GIN: 測試載入HTML模板", }) }) router.Run(":8888") }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

客戶端測試程式碼是:

func main() {
    // 測試載入HTML模板
    resp,_ = http.Get("http://0.0.0.0:8888/index")
    helpRead(resp)
}
  • 1
  • 2
  • 3
  • 4
  • 5

在服務端, 我們需要載入需要的templates, 這裡有兩種方法: 第一種使用LoadHTMLGlob載入所有的正則匹配的模板, 本例中使用的是*, 即匹配所有檔案, 所以載入的是 templates資料夾下所有的模板. 第二種使用LoadHTMLFiles載入指定檔案. 在本例伺服器路徑下有一個templates目錄, 下面有一個index.tmpl模板, 模板的 內容是:

<html>
    <h1>
       { { .title } }
    </h1> </html>
  • 1
  • 2
  • 3
  • 4
  • 5

當客戶端請求/index時, 伺服器使用這個模板, 並填充相應的引數, 此處引數只有title, 然後將HTML資料返回給客戶端. 
你也可以在瀏覽器請求0.0.0.0:8888/index, 效果如下圖所示: 
這裡寫圖片描述

<8> 重定向

重定向相對比較簡單, 服務端程式碼是:

func main(){
    router := gin.Default() // 下面測試重定向 router.GET("/redirect", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://shanshanpt.github.io/") }) router.Run(":8888") }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

客戶端測試程式碼是:

func main() {
    // 下面測試重定向
    resp,_ = http.Get("http://0.0.0.0:8888/redirect")
    helpRead(resp)
}
  • 1
  • 2
  • 3
  • 4
  • 5

當我們請求http://0.0.0.0:8888/redirect的時候, 會重定向到http://shanshanpt.github.io/這個站點.

<9> 使用middleware

這裡使用了兩個例子, 一個是logger, 另一個是BasiAuth, 具體看伺服器程式碼:

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        // 設定example變數到Context的Key中,通過Get等函式可以取得
        c.Set("example", "12345") // 傳送request之前 c.Next() // 傳送request之後 latency := time.Since(t) log.Print(latency) // 這個c.Write是ResponseWriter,我們可以獲得狀態等資訊 status := c.Writer.Status() log.Println(status) } } func main(){ router := gin.Default() // 1 router.Use(Logger()) router.GET("/logger", func(c *gin.Context) { example := c.MustGet("example").(string) log.Println(example) }) // 2 // 下面測試BasicAuth()中介軟體登入認證 // var secrets = gin.H{ "foo": gin.H{"email": "[email protected]", "phone": "123433"}, "austin": gin.H{"email": "[email protected]", "phone": "666"}, "lena": gin.H{"email": "[email protected]", "phone": "523443"}, } // Group using gin.BasicAuth() middleware // gin.Accounts is a shortcut for map[string]string authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{ "foo": "bar", "austin": "1234", "lena": "hello2", "manu": "4321", })) // 請求URL: 0.0.0.0:8888/admin/secrets authorized.GET("/secrets", func(c *gin.Context) { // get user, it was set by the BasicAuth middleware user := c.MustGet(gin.AuthUserKey).(string) if secret, ok := secrets[user]; ok { c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) } }) router.Run(":8888") }
  • 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
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

客戶端測試程式碼是:

func main() {
    // 下面測試使用中介軟體
    resp,_ = http.Get("http://0.0.0.0:8888/logger")
    helpRead(resp)

    // 測試驗證許可權中介軟體BasicAuth
    resp,_ = http.Get("http://0.0.0.0:8888/admin/secrets") helpRead(resp) }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

服務端使用Use方法匯入middleware, 當請求/logger來到的時候, 會執行Logger(), 並且我們知道在GET註冊的時候, 同時註冊了匿名函式, 所有請看Logger函式中存在一個c.Next()的用法, 它是取出所有的註冊的函式都執行一遍, 然後再回到本函式中, 所以, 本例中相當於是先執行了 c.Next()即註冊的匿名函式, 然後回到本函式繼續執行. 所以本例的Print的輸出順序是: 
log.Println(example) 
log.Print(latency) 
log.Println(status) 
如果將c.Next()放在log.Print(latency)後面, 那麼log.Println(example)和log.Print(latency)執行的順序就調換了. 所以一切都取決於c.Next()執行的位置. c.Next()的核心程式碼如下:

// Next should be used only in the middlewares.
// It executes the pending handlers in the chain inside the calling handler.
// See example in github.
func (c *Context) Next() {
    c.index++
    s := int8(len(c.handlers))
    for ; c.index < s; c.index++ { c.handlers[c.index](c) } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

它其實是執行了後面所有的handlers. 
關於使用gin.BasicAuth() middleware, 可以直接使用一個router group進行處理, 本質和logger一樣.

<10> 繫結http server

之前所有的測試中, 我們都是使用router.Run(“:8888”)開始執行監聽, 其實還有兩種方法:

// 方法二
http.ListenAndServe(":8888", router)

// 方法三:
server := &http.Server{
    Addr:           ":8888", Handler: router, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } server.ListenAndServe() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

至此, gin最基本的一些應用都整理完了, 下面就具體看看程式碼中的一些實現. 有時間再記錄吧. 
寫幾個注意事項把: 
1.中介軟體的使用插拔可以這樣寫:router.Use(MymiddleWare),寫在該語句後面的路由轉發,都會經過中介軟體的過濾,之前的不受影響,對單個路由轉發使用一個或者多箇中間件可以這樣寫 router.GET(“/”,MymiddleWare1,MymiddleWare2,HandleFunc) 
2.對c *gin.Context的c.Query(),c.Param()都只適用於GET請求的地址後的引數,如果要接收POST資料,必須使用c.Bind(&buf)

3.參考:

gin-github