gin框架原理详解

1. gin框架概述

地址:https://github.com/gin-gonic/gin 

gin作为web框架,在社区中拥有较高的活跃度,通过使用前缀树结构存储,减少内存消耗,同时加快了路由查询速度,并且在基本功能的基础上提供了各种middleware,可以在访问对应的url时回调对应的handler

也给大家推荐一个go技术选型参考

组件 主选 备选

选型主要考虑

对比

Web框架 Gin[GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.] Echo、Fiber 社区活跃度/成熟度/易用性/性能

Gin/Echo: 社区活跃度较高、性能较高

Fiber: 基于FastHttp,性能很高

Redis go-redis[GitHub - go-redis/redis: Type-safe Redis client for Golang] redigo 社区活跃度/性能/功能

go-redis: 社区活跃度较高、性能较高、集群支持好

redigo: 社区活跃度较高

SQL gendry[GitHub - didi/gendry: a golang library for sql builder] Gorm、Xorm、sqlx 性能/可维护性/功能

gendry: sql builder简单封装,易维护

Gorm/Xorm: 功能强大,社区活跃度高

sqlx: sql builder封装,社区活跃度高

日志 zap[GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.] logrus、seelog 性能/功能

zap: 性能较高,结构化日志首选

logrus: 性能适中,格式定义灵活

seelog: 性能一般,简单易用

配置 go-toml[GitHub - pelletier/go-toml: Go library for the TOML file format] viper 易用性/功能

go-toml: toml格式处理,简单易用

viper: 支持多种格式配置、功能强大、依赖较复杂

2. 框架基本功能详解

2.1 一个简单的demo

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"os"
)

func main() {
	//r := gin.Default()
	r := gin.New()

	//use must before register route
    //注册gin自带中间件
	r.Use(gin.Logger())
	r.Use(gin.Recovery())

    //注册一个路由和对应的handler
	r.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "hello",
		})
	})

    //在/test2的路由组中注册中间件,不影响/test
	test := r.Group("/test2")
	test.Use(MyLogger)
	test.Use(MyLogger2)
	test.GET("/hello", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "hello gin",
		})
	})

	r.Run("127.0.0.1:8899")
}

func MyLogger(c *gin.Context) {
	fmt.Fprintf(os.Stdout, "%s|%s  mylogger attach", c.Request.Method, c.Request.URL)
}

func MyLogger2(c *gin.Context) {
	fmt.Fprintf(os.Stdout, "%s|%s  mylogger222222 attach", c.Request.Method, c.Request.URL)
}

这个demo包含了gin框架的一些常用用法,包括自己实现一个简单的middleware,启动后,通过访问localhost:8899/test和localhost:8899/test2/hello可以看到对应的返回,并且可以在控制台看到注册的middleware被调用

可以看到gin不仅提供了middleware功能,还提供了根据url path注册不同的middleware功能,middleware可以是全局的也可以只针对某个path

2.2 gin.Use

// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	engine.rebuild404Handlers()
	engine.rebuild405Handlers()
	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()
}

// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
// Create an instance of Engine, by using New() or Default()
type Engine struct {
	RouterGroup
    ......
}

// 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
	root     bool
}

engine就是gin.New()或者gin.Default()创建出来的全局实例,其中比较重要的属性之一就是RouterGroup,路由组中包括了注册路由需要的基本信息

Use注册中间件流程:

1. 先看透本质,一个middleware其实就是一个handlerFunc,和注册到path上的api方法签名相同

2. 通过路由组进行注册,将中间件放到RouterGroup路由组的handlers切片中

3. 返回对应的RouterGroup,所以Use函数还可以采用链式调用:

	test.Use(MyLogger).Use(MyLogger2)
	test.Use(MyLogger, MyLogger2)

2.3 gin.Get

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
	return group.handle(http.MethodGet, relativePath, handlers)
}


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 (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
}

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)

    ......
}

从2.2中可以知道engine使用了组合的特性,将RouterGroup组合使用,所以RouterGroup实现的方法,engine也可以调用

Get、DELETE、POST等方法都调用了handle

handle注册api主要流程:

1. 通过combineHandlers方法,将之前在RouterGroup中的中间件和对应的api方法进行合并,并且api方法放到后面,关系到之后的调用顺序,可以从这里看出,先注册的中间件先调用,然后才到api的handler

2. 从前缀树中找到get对应的树,并把path和合并后的handlers放到树中(前缀树的代码会在后续的文章中为大家详解)

3. 这里也可以理解,handler最开始都是放到RouterGroup的切片中,并不会生效,只有通过handle方法,将合并后的handlers放到前缀树中才算生效,所以在Get、Post等方法后使用use是不符合预期的

2.4 Engine.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) {
    ......

	err = http.ListenAndServe(address, engine.Handler())
	return
}

func (engine *Engine) Handler() http.Handler {
	if !engine.UseH2C {
		return engine
	}

	h2s := &http2.Server{}
	return h2c.NewHandler(engine, h2s)
}

// 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)
}

func (engine *Engine) handleHTTPRequest(c *Context) {
  		// 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
		}
}

// 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++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

 Run主要流程:

1. 调用golang原生http库,指定一个http.handler,开启监听

2. engine.handler是gin为了支持H2C后来支持的,原来的代码直接传入的是engine,H2C需要了解的可以自行百度,但是一般是不推荐用的,还是推荐用golang默认支持的H2

3. engine作为一个handler实现了ServeHTTP方法,当访问对应的api时,会走到该方法中

4. serveHTTP主要调用的handleHTTPRequest,其他步骤是通过sync.Pool来操作context,用于减少context的内存分配和初始化次数来增加效率减少GC

5. handleHTTPRequest通过访问的httpMethod和uri,getValue找到前缀树中对应的节点,返回的nodeValue对象就是2.3中添加到树中的节点,其中保存了中间件和api对应的handler

6. 通过Next()函数依次调用handler

3. 最后

还有很多功能未在本文中涉及,但是基本的思想和功能已经涵盖,如需继续拓展可以到第一章中的gin框架链接中学习

 

你可能感兴趣的:(golang,web)