Gin web框架
source link: http://rivenzoo.github.io/2020/05/06/Gin-web%E6%A1%86%E6%9E%B6/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
gin是一个基于Golang标准库net/http
封装的HTTPweb框架。
它提供了方便的路由注册功能,支持捕获URL参数,提供了中间件机制来串连请求处理流程,提供了方便的数据获取和输出方法,所有这些功能提升了开发web服务的效率。
本文将从以下六个方面介绍gin的实现。
Engine
基于Golang标准库net/http
的web服务都需要实现http.Handler
接口,而Engine
就是gin实现该接口的类,它同时也实现了注册路由、run服务等方法。ServeHTTP
是HTTP请求的入口,而主要逻辑在handleHTTPRequest
方法中实现。
// 标准库HTTP接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// 接口实现
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request)
func (engine *Engine) handleHTTPRequest(c *Context)
以下是几个比较重要的成员。
RouterGroup是路由注册的类,收集路由信息和对应的HandlerChain,实际注册还是调用的Engine.addRoute
方法。trees
是底层组织路由的结构。pool
是分配gin.Context
的池,优化内存分配。
Render用于数据渲染。
type Engine struct {
RouterGroup // 路由,engine作为根路由
pool sync.Pool // context池
trees methodTrees // 路由
HTMLRender render.HTMLRender // 渲染数据
FuncMap template.FuncMap // 渲染数据
}
gin定义了两个接口来处理路由信息IRoutes
包含设置中间件、设置HTTP处理函数以及静态文件等方法,应用程序通过它来注册应用的路由处理逻辑。IRouter
继承了IRoutes
,另外添加了一个Group
方法来按URL路径层次组织router。
RouterGroup实现了IRouter
接口,字段包含路径以及请求处理链,保存了Engine的指针,注册路由是通过调用engine.addRoute
实现的。
type RouterGroup struct {
Handlers HandlersChain // 处理链
basePath string
engine *Engine
root bool // 如果是根,那么返回的IRoutes接口对象为engine;否则返回自身
}
addRoute方法根据不同HTTP method获取路由的根节点,然后按路径加入到基数树中。
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)
}
trees类型定义
// 路由组织成一个森林,不同HTTP的方法代表一颗路径树
// HTTP只有9个方法,所有使用数组保存
type methodTrees []methodTree
gin的路由实现似乎参考了httprouter这个项目,代码有点复杂先略过。
每个路由包含一个请求处理链Handlers HandlersChain
。
HandlersChain定义为处理函数切片,处理函数只接收*Context
参数,所有的请求响应需要的字段、以及处理流程控制变量都包含在Context
结构体中。
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
请求中间件和处理函数是同一个类型HandlerFunc
。
分为全局中间件和针对某个URL的中间件。Engine.Use
设置全局中间件。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes
RouterGroup.Use
设置当前路由的中间件。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes
gin提供了以下中间件,开发自定义中间件可以参考它们。
- HTTP验证
BasicAuth
等 - 日志
Logger
等 - 捕获panic
Recovery
Context
Context对象由Engine.pool
分配,请求处理完又放回了池中。
主要字段包含请求、响应对象,捕获的参数,处理链,用来控制处理流程的索引,kv值存储等。
它实现了标准库context.Context
接口,但是内部没有实现Deadline
方法。请求的超时context在Request对象中,可以通过c.Request.Context()
获取。
type Context struct {
writermem responseWriter // HTTP writer的包装对象
Request *http.Request // 请求对象
Writer ResponseWriter // 指向writermem
Params Params // url参数
handlers HandlersChain // 处理链,最多63个处理函数
index int8 // handlers的索引,控制handlers调用。reset函数初始化为-1
fullPath string // 请求路径
engine *Engine
// This mutex protect Keys map
KeysMutex *sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
}
处理控制逻辑由Next
和Abort
两个函数实现。
// Next可以在处理函数内部调用,相当于直接调用下一个处理函数,调用层级+1
func (c *Context) Next() {
c.index++ // 初始化为-1,从0开始执行
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c) // 调用当前函数
c.index++ // 移动到下一个函数
}
}
// Abort终止所有后续的函数
func (c *Context) Abort() {
c.index = abortIndex
}
其他API包括以下几类
- 读请求参数,如
Param
,Query
等 - 读写kv数据,如
Get
,Set
等 - 解析请求数据,如带
Bind
的方法 - 渲染数据,写响应,如
JSON
,Data
,Render
等
gin提供了binding接口来解码请求数据,接口和实现定义在binding目录下面。
为了方便,在Context上实现了许多数据格式解码的帮助函数,包括json、yaml、xml、protobuf等
最基础的是Binding
接口,它定义了从request读数据并且解码的接口BindingBody
定义了从body读数据的接口BindingUri
定义了从URL读数据的接口
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
type BindingBody interface {
Binding
BindBody([]byte, interface{}) error
}
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it read the Params.
type BindingUri interface {
Name() string
BindUri(map[string][]string, interface{}) error
}
Context的帮助函数
func (c *Context) ShouldBindJSON(obj interface{}) error // 不设置状态码
func (c *Context) BindUri(obj interface{}) error // 错误设置400
Context数据渲染方法
func (c *Context) Render(code int, r render.Render)
Render接口定义在render目录下,包括各种常用格式,如json、jsonp、HTML、protobuf等
type Render interface {
// Render writes data with custom ContentType.
Render(http.ResponseWriter) error
// WriteContentType writes custom ContentType.
WriteContentType(w http.ResponseWriter)
}
Context的帮助函数
func (c *Context) JSONP(code int, obj interface{})
func (c *Context) Data(code int, contentType string, data []byte)
有个小坑需要注意,Render函数内部会panic。如果渲染报错了,直接panic。
其他渲染函数都会调用它,所以都有可能panic。
// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) {
c.Status(code)
if !bodyAllowedForStatus(code) {
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}
if err := r.Render(c.Writer); err != nil {
panic(err)
}
}
- 基数树高效组织路由
- sync.pool分配Context
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
可替换的json库。
在internal/json
封装了常用json函数,用// +build jsoniter
来替换标准库json。字符串和slice转换优化。
在internal/bytesconv
里定义了优化后的转换函数,用了黑魔法避免内存拷贝。
// StringToBytes converts string to byte slice without a memory allocation.
func StringToBytes(s string) (b []byte) {
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len
return b
}
// BytesToString converts byte slice to string without a memory allocation.
func BytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
这里只记录了gin框架的大体架构,涉及到细节的地方大多都略过了。例如HTTP请求处理逻辑、路由trees的匹配算法等,希望以后有时间可以补上。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK