Open hhstore opened 5 years ago
2019.03
, 目前综合最好的 go web
框架gin v1.3.0
:
模块 | 实现方式 | 依赖 |
---|---|---|
gin.Engine.pool | 使用 sync.Pool 临时对象池 | 标准库 sync.Pool |
gin.ServeHTTP() | 使用 sync.pool 优化 | 标准库 net.http 接口实现 |
gin.Context | 核心模块, 包含HTTP请求处理全过程 | 标准库 |
xxxx | xxxxx | xxxx |
xxxx | xxxxx | xxxx |
xxxx | xxxxx | xxxx |
context.go : 包含了 HTTP 请求上下文全部处理过程,
request
和response
两部分. gin 主体功能代码, 都在这里. 应该把 request 和 response 单独拆分开, 比较好. 虽然混在一起, 整体结构还是很清晰的.
-
gin-gonic/gin/gin.go
- 找到第一个入口
gin.Default()
, 由此展开整个gin框架.- 第二关键处:
r.Run()
, 后续会逐步分析到.- 通过这2个关键入口, 庖丁解牛, 把整个 gin 框架拆解开.
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() // listen and serve on 0.0.0.0:8080
}
gin-gonic/gin/gin.go
gin.Engine
- 创建 gin 框架对象
- 配置 gin 默认中间件
- 返回 gin 框架对象
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New() // 创建框架对象
engine.Use(Logger(), Recovery()) // 配置默认中间件
return engine // 返回框架对象
}
- 初始化 Engine 对象, 关键步骤: 初始化 路由组
- 初始化 pool, 这是核心步骤. pool 用来存储 context 上下文对象. 用来优化处理 http 请求时的性能.
- 后续会重点分析
engine.pool
.
func New() *Engine {
debugPrintWARNINGNew()
// 初始化框架对象
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
AppEngine: defaultAppEngine,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
....
}
engine.RouterGroup.engine = engine
// 关键代码: 初始化 pool
engine.pool.New = func() interface{} {
return engine.allocateContext() // 关键调用: 初始化上下文对象
}
return engine
}
- 暂时不细展开.
type Engine struct {
RouterGroup // 关键: 路由组
// 几个配置开关:
RedirectTrailingSlash bool
RedirectFixedPath bool
HandleMethodNotAllowed bool
ForwardedByClientIP bool
AppEngine bool
UseRawPath bool
UnescapePathValues bool
MaxMultipartMemory int64
delims render.Delims
secureJsonPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
allNoRoute HandlersChain
allNoMethod HandlersChain
noRoute HandlersChain
noMethod HandlersChain
pool sync.Pool // 关键: 临时对象池: 用于处理 context
trees methodTrees
}
gin.Default()
的分析, 卡住.r.Run()
, 继续展开分析.// 启动框架, 处理 http 请求
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr) // 获取 IP+Port
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine) // 处理 HTTP 请求, 关键代码: 注意 engine 传参
return
}
- 解析 IP + Port
- 使用 标准库
http.ListenAndServe()
启动 web 监听服务, 处理HTTP请求.- 这里面, 需要特别注意传入了
engine
对象.
go/1.12/libexec/src/net/http/server.go
net.http
包// 注意 handler 的类型是 接口
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
handler Handler
, 发现 Handler 是个接口类型.// go/1.12/libexec/src/net/http/server.go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
- Handler {} 是个接口类型
- Handler 内部只有一个 ServeHTTP() 方法声明. 这是关键点,
- gin 自己实现了 gin.ServeHTTP() 方法, 使 Engine 符合该接口声明.
注意: 本方法, 是核心中的核心实现 ! ! !
注意: 本方法, 是核心中的核心实现 ! ! !
注意: 本方法, 是核心中的核心实现 ! ! !
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context) // 从 临时对象池 pool 获取 context 上下文对象
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c) // 处理 HTTP 请求
engine.pool.Put(c) // 使用完 context 对象, 归还给 pool
}
gin.Engine {}
是 interface http.Handler{ }
接口的实现.
- 源码注释, 清楚说明是
http.Handler
接口实现.- engine.pool.Get().(*Context): 从临时对象池 取出一个 context 上下文对象
- engine.handleHTTPRequest(c): 处理 HTTP 请求
- engine.pool.Put(c): 处理完HTTP请求, 归还 context 对象到 pool
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
path := c.Request.URL.Path
...
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
...
root := t[i].root
// Find route in tree
handlers, params, tsr := root.getValue(path, c.Params, unescape)
if handlers != nil {
c.handlers = handlers
c.Params = params
// 关键调用 ! ! !
c.Next() // 具体执行处理
c.writermem.WriteHeaderNow()
return
}
if httpMethod != "CONNECT" && path != "/" {
....
}
break
}
// 后续是出错返回, 省略之...
....
}
- 省略一下非关键处理代码
c.Next()
是最核心的代码: c是Context
对象. 引出 Context 的实现细节.
/gin/context.go
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
// 注意: 逐个遍历, 执行 handler()
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c) // 注意传参 (c), 执行具体handler()方法
}
}
- 注意原注释说明: Next() 只允许在 内部 中间件使用, 不可滥用.
- for 循环, 遍历 handlers 是个 数组: 内部存 方法集合.
Context
是 gin 实现的 关键核心
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
writermem responseWriter
Request *http.Request // HTTP 请求
Writer ResponseWriter // HTTP 响应
Params Params
handlers HandlersChain // 关键: 数组: 内包含方法集合
index int8
engine *Engine // 关键: 引擎
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
}
- 关键:
Request http.Request : HTTP 请求 Writer ResponseWriter : HTTP 响应 handlers HandlersChain : 是方法集 engine Engine : gin框架对象
// 方法类型:
type HandlerFunc func(*Context)
// 数组: 方法集
type HandlersChain []HandlerFunc
HandlersChain 类型: 方法集合
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() // listen and serve on 0.0.0.0:8080
}
由
r.GET("/ping", func(c *gin.Context){})
: 这里可见: 定义 HTTP 请求路由. 进而找到gin.GET()/ gin.POST()
等方法实现.
/gin/routergroup.go
// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("POST", relativePath, handlers)
}
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
- gin.POST() 定义 POST 请求路由
- gin.GET() 定义 GET 请求路由
- 注意:
group *RouterGroup
- 注意: 返回值类型:
IRoutes
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix
// and an array of handlers (middleware).
type RouterGroup struct {
Handlers HandlersChain // 数组: 路由集
basePath string
engine *Engine // gin 引擎
root bool
}
注意: 原注释提示. 配置路由.
type IRoutes interface {
Use(...HandlerFunc) IRoutes
Handle(string, string, ...HandlerFunc) IRoutes
Any(string, ...HandlerFunc) IRoutes
GET(string, ...HandlerFunc) IRoutes // HTTP GET
POST(string, ...HandlerFunc) IRoutes // HTTP POST
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
StaticFile(string, string) IRoutes
Static(string, string) IRoutes
StaticFS(string, http.FileSystem) IRoutes
}
/gin/routergroup.go: handle()
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()
}
- 添加路由
gin 源码剖析:
Related:
129 : pipe 博客 源码剖析
130 : digota 电商平台 源码剖析