Gin是一個golang的微框架,封裝比較優雅,API友好,源碼註釋比較明確,已經發布了1.0版本。具備快速靈活,容錯方便等特色。其實對於golang而言,web框架的依賴要遠比Python,Java之類的要小。自身的net/http足夠簡單,性能也很是不錯。框架更像是一些經常使用函數或者工具的集合。藉助框架開發,不只能夠省去不少經常使用的封裝帶來的時間,也有助於團隊的編碼風格和造成規範。html
下面就Gin的用法作一個簡單的介紹。python
首先須要安裝,安裝比較簡單,使用go get便可:git
go get -u github.com/gin-gonic/gin
使用Gin實現Hello world
很是簡單,建立一個router,而後使用其Run的方法:github
import ( "gopkg.in/gin-gonic/gin.v1" "net/http" ) func main(){ router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World") }) router.Run(":8000") }
gin的路由來自httprouter庫。所以httprouter具備的功能,gin也具備,不過gin不支持路由正則表達式:golang
func main(){ router := gin.Default() router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) }) }
冒號:
加上一個參數名組成路由參數。可使用c.Params的方法讀取其值。固然這個值是字串string。諸如/user/rsj217
,和/user/hello
均可以匹配,而/user/
和/user/rsj217/
不會被匹配。web
☁ ~ curl http://127.0.0.1:8000/user/rsj217 Hello rsj217% ☁ ~ curl http://127.0.0.1:8000/user/rsj217/ 404 page not found% ☁ ~ curl http://127.0.0.1:8000/user/ 404 page not found%
除了:
,gin還提供了*
號處理參數,*
號能匹配的規則就更多。正則表達式
func main(){ router := gin.Default() router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") message := name + " is " + action c.String(http.StatusOK, message) }) }
訪問效果以下typescript
☁ ~ curl http://127.0.0.1:8000/user/rsj217/ rsj217 is /% ☁ ~ curl http://127.0.0.1:8000/user/rsj217/中國 rsj217 is /中國%
web提供的服務一般是client和server的交互。其中客戶端向服務器發送請求,除了路由參數,其餘的參數無非兩種,查詢字符串query string和報文體body參數。所謂query string,即路由用,用?
之後鏈接的key1=value2&key2=value2
的形式的參數。固然這個key-value是通過urlencode編碼。json
對於參數的處理,常常會出現參數不存在的狀況,對因而否提供默認值,gin也考慮了,而且給出了一個優雅的方案:服務器
func main(){ router := gin.Default() router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") lastname := c.Query("lastname") c.String(http.StatusOK, "Hello %s %s", firstname, lastname) }) router.Run() }
使用c.DefaultQuery方法讀取參數,其中當參數不存在的時候,提供一個默認值。使用Query方法讀取正常參數,當參數不存在的時候,返回空字串:
☁ ~ curl http://127.0.0.1:8000/welcome Hello Guest % ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=中國 Hello 中國 % ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=中國\&lastname\=天朝 Hello 中國 天朝% ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=\&lastname\=天朝 Hello 天朝% ☁ ~ curl http://127.0.0.1:8000/welcome\?firstname\=%E4%B8%AD%E5%9B%BD Hello 中國 %
之因此使用中文,是爲了說明urlencode。注意,當firstname爲空字串的時候,並不會使用默認的Guest值,空值也是值,DefaultQuery只做用於key不存在的時候,提供默認值。
http的報文體傳輸數據就比query string稍微複雜一點,常見的格式就有四種。例如application/json
,application/x-www-form-urlencoded
, application/xml
和multipart/form-data
。後面一個主要用於圖片上傳。json格式的很好理解,urlencode其實也不難,無非就是把query string的內容,放到了body體裏,一樣也須要urlencode。默認狀況下,c.PostFROM解析的是x-www-form-urlencoded
或from-data
的參數。
func main(){ router := gin.Default() router.POST("/form_post", func(c *gin.Context) { message := c.PostForm("message") nick := c.DefaultPostForm("nick", "anonymous") c.JSON(http.StatusOK, gin.H{ "status": gin.H{ "status_code": http.StatusOK, "status": "ok", }, "message": message, "nick": nick, }) }) }
與get處理query參數同樣,post方法也提供了處理默認參數的狀況。同理,若是參數不存在,將會獲得空字串。
☁ ~ curl -X POST http://127.0.0.1:8000/form_post -H "Content-Type:application/x-www-form-urlencoded" -d "message=hello&nick=rsj217" | python -m json.tool { "message": "hello", "nick": "rsj217", "status": { "status": "ok", "status_code": 200 } }
發送數據給服務端,並非post方法才行,put方法同樣也能夠。同時querystring和body也不是分開的,兩個同時發送也能夠:
func main(){ router := gin.Default() router.PUT("/post", func(c *gin.Context) { id := c.Query("id") page := c.DefaultQuery("page", "0") name := c.PostForm("name") message := c.PostForm("message") fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message) c.JSON(http.StatusOK, gin.H{ "status_code": http.StatusOK, }) }) }
上面的例子,展現了同時使用查詢字串和body參數發送數據給服務器。
咱們先要寫一個表單頁面,所以須要引入gin如何render模板。前面咱們見識了c.String和c.JSON。下面就來看看c.HTML方法。
首先須要定義一個模板的文件夾。而後調用c.HTML渲染模板,能夠經過gin.H給模板傳值。至此,不管是String,JSON仍是HTML,以及後面的XML和YAML,均可以看到Gin封裝的接口簡明易用。
建立一個文件夾templates,而後再裏面建立html文件login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>upload</title> </head> <body> <h3>Login</h3> <form action="/form_post", method="post" > <input type="text" name="name" /> <input type="text" name="password" /> <input type="submit" value="提交" /> </form> </body> </html>
使用LoadHTMLGlob定義模板文件路徑。
router.LoadHTMLGlob("templates/*") router.GET("/login", func(c *gin.Context) { c.HTML(http.StatusOK, "login.html", gin.H{}) })
gin對於重定向的請求,至關簡單。調用上下文的Redirect方法:
router.GET("/redict/google", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "https://google.com") })
v1 := router.Group("/v1") v1.GET("/login", func(c *gin.Context) { c.String(http.StatusOK, "v1 login") }) v2 := router.Group("/v2") v2.GET("/login", func(c *gin.Context) { c.String(http.StatusOK, "v2 login") })
訪問效果以下:
☁ ~ curl http://127.0.0.1:8000/v1/login v1 login% ☁ ~ curl http://127.0.0.1:8000/v2/login v2 login%
golang的net/http設計的一大特色就是特別容易構建中間件。gin也提供了相似的中間件。須要注意的是中間件只對註冊過的路由函數起做用。對於分組路由,嵌套使用中間件,能夠限定中間件的做用範圍。中間件分爲全局中間件,單個路由中間件和羣組中間件。
先定義一箇中間件函數:
//全局中間件 func MiddleWare() gin.HandlerFunc { return func(c *gin.Context) { c.Set("salt", "qingdao") c.Next() } }
該函數很簡單,只會給c上下文添加一個屬性,並賦值。後面的路由處理器,能夠根據被中間件裝飾後提取其值。須要注意,雖然名爲全局中間件,只要註冊中間件的過程以前設置的路由,將不會受註冊的中間件所影響。只有註冊了中間件一下代碼的路由函數規則,纔會被中間件裝飾。
//使用中間件 router.Use(MiddleWare()) { router.GET("/middleware", func(c *gin.Context) { salt, _ := c.Get("salt") c.JSON(http.StatusOK, gin.H{ "salt":salt, }) }) }
使用router裝飾中間件,而後在/middlerware
便可讀取request的值,注意在router.Use(MiddleWare())
代碼以上的路由函數,將不會有被中間件裝飾的效果。
使用花括號包含被裝飾的路由函數只是一個代碼規範,即便沒有被包含在內的路由函數,只要使用router進行路由,都等於被裝飾了。想要區分權限範圍,可使用組返回的對象註冊中間件。
☁ ~ curl http://127.0.0.1:8000/middleware
{"salt":"qingdao"}
上面的註冊裝飾方式,會讓全部下面所寫的代碼都默認使用了router的註冊過的中間件。
固然,gin也提供了針對指定的路由函數進行註冊。
router.GET("/before", MiddleWare(), func(c *gin.Context) { request := c.MustGet("request").(string) c.JSON(http.StatusOK, gin.H{ "middile_request": request, }) })
羣組的中間件也相似,只要在對於的羣組路由上註冊中間件函數便可:
authorized := router.Group("/", MyMiddelware()) // 或者這樣用: authorized := router.Group("/") authorized.Use(MyMiddelware()) { authorized.POST("/login", loginEndpoint) }
羣組能夠嵌套,由於中間件也能夠根據羣組的嵌套規則嵌套。
中間件最大的做用,莫過於用於一些記錄log,錯誤handler,還有就是對部分接口的鑑權。下面就實現一個簡易的鑑權中間件。
router.GET("/auth/signin", func(c *gin.Context) { cookie := &http.Cookie{ Name: "session_id", Value: "123", Path: "/", HttpOnly: true, } http.SetCookie(c.Writer, cookie) c.String(http.StatusOK, "Login successful") }) router.GET("/home", AuthMiddleWare(), func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"data": "home"}) })
登陸函數會設置一個session_id的cookie,注意這裏須要指定path爲/
,否則gin會自動設置cookie的path爲/auth
,一個特別奇怪的問題。/homne
的邏輯很簡單,使用中間件AuthMiddleWare註冊以後,將會先執行AuthMiddleWare的邏輯,而後纔到/home
的邏輯。
AuthMiddleWare的代碼以下:
func AuthMiddleWare() gin.HandlerFunc { return func(c *gin.Context) { if cookie, err := c.Request.Cookie("session_id"); err == nil { value := cookie.Value fmt.Println(value) if value == "123" { c.Next() return } } c.JSON(http.StatusUnauthorized, gin.H{ "error": "Unauthorized", }) c.Abort() return } }
從上下文的請求中讀取cookie,而後校對cookie,若是有問題,則終止請求,直接返回,這裏使用了c.Abort()方法。
In [7]: resp = requests.get('http://127.0.0.1:8000/home') In [8]: resp.json() Out[8]: {u'error': u'Unauthorized'} In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin') In [10]: login.cookies Out[10]: <RequestsCookieJar[Cookie(version=0, name='session_id', value='123', port=None, port_specified=False, domain='127.0.0.1', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)]> In [11]: resp = requests.get('http://127.0.0.1:8000/home', cookies=login.cookies) In [12]: resp.json() Out[12]: {u'data': u'home'}