使用 gin

使用 go module

要求 Go version >= 1.13 或者 Go version >= 1.11 同时将 GO111MODULE=on 开启。

初始化 go module

创建项目目录,任意目录即可不必在 GOPATH/src 目录,并进入项目目录

mkdir ginProj
cd ginProj
1
2

执行 go mod init moduleName 初始化 go module 项目:

$ go mod init ginProj
go: creating new go.mod: module ginProj
1
2

其中 ginProj 是自定义的 moduleName,本例与目录一致,其实是任意名称。

编写 main.main()

利用编辑器(推荐 GoLand),创建 ginProj/main.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() // 监听并在 0.0.0.0:8080 上启动服务
}
1
2
3
4
5
6
7
8
9
10
11
12
13

运行

直接运行 go run main.go 即可。

$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> 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
exit status 2
1
2
3
4
5
6
7
8
9
10
11

表示运行成功。

通过浏览器或者其他请求工具(例如 postman,curl 等),访问 localhost:8080/ping 即可获取 JSON 数据格式响应 {"message": "pong"}

GOPATH

安装

利用 go get 完成下载安装。

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

编写 main.main()

在 GOPATH/src 目录创建项目目录,例如 ginProj,利用编辑器(推荐 GoLand),创建 ginProj/main.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() // 监听并在 0.0.0.0:8080 上启动服务
}
1
2
3
4
5
6
7
8
9
10
11
12
13

运行

直接运行 go run main.go 即可。

$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> 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
exit status 2
1
2
3
4
5
6
7
8
9
10
11

表示运行成功。

通过浏览器或者其他请求工具(例如 postman,curl 等),访问 localhost:8080/ping 即可获取 JSON 数据格式响应 {"message": "pong"}

构建发布

直接运行 go run main.go 会先编译再运行。在生产部署时,应该先编译形成可执行性文件,再运行可执行性文件。使用命令 go build 完成:

$ go build -o backend main.go
./backend
1
2

以上命令会编译生成 backend 执行文件,若省略 -o 选项,默认生成 main 执行文件。

在生成环境部署时,应该使用 releaseMode 发布模式,代码中默认使用的为 debug 调试模式,调用函数 gin.SetMode(gin.ReleaseMode) 可设置为发布模式。在 main.go 中:

func main() {
    // 设置为发布模式
    gin.SetMode(gin.ReleaseMode)
	/*
    	其他代码
    */
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
1
2
3
4
5
6
7
8

发布模式会减少一些调试输出和做出一些特定优化等,例如路由日志在发布模式下就不会输出到终端。

推荐目录结构

gin 框架对于项目目录结构没有要求,只要符合典型的包管理机制即可。根据典型的 MVC 框架的代码架构,建议的目录结构为:

/main.go 入口文件
/config/ 配置
/router/ 路由
	api.go
	web.go
/handler/ 请求处理器
	user.go
/middleware/ 中间件
	jwtToken.go
/model/ 模型
	user.go
/test/ 测试
1
2
3
4
5
6
7
8
9
10
11
12

以上结构是推荐而不是强制的。

停止监听

暴力停止

我们在 go run main.go 后,若需要停止监听,需要使用 ctrl+c 终止监听。该方案会立即终止服务器监听,同时结束正在处理的请求,也就是说若存在未处理完毕的请求,是不能继续处理的。

优雅停止

代替示例代码中 router.Run() 方法,我们可以使用 http.Server 内置的 Shutdown() 方法优雅地停止。所谓优雅,指的是可以将正在处理的请求处理完毕后再关闭服务器。示例代码如下 main.go :

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,
	}
	// 利用 goroutine 启动监听
	go func() {
		// srv.ListenAndServe() 监听
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)
    // quit 信道是同步信道,若没有信号进来,处于阻塞状态
    // 反之,则执行后续代码
	<-quit
	log.Println("Shutdown Server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
    // 调用 srv.Shutdown() 完成优雅停止
    // 调用时传递了一个上下文对象,对象中定义了超时时间
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	log.Println("Server exiting")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

以上代码的思路是:主 goroutine 用于监听信号,若进来信号则优雅停止。而调用的 goroutine 用于监听 HTTP 请求。

测试以上代码,结果为:

$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
2019/11/05 22:49:04 Shutdown Server ...
[GIN] 2019/11/05 - 22:49:07 | 200 |    5.0000438s |             ::1 | GET      /
2019/11/05 22:49:08 Server exiting
1
2
3
4
5
6
7
8
9
10
11

本例测试中,请求处理器增加一个 5s 的延迟,我们用请求代理端进行请求,在服务器响应完成前,通过 ctrl+c 关闭服务器。测试服务器还是会将该请求处理完毕,再关闭!优雅不?