首页 > 其他分享 >gin-net-http

gin-net-http

时间:2024-01-20 17:55:07浏览次数:20  
标签:engine http handlers len fullPath path gin net 节点

package main
import (
	"net/http"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(200, "Hello!!!!")
	})
}

  gin框架使用的是定制版本的httprouter,其路由的原理是大量使用公共前缀的树结构,它基本上是一个紧凑的Trie tree(或者只是Radix Tree)。具有公共前缀的节点也共享一个公共父节点。

获取请求方法树

在gin的路由中,每一个HTTP Method(GET、POST、PUT、DELETE…)都对应了一棵 radix tree,我们注册路由的时候会调用下面的addRoute函数:

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

在注册路由的时候都是先根据请求方法获取对应的树,也就是gin框架会为每一个请求方法创建一棵对应的树,gin框架中保存请求方法对应树关系使用的是slice;engine.trees的类型是methodTrees,其定义如下:

type methodTree struct {
	method string
	root   *node
}
type methodTrees []methodTree

获取请求方法对应树的get方法定义如下:---------->HTTP请求方法的数量是固定的,而且常用的就那几种,所以即使使用切片存储查询起来效率也足够了

func (trees methodTrees) get(method string) *node {
	for _, tree := range trees {
		if tree.method == method {
			return tree.root
		}
	}
	return nil
}

所以tress初始化的时候就直接make申请了内存

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,
        // 初始化容量为9的切片(HTTP1.1请求方法共9种)
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJSONPrefix:       "while(1);",
		trustedProxies:         []string{"0.0.0.0/0", "::/0"},
		trustedCIDRs:           defaultTrustedCIDRs,
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() any {
		return engine.allocateContext(engine.maxParams)
	}
	return engine
}

 

获取请求方法树后---->执行注册路由

先看下路由树节点

路由树是由一个个节点构成的,gin框架路由树的节点由node结构体表示,它有以下字段:

r.GET("/search/", func2)
r.GET("/support/", func3)

type node struct {
	path      string// 节点路径,比如上面的s,earch,和upport
    // 和children字段对应, 保存的是分裂的分支的第一个字符
	// 例如search和support, 那么s节点的indices对应的"eu"
	// 代表有两个分支, 分支的首字母分别是e和u
	indices   string
	wildChild bool
    // 节点类型,包括static, root, param, catchAll
	// static: 静态节点(默认),比如上面的s,earch等节点
	// root: 树的根节点
	// catchAll: 有*匹配的节点
	// param: 参数节点
	nType     nodeType
    // 优先级,子节点、子子节点等注册的handler数量
	priority  uint32
	children  []*node // child nodes, at most 1 :param style node at the end of the array
	handlers  HandlersChain// 处理函数chain(切片)
	fullPath  string// 完整路径
}

注册路由的逻辑主要有addRoute函数和insertChild方法。

root := engine.trees.get(method)
root.addRoute(path, handlers)
// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handlers HandlersChain) {
	fullPath := path
	n.priority++

	// Empty tree 空树就直接插入当前节点
	if len(n.path) == 0 && len(n.children) == 0 {
		n.insertChild(path, fullPath, handlers)
		n.nType = root
		return
	}

	parentFullPathIndex := 0

walk:
	for {
		// Find the longest common prefix.
		// This also implies that the common prefix contains no ':' or '*'
		// since the existing key can't contain those chars.
        // 找到最长的通用前缀
		// 这也意味着公共前缀不包含“:”"或“*” /
		// 因为现有键不能包含这些字符。
		i := longestCommonPrefix(path, n.path)

		// Split edge
        // 分裂边缘(此处分裂的是当前树节点)
		// 例如一开始path是search,新加入support,s是他们通用的最长前缀部分
		// 那么会将s拿出来作为parent节点,增加earch和upport作为child节点
		if i < len(n.path) {
			child := node{
				path:      n.path[i:], // 公共前缀后的部分作为子节点
				wildChild: n.wildChild,
				nType:     static,
				indices:   n.indices,
				children:  n.children,
				handlers:  n.handlers,
				priority:  n.priority - 1,//子节点优先级-1
				fullPath:  n.fullPath,
			}

			n.children = []*node{&child}
			// []byte for proper unicode char conversion, see #65
			n.indices = bytesconv.BytesToString([]byte{n.path[i]})
			n.path = path[:i]
			n.handlers = nil
			n.wildChild = false
			n.fullPath = fullPath[:parentFullPathIndex+i]
		}

		// Make new node a child of this node
        // 将新来的节点插入新的parent节点作为子节点
		if i < len(path) {
			path = path[i:]
			c := path[0]

			// '/' after param  处理参数后加斜线情况
			if n.nType == param && c == '/' && len(n.children) == 1 {
				parentFullPathIndex += len(n.path)
				n = n.children[0]
				n.priority++
				continue walk
			}

			// Check if a child with the next path byte exists
            // 检查路path下一个字节的子节点是否存在
			// 比如s的子节点现在是earch和upport,indices为eu
			// 如果新加一个路由为super,那么就是和upport有匹配的部分u,将继续分列现在的upport节点
			for i, max := 0, len(n.indices); i < max; i++ {
				if c == n.indices[i] {
					parentFullPathIndex += len(n.path)
					i = n.incrementChildPrio(i)
					n = n.children[i]
					continue walk
				}
			}

			// Otherwise insert it 否则就插入
			if c != ':' && c != '*' && n.nType != catchAll {
				// []byte for proper unicode char conversion, see #65
                // 注意这里是直接拼接第一个字符到n.indices
				n.indices += bytesconv.BytesToString([]byte{c})
				child := &node{
					fullPath: fullPath,
				}
                // 追加子节点
				n.addChild(child)
				n.incrementChildPrio(len(n.indices) - 1)
				n = child
			} else if n.wildChild {// 如果是参数节点
				// inserting a wildcard node, need to check if it conflicts with the existing wildcard
				n = n.children[len(n.children)-1]
				n.priority++

				// Check if the wildcard matches 检查通配符是否匹配
				if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
					// Adding a child to a catchAll is not possible
					n.nType != catchAll &&
					// Check for longer wildcard, e.g. :name and :names
					(len(n.path) >= len(path) || path[len(n.path)] == '/') {
					continue walk
				}

				// Wildcard conflict
				pathSeg := path
				if n.nType != catchAll {
					pathSeg = strings.SplitN(pathSeg, "/", 2)[0]
				}
				prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
				panic("'" + pathSeg +
					"' in new path '" + fullPath +
					"' conflicts with existing wildcard '" + n.path +
					"' in existing prefix '" + prefix +
					"'")
			}

			n.insertChild(path, fullPath, handlers)
			return
		}

		// Otherwise add handle to current node 已经注册过的节点
		if n.handlers != nil {
			panic("handlers are already registered for path '" + fullPath + "'")
		}
		n.handlers = handlers
		n.fullPath = fullPath
		return
	}
}
  1. 第一次注册路由,例如注册search
  2. 继续注册一条没有公共前缀的路由,例如blog
  3. 注册一条与先前注册的路由有公共前缀的路由,例如support

标签:engine,http,handlers,len,fullPath,path,gin,net,节点
From: https://www.cnblogs.com/codestack/p/17976863

相关文章

  • [译] kubernetes:kube-scheduler 调度器代码结构概述
    本文翻译自https://github.com/kubernetes/community/blob/master/contributors/devel/sig-scheduling/scheduling_code_hierarchy_overview.md译者:胡云Troy调度器代码层次结构概述介绍调度器监视新创建的还没有分配节点的Pod。当发现这样的Pod后,调度器将Pod调度到最......
  • 前后端都用得上的 Nginx 日常使用经验-补充篇
    之前分享了前后端都用得上的Nginx日常使用经验,在配置elk的时候增加了nginxbasicauth和IP百名的配置,作为补充分享。配置nginx域名转发常规的转发配置,不需要https部分去掉即可,一般只需要修改域名和转发地址server{listen80;listen443ssl;s......
  • node-sass 安装出错 Cannot download "https://github.com/sass/node-sass...
    Downloadingbinaryfromhttps://github.com/sass/node-sass/releases/download/v4.14.1/win32-x64-83_binding.nodeCannotdownload"https://github.com/sass/node-sass/releases/download/v4.14.1/win32-x64-83_binding.node": github网站大多时候都访问不到,下载 win32-x......
  • 人人都会Kubernetes(二):使用KRM实现快速部署服务,并且通过域名发布
    1.上节回顾上一小节《人人都会Kubernetes(一):告别手写K8syaml,运维效率提升500%》介绍了KRM的一些常用功能,并且使用KRM的DEMO环境,无需安装就可以很方便的生成一些资源的YAML数据并使用。本节将实现在自己的集群中安装KRM,并且使用KRM去管理分布在各个地方的K8s集群,同时将实现快速......
  • dotnet 多数据库 sqlite efcore model和entity区别 一对多 多对一 多对多
    efcore-multi-db/MultiDb.slnMicrosoftVisualStudioSolutionFile,FormatVersion12.00#VisualStudio15VisualStudioVersion=15.0.27130.2024MinimumVisualStudioVersion=10.0.40219.1Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}")="......
  • vscode netcore运行配置
    launch.json{  "version":"0.2.0",  "configurations":[   {    "name":".NETCoreLaunch(web)",    "type":"coreclr",    "request":"launch&qu......
  • dotnet efcore 多数据库 使用
    efcore的使用依赖包efcore-multi-db/MultiDb.Two/MultiDb.Two.csproj<ProjectSdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net7.0</TargetFramework></PropertyGroup><ItemGroup><Packa......
  • TDengine清理数据文件
    清理会丢失用户以及库信息,需提前准备好备份停止taosd、taosAdaptersystemctlstoptaosadaptersystemctlstatustaosadaptersystemctlstoptaosdsystemctlstatustaosd删除数据文件rm-rf/var/lib/taos/*启动taosd、taosAdaptersystemctlstarttaosdsy......
  • https速度慢解决办法(开启OCSP Stapling)
    网站通过certbot配置好之后,只要在末尾加两行即可。ssl_staplingon;ssl_stapling_verifyon;详细位置。server{listen443ssl;server_namexx.xx.com;indexindex.htmlindex.htmindex.jsp;ssl_certificateserver.pem;#证书的.cer文件路......
  • 在 .net 8 Blazor Identity 中添加Claim
    .net8BlazorIdentity使用IndividualAccount模版时,默认的UserInfo只有Id,Email和UserName。如果想让客户端共享更多用户信息,可以使用自定义的ClaimsPrincipalFactory。代码如下:publicclassFlowYogaClaimsPrincipalFactory(UserManager<YourCustomUserClass>userMana......