golang-gin介紹(轉寄)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

轉寄自:http://shanshanpt.github.io/2016/05/03/go-gin.html

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

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

###2. 使用方法

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

gin服務端代碼是:

// func1: 處理最基本的GETfunc func1(c*gin.Context){// 回複一個200OK,在client的http-get的resp的body中擷取資料c.String(http.StatusOK,"test1 OK")}// func2: 處理最基本的POSTfunc 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)// 綁定連接埠是8888router.Run(":8888")}

用戶端代碼是:

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)}

在服務端, 執行個體化了一個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(addrstring)(err error){debugPrint("Listening and serving HTTP on %s\n",addr)defer func(){debugPrintError(err)}()// 核心代碼err=http.ListenAndServe(addr,engine)return}

其本質就是http.ListenAndServe(addr, engine).

注意: helpRead函數是用於讀取response的Body的函數, 你可以自己定義, 本文中此函數定義為:

// 用於讀取resp的bodyfunc helpRead(resp*http.Response){defer resp.Body.Close()body,err:=ioutil.ReadAll(resp.Body)iferr!=nil{fmt.Println("ERROR2!: ",err)}fmt.Println(string(body))}

<2> 傳遞參數

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

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

對應的服務端代碼為:

// func3: 處理帶參數的path-GETfunc 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-POSTfunc 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")}

用戶端測試代碼是:

func main(){// GET傳參數,使用gin的Param解析格式: /test3/:name/:passwdresp,_=http.Get("http://0.0.0.0:8888/test3/name=TAO/passwd=123")helpRead(resp)// POST傳參數,使用gin的Param解析格式: /test3/:name/:passwdresp,_=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)}

注意上面定義參數的方法有兩個輔助符號: ‘:’和’*’. 如果使用’:’參數方法, 那麼這個參數是必須要匹配的, 例如上面的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=Doerouter.GET("/test6",func6)router.POST("/test7",func7)router.Run(":8888")}

用戶端測試代碼是:

func main(){// 使用Query擷取參數形式/test6?firstname=Jane&lastname=Doeresp,_=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)}

這種方法的參數也是接在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")}

用戶端代碼是:

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)}

由於我們使用了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.Filenamefmt.Println(header.Filename)// 建立臨時接收檔案out,err:=os.Create("copy_"+filename)iferr!=nil{log.Fatal(err)}deferout.Close()// Copy資料_,err=io.Copy(out,file)iferr!=nil{log.Fatal(err)}c.String(http.StatusOK,"upload file success")}func main(){router:=gin.Default()// 接收上傳的檔案,需要使用router.POST("/upload",func9)router.Run(":8888")}

用戶端代碼是:

func main(){// 上傳檔案POST// 下面構造一個檔案buf作為POST的BODYbuf:=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)}

首先用戶端本地需要有一張”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端發送的時候必須存在,否則報錯!typeLoginstruct{Userstring`form:"user" json:"user" binding:"required"`Passwordstring`form:"password" json:"password" binding:"required"`}// bind JSON資料func funcBindJSON(c*gin.Context){varjsonLogin// binding JSON,本質是將request中的Body中的資料按照JSON格式解析到json變數中ifc.BindJSON(&json)==nil{ifjson.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){varformLogin// 本質是將c中的request中的BODY資料解析到form中// 方法一: 對於FORM資料直接使用Bind函數, 預設使用使用form格式解析,if c.Bind(&form) == nil// 方法二: 使用BindWith函數,如果你明確知道資料的類型ifc.BindWith(&form,binding.Form)==nil{ifform.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等格式的renderingrouter.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不是Uservarmsgstruct{Namestring`json:"user"`MessagestringNumberint}msg.Name="TAO"msg.Message="hey, budy"msg.Number=123// 下面的在client的顯示是"user": "TAO",不是"User": "TAO"// 所以總體的顯示是:{"user": "TAO", "Message": "hey, budy", "Number": 123c.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")}

用戶端代碼:

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)}

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

func(_ jsonBinding)Bind(req*http.Request,objinterface{})error{// 核心代碼: decode請求的body到obj中decoder:=json.NewDecoder(req.Body)iferr:=decoder.Decode(obj);err!=nil{returnerr}returnvalidate(obj)}

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

用戶端測試代碼:

func main(){// 下面測試router 的GROUPresp,_=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)}

在服務端代碼中, 首先建立了一個組group1 := router.Group(“/g1”), 並在這個組下註冊了兩個服務, group1.GET(“/read1”, func10) 和group1.GET(“/read2”, func11), 那麼當使用http://0.0.0.0:8888/g1/read1和http://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")}

首先你需要在伺服器的路徑下建立一個assert檔案夾, 並且放入1.png檔案. 如果已經存在, 請忽略.

測試代碼: 請在瀏覽器中輸入0.0.0.0:8888/showDir, 顯示的是伺服器當前路徑下地檔案資訊:


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


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


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

用戶端測試代碼是:

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

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

{ { .title } }

當用戶端請求/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")}

用戶端測試代碼是:

func main(){// 下面測試重新導向resp,_=http.Get("http://0.0.0.0:8888/redirect")helpRead(resp)}

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

<9> 使用middleware

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

funcLogger()gin.HandlerFunc{returnfunc(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()// 1router.Use(Logger())router.GET("/logger",func(c*gin.Context){example:=c.MustGet("example").(string)log.Println(example)})// 2// 下面測試BasicAuth()中介軟體登入認證//varsecrets=gin.H{"foo":gin.H{"email":"foo@bar.com","phone":"123433"},"austin":gin.H{"email":"austin@example.com","phone":"666"},"lena":gin.H{"email":"lena@guapa.com","phone":"523443"},}// Group using gin.BasicAuth() middleware// gin.Accounts is a shortcut for map[string]stringauthorized:=router.Group("/admin",gin.BasicAuth(gin.Accounts{"foo":"bar","austin":"1234","lena":"hello2","manu":"4321",}))// 請求URL: 0.0.0.0:8888/admin/secretsauthorized.GET("/secrets",func(c*gin.Context){// get user, it was set by the BasicAuth middlewareuser:=c.MustGet(gin.AuthUserKey).(string)ifsecret,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")}

用戶端測試代碼是:

func main(){// 下面測試使用中介軟體resp,_=http.Get("http://0.0.0.0:8888/logger")helpRead(resp)// 測實驗證許可權中介軟體BasicAuthresp,_=http.Get("http://0.0.0.0:8888/admin/secrets")helpRead(resp)}

服務端使用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

它其實是執行了後面所有的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()

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.