в предыдущей статьеИзучение исходного кода Gin (3) 丨 Как строится и сопоставляется маршрутизация?В разделе я объяснил, как реализована маршрутизация Gin.Затем, когда маршрут успешно сопоставлен или сопоставление не выполнено, как это будет обрабатываться в Gin?
В этой статье я объясню, как Gin обрабатывает определенный HTTP-запрос.
Далее содержимое запроса после того, как он войдет в область обработки Gin, будет расширено шаг за шагом, и будет объяснен поток обработки запроса Gin.
Перейти версия: 1.14
Джин-версия: v1.5.0
содержание
- Процесс обработки запроса
- резюме
Процесс обработки запроса
В предыдущей статье мы упомянули, что Gin фактически реализует собственную библиотеку функций Go.net/http
в библиотекеHandler
интерфейса, и из исходного кода реализации можно узнать, что когда HTTP-запрос достигает области обработки Gin, он первым в GinEngine
типServeHTTP(w http.ResponseWriter, req *http.Request)
В методе сохраняется контекстная информация для Джинаgin.Context
Выполните настройку свойств и операции сброса, а затем используйтеengine.handleHTTPRequest(c *Context)
способ обработки HTTP-запроса, давайте шаг за шагом проанализируем соответствующий исходный код:
// ServeHTTP conforms to the http.Handler interface.
// 符合 http.Handler 接口的约定
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 从对象池中获取已存在的上下文对象
c := engine.pool.Get().(*Context)
// 重置该上下文对象的 ResponseWriter 属性
c.writermem.reset(w)
// 设置该上下文对象的 Request 属性
c.Request = req
// 重置上下文中的其他属性信息
c.reset()
// 对请求进行处理
engine.handleHTTPRequest(c)
// 将该上下文对象重新放回对象池中
engine.pool.Put(c)
}
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
// 上下文是 Gin 最重要的部分.
// 它允许我们在中间件之间传递变量, 管理流程, 例如验证请求的 JSON 并呈现 JSON 响应.
type Context struct {
// 对 net/http 库中的 ResponseWriter 进行了封装
writermem responseWriter
// 请求对象
Request *http.Request
// 非 net/http 库中的 ResponseWriter
// 而是 Gin 用来构建 HTTP 响应的一个接口
Writer ResponseWriter
// 存放请求中的 URI 参数
Params Params
// 存放该请求的处理函数切片, 包括中间件加最终处理函数
handlers HandlersChain
// 用于标记当前执行的处理函数
index int8
// 请求的完整路径
fullPath string
// Gin 引擎对象
engine *Engine
// Keys is a key/value pair exclusively for the context of each request.
// 用于上下文之间的变量传递
Keys map[string]interface{}
// 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 use url.ParseQuery cached the param query result from c.Request.URL.Query()
// 用于缓存请求的 URL 参数
queryCache url.Values
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
// or PUT body parameters.
// 用于缓存请求体中的参数
formCache url.Values
}
В приведенном выше исходном коде показаноengine.ServeHTTP(w http.ResponseWriter, req *http.Request)
выполнение метода иgin.Context
Внутреннее строение типа, следует отметить, что,gin.Context
РеализуетContext
интерфейса, но для него нет параллельной обработки безопасности. Поэтому несколько горутин должны избегать одновременного доступа к одному и тому же контексту. Если это произойдет, используйтеgin.Context.Copy()
метод, даgin.Context
Использовать для копирования.
Кроме того, Gin использует пул объектов для хранения контекстной информации, что является очень умной идеей, поскольку в Gin много запрошенной информации об обработке хранится вgin.Context
, а Go — это язык с GC (сборкой мусора), если в сценарии с большим объемом доступа, если для буферизации не используется пул объектовgin.Context
объект, затем создайте его для каждого запросаgin.Context
объект, и когда запрос обрабатывается,gin.Context
Объекты передаются в GC для обработки, что, несомненно, создает большую нагрузку на GC. потому чтоgin.Context
Он используется только для сохранения информации об обработке текущего запроса и для передачи параметров между контекстами.Это полностью повторно используемый объект.Поэтому использование пула объектов для его хранения может в определенной степени снизить нагрузку на сборщик мусора.
Давайте посмотрим наengine.ServeHTTP(w http.ResponseWriter, req *http.Request)
способgin.Context
Какое начальное значение устанавливается при инициализации:
const (
// 表示未写入
noWritten = -1
// 200 状态码
defaultStatus = http.StatusOK
)
type responseWriter struct {
// net/http 库中的 ResponseWriter
http.ResponseWriter
// 响应内容大小
size int
// 响应状态码
status int
}
func (w *responseWriter) reset(writer http.ResponseWriter) {
w.ResponseWriter = writer
w.size = noWritten
w.status = defaultStatus
}
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[0:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[0:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
}
Здесь следует отметить, чтоgin.Context.index
Начальное значение Gin использует это значение для вызова обработчика и определения того, завершается ли текущий контекст.
Теперь давайте взглянем на поток обработки запросов Gin. Давайте сначала посмотрим.engine.handleHTTPRequest(c *Context)
метод, этот метод много раз появлялся в предыдущих статьях по изучению исходного кода Gin, так что здесь то же самое, только сохраните его исходный код, связанный с текущей темой статьи:
func (engine *Engine) handleHTTPRequest(c *Context) {
// 省略...
// 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, unescape)
if value.handlers != nil {
c.handlers = value.handlers
c.Params = value.params
c.fullPath = value.fullPath
// 开始对请求执行中间件和处理函数
c.Next()
// 设置响应头信息
c.writermem.WriteHeaderNow()
return
}
// 省略...
break
}
// 省略...
c.handlers = engine.allNoRoute
// 处理 404 错误
serveError(c, http.StatusNotFound, default404Body)
}
В предыдущей статье я упомянул, что сопоставление маршрутизации запросов Gin находится вroot.getValue(path string, po Params, unescape bool)
реализован в методе, и, когда возвращаемыйvalue
в объектеhandlers
имущество неnil
, значит есть функция обработки запроса, и тогдаvalue
Набор фрагментов функции обработки, параметры запроса и полная информация о пути запроса в объекте сохраняются в объекте контекста запроса, а затем вызываютсяcontext.Next()
метод, давайте посмотрим на исходный код этого метода:
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// Next 只能在中间件内部使用
// 它在调用处理程序内的链中执行挂起的处理程序
// 类似于递归调用或函数装饰器
func (c *Context) Next() {
// index 初始值为 -1
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}
context.Next()
Логика метода относительно проста. На самом деле, он состоит в том, чтобы пройти по срезу промежуточного программного обеспечения/функции обработки, хранящемуся в контексте Gin, и вызвать его. В предыдущей статье мы также говорили:context.handlers
В срезах имеется несколькоHandlerFunc
В то время, кроме последней функции обработчика маршрута, остальные являются промежуточным программным обеспечением.
Здесь следует отметить, чтоNext()
Метод, когда он используется, может использоваться только внутри промежуточного программного обеспечения, то есть в повседневной разработке метод может появляться только в промежуточном программном обеспечении, написанном вами, но не в других местах.
Ниже мы беремgin.Default()
Создайтеgin.Engine
Два промежуточных ПО по умолчанию добавляются, когдаLogger
а такжеRecovery
, в сочетании с промежуточным программным обеспечением, которое имитирует аутентификациюAuth
Например, чтобы подробно объяснить рабочий процесс промежуточного программного обеспечения Gin, давайте сначала рассмотримgin.Default()
Промежуточное ПО по умолчанию, добавленное по методуLogger
Соответствующий исходный код для:
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default gin.DefaultWriter = os.Stdout.
// Logger 是一个中间件, 该中间件会将日志写入 gin.DefaultWriter.
// 默认的 gin.DefaultWriter 为 os.Stdout, 即标准输出流, 控制台
func Logger() HandlerFunc {
return LoggerWithConfig(LoggerConfig{})
}
// LoggerConfig defines the config for Logger middleware.
// Logger 中间件的相关配置
type LoggerConfig struct {
// Optional. Default value is gin.defaultLogFormatter
// 用于输出内容的格式化, 默认为 gin.defaultLogFormatter
Formatter LogFormatter
// Output is a writer where logs are written.
// Optional. Default value is gin.DefaultWriter.
// 日志输出对象
Output io.Writer
// SkipPaths is a url path array which logs are not written.
// Optional.
// 忽略日志输出的 URL 切片
SkipPaths []string
}
// LoggerWithConfig instance a Logger middleware with config.
// 使用 LoggerConfig 配置的 Logger 中间件
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
formatter := conf.Formatter
if formatter == nil {
formatter = defaultLogFormatter
}
out := conf.Output
if out == nil {
out = DefaultWriter
}
notlogged := conf.SkipPaths
// 是否输出至终端
isTerm := true
if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
isTerm = false
}
// 标记忽略日志的 path
var skip map[string]struct{}
if length := len(notlogged); length > 0 {
skip = make(map[string]struct{}, length)
for _, path := range notlogged {
skip[path] = struct{}{}
}
}
return func(c *Context) {
// Start timer
// 记录开始时间
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
// Process request
// 继续执行下一个中间件
c.Next()
// Log only when path is not being skipped
// 如果 path 在 skip 中, 则忽略日志记录
if _, ok := skip[path]; !ok {
param := LogFormatterParams{
Request: c.Request,
isTerm: isTerm,
Keys: c.Keys,
}
// Stop timer
// 记录结束时间
param.TimeStamp = time.Now()
// 计算耗时
param.Latency = param.TimeStamp.Sub(start)
// 客户端 IP
param.ClientIP = c.ClientIP()
// 请求方法
param.Method = c.Request.Method
// 请求状态码
param.StatusCode = c.Writer.Status()
// 错误信息
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
// 响应体大小
param.BodySize = c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
param.Path = path
// 日志打印
fmt.Fprint(out, formatter(param))
}
}
}
По сути, промежуточное программное обеспечение также является декоратором или замыканием, которое, по сути, является типом возвращаемого значения.HandlerFunc
Функция, с точки зрения непрофессионала, — это функция, которая возвращает функцию, цель которой — декорировать или обработать внутреннюю функцию во внешней функции, а затем вернуть декорированную или обработанную внутреннюю функцию.
потому чтоHandlerFunc
Функция может принимать только одинgin.Context
параметры, поэтому в приведенном выше исходном кодеLoggerWithConfig(conf LoggerConfig)
функция, использованиеLoggerConfig
конфигурация, даHandlerFunc
Украсьте и верните.
Так же и в возвращенномHandlerFunc
В анонимной функции сначала запишите некоторую информацию при входе в промежуточное ПО, включая время, а затем вызовитеcontext.Next()
метод, приостановить текущий обработчик, рекурсивно вызвать последующее промежуточное ПО, когда все последующие промежуточное ПО и функции обработки будут выполнены, вернитесь сюда, если вы хотите записатьpath
log, затем снова получите текущее время и рассчитайте его со временем начала записи, затем вы можете получить время, затрачиваемое на обработку этого запроса, а затем сохранить другую информацию, включая IP-адрес запроса и соответствующую информацию ответа, и т.д., и, наконец, запрос Журнал печатается, это делается с помощьюgin.Default()
экземпляр одинgin.Engine
добавлено по умолчаниюLogger()
Процесс обработки промежуточного программного обеспечения.
Далее, давайте взглянем на другое промежуточное ПО по умолчанию.Recovery()
Соответствующий исходный код для:
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
// Recovery 中间件用于捕获处理流程中出现 panic 的错误
// 如果连接未断开, 则返回 500 错误响应
func Recovery() HandlerFunc {
// DefaultErrorWriter = os.Stderr
return RecoveryWithWriter(DefaultErrorWriter)
}
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
// 使用传递的 out 对 Recovery 中间件进行装饰
func RecoveryWithWriter(out io.Writer) HandlerFunc {
var logger *log.Logger
if 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 bool
// 从错误信息中判断连接是否断开
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
// 省略, 日志打印相关...
// If the connection is dead, we can't write a status to it.
if brokenPipe { // 如果连接已断开, 则已经无法为其写入状态码
// 添加错误信息至上下文中, 用于日志输出
c.Error(err.(error)) // nolint: errcheck
// 终止该上下文
c.Abort()
} else { // 连接未断开
// 终止该上下文并写入 500 错误状态码
c.AbortWithStatus(http.StatusInternalServerError)
}
}
}()
// 继续执行下一个中间件
c.Next()
}
}
а такжеLoggerWithConfig(conf LoggerConfig)
та же функция,RecoveryWithWriter(out io.Writer)
Функция предназначена только для промежуточного программного обеспечения, которое, наконец, возвращаетHandlerFunc
функцию украшения, в этом промежуточном программном обеспечении ее можно разделить на два логических блока, один из которыхdefer
,одинNext()
,Next()
а такжеLogger()
в промежуточном программном обеспеченииNext()
работает аналогично, здесьdefer
используется вrecover()
для захвата в последующем промежуточном программном обеспеченииpanic
сообщение об ошибке и обработайте сообщение об ошибке.
В этом промежуточном программном обеспечении сначала определяется, было ли прервано текущее соединение, а затем выполняется соответствующая обработка журнала.Наконец, если соединение прервано, информация об ошибке устанавливается напрямую, а контекст завершается, в противном случае контекст завершается и возвращается ошибка 500. response.
Далее давайте посмотримcontext.Abort()
Методы иcontext.AbortWithStatus(code int)
Соответствующий исходный код метода:
// 63
const abortIndex int8 = math.MaxInt8 / 2
// 终止上下文
func (c *Context) Abort() {
c.index = abortIndex
}
// 判断上下文是否终止
func (c *Context) IsAborted() bool {
return c.index >= abortIndex
}
// 终止上下文并将 code 写入响应头中
func (c *Context) AbortWithStatus(code int) {
c.Status(code)
c.Writer.WriteHeaderNow()
c.Abort()
}
context.Abort()
метод преобразует текущий контекстindex
Значение установлено на 63, чтобы отметить завершение контекста.
context.AbortWithStatus(code int)
Также завершает текущий контекст, но дополнительно используетcode
параметр, который устанавливает информацию заголовка ответа.
Наконец, давайте рассмотрим промежуточное программное обеспечение, которое имитирует проверку личности.Auth
, соответствующий исходный код его реализации выглядит следующим образом:
type RequestData struct{
Action string `json:"action"`
UserID int `json:"user_id"`
}
func main() {
router := gin.Default()
// 注册中间件
router.Use(Auth())
router.POST("/action", func(c *gin.Context) {
var RequestData RequestData
if err := c.BindJSON(&RequestData); err == nil {
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "success"})
}
})
router.Run(":8000")
}
func Auth() gin.HandlerFunc {
// TODO: 可模仿 Logger() 或 Recovery() 中间件, 结合该函数的调用参数, 在此处做一些配置操作
return func(c *gin.Context) {
// TODO: 可模仿 Logger() 中间件, 在此处对请求的 path 进行忽略处理
if auth(c.Request) {
c.Next()
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"msg": "Unauthorized"})
}
}
}
func auth(req *http.Request) bool {
// TODO: 对 http.Request 中的信息进行校验, 如 cookie, token...
return req.Header.Get("Auth") == "colelie"
}
прежде всегоAuth()
функция, которая используется для оформления и возвратаgin.HandlerFunc
функция внутри этой функции возвращаетgin.HandlerFunc
Анонимная функция, в которой анонимная функция вызывается вызовомauth(req *http.Request)
Функция проверяет информацию запроса, вот простоAuth
аутентификация.
Итак, в этом случае, когда мы посещаем/action
интерфейс, он сначала войдетLogger()
промежуточное ПО, затем введитеRecovery()
промежуточное ПО, затем введитеAuth()
Middleware, когда ни одно из предыдущих middleware не завершило контекст, оно войдет в тот, который мы объявили.router.POST("/action", func)
функция обработчика.
когда мы/action
Когда интерфейс инициирует обычный запрос POST, он получит следующий ответ:
Это связано сAuth()
Проверка личности в промежуточном программном обеспечении не удалась, мы добавляем ключ к информации заголовка запроса какAuth
, Значениеcolelie
поле, вы получите следующий ответ:
Можно обнаружить, что точно так же есть ответ об ошибке, но на этот раз код ответа об ошибке 400, почему это?
существуетИзучение исходного кода Gin (2) 丨 Как анализируются параметры в теле запроса?, мы сказали, что при использованииMustBind
Когда класс функций привязки, если при синтаксическом анализе параметра возникает ошибка, он будет вызыватьсяc.AbortWithError(http.StatusBadRequest, err)
метод, завершает текущий контекст и возвращает код ошибки ответа 400, как объявлено выше./action
в функции-обработчике, используяcontext.BindJSON(obj interface{})
Метод привязывает параметры запроса, далее, когда мы добавим тело запроса, которое можно успешно привязать к запросу, мы получим следующий ответ:
На этот раз получил правильное содержание ответа.
резюме
В этой статье мы вращаемся вокругgin.Context
Внутренняя структура Gin, рабочий процесс промежуточного программного обеспечения и функций обработки Gin, а также процесс обработки запросов Gin.
Первый вgin.Engine
, используя пул объектовsync.Pool
хранитьgin.Context
Цель этого — уменьшить нагрузку на Go GC.
Затем внутри Gin, когда маршрут будет успешно сопоставлен, он вызоветcontext.Next()
Метод начинает входить в выполнение промежуточного программного обеспечения Gin и функций обработки, и следует отметить, что в повседневной разработке этот метод может вызываться только в промежуточном программном обеспечении.
Наконец, чтобы использоватьgin.Default()
создание методаgin.Engine
Два промежуточных ПО по умолчанию переносятся, когдаLogger()
а такжеRecovery()
, и промежуточное ПО, которое мы написали для имитации проверки личности.Auth()
, в сочетании с зарегистрированным путем как/action
Маршрутизация Gin объясняет рабочий процесс промежуточного программного обеспечения Gin и функций обработки.
На этом четвертая часть изучения исходного кода Gin завершена, спасибо, что прочитали эту статью~~
Добро пожаловать, чтобы отсканировать следующий QR-код, чтобы перейти по личному номеру подписки автора, чтобы получить последнюю статью: