If you need performance and good productivity, you will love Gin.
Это первая статья по изучению исходного кода Gin. Почему именно Gin?
Как указано в официальной документации Gin, Gin — это веб-фреймворк, ориентированный на производительность и производительность, и утверждает, что производительность почти в 40 раз выше, чем у httprouter.Это одна из причин выбора Gin в качестве исходного кода для изучения, поскольку он ориентирован производительность, за которой следует встроенная библиотека функций Gonet
библиотека иcontext
Библиотека, если вы хотите сказать, почему Go так популярен в Китае, то причина должна быть той же, что иnet
библиотека иcontext
библиотеки, поэтому в статьях этой серии будут использованы преимуществаnet
библиотека иcontext
Применение библиотеки в Gin, две библиотеки будут объясняться в соответствии с тенденцией.
Эта серия статей пойдет от простого к сложному, от простого к сложному В процессе объяснения исходного кода Gin в сочетании с собственной библиотекой функций Go будут объяснены некоторые оригинальные конструкции в собственной библиотеке функций Go.
Приступим к первой части изучения исходного кода Gin: как анализируются параметры URL-адреса в запросе?
содержание
Разрешение параметров в путях
func main() {
router := gin.Default()
router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
c.String(http.StatusOK, "%s is %s", name, action)
})
router.Run(":8000")
}
Приводя пример из официальной документации Gin, акцентируем внимание наc.Param(key)
функция выше.
Когда инициируется запрос GET с URI /user/cole/send, полученное тело ответа выглядит следующим образом:
cole is /send
Когда инициируется запрос GET с URI /user/cole/, тело ответа выглядит следующим образом:
cole is /
Внутри Джин, как это делается? Давайте сначала понаблюдаемgin.Context
внутренняя функцияParam()
, его исходный код выглядит следующим образом:
// Param returns the value of the URL param.
// It is a shortcut for c.Params.ByName(key)
// router.GET("/user/:id", func(c *gin.Context) {
// // a GET request to /user/john
// id := c.Param("id") // id == "john"
// })
func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}
Из комментариев в исходном коде известно, чтоc.Param(key)
функция действительно простоc.Params.ByName()
Ярлык для функции, тогда давайте посмотрим на нее еще разc.Params
Что такое сакральное свойство и его тип, его исходный код выглядит следующим образом:
// 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.
type Context struct {
Params Params
}
// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
Key string
Value string
}
// Params is a Param-slice, as returned by the router.
// The slice is ordered, the first URL parameter is also the first slice value.
// It is therefore safe to read values by the index.
type Params []Param
Во-первых,Params
даgin.Context
Параметр в типе (некоторые атрибуты опущены в исходном коде выше),gin.Context
Это самая важная часть Gin, и ее роль аналогична роли в собственной библиотеке Go.context
Библиотеки, которые будут объяснены отдельно в последующих статьях этой серии.
тогда,Params
тип - этоrouter
возвращениеParam
Слайс, между тем, слайс упорядочен, первый параметр URL также является первым значением слайса, иParam
Тип сделанKey
иValue
используется для представления параметров в URL.
Итак, приведенное выше получает URL-адрес вname
параметры иaction
Параметры также можно получить с помощью следующих методов:
name := c.Params[0].Value
action := c.Params[1].Value
И это не то, о чем мы заботимся. Вопрос, который мы хотим знать, заключается в том, как Gin внутренне передает параметры в URL-адресе вc.Params
середина? Взгляните на код ниже:
func main() {
router := gin.Default()
router.GET("/aa", func(c *gin.Context) {})
router.GET("/bb", func(c *gin.Context) {})
router.GET("/u", func(c *gin.Context) {})
router.GET("/up", func(c *gin.Context) {})
router.POST("/cc", func(c *gin.Context) {})
router.POST("/dd", func(c *gin.Context) {})
router.POST("/e", func(c *gin.Context) {})
router.POST("/ep", func(c *gin.Context) {})
// http://127.0.0.1:8000/user/cole/send => cole is /send
// http://127.0.0.1:8000/user/cole/ => cole is /
router.GET("/user/:name/*action", func(c *gin.Context) {
// name := c.Param("name")
// action := c.Param("action")
name := c.Params[0].Value
action := c.Params[1].Value
c.String(http.StatusOK, "%s is %s", name, action)
})
router.Run(":8000")
}
Сосредоточьтесь на привязке маршрутов.Этот код сохраняет первый маршрут GET и создает 4 маршрута GET и 4 маршрута POST.Внутри Gin будет сгенерировано что-то вроде показанного ниже дерева маршрутизации.
Конечно, вопрос о том, как сопоставлен URL-адрес запроса, не является центром этой статьи. Это будет подробно объяснено в последующих статьях. Здесь нам нужно сосредоточиться на узлах вwildChild
Стоимость свойстваtrue
узел. В сочетании с приведенным выше рисунком взгляните на следующий код (некоторые исходные коды опущены для акцента):
func (engine *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
...
...
// 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
}
...
...
}
...
...
}
Во-первых, метод получения запроса и URL-адрес запроса с указанным вышеhttp://127.0.0.1:8000/user/cole/send
Запрос, например,httpMethod
иrPath
соответственноGET
и/user/cole/send
.
Затем используйтеengine.trees
Получите срез дерева маршрутизации (верхняя часть диаграммы дерева маршрутизации выше) и пройдите по срезу через цикл for, чтобы найти тип с помощьюhttpMethod
Корневой узел того же дерева маршрутизации.
Наконец, вызовите корневой узелgetValue(path, po, unescape)
функция, которая возвращаетnodeValue
объект типа,params
Значение атрибутаc.Params
.
Итак, наше внимание переключилось наgetValue(path, po, unescape)
функция,unescape
Параметр используется, чтобы отметить, нужно ли экранировать или нет, и здесь он будет проигнорирован.Исходный код ниже показываетgetValue(path, po, unescape)
Процесс анализа параметров URL в функции также сохраняет только исходный код, относящийся к содержанию этой статьи:
func (n *node) getValue(path string, po Params, unescape bool) (value nodeValue) {
value.params = po
walk: // Outer loop for walking the tree
for {
if len(path) > len(n.path) {
if path[:len(n.path)] == n.path {
path = path[len(n.path):]
// 从根往下匹配, 找到节点中wildChild属性为true的节点
if !n.wildChild {
c := path[0]
for i := 0; i < len(n.indices); i++ {
if c == n.indices[i] {
n = n.children[i]
continue walk
}
}
...
...
return
}
// handle wildcard child
n = n.children[0]
// 匹配两种节点类型: param和catchAll
// 可简单理解为:
// 节点的path值为':xxx', 则节点为param类型节点
// 节点的path值为'/*xxx', 则节点为catchAll类型节点
switch n.nType {
case param:
// find param end (either '/' or path end)
end := 0
for end < len(path) && path[end] != '/' {
end++
}
// save param value
if cap(value.params) < int(n.maxParams) {
value.params = make(Params, 0, n.maxParams)
}
i := len(value.params)
value.params = value.params[:i+1] // expand slice within preallocated capacity
value.params[i].Key = n.path[1:]
val := path[:end]
if unescape {
var err error
if value.params[i].Value, err = url.QueryUnescape(val); err != nil {
value.params[i].Value = val // fallback, in case of error
}
} else {
value.params[i].Value = val
}
// we need to go deeper!
if end < len(path) {
if len(n.children) > 0 {
path = path[end:]
n = n.children[0]
continue walk
}
...
return
}
...
...
return
case catchAll:
// save param value
if cap(value.params) < int(n.maxParams) {
value.params = make(Params, 0, n.maxParams)
}
i := len(value.params)
value.params = value.params[:i+1] // expand slice within preallocated capacity
value.params[i].Key = n.path[2:]
if unescape {
var err error
if value.params[i].Value, err = url.QueryUnescape(path); err != nil {
value.params[i].Value = path // fallback, in case of error
}
} else {
value.params[i].Value = path
}
return
default:
panic("invalid node type")
}
}
}
...
...
return
}
}
Во-первых, черезpath
Сопоставьте в дереве маршрутов и найдите узел вwildChild
значениеtrue
узел, указывающий, что дочерний узел этого узла является узлом с подстановочными знаками, а затем получить дочерний узел этого узла.
Затем используйте переключатель, чтобы определить тип подстановочного узла, если онparam
, затем перехватите, получите ключ и значение параметра и поместите его вvalue.params
в; еслиcatchAll
, вам не нужно перехватывать, получить ключ и значение параметра напрямую, поместить его вvalue.params
в. вn.maxParams
Атрибуты назначаются при создании маршрутов, на что здесь не нужно обращать внимание, и это будет рассмотрено в последующих статьях этой серии.
В приведенном выше коде более извилистая часть — это в основном сопоставление узлов, которое можно рассматривать в сочетании с приведенной выше диаграммой дерева маршрутизации, которая проста для понимания. также опущены.исходный код, не должно быть сложно.
Разбор параметров строк запроса
func main() {
router := gin.Default()
// http://127.0.0.1:8000/welcome?firstname=Les&lastname=An => Hello Les An
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}
Точно так же, приводя примеры в официальной документации Gin, акцентируем внимание наc.DefaultQuery(key, defaultValue)
иc.Query(key)
Ну, конечно, между ними нет никакой разницы.
Когда получение запроса с URI / Welcome? FirstName = LES & LASTNAME = AN инициируется, результат теста ответа выглядит следующим образом:
Hello Les An
Далее посмотрите наc.DefaultQuery(key, defaultValue)
иc.Query(key)
Исходный код:
// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
// GET /path?id=1234&name=Manu&value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) string {
value, _ := c.GetQuery(key)
return value
}
// DefaultQuery returns the keyed url query value if it exists,
// otherwise it returns the specified defaultValue string.
// See: Query() and GetQuery() for further information.
// GET /?name=Manu&lastname=
// c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none"
// c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string {
if value, ok := c.GetQuery(key); ok {
return value
}
return defaultValue
}
Как видно из приведенного выше исходного кода, оба вызываютc.GetQuery(key)
function, далее проследим исходный код:
// GetQuery is like Query(), it returns the keyed url query value
// if it exists `(value, true)` (even when the value is an empty string),
// otherwise it returns `("", false)`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
// GET /?name=Manu&lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool) {
if values, ok := c.GetQueryArray(key); ok {
return values[0], ok
}
return "", false
}
// GetQueryArray returns a slice of strings for a given query key, plus
// a boolean value whether at least one value exists for the given key.
func (c *Context) GetQueryArray(key string) ([]string, bool) {
c.getQueryCache()
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
return values, true
}
return []string{}, false
}
существуетc.GetQuery(key)
Вызывается внутри функцииc.GetQueryArray(key)
функция, в то время какc.GetQueryArray(key)
В функции первый вызовc.getQueryCache()
функция, то вы можете пройтиkey
Непосредственно изc.queryCache
получить соответствующийvalue
ценность, в основномc.getQueryCache()
Функция функции состоит в том, чтобы сохранить параметры строки запроса вc.queryCache
середина. Далее давайте посмотримc.getQueryCache()
Исходный код функции:
func (c *Context) getQueryCache() {
if c.queryCache == nil {
c.queryCache = c.Request.URL.Query()
}
}
судить первымc.queryCache
Является ли значениеnil
, еслиnil
, затем позвонитеc.Request.URL.Query()
функцию, иначе ничего не делать.
Мы ориентируемся наc.Request
выше, это*http.Request
тип, расположенный в библиотеке net/http в собственной библиотеке Go, иc.Request.URL
Он расположен в библиотеке net/url в собственной библиотеке функций Go, что указывает на то, что следующий исходный код исходит из собственной библиотеки функций Go. Давайте проследим исходный код:
// Query parses RawQuery and returns the corresponding values.
// It silently discards malformed value pairs.
// To check errors use ParseQuery.
func (u *URL) Query() Values {
v, _ := ParseQuery(u.RawQuery)
return v
}
// Values maps a string key to a list of values.
// It is typically used for query parameters and form values.
// Unlike in the http.Header map, the keys in a Values map
// are case-sensitive.
type Values map[string][]string
// ParseQuery parses the URL-encoded query string and returns
// a map listing the values specified for each key.
// ParseQuery always returns a non-nil map containing all the
// valid query parameters found; err describes the first decoding error
// encountered, if any.
//
// Query is expected to be a list of key=value settings separated by
// ampersands or semicolons. A setting without an equals sign is
// interpreted as a key set to an empty value.
func ParseQuery(query string) (Values, error) {
m := make(Values)
err := parseQuery(m, query)
return m, err
}
func parseQuery(m Values, query string) (err error) {
for query != "" {
key := query
// 如果key中存在'&'或者';', 则用其对key进行分割
// 例如切割前: key = firstname=Les&lastname=An
// 例如切割后: key = firstname=Les, query = lastname=An
if i := strings.IndexAny(key, "&;"); i >= 0 {
key, query = key[:i], key[i+1:]
} else {
query = ""
}
if key == "" {
continue
}
value := ""
// 如果key中存在'=', 则用其对key进行分割
// 例如切割前: key = firstname=Les
// 例如切割后: key = firstname, value = Les
if i := strings.Index(key, "="); i >= 0 {
key, value = key[:i], key[i+1:]
}
// 对key进行转义处理
key, err1 := QueryUnescape(key)
if err1 != nil {
if err == nil {
err = err1
}
continue
}
// 对value进行转义处理
value, err1 = QueryUnescape(value)
if err1 != nil {
if err == nil {
err = err1
}
continue
}
// 将value追加至m[key]切片中
m[key] = append(m[key], value)
}
return err
}
прежде всегоu.Query()
функция, путем разбораRawQuery
Значение , взяв приведенный выше запрос GET в качестве примера, тогда егоRawQuery
значениеfirstname=Les&lastname=An
, возвращаемое значениеValues
объект типа,Values
Карта, ключ которой — строка, а значение — фрагмент строк.
ПослеParseQuery(query)
функция, в которой создается функцияValues
тип объектаm
и используйте его и пропущенные вquery
в видеparseQuery(m, query)
параметры функции.
Наконец вparseQuery(m, query)
внутри функцииquery
решитьсяm
, на данный момент синтаксический анализ параметра строки запроса завершен.
Суммировать
В этой статье объясняются два способа анализа параметров URL в Gin, а именно анализ параметров в пути и анализ параметров строки запроса.
Среди них процесс анализа параметров в пути сочетает в себе механизм сопоставления маршрутов в Gin. Благодаря оригинальной конструкции механизма сопоставления маршрутов этот метод анализа параметров очень эффективен. Конечно, механизм сопоставления маршрутов немного сложен, что будет обсуждаться позже в этой серии.Это будет подробно объяснено в статье, затем синтаксический анализ параметров символов запроса, разница между этим методом синтаксического анализа параметров и собственной библиотекой функций Go net/url library заключается в том, что Gin сохраняет проанализированные параметры в контексте, так что если это так, при получении нескольких параметров нет необходимости повторно анализировать строку запроса, что значительно повышает эффективность получения нескольких параметров, что является одной из причин, почему Gin так эффективен.
На этом эта статья закончена. Спасибо за прочтение. В следующей статье из этой серии объясняется, как данные формы в запросе POST анализируются внутри Gin.