1. 程式人生 > >[轉]Go語言(Golang)的Web框架比較:gin VS echo

[轉]Go語言(Golang)的Web框架比較:gin VS echo

所謂框架

框架一直是敏捷開發中的利器,能讓開發者很快的上手並做出應用,甚至有的時候,脫離了框架,一些開發者都不會寫程式了。成長總不會一蹴而就,從寫出程式獲取成就感,再到精通框架,快速構造應用,當這些方面都得心應手的時候,可以嘗試改造一些框架,或是自己創造一個。

曾經我以為Python世界裡的框架已經夠多了,後來發現相比golang簡直小巫見大巫。golang提供的net/http庫已經很好了,對於http的協議的實現非常好,基於此再造框架,也不會是難事,因此生態中出現了很多框架。既然構造框架的門檻變低了,那麼低門檻同樣也會帶來質量參差不齊的框架。

考察了幾個框架,通過其github的活躍度,維護的team,以及生產環境中的使用率。發現

Gin還是一個可以學習的輕巧框架。

Gin

Gin是一個golang的微框架,封裝比較優雅,API友好,原始碼註釋比較明確,已經發布了1.0版本。具有快速靈活,容錯方便等特點。其實對於golang而言,web框架的依賴要遠比Python,Java之類的要小。自身的net/http足夠簡單,效能也非常不錯。框架更像是一些常用函式或者工具的集合。藉助框架開發,不僅可以省去很多常用的封裝帶來的時間,也有助於團隊的編碼風格和形成規範。

下面就Gin的用法做一個簡單的介紹。

首先需要安裝,安裝比較簡單,使用go get即可:

go get gopkg.in/gin-gonic/gin.v1

gin的版本託管再 gopkg的網站上。我在安裝的過程中,gokpg卡住了,後來不得不根據gin裡的godep的檔案,把響應的原始碼從github上下載,然後copy到對應的目錄。

關於golang的包管理和依賴,我們以後再討論。

Hello World

使用Gin實現Hello world非常簡單,建立一個router,然後使用其Run的方法:

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

簡單幾行程式碼,就能實現一個web服務。使用gin的Default方法建立一個路由handler。然後通過HTTP方法繫結路由規則和路由函式。不同於net/http庫的路由函式,gin進行了封裝,把request和response都封裝到gin.Context的上下文環境。最後是啟動路由的Run方法監聽埠。麻雀雖小,五臟俱全。當然,除了GET方法,gin也支援POST,PUT,DELETE,OPTION等常用的restful方法。

restful路由

gin的路由來自httprouter庫。因此httprouter具有的功能,gin也具有,不過gin不支援路由正則表示式:

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/不會被匹配。

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

訪問效果如下

☁  ~  curl http://127.0.0.1:8000/user/rsj217/
rsj217 is /%                                                                  ☁  ~  curl http://127.0.0.1:8000/user/rsj217/中國
rsj217 is /中國%

query string引數與body引數

web提供的服務通常是client和server的互動。其中客戶端向伺服器傳送請求,除了路由引數,其他的引數無非兩種,查詢字串query string和報文體body引數。所謂query string,即路由用,用?以後連線的key1=value2&key2=value2的形式的引數。當然這個key-value是經過urlencode編碼。

query string

對於引數的處理,經常會出現引數不存在的情況,對於是否提供預設值,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不存在的時候,提供預設值。

body

http的報文體傳輸資料就比query string稍微複雜一點,常見的格式就有四種。例如application/jsonapplication/x-www-form-urlencoded, application/xmlmultipart/form-data。後面一個主要用於圖片上傳。json格式的很好理解,urlencode其實也不難,無非就是把query string的內容,放到了body體裡,同樣也需要urlencode。預設情況下,c.PostFROM解析的是x-www-form-urlencodedfrom-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
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   104  100    79  100    25  48555  15365 --:--:-- --:--:-- --:--:-- 79000
{
    "message": "hello",
    "nick": "rsj217",
    "status": {
        "status": "ok",
        "status_code": 200
    }
}

前面我們使用c.String返回響應,顧名思義則返回string型別。content-type是plain或者text。呼叫c.JSON則返回json資料。其中gin.H封裝了生成json的方式,是一個強大的工具。使用golang可以像動態語言一樣寫字面量的json,對於巢狀json的實現,巢狀gin.H即可。

傳送資料給服務端,並不是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引數傳送資料給伺服器。

檔案上傳

上傳單個檔案

前面介紹了基本的傳送資料,其中multipart/form-data轉用於檔案上傳。gin檔案上傳也很方便,和原生的net/http方法類似,不同在於gin把原生的request封裝到c.Request中了。

func main(){
    router := gin.Default()
    
    router.POST("/upload", func(c *gin.Context) {
        name := c.PostForm("name")
        fmt.Println(name)
        file, header, err := c.Request.FormFile("upload")
        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")
            return
        }
        filename := header.Filename

        fmt.Println(file, err, filename)

        out, err := os.Create(filename)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusCreated, "upload successful")
    })
    router.Run(":8000")
}

使用c.Request.FormFile解析客戶端檔案name屬性。如果不傳檔案,則會拋錯,因此需要處理這個錯誤。一種方式是直接返回。然後使用os的操作,把檔案資料複製到硬碟上。

使用下面的命令可以測試上傳,注意upload為c.Request.FormFile指定的引數,其值必須要是絕對路徑:

curl -X POST http://127.0.0.1:8000/upload -F "[email protected]/Users/ghost/Desktop/pic.jpg" -H "Content-Type: multipart/form-data"

上傳多個檔案

單個檔案上傳很簡單,別以為多個檔案就會很麻煩。依葫蘆畫瓢,所謂多個檔案,無非就是多一次遍歷檔案,然後一次copy資料儲存即可。下面只寫handler,省略main函式的初始化路由和開啟伺服器監聽了:

router.POST("/multi/upload", func(c *gin.Context) {
        err := c.Request.ParseMultipartForm(200000)
        if err != nil {
            log.Fatal(err)
        }

        formdata := c.Request.MultipartForm 

        files := formdata.File["upload"] 
        for i, _ := range files { /
            file, err := files[i].Open()
            defer file.Close()
            if err != nil {
                log.Fatal(err)
            }

            out, err := os.Create(files[i].Filename)

            defer out.Close()

            if err != nil {
                log.Fatal(err)
            }

            _, err = io.Copy(out, file)

            if err != nil {
                log.Fatal(err)
            }

            c.String(http.StatusCreated, "upload successful")

        }

    })

與單個檔案上傳類似,只不過使用了c.Request.MultipartForm得到檔案控制代碼,再獲取檔案資料,然後遍歷讀寫。

使用curl上傳

curl -X POST http://127.0.0.1:8000/multi/upload -F "[email protected]/Users/ghost/Desktop/pic.jpg" -F "[email protected]/Users/ghost/Desktop/journey.png" -H "Content-Type: multipart/form-data"

表單上傳

上面我們使用的都是curl上傳,實際上,使用者上傳圖片更多是通過表單,或者ajax和一些requests的請求完成。下面展示一下web的form表單如何上傳。

我們先要寫一個表單頁面,因此需要引入gin如何render模板。前面我們見識了c.String和c.JSON。下面就來看看c.HTML方法。

首先需要定義一個模板的資料夾。然後呼叫c.HTML渲染模板,可以通過gin.H給模板傳值。至此,無論是String,JSON還是HTML,以及後面的XML和YAML,都可以看到Gin封裝的介面簡明易用。

建立一個資料夾templates,然後再裡面建立html檔案upload.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
<h3>Single Upload</h3>
<form action="/upload", method="post" enctype="multipart/form-data">
    <input type="text" value="hello gin" />
    <input type="file" name="upload" />
    <input type="submit" value="upload" />
</form>


<h3>Multi Upload</h3>
<form action="/multi/upload", method="post" enctype="multipart/form-data">
    <input type="text" value="hello gin" />
    <input type="file" name="upload" />
    <input type="file" name="upload" />
    <input type="submit" value="upload" />
</form>

</body>
</html>

upload 很簡單,沒有引數。一個用於單個檔案上傳,一個用於多個檔案上傳。

    router.LoadHTMLGlob("templates/*")
    router.GET("/upload", func(c *gin.Context) {
        c.HTML(http.StatusOK, "upload.html", gin.H{})
    })

使用LoadHTMLGlob定義模板檔案路徑。

引數繫結

我們已經見識了x-www-form-urlencoded型別的引數處理,現在越來越多的應用習慣使用JSON來通訊,也就是無論返回的response還是提交的request,其content-type型別都是application/json的格式。而對於一些舊的web表單頁還是x-www-form-urlencoded的形式,這就需要我們的伺服器能改hold住這多種content-type的引數了。

Python的世界裡很好解決,畢竟動態語言不需要實現定義資料模型。因此可以寫一個裝飾器將兩個格式的資料封裝成一個數據模型。golang中要處理並非易事,好在有gin,他們的model bind功能非常強大。

type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Passwd   string `form:"passwd" json:"passwd" bdinding:"required"`
    Age      int    `form:"age" json:"age"`
}

func main(){
    router := gin.Default()
    
    router.POST("/login", func(c *gin.Context) {
        var user User
        var err error
        contentType := c.Request.Header.Get("Content-Type")

        switch contentType {
        case "application/json":
            err = c.BindJSON(&user)
        case "application/x-www-form-urlencoded":
            err = c.BindWith(&user, binding.Form)
        }

        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "user":   user.Username,
            "passwd": user.Passwd,
            "age":    user.Age,
        })

    })

}

先定義一個User模型結構體,然後針對客戶端的content-type,一次使BindJSONBindWith方法。

☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&age=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    79  100    46  100    33  41181  29543 --:--:-- --:--:-- --:--:-- 46000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    78  100    45  100    33  37751  27684 --:--:-- --:--:-- --:--:-- 45000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

可以看到,結構體中,設定了binding標籤的欄位(username和passwd),如果沒傳會拋錯誤。非banding的欄位(age),對於客戶端沒有傳,User結構會用零值填充。對於User結構沒有的引數,會自動被忽略。

改成json的效果類似:

☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "age": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    96  100    46  100    50  32670  35511 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    95  100    45  100    50  49559  55066 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded
☁  ~  curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

使用json還需要注意一點,json是有資料型別的,因此對於 {"passwd": "123"}{"passwd": 123}是不同的資料型別,解析需要符合對應的資料型別,否則會出錯。

當然,gin還提供了更加高階方法,c.Bind,它會更加content-type自動推斷是bind表單還是json的引數。

    router.POST("/login", func(c *gin.Context) {
        var user User
        
        err := c.Bind(&user)
        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "username":   user.Username,
            "passwd":     user.Passwd,
            "age":        user.Age,
        })

    })

多格式渲染

既然請求可以使用不同的content-type,響應也如此。通常響應會有html,text,plain,json和xml等。
gin提供了很優雅的渲染方法。到目前為止,我們已經見識了c.String, c.JSON,c.HTML,下面介紹一下c.XML。

    router.GET("/render", func(c *gin.Context) {
        contentType := c.DefaultQuery("content_type", "json")
        if contentType == "json" {
            c.JSON(http.StatusOK, gin.H{
                "user":   "rsj217",
                "passwd": "123",
            })
        } else if contentType == "xml" {
            c.XML(http.StatusOK, gin.H{
                "user":   "rsj217",
                "passwd": "123",
            })
        }

    })

結果如下:

☁  ~  curl  http://127.0.0.1:8000/render\?content_type\=json
{"passwd":"123","user":"rsj217"}
☁  ~  curl  http://127.0.0.1:8000/render\?content_type\=xml
<map><user>rsj217</user><passwd>123</passwd></map>%

重定向

gin對於重定向的請求,相當簡單。呼叫上下文的Redirect方法:

    router.GET("/redict/google", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "https://google.com")
    })

分組路由

熟悉Flask的同學應該很瞭解藍圖分組。Flask提供了藍圖用於管理組織分組api。gin也提供了這樣的功能,讓你的程式碼邏輯更加模組化,同時分組也易於定義中介軟體的使用範圍。

    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%

middleware中介軟體

golang的net/http設計的一大特點就是特別容易構建中介軟體。gin也提供了類似的中介軟體。需要注意的是中介軟體只對註冊過的路由函式起作用。對於分組路由,巢狀使用中介軟體,可以限定中介軟體的作用範圍。中介軟體分為全域性中介軟體,單個路由中介軟體和群組中介軟體。

全域性中介軟體

先定義一箇中間件函式:

func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}

該函式很簡單,只會給c上下文新增一個屬性,並賦值。後面的路由處理器,可以根據被中介軟體裝飾後提取其值。需要注意,雖然名為全域性中介軟體,只要註冊中介軟體的過程之前設定的路由,將不會受註冊的中介軟體所影響。只有註冊了中介軟體一下程式碼的路由函式規則,才會被中介軟體裝飾。

    router.Use(MiddleWare())
    {
        router.GET("/middleware", func(c *gin.Context) {
            request := c.MustGet("request").(string)
            req, _ := c.Get("request")
            c.JSON(http.StatusOK, gin.H{
                "middile_request": request,
                "request": req,
            })
        })
    }

使用router裝飾中介軟體,然後在/middlerware即可讀取request的值,注意在router.Use(MiddleWare())程式碼以上的路由函式,將不會有被中介軟體裝飾的效果。

使用花括號包含被裝飾的路由函式只是一個程式碼規範,即使沒有被包含在內的路由函式,只要使用router進行路由,都等於被裝飾了。想要區分許可權範圍,可以使用組返回的物件註冊中介軟體。

☁  ~  curl  http://127.0.0.1:8000/middleware
{"middile_request":"clinet_request","request":"clinet_request"}

如果沒有註冊就使用MustGet方法讀取c的值將會拋錯,可以使用Get方法取而代之。

上面的註冊裝飾方式,會讓所有下面所寫的程式碼都預設使用了router的註冊過的中介軟體。

單個路由中介軟體

當然,gin也提供了針對指定的路由函式進行註冊。

    router.GET("/before", MiddleWare(), func(c *gin.Context) {
        request := c.MustGet("request").(string)
        c.JSON(http.StatusOK, gin.H{
            "middile_request": request,
        })
    })

把上述程式碼寫在 router.Use(Middleware())之前,同樣也能看見/before被裝飾了中介軟體。

群組中介軟體

群組的中介軟體也類似,只要在對於的群組路由上註冊中介軟體函式即可:

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

非同步協程

golang的高併發一大利器就是協程。gin裡可以藉助協程實現非同步任務。因為涉及非同步過程,請求的上下文需要copy到非同步的上下文,並且這個上下文是隻讀的。

    router.GET("/sync", func(c *gin.Context) {
        time.Sleep(5 * time.Second)
        log.Println("Done! in path" + c.Request.URL.Path)
    })

    router.GET("/async", func(c *gin.Context) {
        cCp := c.Copy()
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Done! in path" + cCp.Request.URL.Path)
        }()
    })

在請求的時候,sleep5秒鐘,同步的邏輯可以看到,服務的程序睡眠了。非同步的邏輯則看到響應返回了,然後程式還在後臺的協程處理。

自定義router

gin不僅可以使用框架本身的router進行Run,也可以配合使用net/http本身的功能:

func main() {
    router := gin.Default()
    http.ListenAndServe(":8080", router)
}

或者

func main() {
    router := gin.Default()

    s := &http.Server{
        Addr:           ":8000",
        Handler:        router,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}

當然還有一個優雅的重啟和結束程序的方案。後面將會探索使用supervisor管理golang的程序。

總結

Gin是一個輕巧而強大的golang web框架。涉及常見開發的功能,我們都做了簡單的介紹。關於服務的啟動,請求引數的處理和響應格式的渲染,以及針對上傳和中介軟體鑑權做了例子。更好的掌握來自實踐,同時gin的原始碼註釋很詳細,可以閱讀原始碼瞭解更多詳細的功能和魔法特性。

文中的部分程式碼

相關推薦

[]Go語言(Golang)的Web框架比較gin VS echo

所謂框架 框架一直是敏捷開發中的利器,能讓開發者很快的上手並做出應用,甚至有的時候,脫離了框架,一些開發者都不會寫程式了。成長總不會一蹴而就,從寫出程式獲取成就感,再到精通框架,快速構造應用,當這些方面都得心應手的時候,可以嘗試改造一些框架,或是自己創造一個。 曾經我以為Python世界裡的框架已經夠多了,後

go語言幾個最快最好運用最廣的web框架比較(大多數人不瞭解的特性)

令人敬畏的Web框架 如果你為自己設計一個小應用程式,你可能不需要一個Web框架,但如果你正在進行生產,那麼你肯定需要一個,一個好的應用程式。 雖然您認為自己擁有必要的知識和經驗,但您是否願意自行編寫所有這些功能的程式碼? 您是否有時間找到生產級外部包來完成這項工作? 您確定這將與您應用的其餘部分保持一致嗎?

Golang語言快速上手到綜合實戰(Go語言、Beego框架、高併發聊天室、豆瓣電影爬蟲) 下載

  Go是Google開發的一種編譯型,可並行化,並具有垃圾回收功能的程式語言。2015,Go迎來了全迸發的一年。時隔一年,回頭再看,Go已躋身主流程式語言行列。在國內,Go的熱度更是不凡。七牛雲、百度、滴滴等一線網際網路公司正在逐步將Go應用到自身的專案之中。 講師本人之前在滴滴從事後臺開發時,

為什麼我堅持用Go語言Web應用開發框架

點選上方“CSDN”,選擇“置頂公眾號”關鍵時刻,第一時間送達!【CSDN編者按】很多情況下,企

Python Web框架Django Form組件

multipart ima class mage 決定 red ttr 提示 echo Form簡介 在HTTP中,表單(form標簽),是用來提交數據的,其action屬性說明了其傳輸數據的方法:如何傳、如何接收。 訪問網站時,表單可以實現客戶端與服務器之間的通信。例如查

python web框架views視圖函數

則表達式 定義 string 給定 語言 lan 模板引擎 rem nag Django請求的生命周期是怎樣的? 簡單地說,通過URL對應關系匹配 ->找到對應的函數(或者類)->返回字符串(或者讀取Html之後返回渲染的字符串) 解剖起來如下: 1. 當用戶在

【區塊鏈Go語言實現】第一部分區塊鏈基本原型

ont 構建 獲得 列表 append 檢查 世紀 正常 私有 0x00 介紹 區塊鏈(Blockchain)是21世紀最具革命性的技術之一,目前它仍處於逐漸成熟階段,且其發展潛力尚未被完全意識到。從本質上講,區塊鏈只是一種記錄的分布式數據庫。但它之所以獨特,是因為它並

Go語言學習筆記十一 切片(slice)

操作 容量 方括號 一個 組類型 學習 中學 slice 修改 Go語言學習筆記十一: 切片(slice) 切片這個概念我是從python語言中學到的,當時感覺這個東西真的比較好用。不像java語言寫起來就比較繁瑣。不過我覺得未來java語法也會支持的。 定義切片 切片可以

Go語言的文件操作文件的讀寫,文件的新建打開和刪除

文件讀取 and 調用 inux 一個 find %s 刪除 數據 # 建立與打開文件 // 新建文件可以通過如下兩個方法: func Create(name string) (file *File, err Error) 根據提供的文件名創建新的文件,返回一個文件對象,默

API網關性能比較NGINX vs. ZUUL vs. Spring Cloud Gateway vs. Linkerd(

master 優點 進程間 ring 每次 32gb 性能比較 servlet 以及 前幾天拜讀了 OpsGenie 公司(一家致力於 Dev & Ops 的公司)的資深工程師 Turgay ?elik 博士寫的一篇文章(鏈接在文末),文中介紹了他們最初也是采用 N

ABAP,Java, nodejs和go語言web server編程

ges any nsh shu ava alt Go語言 function || ABAP and Java see my blog. nodejs 用nodejs現成的express module,幾行代碼就能寫個server出來: var express = requ

spring-webflux函數語言程式設計web框架

Spring 5.0 Spring-webflux 是一個全新的非堵塞的函式式 Reactive Web 框架,可以用來構建非同步的、非堵塞的、事件驅動的服務。 springboot2.0釋出不久,最近研究了一下springboot2.0的新特性,其中就發現了webflux。 下

ABAP,Java, nodejs和go語言web server程式設計

ABAP and Java see my blog. nodejs 用nodejs現成的express module,幾行程式碼就能寫個server出來: var express = require('express'); var routesEngine = require('./jerryap

Go語言(Golang)環形佇列

1 package main 2 3 import ( 4 "fmt" 5 "errors" 6 "os" 7 ) 8 9 //管理環形佇列的結構 10 type Queue struct { 11 maxSize int 1

Go語言(Golang)雙鏈表

package main import ( "fmt" ) //雙鏈表結構 type TwoLinkTable struct { no int name string pre *TwoLinkTable next *TwoLinkTable } //直接在隊尾插入 func Inser

Go語言(Golang)約瑟夫遊戲(Joseph)

package main import ( "fmt" ) //假設是一群小孩在玩這個遊戲 //建立一個小孩的結構體 type BoyNode struct { No int //給每個小孩一個唯一的身份編號 next *BoyNode //指向下一個小孩 } //假設有number個小孩在

Go語言(Golang)插入排序

package main import ( "fmt" ) func InsertSort(arr *[6]int) { for i := 1; i < len(arr); i++ { val := arr[i] index := i - 1 for index >=

Go語言(Golang)氣泡排序

package main import ( "fmt" ) func BubbleSort(arr *[5]int) { for i := 0; i < len(arr); i++ { for j := i + 1; j < len(arr); j++ { if arr[i

Go語言(Golang)選擇排序

package main import ( "fmt" ) func SelectSort(arr *[6]int) { for j := 0; j < len(arr ) - 1; j++ { max := arr[j]

Go語言內幕(2)深入 Go 編譯器

當你通過介面引用使用一個變數時,你知道 Go 執行時到底做了哪些工作嗎?這個問題並不容易回答。這是因為在 Go 中,一個型別實現了一個介面,但是這個型別並沒有包含任何對這個介面的引用。與上一篇部落格《Go語言內幕(1):主要概念與專案結構》一樣,你可以用 Go 編譯