Gin中文文檔

介紹

Gin 是一個用 Go (Golang) 編寫的 web 框架。 它是一個類似于 martini 但擁有更好性能的 API 框架, 由于 httprouter,速度提高了近 40 倍。 如果你是性能和高效的追求者, 你會愛上 Gin.

在本節(jié)中,我們將介紹 Gin 是什么,它解決了哪些問題,以及它如何幫助你的項目爪飘。

或者, 如果你已經(jīng)準備在項目中使用 Gin,請訪問快速入門.

特性

快速

基于 Radix 樹的路由拉背,小內(nèi)存占用师崎。沒有反射∫喂祝可預測的 API 性能犁罩。

支持中間件

傳入的 HTTP 請求可以由一系列中間件和最終操作來處理。 例如:Logger两疚,Authorization床估,GZIP,最終操作 DB诱渤。

Crash 處理

Gin 可以 catch 一個發(fā)生在 HTTP 請求中的 panic 并 recover 它丐巫。這樣,你的服務器將始終可用勺美。例如递胧,你可以向 Sentry 報告這個 panic!

JSON 驗證

Gin 可以解析并驗證請求的 JSON赡茸,例如檢查所需值的存在缎脾。

路由組

更好地組織路由。是否需要授權(quán)占卧,不同的 API 版本…… 此外遗菠,這些組可以無限制地嵌套而不會降低性能联喘。

錯誤管理

Gin 提供了一種方便的方法來收集 HTTP 請求期間發(fā)生的所有錯誤。最終辙纬,中間件可以將它們寫入日志文件耸袜,數(shù)據(jù)庫并通過網(wǎng)絡發(fā)送。

內(nèi)置渲染

Gin 為 JSON牲平,XML 和 HTML 渲染提供了易于使用的 API。

可擴展性

新建一個中間件非常簡單域滥,去查看示例代碼吧纵柿。

快速入門

要求

  • Go 1.6 及以上版本

很快會需要 Go 1.8 版本.

安裝

要安裝 Gin 軟件包,需要先安裝 Go 并設置 Go 工作區(qū)启绰。

1.下載并安裝 gin:

$ go get -u github.com/gin-gonic/gin

2.將 gin 引入到代碼中:

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

3.(可選)如果使用諸如 http.StatusOK 之類的常量昂儒,則需要引入 net/http 包:

import "net/http"

使用 Govendor 工具創(chuàng)建項目

1.go get govendor

$ go get github.com/kardianos/govendor

2.創(chuàng)建項目并且 cd 到項目目錄中

$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"

3.使用 govendor 初始化項目,并且引入 gin

$ govendor init
$ govendor fetch github.com/gin-gonic/gin@v1.3

4.復制啟動文件模板到項目目錄中

$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go

5.啟動項目

$ go run main.go

開始

不確定如何編寫和執(zhí)行 Go 代碼? 點擊這里.

首先委可,創(chuàng)建一個名為 example.go 的文件

$ touch example.go

接下來, 將如下的代碼寫入 example.go 中:

package main

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

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
}

然后, 執(zhí)行 go run example.go 命令來運行代碼:

# 運行 example.go 并且在瀏覽器中訪問 0.0.0.0:8080/ping
$ go run example.go

基準測試

Gin 使用了自定義版本的 HttpRouter

查看所有基準測試

Benchmark name (1) (2) (3) (4)
BenchmarkGin_GithubAll 30000 48375 0 0
BenchmarkAce_GithubAll 10000 134059 13792 167
BenchmarkBear_GithubAll 5000 534445 86448 943
BenchmarkBeego_GithubAll 3000 592444 74705 812
BenchmarkBone_GithubAll 200 6957308 698784 8453
BenchmarkDenco_GithubAll 10000 158819 20224 167
BenchmarkEcho_GithubAll 10000 154700 6496 203
BenchmarkGocraftWeb_GithubAll 3000 570806 131656 1686
BenchmarkGoji_GithubAll 2000 818034 56112 334
BenchmarkGojiv2_GithubAll 2000 1213973 274768 3712
BenchmarkGoJsonRest_GithubAll 2000 785796 134371 2737
BenchmarkGoRestful_GithubAll 300 5238188 689672 4519
BenchmarkGorillaMux_GithubAll 100 10257726 211840 2272
BenchmarkHttpRouter_GithubAll 20000 105414 13792 167
BenchmarkHttpTreeMux_GithubAll 10000 319934 65856 671
BenchmarkKocha_GithubAll 10000 209442 23304 843
BenchmarkLARS_GithubAll 20000 62565 0 0
BenchmarkMacaron_GithubAll 2000 1161270 204194 2000
BenchmarkMartini_GithubAll 200 9991713 226549 2325
BenchmarkPat_GithubAll 200 5590793 1499568 27435
BenchmarkPossum_GithubAll 10000 319768 84448 609
BenchmarkR2router_GithubAll 10000 305134 77328 979
BenchmarkRivet_GithubAll 10000 132134 16272 167
BenchmarkTango_GithubAll 3000 552754 63826 1618
BenchmarkTigerTonic_GithubAll 1000 1439483 239104 5374
BenchmarkTraffic_GithubAll 100 11383067 2659329 21848
BenchmarkVulcan_GithubAll 5000 394253 19894 609
  • (1):在一定的時間內(nèi)實現(xiàn)的總調(diào)用數(shù)渊跋,越高越好
  • (2):單次操作耗時(ns/op),越低越好
  • (3):堆內(nèi)存分配 (B/op), 越低越好
  • (4):每次操作的平均內(nèi)存分配次數(shù)(allocs/op)着倾,越低越好

特性

Gin v1 穩(wěn)定的特性:
  • 零分配路由拾酝。
  • 仍然是最快的 http 路由器和框架。
  • 完整的單元測試支持卡者。
  • 實戰(zhàn)考驗蒿囤。
  • API 凍結(jié),新版本的發(fā)布不會破壞你的代碼崇决。

Jsoniter

使用 jsoniter 編譯

Gin 使用 encoding/json 作為默認的 json 包材诽,但是你可以在編譯中使用標簽將其修改為 jsoniter

$ go build -tags=jsoniter .

示例

該節(jié)列出了 api 的用法恒傻。

AsciiJSON

使用 AsciiJSON 生成具有轉(zhuǎn)義的非 ASCII 字符的 ASCII-only JSON脸侥。

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

    r.GET("/someJSON", func(c *gin.Context) {
        data := map[string]interface{}{
            "lang": "GO語言",
            "tag":  "<br>",
        }

        // 輸出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
        c.AsciiJSON(http.StatusOK, data)
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

HTML 渲染

使用 LoadHTMLGlob() 或者 LoadHTMLFiles()

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/*")
    //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
    router.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "Main website",
        })
    })
    router.Run(":8080")
}

templates/index.tmpl

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

使用不同目錄下名稱相同的模板

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/**/*")
    router.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "Posts",
        })
    })
    router.GET("/users/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "Users",
        })
    })
    router.Run(":8080")
}

templates/posts/index.tmpl

{{ define "posts/index.tmpl" }}
<html><h1>
    {{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}

templates/users/index.tmpl

{{ define "users/index.tmpl" }}
<html><h1>
    {{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}

自定義模板渲染器

你可以使用自定義的 html 模板渲染

import "html/template"

func main() {
    router := gin.Default()
    html := template.Must(template.ParseFiles("file1", "file2"))
    router.SetHTMLTemplate(html)
    router.Run(":8080")
}

自定義分隔符

你可以使用自定義分隔

    r := gin.Default()
    r.Delims("{[{", "}]}")
    r.LoadHTMLGlob("/path/to/templates")

自定義模板功能

查看詳細示例代碼

main.go

import (
    "fmt"
    "html/template"
    "net/http"
    "time"

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

func formatAsDate(t time.Time) string {
    year, month, day := t.Date()
    return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}

func main() {
    router := gin.Default()
    router.Delims("{[{", "}]}")
    router.SetFuncMap(template.FuncMap{
        "formatAsDate": formatAsDate,
    })
    router.LoadHTMLFiles("./testdata/template/raw.tmpl")

    router.GET("/raw", func(c *gin.Context) {
        c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
        })
    })

    router.Run(":8080")
}

raw.tmpl

Date: {[{.now | formatAsDate}]}

結(jié)果:

Date: 2017/07/01

HTTP2 server 推送

http.Pusher 僅支持 go1.8+盈厘。 更多信息睁枕,請查閱 golang blog

package main

import (
    "html/template"
    "log"

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

var html = template.Must(template.New("https").Parse(`
<html>
<head>
  <title>Https Test</title>
  <script src="/assets/app.js"></script>
</head>
<body>
  <h1 style="color:red;">Welcome, Ginner!</h1>
</body>
</html>
`))

func main() {
    r := gin.Default()
    r.Static("/assets", "./assets")
    r.SetHTMLTemplate(html)

    r.GET("/", func(c *gin.Context) {
        if pusher := c.Writer.Pusher(); pusher != nil {
            // 使用 pusher.Push() 做服務器推送
            if err := pusher.Push("/assets/app.js", nil); err != nil {
                log.Printf("Failed to push: %v", err)
            }
        }
        c.HTML(200, "https", gin.H{
            "status": "success",
        })
    })

    // 監(jiān)聽并在 https://127.0.0.1:8080 上啟動服務
    r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
}

JSONP

使用 JSONP 向不同域的服務器請求數(shù)據(jù)扑庞。如果查詢參數(shù)存在回調(diào)譬重,則將回調(diào)添加到響應體中。

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

    r.GET("/JSONP?callback=x", func(c *gin.Context) {
        data := map[string]interface{}{
            "foo": "bar",
        }
        
        // callback 是 x
        // 將輸出:x({\"foo\":\"bar\"})
        c.JSONP(http.StatusOK, data)
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

Multipart/Urlencoded 綁定

package main

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

type LoginForm struct {
    User     string `form:"user" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func main() {
    router := gin.Default()
    router.POST("/login", func(c *gin.Context) {
        // 你可以使用顯式綁定聲明綁定 multipart form:
        // c.ShouldBindWith(&form, binding.Form)
        // 或者簡單地使用 ShouldBind 方法自動綁定:
        var form LoginForm
        // 在這種情況下罐氨,將自動選擇合適的綁定
        if c.ShouldBind(&form) == nil {
            if form.User == "user" && form.Password == "password" {
                c.JSON(200, gin.H{"status": "you are logged in"})
            } else {
                c.JSON(401, gin.H{"status": "unauthorized"})
            }
        }
    })
    router.Run(":8080")
}

測試:

$ curl -v --form user=user --form password=password http://localhost:8080/login

Multipart/Urlencoded 表單

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

    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(200, gin.H{
            "status":  "posted",
            "message": message,
            "nick":    nick,
        })
    })
    router.Run(":8080")
}

PureJSON

通常臀规,JSON 使用 unicode 替換特殊 HTML 字符,例如 < 變?yōu)?\ u003c栅隐。如果要按字面對這些字符進行編碼塔嬉,則可以使用 PureJSON玩徊。Go 1.6 及更低版本無法使用此功能。

func main() {
    r := gin.Default()
    
    // 提供 unicode 實體
    r.GET("/json", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "html": "<b>Hello, world!</b>",
        })
    })
    
    // 提供字面字符
    r.GET("/purejson", func(c *gin.Context) {
        c.PureJSON(200, gin.H{
            "html": "<b>Hello, world!</b>",
        })
    })
    
    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

Query 和 post form

POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=manu&message=this_is_great
func main() {
    router := gin.Default()

    router.POST("/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", id, page, name, message)
    })
    router.Run(":8080")
}
id: 1234; page: 1; name: manu; message: this_is_great

SecureJSON

使用 SecureJSON 防止 json 劫持谨究。如果給定的結(jié)構(gòu)是數(shù)組值恩袱,則默認預置 "while(1)," 到響應體。

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

    // 你也可以使用自己的 SecureJSON 前綴
    // r.SecureJsonPrefix(")]}',\n")

    r.GET("/someJSON", func(c *gin.Context) {
        names := []string{"lena", "austin", "foo"}

        // 將輸出:while(1);["lena","austin","foo"]
        c.SecureJSON(http.StatusOK, names)
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

XML/JSON/YAML/ProtoBuf 渲染

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

    // gin.H 是 map[string]interface{} 的一種快捷方式
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/moreJSON", func(c *gin.Context) {
        // 你也可以使用一個結(jié)構(gòu)體
        var msg struct {
            Name    string `json:"user"`
            Message string
            Number  int
        }
        msg.Name = "Lena"
        msg.Message = "hey"
        msg.Number = 123
        // 注意 msg.Name 在 JSON 中變成了 "user"
        // 將輸出:{"user": "Lena", "Message": "hey", "Number": 123}
        c.JSON(http.StatusOK, msg)
    })

    r.GET("/someXML", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        label := "test"
        // protobuf 的具體定義寫在 testdata/protoexample 文件中胶哲。
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        // 請注意畔塔,數(shù)據(jù)在響應中變?yōu)槎M制數(shù)據(jù)
        // 將輸出被 protoexample.Test protobuf 序列化了的數(shù)據(jù)
        c.ProtoBuf(http.StatusOK, data)
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

上傳文件

本節(jié)列出了上傳圖片的 api 用法。

單文件

參考 issue #774 和詳細示例代碼.

func main() {
    router := gin.Default()
    // 為 multipart forms 設置較低的內(nèi)存限制 (默認是 32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // 單文件
        file, _ := c.FormFile("file")
        log.Println(file.Filename)

        // 上傳文件至指定目錄
        // c.SaveUploadedFile(file, dst)

        c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
    })
    router.Run(":8080")
}

如何使用 curl

curl -X POST http://localhost:8080/upload \
  -F "file=@/Users/appleboy/test.zip" \
  -H "Content-Type: multipart/form-data"

多文件

查看詳細示例代碼.

func main() {
    router := gin.Default()
    // 為 multipart forms 設置較低的內(nèi)存限制 (默認是 32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // Multipart form
        form, _ := c.MultipartForm()
        files := form.File["upload[]"]

        for _, file := range files {
            log.Println(file.Filename)

            // 上傳文件至指定目錄
            // c.SaveUploadedFile(file, dst)
        }
        c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
    })
    router.Run(":8080")
}

如何使用 curl:

curl -X POST http://localhost:8080/upload \
  -F "upload[]=@/Users/appleboy/test1.zip" \
  -F "upload[]=@/Users/appleboy/test2.zip" \
  -H "Content-Type: multipart/form-data"

不使用默認的中間件

使用

r := gin.New()

代替

// Default 使用 Logger 和 Recovery 中間件
r := gin.Default()

從 reader 讀取數(shù)據(jù)

func main() {
    router := gin.Default()
    router.GET("/someDataFromReader", func(c *gin.Context) {
        response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
        if err != nil || response.StatusCode != http.StatusOK {
            c.Status(http.StatusServiceUnavailable)
            return
        }

        reader := response.Body
        contentLength := response.ContentLength
        contentType := response.Header.Get("Content-Type")

        extraHeaders := map[string]string{
            "Content-Disposition": `attachment; filename="gopher.png"`,
        }

        c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
    })
    router.Run(":8080")
}

優(yōu)雅地重啟或停止

你想優(yōu)雅地重啟或停止 web 服務器嗎鸯屿?有一些方法可以做到這一點澈吨。

我們可以使用 fvbock/endless 來替換默認的 ListenAndServe。更多詳細信息寄摆,請參閱 issue #296缔刹。

router := gin.Default()
router.GET("/", handler)
// [...]
endless.ListenAndServe(":4242", router)

替代方案:

  • manners:可以優(yōu)雅關(guān)機的 Go Http 服務器圆凰。
  • graceful:Graceful 是一個 Go 擴展包杆融,可以優(yōu)雅地關(guān)閉 http.Handler 服務器畅涂。
  • grace:Go 服務器平滑重啟和零停機時間部署。

如果你使用的是 Go 1.8勾邦,可以不需要這些庫蚣录!考慮使用 http.Server 內(nèi)置的 Shutdown() 方法優(yōu)雅地關(guān)機. 請參閱 gin 完整的 graceful-shutdown 示例。

// +build go1.8

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "time"

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

func main() {
    router := gin.Default()
    router.GET("/", func(c *gin.Context) {
        time.Sleep(5 * time.Second)
        c.String(http.StatusOK, "Welcome Gin Server")
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }

    go func() {
        // 服務連接
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("listen: %s\n", err)
        }
    }()

    // 等待中斷信號以優(yōu)雅地關(guān)閉服務器(設置 5 秒的超時時間)
    quit := make(chan os.Signal)
    signal.Notify(quit, os.Interrupt)
    <-quit
    log.Println("Shutdown Server ...")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }
    log.Println("Server exiting")
}

使用 BasicAuth 中間件

// 模擬一些私人數(shù)據(jù)
var secrets = 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"},
}

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

    // 路由組使用 gin.BasicAuth() 中間件
    // gin.Accounts 是 map[string]string 的一種快捷方式
    authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
        "foo":    "bar",
        "austin": "1234",
        "lena":   "hello2",
        "manu":   "4321",
    }))

    // /admin/secrets 端點
    // 觸發(fā) "localhost:8080/admin/secrets
    authorized.GET("/secrets", func(c *gin.Context) {
        // 獲取用戶眷篇,它是由 BasicAuth 中間件設置的
        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 :("})
        }
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

使用 HTTP 方法

func main() {
    // 禁用控制臺顏色
    // gin.DisableConsoleColor()

    // 使用默認中間件(logger 和 recovery 中間件)創(chuàng)建 gin 路由
    router := gin.Default()

    router.GET("/someGet", getting)
    router.POST("/somePost", posting)
    router.PUT("/somePut", putting)
    router.DELETE("/someDelete", deleting)
    router.PATCH("/somePatch", patching)
    router.HEAD("/someHead", head)
    router.OPTIONS("/someOptions", options)

    // 默認在 8080 端口啟動服務包归,除非定義了一個 PORT 的環(huán)境變量。
    router.Run()
    // router.Run(":3000") hardcode 端口號
}

使用中間件

func main() {
    // 新建一個沒有任何默認中間件的路由
    r := gin.New()

    // 全局中間件
    // Logger 中間件將日志寫入 gin.DefaultWriter铅歼,即使你將 GIN_MODE 設置為 release公壤。
    // By default gin.DefaultWriter = os.Stdout
    r.Use(gin.Logger())

    // Recovery 中間件會 recover 任何 panic。如果有 panic 的話椎椰,會寫入 500厦幅。
    r.Use(gin.Recovery())

    // 你可以為每個路由添加任意數(shù)量的中間件。
    r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

    // 認證路由組
    // authorized := r.Group("/", AuthRequired())
    // 和使用以下兩行代碼的效果完全一樣:
    authorized := r.Group("/")
    // 路由組中間件! 在此例中慨飘,我們在 "authorized" 路由組中使用自定義創(chuàng)建的 
    // AuthRequired() 中間件
    authorized.Use(AuthRequired())
    {
        authorized.POST("/login", loginEndpoint)
        authorized.POST("/submit", submitEndpoint)
        authorized.POST("/read", readEndpoint)

        // 嵌套路由組
        testing := authorized.Group("testing")
        testing.GET("/analytics", analyticsEndpoint)
    }

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

只綁定 url 查詢字符串

ShouldBindQuery 函數(shù)只綁定 url 查詢參數(shù)而忽略 post 數(shù)據(jù)确憨。參閱詳細信息.

package main

import (
    "log"

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

type Person struct {
    Name    string `form:"name"`
    Address string `form:"address"`
}

func main() {
    route := gin.Default()
    route.Any("/testing", startPage)
    route.Run(":8085")
}

func startPage(c *gin.Context) {
    var person Person
    if c.ShouldBindQuery(&person) == nil {
        log.Println("====== Only Bind By Query String ======")
        log.Println(person.Name)
        log.Println(person.Address)
    }
    c.String(200, "Success")
}

在中間件中使用 Goroutine

當在中間件或 handler 中啟動新的 Goroutine 時,不能使用原始的上下文瓤的,必須使用只讀副本休弃。

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

    r.GET("/long_async", func(c *gin.Context) {
        // 創(chuàng)建在 goroutine 中使用的副本
        cCp := c.Copy()
        go func() {
            // 用 time.Sleep() 模擬一個長任務。
            time.Sleep(5 * time.Second)

            // 請注意您使用的是復制的上下文 "cCp"圈膏,這一點很重要
            log.Println("Done! in path " + cCp.Request.URL.Path)
        }()
    })

    r.GET("/long_sync", func(c *gin.Context) {
        // 用 time.Sleep() 模擬一個長任務塔猾。
        time.Sleep(5 * time.Second)

        // 因為沒有使用 goroutine,不需要拷貝上下文
        log.Println("Done! in path " + c.Request.URL.Path)
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

多模板

Gin 默認允許只使用一個 html 模板稽坤。 查看多模板渲染 以使用 go 1.6 block template 等功能丈甸。

如何記錄日志

func main() {
    // 禁用控制臺顏色糯俗,將日志寫入文件時不需要控制臺顏色。
    gin.DisableConsoleColor()

    // 記錄到文件睦擂。
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)

    // 如果需要同時將日志寫入文件和控制臺得湘,請使用以下代碼。
    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    router.Run(":8080")
}

定義路由日志的格式

默認的路由日志格式:

[GIN-debug] POST   /foo                      --> main.main.func1 (3 handlers)
[GIN-debug] GET    /bar                      --> main.main.func2 (3 handlers)
[GIN-debug] GET    /status                   --> main.main.func3 (3 handlers)

如果你想要以指定的格式(例如 JSON顿仇,key values 或其他格式)記錄信息淘正,則可以使用 gin.DebugPrintRouteFunc 指定格式。 在下面的示例中臼闻,我們使用標準日志包記錄所有路由跪帝,但你可以使用其他滿足你需求的日志工具。

import (
    "log"
    "net/http"

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

func main() {
    r := gin.Default()
    gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
        log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
    }

    r.POST("/foo", func(c *gin.Context) {
        c.JSON(http.StatusOK, "foo")
    })

    r.GET("/bar", func(c *gin.Context) {
        c.JSON(http.StatusOK, "bar")
    })

    r.GET("/status", func(c *gin.Context) {
        c.JSON(http.StatusOK, "ok")
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run()
}

將 request body 綁定到不同的結(jié)構(gòu)體中

一般通過調(diào)用 c.Request.Body 方法綁定數(shù)據(jù)些阅,但不能多次調(diào)用這個方法。

type formA struct {
  Foo string `json:"foo" xml:"foo" binding:"required"`
}

type formB struct {
  Bar string `json:"bar" xml:"bar" binding:"required"`
}

func SomeHandler(c *gin.Context) {
  objA := formA{}
  objB := formB{}
  // c.ShouldBind 使用了 c.Request.Body斑唬,不可重用市埋。
  if errA := c.ShouldBind(&objA); errA == nil {
    c.String(http.StatusOK, `the body should be formA`)
  // 因為現(xiàn)在 c.Request.Body 是 EOF,所以這里會報錯恕刘。
  } else if errB := c.ShouldBind(&objB); errB == nil {
    c.String(http.StatusOK, `the body should be formB`)
  } else {
    ...
  }
}

要想多次綁定缤谎,可以使用 c.ShouldBindBodyWith.

func SomeHandler(c *gin.Context) {
  objA := formA{}
  objB := formB{}
  // 讀取 c.Request.Body 并將結(jié)果存入上下文。
  if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
    c.String(http.StatusOK, `the body should be formA`)
  // 這時, 復用存儲在上下文中的 body褐着。
  } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
    c.String(http.StatusOK, `the body should be formB JSON`)
  // 可以接受其他格式
  } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
    c.String(http.StatusOK, `the body should be formB XML`)
  } else {
    ...
  }
}
  • c.ShouldBindBodyWith 會在綁定之前將 body 存儲到上下文中坷澡。 這會對性能造成輕微影響,如果調(diào)用一次就能完成綁定的話含蓉,那就不要用這個方法频敛。
  • 只有某些格式需要此功能,如 JSON, XML, MsgPack, ProtoBuf馅扣。 對于其他格式, 如 Query, Form, FormPost, FormMultipart 可以多次調(diào)用 c.ShouldBind() 而不會造成任任何性能損失 (詳見 #1341)斟赚。

支持 Let's Encrypt

一行代碼支持 LetsEncrypt HTTPS servers 示例。

package main

import (
    "log"

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

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

    // Ping handler
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
}

自定義 autocert manager 示例差油。

package main

import (
    "log"

    "github.com/gin-gonic/autotls"
    "github.com/gin-gonic/gin"
    "golang.org/x/crypto/acme/autocert"
)

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

    // Ping handler
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    m := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
        Cache:      autocert.DirCache("/var/www/.cache"),
    }

    log.Fatal(autotls.RunWithManager(r, &m))
}

映射查詢字符串或表單參數(shù)

POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencoded

names[first]=thinkerou&names[second]=tianou
func main() {
    router := gin.Default()

    router.POST("/post", func(c *gin.Context) {

        ids := c.QueryMap("ids")
        names := c.PostFormMap("names")

        fmt.Printf("ids: %v; names: %v", ids, names)
    })
    router.Run(":8080")
}
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]

查詢字符串參數(shù)

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

    // 使用現(xiàn)有的基礎請求對象解析查詢字符串參數(shù)拗军。
    // 示例 URL: /welcome?firstname=Jane&lastname=Doe
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") 的一種快捷方式

        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
    router.Run(":8080")
}

模型綁定和驗證

要將請求體綁定到結(jié)構(gòu)體中,使用模型綁定蓄喇。 Gin目前支持JSON发侵、XML、YAML和標準表單值的綁定(foo=bar&boo=baz)妆偏。

Gin使用 go-playground/validator.v8 進行驗證刃鳄。 查看標簽用法的全部文檔.

使用時,需要在要綁定的所有字段上钱骂,設置相應的tag铲汪。 例如熊尉,使用 JSON 綁定時,設置字段標簽為 json:"fieldname"掌腰。

Gin提供了兩類綁定方法:

  • Type

- Must bind

  • Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML

  • Behavior - 這些方法屬于 MustBindWith 的具體調(diào)用狰住。 如果發(fā)生綁定錯誤,則請求終止齿梁,并觸發(fā) c.AbortWithError(400, err).SetType(ErrorTypeBind)催植。響應狀態(tài)碼被設置為 400 并且 Content-Type 被設置為 text/plain; charset=utf-8。 如果您在此之后嘗試設置響應狀態(tài)碼勺择,Gin會輸出日志 [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422创南。 如果您希望更好地控制綁定,考慮使用 ShouldBind 等效方法省核。

  • Type

- Should bind

  • Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
  • Behavior - 這些方法屬于 ShouldBindWith 的具體調(diào)用稿辙。 如果發(fā)生綁定錯誤,Gin 會返回錯誤并由開發(fā)者處理錯誤和請求气忠。

使用 Bind 方法時邻储,Gin 會嘗試根據(jù) Content-Type 推斷如何綁定。 如果你明確知道要綁定什么旧噪,可以使用 MustBindWithShouldBindWith吨娜。

你也可以指定必須綁定的字段。 如果一個字段的 tag 加上了 binding:"required"淘钟,但綁定時是空值, Gin 會報錯宦赠。

// 綁定 JSON
type Login struct {
    User     string `form:"user" json:"user" xml:"user"  binding:"required"`
    Password string `form:"password" json:"password" xml:"password" binding:"required"`
}

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

    // 綁定 JSON ({"user": "manu", "password": "123"})
    router.POST("/loginJSON", func(c *gin.Context) {
        var json Login
        if err := c.ShouldBindJSON(&json); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        if json.User != "manu" || json.Password != "123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        } 
        
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })

    // 綁定 XML (
    //  <?xml version="1.0" encoding="UTF-8"?>
    //  <root>
    //      <user>user</user>
    //      <password>123</password>
    //  </root>)
    router.POST("/loginXML", func(c *gin.Context) {
        var xml Login
        if err := c.ShouldBindXML(&xml); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        if xml.User != "manu" || xml.Password != "123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        } 
        
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })

    // 綁定 HTML 表單 (user=manu&password=123)
    router.POST("/loginForm", func(c *gin.Context) {
        var form Login
        // 根據(jù) Content-Type Header 推斷使用哪個綁定器。
        if err := c.ShouldBind(&form); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        
        if form.User != "manu" || form.Password != "123" {
            c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
            return
        } 
        
        c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    router.Run(":8080")
}

示例請求

$ curl -v -X POST \
  http://localhost:8080/loginJSON \
  -H 'content-type: application/json' \
  -d '{ "user": "manu" }'
> POST /loginJSON HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Fri, 04 Aug 2017 03:51:31 GMT
< Content-Length: 100
<
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

忽略驗證

使用上述的 curl 命令運行上面的示例時會返回錯誤米母。 因為示例中 Password 使用了 binding:"required"勾扭。 如果 Password 使用 binding:"-", 再次運行上面的示例就不會返回錯誤铁瞒。

綁定 HTML 復選框

參見詳細信息

main.go

...

type myForm struct {
    Colors []string `form:"colors[]"`
}

...

func formHandler(c *gin.Context) {
    var fakeForm myForm
    c.ShouldBind(&fakeForm)
    c.JSON(200, gin.H{"color": fakeForm.Colors})
}

...

form.html

<form action="/" method="POST">
    <p>Check some colors</p>
    <label for="red">Red</label>
    <input type="checkbox" name="colors[]" value="red" id="red">
    <label for="green">Green</label>
    <input type="checkbox" name="colors[]" value="green" id="green">
    <label for="blue">Blue</label>
    <input type="checkbox" name="colors[]" value="blue" id="blue">
    <input type="submit">
</form>

結(jié)果:

{"color":["red","green","blue"]}

綁定 Uri

查看詳細信息.

package main

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

type Person struct {
    ID string `uri:"id" binding:"required,uuid"`
    Name string `uri:"name" binding:"required"`
}

func main() {
    route := gin.Default()
    route.GET("/:name/:id", func(c *gin.Context) {
        var person Person
        if err := c.ShouldBindUri(&person); err != nil {
            c.JSON(400, gin.H{"msg": err})
            return
        }
        c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
    })
    route.Run(":8088")
}

測試:

$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid

綁定查詢字符串或表單數(shù)據(jù)

查看詳細信息尺借。

package main

import (
    "log"
    "time"

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

type Person struct {
    Name     string    `form:"name"`
    Address  string    `form:"address"`
    Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}

func main() {
    route := gin.Default()
    route.GET("/testing", startPage)
    route.Run(":8085")
}

func startPage(c *gin.Context) {
    var person Person
    // 如果是 `GET` 請求,只使用 `Form` 綁定引擎(`query`)精拟。
    // 如果是 `POST` 請求燎斩,首先檢查 `content-type` 是否為 `JSON` 或 `XML`,然后再使用 `Form`(`form-data`)蜂绎。
    // 查看更多:https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
    if c.ShouldBind(&person) == nil {
        log.Println(person.Name)
        log.Println(person.Address)
        log.Println(person.Birthday)
    }

    c.String(200, "Success")
}

測試:

$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"

綁定表單數(shù)據(jù)至自定義結(jié)構(gòu)體

以下示例使用自定義結(jié)構(gòu)體:

type StructA struct {
    FieldA string `form:"field_a"`
}

type StructB struct {
    NestedStruct StructA
    FieldB string `form:"field_b"`
}

type StructC struct {
    NestedStructPointer *StructA
    FieldC string `form:"field_c"`
}

type StructD struct {
    NestedAnonyStruct struct {
        FieldX string `form:"field_x"`
    }
    FieldD string `form:"field_d"`
}

func GetDataB(c *gin.Context) {
    var b StructB
    c.Bind(&b)
    c.JSON(200, gin.H{
        "a": b.NestedStruct,
        "b": b.FieldB,
    })
}

func GetDataC(c *gin.Context) {
    var b StructC
    c.Bind(&b)
    c.JSON(200, gin.H{
        "a": b.NestedStructPointer,
        "c": b.FieldC,
    })
}

func GetDataD(c *gin.Context) {
    var b StructD
    c.Bind(&b)
    c.JSON(200, gin.H{
        "x": b.NestedAnonyStruct,
        "d": b.FieldD,
    })
}

func main() {
    r := gin.Default()
    r.GET("/getb", GetDataB)
    r.GET("/getc", GetDataC)
    r.GET("/getd", GetDataD)

    r.Run()
}

使用 curl 命令結(jié)果:

$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"FieldA":"hello"},"b":"world"}
$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":{"FieldA":"hello"},"c":"world"}
$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
{"d":"world","x":{"FieldX":"hello"}}

注意:不支持以下格式結(jié)構(gòu)體:

type StructX struct {
    X struct {} `form:"name_x"` // 有 form
}

type StructY struct {
    Y StructX `form:"name_y"` // 有 form
}

type StructZ struct {
    Z *StructZ `form:"name_z"` // 有 form
}

總之, 目前僅支持沒有 form 的嵌套結(jié)構(gòu)體栅表。

自定義 HTTP 配置

直接使用 http.ListenAndServe(),如下所示:

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

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

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

自定義中間件

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        // 設置 example 變量
        c.Set("example", "12345")

        // 請求前

        c.Next()

        // 請求后
        latency := time.Since(t)
        log.Print(latency)

        // 獲取發(fā)送的 status
        status := c.Writer.Status()
        log.Println(status)
    }
}

func main() {
    r := gin.New()
    r.Use(Logger())

    r.GET("/test", func(c *gin.Context) {
        example := c.MustGet("example").(string)

        // 打邮υ妗:"12345"
        log.Println(example)
    })

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    r.Run(":8080")
}

自定義驗證器

注冊自定義驗證器怪瓶,查看示例代碼.

package main

import (
    "net/http"
    "reflect"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "gopkg.in/go-playground/validator.v8"
)

// Booking 包含綁定和驗證的數(shù)據(jù)。
type Booking struct {
    CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
    CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

func bookableDate(
    v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
    field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
    if date, ok := field.Interface().(time.Time); ok {
        today := time.Now()
        if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
            return false
        }
    }
    return true
}

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

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("bookabledate", bookableDate)
    }

    route.GET("/bookable", getBookable)
    route.Run(":8085")
}

func getBookable(c *gin.Context) {
    var b Booking
    if err := c.ShouldBindWith(&b, binding.Query); err == nil {
        c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
    } else {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    }
}
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
{"message":"Booking dates are valid!"}

$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}

結(jié)構(gòu)體級別的驗證器 也可以通過其他的方式注冊践美。更多信息請參閱 struct-lvl-validation 示例洗贰。

設置和獲取 Cookie

import (
    "fmt"

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

func main() {

    router := gin.Default()

    router.GET("/cookie", func(c *gin.Context) {

        cookie, err := c.Cookie("gin_cookie")

        if err != nil {
            cookie = "NotSet"
            c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
        }

        fmt.Printf("Cookie value: %s \n", cookie)
    })

    router.Run()
}

路由參數(shù)

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

    // 此 handler 將匹配 /user/john 但不會匹配 /user/ 或者 /user
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })

    // 此 handler 將匹配 /user/john/ 和 /user/john/send
    // 如果沒有其他路由匹配 /user/john找岖,它將重定向到 /user/john/
    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)
    })

    router.Run(":8080")
}

路由組

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

    // 簡單的路由組: v1
    v1 := router.Group("/v1")
    {
        v1.POST("/login", loginEndpoint)
        v1.POST("/submit", submitEndpoint)
        v1.POST("/read", readEndpoint)
    }

    // 簡單的路由組: v2
    v2 := router.Group("/v2")
    {
        v2.POST("/login", loginEndpoint)
        v2.POST("/submit", submitEndpoint)
        v2.POST("/read", readEndpoint)
    }

    router.Run(":8080")
}

運行多個服務

請參閱 issues 并嘗試以下示例:

package main

import (
    "log"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "golang.org/x/sync/errgroup"
)

var (
    g errgroup.Group
)

func router01() http.Handler {
    e := gin.New()
    e.Use(gin.Recovery())
    e.GET("/", func(c *gin.Context) {
        c.JSON(
            http.StatusOK,
            gin.H{
                "code":  http.StatusOK,
                "error": "Welcome server 01",
            },
        )
    })

    return e
}

func router02() http.Handler {
    e := gin.New()
    e.Use(gin.Recovery())
    e.GET("/", func(c *gin.Context) {
        c.JSON(
            http.StatusOK,
            gin.H{
                "code":  http.StatusOK,
                "error": "Welcome server 02",
            },
        )
    })

    return e
}

func main() {
    server01 := &http.Server{
        Addr:         ":8080",
        Handler:      router01(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    server02 := &http.Server{
        Addr:         ":8081",
        Handler:      router02(),
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    g.Go(func() error {
        return server01.ListenAndServe()
    })

    g.Go(func() error {
        return server02.ListenAndServe()
    })

    if err := g.Wait(); err != nil {
        log.Fatal(err)
    }
}

重定向

HTTP 重定向很容易。 內(nèi)部敛滋、外部重定向均支持许布。

r.GET("/test", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})

路由重定向,使用 HandleContext

r.GET("/test", func(c *gin.Context) {
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(200, gin.H{"hello": "world"})
})

靜態(tài)文件服務

func main() {
    router := gin.Default()
    router.Static("/assets", "./assets")
    router.StaticFS("/more_static", http.Dir("my_file_system"))
    router.StaticFile("/favicon.ico", "./resources/favicon.ico")

    // 監(jiān)聽并在 0.0.0.0:8080 上啟動服務
    router.Run(":8080")
}

靜態(tài)資源嵌入

你可以使用 go-assets 將靜態(tài)資源打包到可執(zhí)行文件中绎晃。

func main() {
    r := gin.New()

    t, err := loadTemplate()
    if err != nil {
        panic(err)
    }
    r.SetHTMLTemplate(t)

    r.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "/html/index.tmpl",nil)
    })
    r.Run(":8080")
}

// loadTemplate 加載由 go-assets-builder 嵌入的模板
func loadTemplate() (*template.Template, error) {
    t := template.New("")
    for name, file := range Assets.Files {
        if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
            continue
        }
        h, err := ioutil.ReadAll(file)
        if err != nil {
            return nil, err
        }
        t, err = t.New(name).Parse(string(h))
        if err != nil {
            return nil, err
        }
    }
    return t, nil
}

請參閱 examples/assets-in-binary 目錄中的完整示例蜜唾。

測試

怎樣編寫 Gin 的測試用例

HTTP 測試首選 net/http/httptest 包。

package main

func setupRouter() *gin.Engine {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })
    return r
}

func main() {
    r := setupRouter()
    r.Run(":8080")
}

上面這段代碼的測試用例:

package main

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestPingRoute(t *testing.T) {
    router := setupRouter()

    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/ping", nil)
    router.ServeHTTP(w, req)

    assert.Equal(t, 200, w.Code)
    assert.Equal(t, "pong", w.Body.String())
}

用戶

使用 Gin web 框架的知名項目:
  • gorush:Go 編寫的通知推送服務器庶艾。
  • fnproject:原生容器袁余,云 serverless 平臺。
  • photoprism:由 Go 和 Google TensorFlow 提供支持的個人照片管理工具咱揍。
  • krakend:擁有中間件的超高性能 API 網(wǎng)關(guān)颖榜。
  • picfit:Go 編寫的圖像尺寸調(diào)整服務器。
  • gotify:使用實時 web socket 做消息收發(fā)的簡單服務器煤裙。
  • cds:企業(yè)級持續(xù)交付和 DevOps 自動化開源平臺掩完。

FAQ

TODO:記錄 GitHub Issue 中的一些常見問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末积暖,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子怪与,更是在濱河造成了極大的恐慌夺刑,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件分别,死亡現(xiàn)場離奇詭異遍愿,居然都是意外死亡,警方通過查閱死者的電腦和手機耘斩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門沼填,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人括授,你說我怎么就攤上這事坞笙。” “怎么了荚虚?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵薛夜,是天一觀的道長。 經(jīng)常有香客問我版述,道長梯澜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任渴析,我火速辦了婚禮晚伙,結(jié)果婚禮上吮龄,老公的妹妹穿的比我還像新娘。我一直安慰自己咆疗,他們只是感情好漓帚,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著民傻,像睡著了一般胰默。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漓踢,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天牵署,我揣著相機與錄音,去河邊找鬼喧半。 笑死奴迅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的挺据。 我是一名探鬼主播取具,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扁耐!你這毒婦竟也來了暇检?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤婉称,失蹤者是張志新(化名)和其女友劉穎块仆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體王暗,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡悔据,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俗壹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片科汗。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绷雏,靈堂內(nèi)的尸體忽然破棺而出头滔,到底是詐尸還是另有隱情,我是刑警寧澤涎显,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布拙毫,位于F島的核電站,受9級特大地震影響棺禾,放射性物質(zhì)發(fā)生泄漏缀蹄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缺前。 院中可真熱鬧蛀醉,春花似錦、人聲如沸衅码。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逝段。三九已至垛玻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奶躯,已是汗流浹背帚桩。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘹黔,地道東北人账嚎。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像儡蔓,于是被迫代替她去往敵國和親郭蕉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫喂江、插件召锈、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,118評論 4 61
  • 轉(zhuǎn)發(fā)自:http://shanshanpt.github.io/2016/05/03/go-gin.html gi...
    dncmn閱讀 6,050評論 0 1
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981閱讀 15,937評論 2 11
  • 我最喜歡的季節(jié)就是春天,因為花草樹木都都開始發(fā)芽获询,小燕子也飛回來了涨岁,許多蝴蝶花朵上跳舞,小鳥在樹枝上嘰嘰喳喳地叫著...
    抬頭擁有屬于我的守候閱讀 240評論 0 0
  • 我看滿天云不動阻肿,敵不動我也不動瓦戚。 你追我,如果你追到我丛塌,我就讓你嘿嘿嘿较解。 打死你個龜孫兒。 我不是一般的人赴邻,我是二...
    那時我們素面朝天閱讀 345評論 0 1