前言
学习完golang基础语法后,学习gin框架,参考李文周老师的博客进行学习。
gin框架主要分为一下二个部分
1.gin框架的路由注册
2.gin框架的中间件使用
最常见的gin框架使用如霞:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/",func(context *gin.Context){
context.JSON(200,gin.H{
"massage":"hello",
})
})
r.Run()
}
gin.Default(),r.GET() , r.Run()都干了什么接下来通过源码详细探索一下。
Run方法
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) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return
}
可以看到Run方法调用的是Engine结构体指针,这个结构体指针是gin.Default()
获取的,并且真正执行http方法的是err = http.ListenAndServe(address, engine.Handler())
,传入了engine。所以我们来看一下Engine这个结构体。
r.Run()
type Engine struct {
RouterGroup
RemoveExtraSlash bool
RemoteIPHeaders []string
UseH2C bool
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
}
var _ IRouter = &Engine{}
Engine 继承了RouterGroup结构体,并且实现了Handler接口。在var _ IRouter = &Engine{}
,这句的意思这种方法的作用就是确保一个结构体实现了这个方法,把问题暴露在编译阶段如果这个ENgine引擎没有实现Irouter结构就报错,属于_妙用。
其中Engine结构体中有methodTrees,type methodTrees []methodTree
而methodTree这个结构体,有两个属性一个string,一个node指针。而node指针是典型的PAT树节点。
PAT是一种更节省空间的前缀树。相较于普通前缀树,他会把公共部分单独形成一个子节点。而gin框架在有新的路由注册时候,会将原来注册的和新注册的提取公共部分,作为新的父亲节点。在gin框架中,每一类请求 get post 请求都是一颗上面这样的pat前缀树。当一个用户请求一个url的时候,gin框架去做路由匹配的原理是采用路径补偿,直接去当前节中匹配最长的子节点,最后匹配最短的。
而Handler结构有一个HTTP方法,我们看Engine是如何实现的
ServeHTTP
// ServeHTTP conforms to the http.Handler interface.
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)
}
engine.pool.Get().(*Context)
典型对象池,减少gc,减少内存申请Get就是从池中获取一个对象,并强制转换为一个context指针。
c.reset()
在取出池子之后做初始化逻辑和java线程池有点区别java线程池要在放入池子的时侯gc这个是放直接放,取的时候初始化。
engine.handleHTTPRequest(c)
处理http请求
engine.pool.Put(c)
放回池子中
handleHTTPRequest
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
for i, tl := 0, len(t); i < tl; i++ {
相比于for i, tl := 0, i < len(t); i++ {
方法萧少了每次求len的方法调用值得学习。
if t[i].method != httpMethod { continue }
可以看到这是在匹配httpMEthod方法,我们常知道的http方法,如get,post,put,delete方法。在这engine会去取自己的属性tree,通过node接单中的方法确认接下来是那种方法调用。
method 采用切片方式去设计node节点,因为httpMethod一共九种,直接遍历即可。
New()
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"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0", "::/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
make(methodTrees, 0, 9)
可以看到new方法中直接创建了一个容量为9的固定切片
注册路由
注册路由方法
r.GET("/",func(context *gin.Context){
context.JSON(200,gin.H{
"massage":"hello",
})
})
可以看到gin框架中注册路由直接采用的api,我们来看一下get方法
GET
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
因为engine继承了RouterGroup,而type HandlersChain []HandlerFunc
采用切片去保存的,而HandlersChain 就是func函数的集合type HandlerFunc func(*Context)
。而方法入参可以看到是可以传递多个func的。而核心方法是RouterGroup中的hander方法。
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()
}
group.calculateAbsolutePath(relativePath)
是将传入进来的路由地址转为HandlersChain类型
group.combineHandlers(handlers)
方法如下,就是将原来方法中的路由和新注册的路由存放到新的一个slice中。首先先获取最大长度,之后申请一个最大长度的slice,然后将group中的先放入slice,然后再将handlers放入(位置在group之后)。
combineHandlers
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
group.engine.addRoute(httpMethod, absolutePath, handlers)
是添加路由方法,核心方法在root.addRoute(path, handlers)
这个方法就是向PAT树去注册新节点。
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers)
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)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
路由寻找
当我们启动服务的时候,请求一个get请求在gin框架中是怎么为我们寻找到我们请求的接口的?
engine.handleHTTPRequest©
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}
if engine.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
if engine.HandleMethodNotAllowed {
for _, tree := range engine.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)
}
我们来看handleHTTPRequest方法中,value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
,而这个方法中,返回nodeValue保存注册的处理函数和匹配到的路径参数数据。
err = http.ListenAndServe(address, engine.Handler())
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
默认中间件
gin.Default()默认使用了Logger和Recovery中间件(Gin中的中间件必须是一个gin.HandlerFunc类型。),其中:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
Default
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
可以看到Default方法中Use注册的两个中间件。使用了RouterGroup中的Use方法这个方法就是往HandlerFunc中去append新注册的中间件,而group.Handlers
本身是HandlerFunc的slice。
而handleHTTPRequest中用到了engine的Next()方法,此方法的作用就是继续执行
接下来对next方法进行测试
main
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func func1(c *gin.Context) {
fmt.Println("into func1")
c.Next()
fmt.Println("out func1")
}
func func2(c *gin.Context) {
fmt.Println("into func2")
c.Next()
fmt.Println("out func2")
}
func func3(c *gin.Context) {
fmt.Println("into func3")
c.Next()
fmt.Println("out func3")
}
func main() {
r := gin.Default()
r.GET("/hello",func1,func2,func3)
r.Run()
}
而NExt方法的源码
Next()
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
总结
在观看文档的同时自己点击源码并学习。
我的目标
希望在年底学习一下内容:
java学习内容:
1.tomcat源码
2.dubbo源码
3.zookeeper源码
4.netty源码
go学习内容:
1.gin框架学习
2.简单go项目
3.go基础知识进阶(gmp,gc,channel,map,slice源码等)
中间件学习内容:
1.kafka使用及源码
框架学习内容:
1.从零开始学架构
算法学习内容:
1.复习leetcode中top 100