Go Gin 简明教程

Gin

关键字:Gin教程 Gin中文文档 Go语言Web框架 Go环境搭建

Gin 简介

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance – up to 40 times faster. If you need smashing performance, get yourself some Gin.

Gin 是使用 Go/golang 语言实现的 HTTP Web 框架。接口简洁,性能极高。截止 1.4.0 版本,包含测试代码,仅14K,其中测试代码 9K 左右,也就是说框架源码仅 5K 左右。

1
2
3
4
$ find . -name "*_test.go" | xargs cat | wc -l
8657
$ find . -name "*.go" | xargs cat | wc -l
14115

Gin 特性

KoaRestful API

安装Go & Gin

初学者建议先阅读 Go 语言简明教程。
一篇文章介绍了 Go 基本类型,结构体,单元测试,并发编程,依赖管理等内容。Go 1.13 以上版本的安装推荐该教程的方式。

  • 安装 Go (Ubuntu)
1
2
3
$ sudo apt-get install golang-go
$ go version
# go version go1.6.2 linux/amd64

Ubuntu自带版本太老了,安装新版可以使用如下命令。

1
2
3
$ sudo add-apt-repository ppa:gophers/archive
$ sudo apt-get update
$ sudo apt-get install golang-1.11-go
/usr/lib/go-1.11/binsource ~/.bashrc
1
export PATH=$PATH:/usr/lib/go-1.11/bin
  • 安装 Go (Mac)
1
2
3
$ brew install go
$ go version
# go version go1.12.5 darwin/amd64
  • 设置环境变量

在 ~/.bashrc 中添加 GOPATH 变量

1
2
export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin
source ~/.bashrc
  • 安装一些辅助的工具库

由于网络原因,不能够直接访问 golang.org,但相关的库已经镜像到 Golang - Github

golang.org/x/toolsgo-outline
1
2
3
4
5
$ go get -u -v github.com/ramya-rao-a/go-outline
github.com/ramya-rao-a/go-outline (download)
Fetching https://golang.org/x/tools/go/buildutil?go-get=1
https fetch failed: Get https://golang.org/x/tools/go/buildutil?go-get=1:
dial tcp 216.239.37.1:443: i/o timeout
go-outlinegoreturns
1
2
3
4
git clone https://github.com/golang/tools.git $GOPATH/src/golang.org/x/tools
go get -v github.com/ramya-rao-a/go-outline
go get -v github.com/sqs/goreturns
go get -v github.com/rogpeppe/godef
VSCode
  • 安装 Gin
1
go get -u -v github.com/gin-gonic/gin
-v-u

第一个Gin程序

main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
// geektutu.com
// main.go
package main

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

func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, Geektutu")
})
r.Run() // listen and serve on 0.0.0.0:8080
}
gin.Default()r.Get("/", ...)r.Run()r.Run(":9999")
  • 运行
1
2
3
4
$ go run main.go
[GIN-debug] GET / --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
http://localhost:8080

Hello Gin

路由(Route)

路由方法有 GET, POST, PUT, PATCH, DELETEOPTIONS,还有Any,可匹配以上任意类型的请求。

无参数

1
2
3
4
// 无参数
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Who are you?")
})
1
2
$ curl http://localhost:9999/
Who are you?
curlhttps://man.linuxde.net/curl

解析路径参数

/user/:name/user/:name/*role*
1
2
3
4
5
// 匹配 /user/geektutu
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
1
2
$ curl http://localhost:9999/user/geektutu
Hello geektutu

获取Query参数

1
2
3
4
5
6
// 匹配users?name=xxx&role=xxx,role可选
r.GET("/users", func(c *gin.Context) {
name := c.Query("name")
role := c.DefaultQuery("role", "teacher")
c.String(http.StatusOK, "%s is a %s", name, role)
})
1
2
$ curl "http://localhost:9999/users?name=Tom&role=student"
Tom is a student

获取POST参数

1
2
3
4
5
6
7
8
9
10
// POST
r.POST("/form", func(c *gin.Context) {
username := c.PostForm("username")
password := c.DefaultPostForm("password", "000000") // 可设置默认值

c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
})
})
1
2
$ curl http://localhost:9999/form  -X POST -d 'username=geektutu&password=1234'
{"password":"1234","username":"geektutu"}

Query和POST混合参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// GET 和 POST 混合
r.POST("/posts", func(c *gin.Context) {
id := c.Query("id")
page := c.DefaultQuery("page", "0")
username := c.PostForm("username")
password := c.DefaultPostForm("username", "000000") // 可设置默认值

c.JSON(http.StatusOK, gin.H{
"id": id,
"page": page,
"username": username,
"password": password,
})
})
1
2
$ curl "http://localhost:9999/posts?id=9876&page=7"  -X POST -d 'username=geektutu&password=1234'
{"id":"9876","page":"7","password":"1234","username":"geektutu"}

Map参数(字典参数)

1
2
3
4
5
6
7
8
9
r.POST("/post", func(c *gin.Context) {
ids := c.QueryMap("ids")
names := c.PostFormMap("names")

c.JSON(http.StatusOK, gin.H{
"ids": ids,
"names": names,
})
})
1
2
$ curl -g "http://localhost:9999/post?ids[Jack]=001&ids[Tom]=002" -X POST -d 'names[a]=Sam&names[b]=David'
{"ids":{"Jack":"001","Tom":"002"},"names":{"a":"Sam","b":"David"}}

重定向(Redirect)

1
2
3
4
5
6
7
8
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/index")
})

r.GET("/goindex", func(c *gin.Context) {
c.Request.URL.Path = "/"
r.HandleContext(c)
})
1
2
3
4
5
6
7
8
9
10
11
$ curl -i http://localhost:9999/redirect
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: /
Date: Thu, 08 Aug 2019 17:22:14 GMT
Content-Length: 36

<a href="/">Moved Permanently</a>.

$ curl "http://localhost:9999/goindex"
Who are you?

分组路由(Grouping Routes)

/api/v1/api/v1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// group routes 分组路由
defaultHandler := func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"path": c.FullPath(),
})
}
// group: v1
v1 := r.Group("/v1")
{
v1.GET("/posts", defaultHandler)
v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
v2.GET("/posts", defaultHandler)
v2.GET("/series", defaultHandler)
}
1
2
3
4
$ curl http://localhost:9999/v1/posts
{"path":"/v1/posts"}
$ curl http://localhost:9999/v2/posts
{"path":"/v2/posts"}

上传文件

单个文件

1
2
3
4
5
r.POST("/upload1", func(c *gin.Context) {
file, _ := c.FormFile("file")
// c.SaveUploadedFile(file, dst)
c.String(http.StatusOK, "%s uploaded!", file.Filename)
})

多个文件

1
2
3
4
5
6
7
8
9
10
11
r.POST("/upload2", 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, "%d files uploaded!", len(files))
})

HTML模板(Template)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type student struct {
Name string
Age int8
}

r.LoadHTMLGlob("templates/*")

stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/arr", func(c *gin.Context) {
c.HTML(http.StatusOK, "arr.tmpl", gin.H{
"title": "Gin",
"stuArr": [2]*student{stu1, stu2},
})
})
1
2
3
4
5
6
7
8
9
<!-- templates/arr.tmpl -->
<html>
<body>
<p>hello, {{.title}}</p>
{{range $index, $ele := .stuArr }}
<p>{{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old</p>
{{ end }}
</body>
</html>
1
2
3
4
5
6
7
8
9
$ curl http://localhost:9999/arr

<html>
<body>
<p>hello, Gin</p>
<p>0: Geektutu is 20 years old</p>
<p>1: Jack is 22 years old</p>
</body>
</html>

中间件(Middleware)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 作用于全局
r.Use(gin.Logger())
r.Use(gin.Recovery())

// 作用于单个路由
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

// 作用于某个组
authorized := r.Group("/")
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
}

如何自定义中间件呢?

1
2
3
4
5
6
7
8
9
10
11
12
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 给Context实例设置一个值
c.Set("geektutu", "1111")
// 请求前
c.Next()
// 请求后
latency := time.Since(t)
log.Print(latency)
}
}

热加载调试 Hot Reload

Flask

Gin 原生不支持,但有很多额外的库可以支持。例如

  • github.com/codegangsta/gin
  • github.com/pilu/fresh

这次,我们采用 github.com/pilu/fresh

1
go get -v -u github.com/pilu/fresh
go run main.gofresh

相关链接