Gin Crash Course
Gin Web Framework
Gin是Go语言编写的Web框架,功能完善,使用简单,性能高,Gin核心的路由功能是通过HttpRouter实现,具有很高的路由性能.
Web服务核心功能
基础功能
1
2
3
4
5
6
7
8
9
10
11
> HTTP方法,请求路径匹配到处理这个请求的函数,最终由改函数处理这次请求,并返回结果
1. 参数解析
2. 参数校验
3. 逻辑处理
4. 返回结果高级功能
1
2
3# 中间件
> Gin支持中间件,HTTP请求在转发到实际的处理函数之前,会被一系列加载的中间件进行处理
gin.Engine.Use() 方法加载中间件gin.Logger(): Logger中间件将日志写到gin.DefaultWriter, gin.DefaultWriter默认为os.Stdout
gin.Recovery(): Recovery中间件可以从任何panic恢复,并写入500状态
gin.CustomRecovery(handle gin.RecoverFunc):
gin.BasicAuth(): HTTP 请求基本认证1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|中间件 | 功能 |
|:--------|:----------------------|
| gin-jwt | JWT中间件,实现JWT认证|
| gin-swagger| 自动生成Swagger 2.0格式的RESTful API文档|
| cors| 实现HTTP请求跨域|
|sessions|会话管理中间件|
|authz| 基于casbin授权中间件|
|pprof| gin pprof 中间件|
|go-gin-prometheus|Prometheus metrics exporter|
|gzip| 支持HTTP请求和响应的gzip压缩|
|gin-limit|HTTP请求并发控制中间件|
|requestid|给每个Request生成uuid,并添加在返回的X-Request-ID Header中|
# 认证认证: Authentication: 用来验证某个用户是否具有访问系统的权限 – 证明你是谁
授权: Authorization: 用来验证某个用户是否具有访问某个资源的权限 – 决定你能做什么1
1. Basic: 基础认证
用户名:密码: base64编码后,放到HTTP Authorization Header
github.com/chyidl/noone via 🐹 v1.17
➜ basic=echo -n 'admin:Admin@2021'|base64
github.com/chyidl/noone via 🐹 v1.17
➜ echo $basic
YWRtaW46QWRtaW5AMjAyMQ==github.com/chyidl/noone via 🐹 v1.17
➜ echo $basic | base64 –decode
admin:Admin@2021%Basic认证简单,但是不安全, 使用Basic认证+SSL配合使用确保整个认证过程安全
不要再请求参数中使用明文密码,不要在任何存储中保存明文密码
1
2
3
42. Digest
3. OAuth
4. Bearer
> Bearer认证称为令牌认证。是一种HTTP身份验证方式. Bearer认证的核心是bearer token.bearer token: 是一个加密字符串,由服务端根据密钥生成, 客户端在请求服务端时,必须在请求头包含Authorization: Bearer
. 服务端收到请求头,解析出 .校验 合法性,Bearer认证配合HTTPS使用,保证认证安全性 JSON Web Token: JWT
JWT是Bearer Token一个具体实现,由JSON数据格式组成, 通过HASH散列算法生成字符串.JWT 认证流程:
不要存放敏感信息到Token中
Payload exp值不要设置太大,一般开发版本2小时,上线版本7天- 客户端使用用户名和密码请求登陆
- 服务端收到请求后,会验证用户名和密码,如果用户名和密码验证成功,服务端签发Token返回给客户端
- 客户端收到请求后将Token缓存起来 Cookie中或者LocalStorage,之后每次请求都会携带该Token
- 服务端收到请求后,验证请求中的Token,验证通过则进行业务逻辑处理,处理完返回处理结果
JWT格式:
1.Header
- 类型声明 JWT - 声明加密算法 HMAC SHA256 - 密钥ID 可选 { "typ": "JWT", "alg": "HS256" "kid": "youknowwhoami" } Header进行base64编码 github.com/chyidl/noone via 🐹 v1.17 ➜ echo -n '{"typ":"JWT","alg":"HS256","kid":"youknowwhoami"}'|base64 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6InlvdWtub3d3aG9hbWkifQ==
2.Payload
- JWT标准中注册的声明
- 公共的声明 > 添加用户相关信息或者其他业务需要信息 - 私有的声明 > 客户端和服务端共同定义的声明,base64是对称加密,不建议存放敏感信息1
2
3
4
5
6
7iss(Issuer): JWT Token的签发者
sub(Subject): 主题
exp(Expiration Time): JWT Token过期时间
aud(Audience): 接受JWT Token的一方
iat(Issued At): JWT Token签发时间
nbf(Not Before): JWT Token生效时间
jti(JWT ID): JWT Token ID,令牌的唯一标识符3.Signature 签名
- header(base64后的) - payload(base64后) - secretKey: 密钥,保存在服务器中,通过配置文件保存 - Salt(加密盐)
1
服务端收到Token后会解析出header.payload 然后用相同的加密算法和密钥对header.payload在进行一次加密,并对加密后的Token和收到的Token是否相同,如果相同则验证通过,不相同返回HTTP 401 Unauthrozied
RequestID
> 定于和跟踪RequestID
跨域
> 当前软件架构中采用前后端分离,前端访问地址和后端方法地址不同,Web服务需要处理浏览器跨域请求
优雅关停
1 | ``` |
Features
- 支持HTTP方法: GET/POST/PUT/PATCH/DELETE/OPTIONS
- 支持不同位置的HTTP参数:
- 路径参数 path tag uri /user/:name name就是路径参数
1
路径参数: ShouldBindUri, BindUri
- 查询字符串参数 query tag form /welcome?firstname=xx firstname就是查询参数
1
查询字符串参数: ShouldBindQuery, BindQuery
- 表单参数 form tag form curl -X POST -F ‘username=colins’ http://mydomain.com/login, username就是表单参数
1
表单参数: ShouldBind
- HTTP头参数 header tag header curl -X POST -H ‘Content-Type:application/json’ http://mydomain.com/login Content-Type就是HTTP头参数
1
HTTP头参数: ShouldBindHeader, BindHeader
- 消息体参数 body tag json/xml curl -X POST -H ‘Content-Type:application/json’ -d ‘{“username”:”colins”}’ http://mydomain.com/login, username就是消息体参数
1
消息体参数: ShouldBindJSON, BindJSON
- 路径参数 path tag uri /user/:name name就是路径参数
- 支持HTTP路由和路由分组
- 支持自定义Log
- 支持binding和validation,
- 支持重定向
- 支持basic auth middleware
- 支持自定义HTTP配置
- 支持优雅关闭
- 支持HTTP2
- 支持设置获取cookie
快速入门
ping-pong
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "pong"})
})
r.Run()
}- 启动 & 运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14➜ 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
# 调用
➜ curl -X GET http://localhost:8080/ping
{"message":"pong"}%
- 启动 & 运行
源码分析
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90# gin.Default
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault() // 检查Go版本是否达到Gin最低要求
engine := New()
engine.Use(Logger(), Recovery()) // 引入中间件
return engine
}
# gin.New
// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash: true
// - RedirectFixedPath: false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP: true
// - UseRawPath: false
// - UnescapePathValues: true
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{ // 初始化
RouterGroup: RouterGroup{ // 路由组
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedProxies: []string{"0.0.0.0/0"},
AppEngine: defaultAppEngine,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory, // const defaultMultipartMemory = 32 << 20 // 32 MB
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"}, // HTML 模版左右定界符
secureJSONPrefix: "while(1);",
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
# r.GET()
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath) // 计算路由的绝对路径
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers) // 追加到树
return group.returnObj()
}
# r.Run()
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
// It is a shortcut for http.ListenAndServe(addr, router)
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
trustedCIDRs, err := engine.prepareTrustedCIDRs()
if err != nil {
return err
}
engine.trustedCIDRs = trustedCIDRs
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
// 上下文池化防止频繁生成上下文对象,提高性能
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context) // sync.Pool 对象池中获取一个上下文对象
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c) // 返回对象池
}