Самый подробный анализ исходного кода джина во всей сети

Gin

вводить

  • ginкаркас, основанный наhttprouterРеализовать самый важный модуль маршрутизации и использовать структуру данных, подобную словарю, для хранения сопоставления между методами маршрутизации и обработки. Это также является причиной высокой производительности платформы. Заинтересованные студенты могут обращаться к ним самостоятельно.
  • В этой статье представленыонлайн ментальная картаСо статьями, чтобы увидеть больше с меньшими усилиями
  • EngineОбъект-контейнер, основа всего фреймворка
  • Engine.treesОтвечает за хранение сопоставления методов маршрутизации и обработки с использованием структуры, похожей на дерево словаря.
  • Engine.RouterGroup, где обработчики хранят все промежуточное ПО
  • ContextОбъект контекста, отвечающий за обработку请求和回应,один из нихhandlersОн хранит промежуточное ПО и методы обработки при обработке запросов.

Инициализировать контейнер

позвонивgin.New()метод создания экземпляраEngine. Хотя есть много параметров, нам нужно только обратить внимание наRouterGroup ,treesа такжеengine.pool.NewПросто

  • engine.pool.Newответственный за созданиеContextобъект, используяsync.PoolУменьшить потребление ресурсов, вызванное частым созданием контекста,
func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		//实例化RouterGroup,其中Handlers为中间件数组
		RouterGroup: RouterGroup{ 
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		RemoveExtraSlash:       false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		//trees 是最重要的点!!!!负责存储路由和handle方法的映射,采用类似字典树的结构
		trees:                  make(methodTrees, 0, 9), 
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJsonPrefix:       "while(1);",
	}
	engine.RouterGroup.engine = engine
	//这里采用 sync/pool 实现context池,减少频繁context实例化带来的资源消耗
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

зарегистрировать промежуточное ПО

Высокая производительность джина в основном зависит отtrees, содержимое каждого узла можно представить какkey->valueсловарное дерево,keyэто маршрут иvalueэто[]HandlerFunc, в котором хранитсяМетоды промежуточного программного обеспечения и контроллера обработки выполняются последовательно, вот очень важно, чтобы проверить!

Зарегистрировать глобальное промежуточное ПО

gin.use()перечислитьRouterGroup.Use()прошлоеRouterGroup.Handlersзаписать запись

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...) 
	engine.rebuild404Handlers() //注册404处理方法
	engine.rebuild405Handlers() //注册405处理方法
	return engine
}

//  其中`Handlers`字段就是一个数组,用来存储中间件
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

Зарегистрировать ПО промежуточного слоя группы маршрутизации

  • пройти черезGroup()метод возвращает вновь сгенерированныйRouterGroupУказатель, используемый для разделения каждой группы маршрутизации для загрузки различного промежуточного программного обеспечения.
  • Обратите внимание здесьHandlers: group.combineHandlers(handlers), эта строка кода скопирует копию глобального ПО промежуточного слоя во вновь сгенерированныйRouterGroup.Handlers, при следующей регистрации маршрута его можно вместе записать в узел дерева
group := g.Group("/test_group")
group.Use(middleware.Test())
{
	//这里会最终路由和中间件以及handle方法一起写入树节点中
	group.GET("/test",handler.TestTool)
}

//返回一个RouterGroup指针
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
	return &RouterGroup{
		//复制一份全局中间件
		Handlers: group.combineHandlers(handlers),
		basePath: group.calculateAbsolutePath(relativePath),
		engine:   group.engine,
	}
}

Регистрация маршрутов и промежуточного ПО

Независимо от метода запроса, в конечном итоге он будет вызванRouterGroup.handle, этот метод имеет две основные функции

  • Обработать формат маршрута и записать маршрут в маршрут, начинающийся с символа «/».

  • Скопируйте RouterGroup.Handlers, добавьте метод handle, соответствующий этому маршруту, сформируйте список и поместите его в узел дерева

  • последний звонокtrees.addRouteдобавить узел

g.GET("/test_tool", middleware.Test(),handler.TestTool)

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
 	//根目录和路由结合起来,将路由拼成 '/' 字符开头的路由
	absolutePath := group.calculateAbsolutePath(relativePath) 
	//复制一份RouterGroup.Handlers,加上相应这次路由的handle方法,组成一个list放入树节点中
	handlers = group.combineHandlers(handlers) 
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

//调用 `trees`增加节点
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)
}

запускать

позвонивnet/httpзапустить службу, так какengineДостигнутоServeHTTPметод, нужно только напрямую пройтиengineОбъект может быть инициализирован и запущен

g.Run()
func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()
	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
//来自 net/http 定义的接口,只要实现了这个接口就可以作为处理请求的函数
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
//实现了ServeHTTP方法
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)
}

обработать запрос

  • Просто обратите внимание здесьhandleHTTPRequest(c *Context) метод в порядке
  • Найдите соответствующий узел дерева с помощью метода запроса и маршрута и получите сохраненный[]HandlerFuncсписок, позвонивc.Next()обработать запрос
  • Постоянно рекурсивно перемещая нижний индекс, окончательно завершите обработку и верните результат
func (engine *Engine) handleHTTPRequest(c *Context) {
	...
	// 
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		...
		// Find route in tree
		value := root.getValue(rPath, c.Params, unescape)
		if value.handlers != nil {
			c.handlers = value.handlers
			c.Params = value.params
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
		...
	}
	...
}

//这里挺巧妙的,通过不停的移动下标递归,最后完成处理返回结果
func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c)
		c.index++
	}
}

впечатление

  • Исходный код фреймворка gin относительно прост и понятен, что как раз и является его преимуществом. Сам язык golang относительно зрелый. Фреймворк - это просто каркас для вашего проекта. Вы можете настроить свой собственный в соответствии с вашими потребностями.ginФреймворк, включая ведение журнала, кэширование, очереди и т. д.
  • Ядром является дерево хранения маршрутизации, хорошо изучающее алгоритм, а структура данных является ключом

Справочная документация

джин китайская документацияhttps://gin-gonic.com/zh-cn/docs/introduction/

адрес проекта Джинhttps://github.com/gin-gonic/gin

httprouter https://github.com/julienschmidt/httprouter