gin源码实战 day2

gin源码实战day2

今天从中间件开始:
中间件比较重要的代码昨天已经说了,就是next方法和它相关的终止调度器的方法。

中间件

注册中间件

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {//Engine继承了RouterGroup,给当前Engine注册中间件。engine.RouterGroup.Use(middleware...)//这两行是调用方法,目的是再添加新的全局中间件后,重写构建处理404和405的内部处理链,因为新加入的中间件可能会影响到这些特殊情况的处理,所以需要重写调整。engine.rebuild404Handlers()engine.rebuild405Handlers()return engine//方法返回 Engine 实例本身。这样做允许链式调用,例如在创建 Engine 实例后连续添加多个中间件或定义路由。
}

Engine 结构体的 Use 方法的实现。Use 方法用于为整个应用添加全局中间件。中间件是一种特殊的函数,它会在处理 HTTP 请求的过程中被调用,可以用于日志记录、用户鉴权、数据处理等。
解读:
middleware …HandlerFunc: 可变参数,允许传入一个或多个中间件。HandlerFunc 是定义在 Gin 中的一个类型,代表请求处理函数或中间件的签名
engine.RouterGroup.Use(middleware…): 这一行将传入的中间件添加到 Engine 内嵌的 RouterGroup 中。RouterGroup 是一个分组路由的概念,Engine 作为最顶层的分组,其实也有分组路由的能力。通过调用 RouterGroup.Use 方法,实现了中间件的注册。
往里面再追一下:

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {group.Handlers = append(group.Handlers, middleware...)return group.returnObj()
}

这个显然就是把中间件(中间件本质是个特殊函数)添加进函数调用链种,中间件也会按添加时间先后顺序来执行。最后调用的这个方法是返回RouterGroup实例本身。
如果看得懂就看这个解释,看不懂那就先把下面该了解的先了解

先解读这个结构体:
RouterGroup 结构体是 Gin Web 框架中的一个核心组件,用于分组路由和中间件。这个结构体允许将具有相同前缀的路由组织在一起,使得路由和中间件的管理变得更加方便和高效。下面是对 RouterGroup 结构体字段的详细解读:
Handlers: 类型为 HandlersChain,这是一个 HandlerFunc 类型的切片这东西就是函数调用链。在 Gin 中,HandlerFunc 是处理 HTTP 请求的函数签名。Handlers 字段存储了应用于当前路由组的所有中间件。当处理到达该路由组的任一路由时,这些中间件将按照它们被添加的顺序被执行。

basePath: 字符串类型,存储了当前路由组的基础路径(URL 前缀)。所有在该路由组中注册的路由都会自动加上这个前缀。例如,如果 basePath 是 /api,那么路由组中的一个路由 /users 实际上会被处理为 /api/users。

engine: 指向 Engine 类型的指针。Engine 是 Gin 框架的核心结构,代表了整个 Web 应用。它负责管理路由、中间件以及启动 HTTP 服务器等。RouterGroup 通过 engine 字段与整个应用的上下文相关联,使得路由组可以访问应用级的设置和方法。

root: 布尔类型,标识当前的 RouterGroup 是否是根路由组。在 Gin 中,最顶级的路由组(通常由 Engine 实例本身表示)被认为是根路由组。根路由组有一些特殊的行为,比如它可以直接访问和修改全局的中间件列表。

然后针对这个结构体的方法可以说太多了:说几个常用的。
Use(middleware …HandlerFunc) IRoutes
添加中间件到当前路由组。这些中间件会被应用到路由组中的所有路由上。

*Group(relativePath string, handlers …HandlerFunc) RouterGroup
创建一个新的路由组,其路径是基于当前路由组的路径。可以为新的路由组添加特定的中间件。

GET(relativePath string, handlers …HandlerFunc) 注册一个 GET 请求的路由,relativePath 是相对于路由组基础路径的相对路径。handlers 是处理该路由的函数列表。

然后我继续看源码可以发现:
RouterGroup是实现了两个相关的接口:IRouter和IRoutes。由于RouterGroup是路由组,而Engine也叫路由,所以Engine也实现了这两个接口,所以可以说这俩的方法差不多。

我心里当时有个问题:实现这两个接口有什么意义
还有这两个接口区别大吗?从源码可以看出,接口差别不是很大,而且Router是继承了Routes。

实现 IRouter 接口的意义
通过实现 IRouter 接口,RouterGroup 可以:

1.支持分组路由:允许创建具有共同前缀的路由组,每个组可以有自己的中间件,这些中间件只对该组内的路由有效。这是组织大型应用路由的一种高效方式。

2.链式调用:返回值类型通常设计为接口本身(如 IRoutes 或 IRouter),这支持了链式调用的编程风格,使得路由和中间件的设置更加流畅。

3.保持接口一致性:无论是在顶级路由(由 Engine 实例管理)还是在路由组中添加路由和中间件,开发者都可以使用相同的方法。这降低了学习成本,并提高了代码的可读性和一致性。

实现 IRoutes 接口的意义:
1. 统一的路由配置接口
IRoutes 接口为路由配置提供了一个统一的接口,使得开发者无论是在应用的顶层还是在路由分组中添加路由时,都能使用相同的方法。这样的设计不仅提高了代码的一致性,也使得路由的配置更加直观和易于理解。通过实现 IRoutes 接口,Gin 确保了不同层级的路由配置具有相同的操作方式和行为表现。

2. 链式调用
IRoutes 接口支持链式调用,这是一种流畅的编程风格,允许开发者通过连续调用方法来配置路由。这种方式简化了路由和中间件的添加过程,使得代码更加简洁和优雅。例如,你可以连续添加多个中间件或路由而不需要重复指定路由组或路由器实例:

router.GET("/path", handler).POST("/path", postHandler).PUT("/path", putHandler)

3. 灵活的路由和中间件管理
通过实现 IRoutes 接口,Gin 能够提供灵活的路由和中间件管理能力。开发者可以轻松地为特定的路由或路由组添加中间件,控制中间件的作用范围,以及组织相关路由的逻辑结构。这对于构建大型和复杂的 Web 应用尤为重要,因为它允许将应用分解成更小、更易管理的部分。

4. 支持 RESTful API 设计
IRoutes 接口提供的方法支持 RESTful API 的设计原则,允许开发者根据不同的 HTTP 方法(如 GET、POST、PUT、DELETE)来定义路由处理函数。这有助于创建清晰、易于维护的 API 接口。

5. 增强的可读性和可维护性
实现 IRoutes 接口的路由配置方式增强了代码的可读性和可维护性。通过使用明确的方法名称(如 GET、POST)和支持链式调用,路由配置变得更加直观。此外,将路由和中间件的配置集中管理也使得维护更为方便,特别是在需要修改或扩展现有路由时。

IRoutes: 方法返回 IRoutes 接口类型,这允许链式调用其他设置路由的方法。

总结:
Use 方法是 Gin 框架提供的一种方便的方式,用于为整个 Web 应用添加全局中间件。不仅可以对单个路由,还可以对路由组使用。极其方便。

Engine对象初始化

通过gin.New或gin.Default()可以初始化一个Engine对象,这通常是gin使用的第一行代码。
gin.New()和gin.Default()的作用和区别:
直接看源码:
关于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,}engine.RouterGroup.engine = engineengine.pool.New = func() any {return engine.allocateContext(engine.maxParams)}return engine
}

解读:
从源码可以看出分为如下三部分:
1.Engine的初始化。
2.Engine 的 pool 字段初始化。

engine.pool.New = func() any {return engine.allocateContext(engine.maxParams)
}

初始化了Engine的sync.Pool,用于复用 Context 对象。sync.Pool 的 New 字段是一个函数,当从池中获取对象时,如果池为空,则调用此函数生成新的对象。这里,New 函数通过调用 allocateContext 方法为每个新请求分配一个新的 Context 实例,以提高性能并减少内存分配。
3.函数返回,返回初始化后的Engine实例。

总结
gin.New 函数创建了一个新的 Gin Engine 实例,该实例通过各种默认设置预配置了路由处理和中间件管理的基本行为。这个实例还提供了一个 sync.Pool 用于 Context 对象的高效复用,以及一系列默认参数配置,旨在为开发者提供一个功能丰富且灵活的 Web 应用框架基础。

关于Default

func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

可以看到是基于New,并且还通过use方法给Engine安装了两个中间件。

总的来说:
New就是创建了一个干净的Engine实例,而Default还预装了一个日志和恢复中间件的实例。可能对于大多数人来说Default更加的方便点。但是New为高级场景提供了更方便的使用。

关于中间件的源码这里我也探索一下:
这个是logger中间件的

func LoggerWithConfig(conf LoggerConfig) HandlerFunc {//初始化日志格式化函数和输出目标//formatter首先检查配置中是否提供了自定义的日志格式化函数。如果没有提供(即 nil),则使用默认的日志格式化函数 defaultLogFormatter。这个格式化函数负责将日志信息转换成特定的字符串格式。formatter := conf.Formatterif formatter == nil {formatter = defaultLogFormatter}//out: 接着检查配置中是否指定了日志的输出目标。如果没有指定(即 nil),则使用默认的输出目标 DefaultWriter,通常是控制台(标准输出)。out := conf.Outputif out == nil {out = DefaultWriter}//这行代码从 conf(一个 LoggerConfig 类型的实例)中获取 SkipPaths 属性的值,并将其赋给本地变量 notlogged。SkipPaths 是一个字符串切片,包含了不应该记录日志的请求路径列表。作用:过滤日志记录:在日志中间件处理过程中,notlogged 用于指定哪些请求路径不需要被记录日志。这对于减少日志噪音、提高日志质量或者出于性能考虑跳过某些高频但不重要的路径的日志记录非常有用。notlogged := conf.SkipPaths//判断输出目标是否为终端//这段代码用于检查日志的输出目标是否为终端,以便决定是否使用终端特有的格式化特性(如颜色)。首先假设输出目标是终端(isTerm := true),然后尝试将输出目标转换为 *os.File 类型,并使用 isatty 库检查文件描述符是否真的指向一个终端设备。如果不是,或者环境变量 TERM 被设置为 "dumb"(表示一个非交互式终端),isTerm 被设置为 false。isTerm := trueif w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {isTerm = false}//初始化跳过日志记录的路径集合//如果配置了不记录日志的路径列表(conf.SkipPaths),则创建一个映射(skip),将这些路径添加到映射中,以便快速检查一个路径是否应该跳过日志记录。var skip map[string]struct{}if length := len(notlogged); length > 0 {skip = make(map[string]struct{}, length)for _, path := range notlogged {skip[path] = struct{}{}}}//日志记录的 HandlerFuncreturn func(c *Context) {// Start timerstart := time.Now()path := c.Request.URL.Pathraw := c.Request.URL.RawQuery// Process requestc.Next()// Log only when path is not being skippedif _, ok := skip[path]; !ok {param := LogFormatterParams{Request: c.Request,isTerm:  isTerm,Keys:    c.Keys,}// Stop timerparam.TimeStamp = time.Now()param.Latency = param.TimeStamp.Sub(start)param.ClientIP = c.ClientIP()param.Method = c.Request.Methodparam.StatusCode = c.Writer.Status()param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()param.BodySize = c.Writer.Size()if raw != "" {path = path + "?" + raw}param.Path = pathfmt.Fprint(out, formatter(param))}}
}

然后这个是Recovery中间件的实现

func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {var logger *log.Loggerif out != nil {logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)}return func(c *Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace.var brokenPipe boolif ne, ok := err.(*net.OpError); ok {var se *os.SyscallErrorif errors.As(ne, &se) {seStr := strings.ToLower(se.Error())if strings.Contains(seStr, "broken pipe") ||strings.Contains(seStr, "connection reset by peer") {brokenPipe = true}}}if logger != nil {stack := stack(3)httpRequest, _ := httputil.DumpRequest(c.Request, false)headers := strings.Split(string(httpRequest), "\r\n")for idx, header := range headers {current := strings.Split(header, ":")if current[0] == "Authorization" {headers[idx] = current[0] + ": *"}}headersToStr := strings.Join(headers, "\r\n")if brokenPipe {logger.Printf("%s\n%s%s", err, headersToStr, reset)} else if IsDebugging() {logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",timeFormat(time.Now()), headersToStr, err, stack, reset)} else {logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",timeFormat(time.Now()), err, stack, reset)}}if brokenPipe {// If the connection is dead, we can't write a status to it.c.Error(err.(error)) //nolint: errcheckc.Abort()} else {handle(c, err)}}}()c.Next()}
}

路由器组

通过router.Group()创建的路由器组。追进去看看源码:

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {return &RouterGroup{Handlers: group.combineHandlers(handlers),basePath: group.calculateAbsolutePath(relativePath),engine:   group.engine,}
}

解读:从这个代码来看你会发现它是RouterGroup的方法,那说明什么?
说明这个创建路由组的方法是基于当前路由组创建一个新的子路由组。

func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup

*group RouterGroup: 方法接收器,表示当前的路由组实例。新的路由组将基于这个实例创建。

relativePath string: 新路由组的相对路径。这个路径会与父路由组的路径结合,形成新路由组的完整路径。

handlers …HandlerFunc: 可变数量的 HandlerFunc,这些是中间件处理函数,将应用于新路由组中的所有路由。

return &RouterGroup{Handlers: group.combineHandlers(handlers),basePath: group.calculateAbsolutePath(relativePath),engine:   group.engine,
}

Handlers: 使用 group.combineHandlers(handlers) 来合并当前路由组已有的中间件和新传入的中间件。这样,新路由组就继承了父路由组的中间件,同时也可以添加自己特有的中间件。这里再追源码:

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {//计算最终的中间件列表大小,计算合并后的中间件列表的大小。它将当前路由组已有的中间件数量(len(group.Handlers))和新传入的中间件数量(len(handlers))相加。finalSize := len(group.Handlers) + len(handlers)//使用 assert1 函数检查合并后的中间件数量是否超过了 Gin 框架设定的上限(abortIndex)。如果超过了,会触发一个断言错误,提示“too many handlers”。这是为了防止因为中间件数量过多而引发的潜在问题。assert1(finalSize < int(abortIndex), "too many handlers")//make(HandlersChain, finalSize):根据计算出的最终大小创建一个新的 HandlersChain 切片,HandlersChain 是 HandlerFunc 类型的切片,用于存储中间件。mergedHandlers := make(HandlersChain, finalSize)//copy(mergedHandlers, group.Handlers):将当前路由组的中间件复制到新创建的 mergedHandlers 切片中。copy(mergedHandlers, group.Handlers)//copy(mergedHandlers[len(group.Handlers):], handlers):然后将新传入的中间件追加到 mergedHandlers 切片中,从 group.Handlers 之后的位置开始复制。这样,copy(mergedHandlers[len(group.Handlers):], handlers)//mergedHandlers 中首先包含了路由组原有的中间件,然后是新添加的中间件。return mergedHandlers
}

basePath: 使用 group.calculateAbsolutePath(relativePath) 计算新路由组的绝对路径。这个方法将 relativePath 与父路由组的路径结合,确保新路由组的路径是基于父路由组的路径的扩展。这个函数的内部源码:

func joinPaths(absolutePath, relativePath string) string {//检查相对路径是否为空if relativePath == "" {//如果传入的相对路径 (relativePath) 为空字符串,那么没有必要进行任何连接操作,直接返回绝对路径 (absolutePath) 即可。return absolutePath}//使用 path.Join 函数将绝对路径 (absolutePath) 和相对路径 (relativePath) 连接起来,生成一个新的路径 (finalPath)。path.Join 会自动处理路径分隔符,确保路径连接正确。finalPath := path.Join(absolutePath, relativePath)//首先,通过 lastChar 函数(这个函数的作用假设是获取路径字符串的最后一个字符)检查传入的相对路径的最后一个字符是否是斜线 ('/')。然后,同样使用 lastChar 函数检查连接后的最终路径的最后一个字符是否是斜线。如果相对路径的最后一个字符是斜线,但最终路径的最后一个字符不是斜线,说明在路径连接过程中丢失了尾部的斜线。在这种情况下,需要将斜线添加到 finalPath 的末尾,以保持路径的一致性。if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {return finalPath + "/"}//如果相对路径不为空且不需要添加尾部斜线,或者已经按需添加了斜线,就返回最终的路径 (finalPath)。return finalPath
}

engine: 新路由组继承了父路由组的 engine 字段。这是因为所有的路由组都是归属于同一个 Gin 引擎的,这样可以保证路由组能够访问到引擎级别的设置和方法。

通过 Group 方法,Gin 框架提供了一种高效且灵活的路由组织方式,使得开发者可以轻松地为应用构建清晰、结构化的路由系统。

从代码层面来说,创建路由组,仅是返回路由组的对象,路由组的本质是一个模板,使用路由器组添加路由,省去用户填写相同路径前缀和中间件的步骤。

注册路由

路由如何注册呢,和路由组又有什么关系?路由组本质只是一个模板,维护了路径前缀、中间件等信息,使用路由组添加路由,用户就省去了重复配置相同前缀和中间件的操作,所以通过路由组注册路由,和普通注册路由的本质是相同的。

直接来看里面用到的这些请求都是注册路由。
先说说什么是注册路由?
注册路由是 Web 开发中的一个常见概念,指的是在 Web 应用或服务的路由系统中定义一条路由规则,以便于应用能够识别和响应特定的 HTTP 请求。在 Gin 框架中,注册路由的操作涉及到指定一个 HTTP 方法(如 GET、POST、PUT 等)、一个路径(URL 模式)以及一个处理该请求的函数(通常称为处理器或控制器)。当框架接收到一个 HTTP 请求时,它会根据请求的方法和路径找到对应的处理器并执行,以生成响应发送回客户端。

注册路由的作用
注册路由的主要目的是将特定的请求映射到对应的处理逻辑上。这样做有几个重要的好处:

结构化的请求处理:通过为不同的请求路径和方法注册不同的处理器,可以清晰地组织你的应用逻辑,使得代码易于理解和维护。

灵活性和扩展性:可以轻松添加或修改路由规则来满足应用的需求,无论是增加新的功能、改变现有功能的访问路径,还是引入新的中间件逻辑。

明确的请求分发:框架根据注册的路由规则自动分发请求到对应的处理器,开发者无需手动解析请求路径,简化了请求处理流程。

总结
注册路由是 Web 应用开发的一个核心概念,它允许开发者定义如何响应不同的 HTTP 请求。在使用 Gin 这样的 Web 框架时,注册路由不仅能帮助你清晰地组织应用逻辑,还能提供灵活和强大的请求处理能力

现在是源码解读
GET

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)//合并处理器,实际上就是将handler追加到原有的处理器组切片中handlers = group.combineHandlers(handlers)//这个就比较重要,添加路由,涉及radix树添加结点方法。group.engine.addRoute(httpMethod, absolutePath, handlers)return group.returnObj()
}

其他方法的都类似

上下文

注意gin和go的上下文不是一回事。
gin上下文的方法分为几大类,创建、流程控制、错误关联、元数据管理,请求数据,响应渲染,内容协商。
方法按内容进行编写,易于查询。

type Context struct {//请求对象writermem responseWriter//响应对象Request   *http.RequestWriter    ResponseWriterParams   Paramshandlers HandlersChainindex    int8fullPath stringengine       *Engineparams       *ParamsskippedNodes *[]skippedNode// This mutex protects Keys map.mu sync.RWMutex// Keys is a key/value pair exclusively for the context of each request.Keys map[string]any// 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// queryCache caches the query result from c.Request.URL.Query().queryCache url.Values// formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH,// or PUT body parameters.formCache url.Values// SameSite allows a server to define a cookie attribute making it impossible for// the browser to send this cookie along with cross-site requests.sameSite http.SameSite
}

这个是上下文的创建方法(初始化方法):

func (c *Context) reset() {c.Writer = &c.writermemc.Params = c.Params[:0]c.handlers = nilc.index = -1c.fullPath = ""c.Keys = nilc.Errors = c.Errors[:0]c.Accepted = nilc.queryCache = nilc.formCache = nilc.sameSite = 0*c.params = (*c.params)[:0]*c.skippedNodes = (*c.skippedNodes)[:0]
}

//拷贝上下文,如果上下文被传递给协程,必须使用拷贝(副本拷贝)。

func (c *Context) Copy() *Context {cp := Context{writermem: c.writermem,Request:   c.Request,Params:    c.Params,engine:    c.engine,}cp.writermem.ResponseWriter = nilcp.Writer = &cp.writermemcp.index = abortIndexcp.handlers = nilcp.Keys = map[string]any{}for k, v := range c.Keys {cp.Keys[k] = v}paramCopy := make([]Param, len(cp.Params))copy(paramCopy, cp.Params)cp.Params = paramCopyreturn &cp
}

用于获取当前请求上下文中的最后一个处理器,在gin框架中,一个请求可能会通过多个处理器,这些处理器被组织成一个链式结构,存储在Context的handlers字段中。

func (c *Context) Handler() HandlerFunc {return c.handlers.Last()
}

c.handlers.Last(): 调用 handlers 切片(HandlersChain 类型)的 Last 方法来获取切片中的最后一个元素,即当前请求上下文中的最后一个处理器。HandlersChain 是 HandlerFunc 类型的切片,用于按顺序存储处理当前请求的所有处理器。

关于Error
它用于在当前请求的上下文中记录一个错误

func (c *Context) Error(err error) *Error {//检查错误是否为 nil//在开始处理之前,这段代码首先检查传入的错误 (err) 是否为 nil。如果是,那么会引发一个 panic,因为向上下文添加一个 nil 错误没有意义,且可能是编程错误。if err == nil {panic("err is nil")}//尝试将错误转换为 *Error//这里使用 Go 标准库的 errors.As 函数尝试将 err 转换(类型断言)为 *Error 类型。*Error 是 Gin 定义的一个错误类型,包含错误信息和错误类型(如 ErrorTypePrivate)。如果转换成功,ok 将为 true,并且 parsedError 将指向转换后的错误。var parsedError *Errorok := errors.As(err, &parsedError)//处理无法转换的情况//如果 err 不能被转换为 *Error 类型(即,它是 Go 的内置错误类型或其他自定义错误类型),这段代码会创建一个新的 *Error 实例,将传入的 err 作为其 Err 字段的值,并将错误类型设置为 ErrorTypePrivate。ErrorTypePrivate 是用来表示这是一个内部错误,通常不会被发送给客户端的。将错误添加到上下文的错误列表中if !ok {parsedError = &Error{Err:  err,Type: ErrorTypePrivate,}}//将错误添加到上下文的错误列表中//这里将处理后的错误(parsedError)添加到当前请求上下文的错误列表 (c.Errors) 中。c.Errors 是 *Error 类型的切片,用于存储请求处理过程中发生的所有错误。c.Errors = append(c.Errors, parsedError)//返回处理后的错误return parsedError
}

总结
Error 方法为 Gin 的请求上下文提供了一种机制,以统一的方式处理和记录请求处理过程中发生的错误。通过将错误记录在请求上下文中,开发者可以在请求的任何后续处理阶段访问和处理这些错误,例如在中间件或路由处理函数中。这样不仅有助于错误的集中管理,还能根据错误类型或内容定制响应。

数据管理:
用于设置键值对。

func (c *Context) Set(key string, value any) {c.mu.Lock()defer c.mu.Unlock()if c.Keys == nil {c.Keys = make(map[string]any)}c.Keys[key] = value
}

这个就是获取键值对

func (c *Context) Get(key string) (value any, exists bool) {c.mu.RLock()defer c.mu.RUnlock()value, exists = c.Keys[key]return
}

可以直接看里面用很多*括起来的就表示不同的作用类型。

看到这里我突然就理解怎么看源码了。其实了解大概的框架之后,剩下的内容全部变成了看方法。

最后做一个重点总结
1.上下文对象的创建,用到sync.pool来复用内存
2.gin底层还是使用net/http包,gin的本质是一个路由处理器。
3.每个请求方法都有一棵radix树
4.添加中间件的过程就是切片添加元素的过程,也决定了中间件会按照添加时间的先后顺序来执行。
5.可以为不同路由组添加不同的中间件。
6.路由组本质只是一个模板,维护了路径前缀,中间件等信息,让用户省去重复配置相同前缀和中间件的操作。
7.新路由器组继承父路由器组的所有处理器。
8.如果上下文需要被并发使用,需要使用上下文副本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/692837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

weblog项目开发记录--SpringBoot后端工程骨架

知识点查漏补缺 跟着犬小哈做项目实战时发现好多知识点都忘了&#xff0c;还有一些小的知识点可能之前没学过&#xff0c;记录下&#xff01;顺带整理下开发流程。 完整项目学习见犬小哈实战专栏 SpringBoot后端工程骨架 搭建好的工程骨架中实现了很多基础功能&#xff0c;…

如何在同一个module里面集成多个数据库的多张表数据

确保本公司数据安全&#xff0c;通常对数据的管理采取很多措施进行隔离访问。 但是&#xff0c;Mendix应怎样访问散布于异地的多个数据库呢&#xff1f; 前几期我们介绍过出海跨境的大企业对于Mendix的技术、人才的诉求后&#xff0c;陆陆续续有其他客户希望更聚焦具体的实际场…

C语言:qsort模拟实现

C语言&#xff1a;qsort模拟实现 冒泡排序版Swap - 交换函数cmp - 比较函数qsort - 排序主体 qsort函数是C语言中的一个标准函数&#xff0c;用于对数组进行快速排序。其函数原型如下&#xff1a; void qsort(void *base, size_t nmemb, size_t size,int (*compar)(const void…

量子计算:数据安全难题

当今数字技术面临的最大挑战之一是安全系统和数据。为此&#xff0c;人们设计了复杂的算法来加密数据并通过称为对称加密的框架来保护数据。虽然这已被证明是成功的&#xff0c;但量子计算的进步&#xff08;利用量子力学比传统计算机更快地解决复杂问题&#xff09;可能会彻底…

【Pytorch 基础教程2】10分钟掌握Tensor基础 VSCode +Pytorch配置

Pytorch 基础教程 02 Tensor PyTorch 作为Numpy的代替品&#xff0c;可以使用GPU的强大计算能力 提供最大的灵活性和告诉的深度学习研究平台 这里补充上实验环境调试&#xff1a;第一次使用VS Code可以参考&#xff1a;PyTorch&#xff08;超详细&#xff09;部署与激活 举起Py…

优先队列C

由于看到P1629 邮递员送信这题,就去学了优先队列.为学习Dijkstra算法做准备 什么是优先队列 优先队列:是一种特殊类型的队列&#xff0c;每个元素都有一个相关的优先级。在优先队列中&#xff0c;元素按照优先级的顺序进行排列&#xff0c;具有最高&#xff08;或最低&#x…

Prometheus 教程

目录 一、简介二、下载安装1、安装 prometheus2、安装 alertmanager3、安装 grafana4、安装 node_exporter5、安装 mysqld_exporter 一、简介 Prometheus 是一个开源的系统监控和警报工具。它最初由 SoundCloud 开发&#xff0c;并于 2012 年发布为开源项目。Prometheus 专注于…

利用vite快速搭建vue3项目

1、选择一个文件夹&#xff0c;在vscode终端打开&#xff0c;输入命令【npm create vitelatest】 npm create vitelatest 2、提示你输入项目名称之后&#xff0c;我这里设置的是【rookiedemo】 3、回车之后&#xff0c;出现选择框架的提示&#xff0c;我们选择【vue】回车 4、…

js中使用for in注意事项,key的类型为string类型

for in是一个非常实用的存在&#xff0c;既可以遍历数组&#xff0c;又可以遍历对象&#xff0c;所以我一般都是会用来遍历可迭代的数据&#xff0c;遍历数组和对象的时候&#xff0c;要注意使用万能遍历方式&#xff1a; const users [1, 3, 45, 6]// const users {// 1…

Polyspace静态检测步骤

Polyspace 是一个代码静态分析和验证的工具&#xff0c;隶属于MATLAB&#xff0c;用于检测代码中的错误和缺陷&#xff0c;包括内存泄漏、数组越界、空指针引用等。帮助开发团队提高代码质量&#xff0c;减少软件开发过程中的错误和风险。 1、打开MATLAB R2018b 2、找到Polys…

AR智能眼镜主板硬件设计_AR眼镜光学方案

AR眼镜凭借其通过导航、游戏、聊天、翻译、音乐、电影和拍照等交互方式&#xff0c;将现实与虚拟进行无缝融合的特点&#xff0c;实现了更加沉浸式的体验。然而&#xff0c;要让AR眼镜真正成为便捷实用的智能设备&#xff0c;需要解决一系列技术难题&#xff0c;如显示、散热、…

Stable Diffusion 绘画入门教程(webui)-图生图

通过之前的文章相信大家对文生图已经不陌生了&#xff0c;那么图生图是干啥的呢&#xff1f; 简单理解就是根据我们给出的图片做为参考进行生成图片。 一、能干啥 这里举两个例子 1、二次元头像 真人转二次元&#xff0c;或者二次元转真人都行&#xff0c; 下图为真人转二次…

小清新卡通人物404错误页面源码

小清新卡通人物404错误页面源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 蓝奏云&#xff1a;https://wfr.lanzout.com/i6XbU1olftde

二叉树(6)——二叉树的创建和销毁

1 二叉树的创建 整体思路 将数组里的元素一直分为根&#xff0c;左子树&#xff0c;右子树&#xff0c;遇到#就返回NULL&#xff0c;链接到上层递归的左子树或者右子树&#xff0c;一定要把一个节点的左子树全部递归完才能返回到右子树。这种方法也可以scanf一个数组里的元素&…

Spring Boot项目怎么对System.setProperty(key, value)设置的属性进行读取加解密

一、前言 之前我写过一篇文章使用SM4国密加密算法对Spring Boot项目数据库连接信息以及yaml文件配置属性进行加密配置&#xff08;读取时自动解密&#xff09;&#xff0c;对Spring Boot项目的属性读取时进行加解密&#xff0c;但是没有说明对System.setProperty(key, value)设…

Mysql全局级别修改SQL模式的详细教程

文章目录 1. 问题描述2. 开发环境3. 解决方法&#xff08;详细步骤&#xff09; 1. 问题描述 Cause: java.sql.SQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column btc-cloud.t1.id which is not functiona…

socket与rpc的区别

如今的游戏开发&#xff0c;不搞个跨服玩法都不好意思说在做游戏了&#xff08;当然&#xff0c;也跟游戏类型有关&#xff0c;一些轻度休闲游戏可以排除在外&#xff09;。跨服玩法的设计&#xff0c;可以进一步激发玩家追求高战力的虚荣心&#xff0c;也可以汇聚玩家数量&…

Docker Compose映射卷的作用是什么,dockerfile这个文件有什么区别和联系?

Docker Compose中映射卷&#xff08;Volumes&#xff09;的作用和Dockerfile之间既有区别也有联系。下面详细解释两者的作用、区别和联系&#xff1a; Docker Compose映射卷的作用 在Docker Compose中&#xff0c;卷&#xff08;Volumes&#xff09;用于数据持久化和数据共享…

【实战 JS逆向+python模拟获取+Redis token会话更新】实战模拟测试 某巴批发网 仅供学习

逆向日期&#xff1a;2024.02.20 使用工具&#xff1a;Node.js、python、Redis 加密方法&#xff1a;md5标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标…

day07-实战-今日指数

今日指数-day07 1.股票Code联想推荐 1.1 股票Code联想推荐功能介绍 1) 原型效果 输入框输入股票编码后&#xff0c;显示关联的股票信息; 2&#xff09;接口定义说明 接口说明&#xff1a; 功能描述&#xff1a;根据输入的个股代码&#xff0c;进行模糊查询&#xff0c;返…