基础
快速入门
- gin完整支持路由框架
- 支持全局异常(错误)处理
- 内置渲染
- 高可扩展
组件
- 在gin框架中四个基本组件是:
- Engine:是web server的根数据结构,也是基础容器;它包含复用器、中间件和配置设置。类似SpringBoot的SpringApplication;
- RouteGroup:路由组,保存了路由路径,Radix Tree数据结构;
- Handler:处理函数、中间件
- Context:一次请求/响应的完整上下文,封装了请求和响应的操作及需要的数据
Engine
基础
- Engine 是框架的实例,它包含复用器、中间件和配置设置。一般使用New () 或Default () 创建 Engine 实例;
- 默认包含:Logger和Recover中间件,用于日志和全局异常处理
- 继承了
RouterGroup
- Engine一般使用
Run(ip:port)
直接启动- 同样支持通过其他方式启动
- 支持设置server服务属性
方法
- Engine的方法大概可以分为
- 全局处理
- 异常处理
- NoMethod:请求的路径的方式不对(405),在开启运行默认处理的时候,会调用这里设置的处理方法
- NoRoute:没有路由路径(404),会调用这里设置的处理方法
- 全局绑定
- Use:绑定全局调用链,相当于spring的拦截器,其中的Context的Next方法决定拦截前后
- LoadHtmlGloba&LoadHTMLFiles:全局绑定加载的Html,避免重复书写路径(之后只需要书写html文件即可),当多个文件路径的时候可以使用
LoadHTMLFiles
- Delims:设置一组用于HTML模板渲染的左右分隔符,默认是
{{}}
- 其他RouterGroup的
StaticXXX
方法,主要是绑定静态资源- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5qnmnKNf-1681483225389)(gin.assets/image-20220610153337575-16548464189554.png)]
- 异常处理
- 运行
- RunXXX系列都是运行相关,即以什么方式运行服务、另外Handler也是
- 扩展
- SetHTMLTemplate:自定义模板解析引擎
- SetFuncMap:模板解析方法
- SetTrustedProxies:设置信任的代理的IP地址,默认都信任
- 全局处理
RouteGroup和Handler
RouteGroup
- 非常简单,就是配置各种路由,支持前缀路由(路由组)配置
Handler
- 就是一系列处理函数,类似Netty的Handler,自定义;
- 在gin中,每个路由会绑定
HandlerChain
,管理handler
Context
-
在go和go框架中,上下文是一个非常核心的概念,上下文具有线程安全和局部可见的特点,当然和原生Context有点不同
-
上下文对象同样是gin的基础,在gin中context方法非常的多
-
//GET系列 Get(key string) (value any, exists bool) MustGet(key string) any GetString(key string) (s string) GetBool(key string) (b bool) GetInt(key string) (i int) GetInt64(key string) (i64 int64) GetUint(key string) (ui uint) GetUint64(key string) (ui64 uint64) GetFloat64(key string) (f64 float64) GetTime(key string) (t time.Time) GetDuration(key string) (d time.Duration) GetStringSlice(key string) (ss []string) GetStringMap(key string) (sm map[string]any) GetStringMapString(key string) (sms map[string]string) GetStringMapStringSlice(key string) (smss map[string][]string) GetQuery(key string) (string, bool) GetQueryArray(key string) (values []string, ok bool) GetQueryMap(key string) (map[string]string, bool) GetHeader(key string) string GetRawData() ([]byte, error) //params Param(key string) string AddParam(key string, value string) //form PostForm(key string) (value string) DefaultPostForm(key string, defaultValue string) string GetPostForm(key string) (string, bool) PostFormArray(key string) (values []string) initFormCache() GetPostFormArray(key string) (values []string, ok bool) PostFormMap(key string) (dicts map[string]string) GetPostFormMap(key string) (map[string]string, bool) FormFile(name string) (*multipart.FileHeader, error) MultipartForm() (*multipart.Form, error) //获取cookie Cookie(name string) (string, error) //query查询是否存在 Query(key string) (value string) DefaultQuery(key string, defaultValue string) string QueryArray(key string) (values []string) initQueryCache() QueryMap(key string) (dicts map[string]string) //handler处理器链 reset() Copy() *Context HandlerName() string HandlerNames() []string Handler() HandlerFunc FullPath() string //下一个 Next() //终止 IsAborted() bool Abort() AbortWithStatus(code int) AbortWithStatusJSON(code int, jsonObj any) AbortWithError(code int, err error) *Error Error(err error) *Error Set(key string, value any) //参数继续引擎绑定,必须成功(BindXXXX),可以失败(ShouldBindXXX) Bind(obj any) error BindJSON(obj any) error BindXML(obj any) error BindQuery(obj any) error BindYAML(obj any) error BindTOML(obj interface{}) error BindHeader(obj any) error BindUri(obj any) error MustBindWith(obj any, b binding.Binding) error BindWith(obj any, b binding.Binding) error ShouldBind(obj any) error ShouldBindJSON(obj any) error ShouldBindXML(obj any) error ShouldBindQuery(obj any) error ShouldBindYAML(obj any) error ShouldBindTOML(obj interface{}) error ShouldBindHeader(obj any) error ShouldBindUri(obj any) error ShouldBindWith(obj any, b binding.Binding) error ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) //响应 //重定向 Redirect(code int, location string) //header、code、message、cookie Status(code int) Header(key string, value string) SetSameSite(samesite http.SameSite) SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) Render(code int, r render.Render) //data,将数据转为对应格式的data在写入resp HTML(code int, name string, obj any) IndentedJSON(code int, obj any) SecureJSON(code int, obj any) JSONP(code int, obj any) JSON(code int, obj any) AsciiJSON(code int, obj any) PureJSON(code int, obj any) XML(code int, obj any) YAML(code int, obj any) TOML(code int, obj interface{}) ProtoBuf(code int, obj any) String(code int, format string, values ...any) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) File(filepath string) FileFromFS(filepath string, fs http.FileSystem) FileAttachment(filepath string, filename string) Stream(step func(w io.Writer) bool) bool //信息 ClientIP() string RemoteIP() string ContentType() string IsWebsocket() bool SSEvent(name string, message any) Negotiate(code int, config Negotiate) NegotiateFormat(offered ...string) string SetAccepted(formats ...string) SaveUploadedFile(file *multipart.FileHeader, dst string) error //go Context Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key any) any
-
方法
- 信息获取
- Get系列
- 可以指定类型获取参数、除了
MustGet
,其他在没有该参数不会有painc
,而是返回false,可以通过GetQuery
查询是否存在该参数 - 获取信息
GetHeader
:获取头部信息GetBody
:获取body的数据
- 可以指定类型获取参数、除了
- Form系列
- 获取表单数据
- 获取表单数据
- Params:获取参数
- 解析获取:即解析成指定的结构体/数据结构
- 必须解析成功:
BindXXX
- 可以解析失败:
ShouldBindXXX
- 必须解析成功:
- Get系列
- 响应
- 重定向
- Redirect
- 信息
- SetCookie、Handler、SetStatus等
- 返回的数据
- HTML、JSON等
- 重定向
- 信息和go的Context
使用
- Engine:基本使用和iris没有很大的区别,即通过
engine
绑定host、路由、handler - Context:和iris的iris.Context一样,gin有gin.Context
- gin支持自动解析(数据绑定),而不需要每个获取
- gin封装了返回数据,可以使用框架的JSON格式
- 支持路由组、restFul风格参数、最佳适配、静态资源和静态资源路由绑定
- 支持中间件(和iris处理逻辑一样)
- 支持模板引擎
- 集成swagger-go
- https://github.com/swaggo/swag/blob/master/README_zh-CN.md
//实例
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
//结构体解析
type User struct {
Name string `json:"userName"`
Password string `json:"password"`
Other string `json:"other"`
}
//统一封装返回
type Result struct {
Code int
Message string
Data interface{}
}
func init() {
}
func login(c *gin.Context) {
var user User
//绑定JSON解析
err := c.BindJSON(&user)
if err != nil {
return
}
fmt.Println(user)
result := Result{
Code: 200,
Message: "SUCCESS",
Data: "ok",
}
//自动将JSON作为返回参数
c.JSON(200, &result)
//执行下一条语句
c.Next()
}
func printHello(c *gin.Context) {
//获取路径参数
fmt.Println(c.Param("hello"))
c.Next()
}
func printUser(c *gin.Context) {
fmt.Println("user" + c.RemoteIP())
c.Next()
}
func globalPrint(c *gin.Context) {
fmt.Println("global")
c.Next()
}
func main() {
engine := gin.New()
//绑定全局处理器
engine.Use(globalPrint)
//绑定静态资源
engine.Static("/img", "./resource/static/img/*")
//绑定全局Html前缀路径
engine.LoadHTMLGlob("web/route/resource/web/*")
engine.GET("/", func(context *gin.Context) {
//html,通过模板引擎gin.H渲染 ---->//在index默认通过{{.name}}获取,可以自定义标识符替换{{}}
context.HTML(200, "index.html", gin.H{
"name": "lili",
})
})
//绑定路由组
group := engine.Group("/user", printUser)
//:hello为restful风格的参数传递
group.GET("/say/:hello", printHello)
//表单提交
engine.POST("/login", login)
//运行
err := engine.Run(":8888")
if err != nil {
fmt.Println("error")
}
}
实现
- 很明显,gin框架就像SpringMVC一样,其实现主要包括
- 路由注册和保存及路由算法
- 请求处理的过程
- 执行的过程(调度的过程)
- Request数据解析
- Response数据绑定
- 模板引擎渲染
- 组件间的关系
启动
Engine
- 实现了Http库的Handler接口,所以可以作为Handler直接使用http库
type Engine struct {
RouterGroup
//是否自动重定向(即/a/,自动重定向为/a;默认为true
RedirectTrailingSlash bool
// 是否对书写(多余/、字母大小写不符)的自动重定向
RedirectFixedPath bool
// 是否对请求的方法不正确(405),通过NotMethod处理
HandleMethodNotAllowed bool
// 请求IP;默认为true
ForwardedByClientIP bool
// 开启路径参数
UseRawPath bool
// 路径参数转移;默认为true
UnescapePathValues bool
// 以从 URL 中解析参数,即使带有额外的斜杠。
RemoveExtraSlash bool
//允许的IP请求头
RemoteIPHeaders []string
// 信任的请求的平台
TrustedPlatform string
// 传入的参数最大占用空间
MaxMultipartMemory int64
//是否支持HTTP2
UseH2C bool
// Context是否可以回退
ContextWithFallback bool
//公共请求和响应设置(模板引擎、对象池、参数等)
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
过程
New方法
- 无论是通过
Default
还是New
获取Engine都会调用New
方法Default
绑定了默认的日志和全局异常处理
New
方法- 绑定默认的Engine参数
- 注入根
RouteGroup
Run方法
- 就是调用内置的http开启监听:
http.ListenAndServe
func (engine *Engine) Run(addr ...string) (err error) {
address := resolveAddress(addr)
//注入engine作为http的handler,即作为监听的端口的所有请求的handler;
//engine.Handler()方法就是返回http1.x或者http2.0的engine
err = http.ListenAndServe(address, engine.Handler())
}
- 到这里启动基本完成,
- 下面介绍启动前注入路由
- 请求到达后的处理
路由
压缩前缀树
- 压缩前缀树就是:通过合并节点实现的前缀树
- 前缀树:即字典树,一般只需要:
final、value、children数组/map(以及全量单词)
,显然前缀树的增删改查都是logn - 合并节点:将以当前节点为根的子树中所有节点都是只有一个节点的子节点合并为一个节点
- 前缀树:即字典树,一般只需要:
路由表的压缩前缀树
//压缩前缀树节点
type node struct {
path string //value
indices string //节点与子节点的分裂的第一个字符
wildChild bool //是否为参数节点
nType nodeType //节点类型:根节点、参数节点、通配符、普通节点
priority uint32 //handler数量
children []*node //子节点
handlers HandlersChain //处理器链
fullPath string //全量路径
}
//封装的获取到压缩前缀书查询结果
type nodeValue struct {
handlers HandlersChain
params *Params
tsr bool
fullPath string
}
路由生成
- 在gin中使用动态路由策略,所以实现路由没有使用hash表,而是压缩前缀树
- 路由生成主要包括
- 路由路径生成:即将可能的前缀路由合并到当前路由
- 处理器链:将当前路由的handler加入到处理器链
- 加入路由表
- 获取方法的根路由
- 压缩前缀书方法加入路由到路由表
//生成路由
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()
}
//获取根路由
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
//根据方法类型获取根路径
root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
//加入路由表
root.addRoute(path, handlers)
}
//添加路由路径
func (n *node) addRoute(path string, handlers HandlersChain) {
fullPath := path
n.priority++
// 空树,直接使用压缩前缀即可
if len(n.path) == 0 && len(n.children) == 0 {
n.insertChild(path, fullPath, handlers)
n.nType = root
return
}
parentFullPathIndex := 0
walk:
for {
//最长公共路径(排除通配符和参数匹配)
i := longestCommonPrefix(path, n.path)
//生成节点
if i < len(n.path) {
child := node{
//...
}
//修改n
n.children = []*node{&child}
// []byte for proper unicode char conversion, see #65
n.indices = bytesconv.BytesToString([]byte{n.path[i]})
n.path = path[:i]
n.handlers = nil
n.wildChild = false
n.fullPath = fullPath[:parentFullPathIndex+i]
}
// 去除参数、通配符、多余/
if i < len(path) {
path = path[i:]
c := path[0]
// '/' after param
if n.nType == param && c == '/' && len(n.children) == 1 {
parentFullPathIndex += len(n.path)
n = n.children[0]
n.priority++
continue walk
}
// Check if a child with the next path byte exists
for i, max := 0, len(n.indices); i < max; i++ {
if c == n.indices[i] {
parentFullPathIndex += len(n.path)
i = n.incrementChildPrio(i)
n = n.children[i]
continue walk
}
}
if c != ':' && c != '*' && n.nType != catchAll {
n.indices += bytesconv.BytesToString([]byte{c})
child := &node{
fullPath: fullPath,
}
n.addChild(child)
n.incrementChildPrio(len(n.indices) - 1)
n = child
}
//插入子节点
n.insertChild(path, fullPath, handlers)
return
}
n.handlers = handlers
n.fullPath = fullPath
return
}
}
路由查找
- 路由查找非常简单
- 找到压缩前缀树根节点
- 找到相应的路由节点
请求处理
- gin使用了原生的http库,实现了其中handler的
ServeHTTP
方法,通过handleHTTPRequest
统一处理请求 - gin使用了对象池技术,减少了对象的创建和释放
- **
handleHTTPRequest
**主要就是根据请求方法+路径确定处理器链和参数、然后调用处理器链进行处理- 根据需要:对于无路由或者请求方法不正确进行处理