Gin源码学习

WAMiii · · 437 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1.request数据是如何流转的?

直接使用net/http,ListenAndServe 开启监听,把handleFunc添加到默认服务路由表DefaultServeMux中,来处理http请求


func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        w.Write([]byte("Hello World"))

    })

    if err := http.ListenAndServe(":8000", nil); err != nil {

        fmt.Println("start http server fail:", err)

    }

}

在浏览器中输入localhost:8000, 会看到Hello World. 下面利用这个简单demo看下request的流转流程.

http.ListenAndServe

ln, err := net.Listen("tcp", addr)做了初试化了socket, bind, listen的操作.

rw, e := l.Accept()进行accept, 等待客户端进行连接

go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发操作

h, _ := mux.Handler(r) 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端

2.gin框架到底扮演了什么角色?

gin就是一个httprouter也不过分, 当然gin也提供了其他比较主要的功能

3.请求从gin流入net/http, 最后又是如何回到gin中

基本流程


package main

import "github.com/gin-gonic/gin"

func main() {

    r := gin.Default()

    r.GET("/ping", func(c *gin.Context) {

        c.JSON(200, gin.H{

            "message": "pong",

        })

    })

    r.Run() // listen and serve on 0.0.0.0:8080

}

  1. r := gin.Default()初始化了相关的参数

  2. /ping将路由及处理handler注册到路由树中

  3. r.Run()调用http.ListenAndServe(address, engine)启动服务

3.1 gin.Engine

在整个gin框架中最重要的一个struct就是Engine, 它包含路由, 中间件, 相关配置信息等. Engine的代码主要就在gin.go中


type Engine struct {

    RouterGroup                // 路由

    pool            sync.Pool  // context pool

    trees            methodTrees // 路由树

    // html template及其他相关属性先暂时忽略

}

3.1.1New()


func New() *Engine {

    // ...

    engine := &Engine{

        RouterGroup: RouterGroup{

            Handlers: nil,

            basePath: "/",

            root:    true,

        },

        // ...

        trees: make(methodTrees, 0, 9),

    }

    engine.RouterGroup.engine = engine

    engine.pool.New = func() interface{} {

        return engine.allocateContext()

    }

    return engine

}

New()主要干的事情:

  1. 初始化了Engine对象

  2. RouterGroup的Handlers(数组)设置成nil, basePath设置成/

  3. 为了使用方便, RouteGroup里面也有一个Engine指针, 这里将刚刚初始化的engine赋值给了RouterGroup的engine指针

  4. 为了防止频繁的context GC造成效率的降低, 在Engine里使用了sync.Pool, 专门存储gin的Context

3.1.2 Default()


func Default() *Engine {

    debugPrintWARNINGDefault()

    engine := New()

    engine.Use(Logger(), Recovery())

    return engine

}

Default()New()几乎一模一样, 就是调用了gin内置的Logger(), Recovery()中间件.

3.1.3 Use()


func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {

    engine.RouterGroup.Use(middleware...)

    engine.rebuild404Handlers()

    engine.rebuild405Handlers()

    return engine

}

Use()就是gin的引入中间件的入口了,其实是在RouteGroup引入中间件的.

3.1.4 addRoute()


func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {

    ...

    root := engine.trees.get(method)

    if root == nil {

        root = new(node)

        engine.trees = append(engine.trees, methodTree{method: method, root: root})

    }

    root.addRoute(path, handlers)

}

利用method, path, 将handlers注册到engine的trees中

3.2 ServeHTTP

这个函数相当重要了, 主要有这个函数的存在, 才能将请求转到gin中, 使用gin的相关函数处理request请求.

利用request中的path, 从Enginetrees中获取路由root节点,再调用已经注册的handler


...

t := engine.trees

for i, tl := 0, len(t); i < tl; i++ {

    if t[i].method != httpMethod {

        continue

    }

    root := t[i].root

    handlers, params, tsr := root.getValue(path, c.Params, unescape)

    if handlers != nil {

        c.handlers = handlers

        c.Params = params

        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++

    }

}

4.gin.context

Request的处理封装到Context中

gin的context实现了的context.Context Interface.


type Context struct {

writermem responseWriter

Request  *http.Request

Writer    ResponseWriter

Params  Params  //路径中的参数

handlers HandlersChain  //

index    int8

fullPath string

engine *Engine

// This mutex protect Keys map

mu sync.RWMutex

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

queryCache url.Values

// formCache use url.ParseQuery cached PostForm 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

}

4.1Param() 查询指定上下文的路径


func (c *Context) Param(key string) string {

  return c.Params.ByName(key)

}

4.2 Query() 查询路由中的参数


func (c *Context) Query(key string) string {

  value, _ := c.GetQuery(key)

  return value

}

4.3PostForm()

对于POST, PUT等这些能够传递参数Body的请求, 要获取其参数, 需要使用PostForm


POST /user/1

{

    "name":manu,

    "message":this_is_great

}

name := c.PostForm("name")

message := c.PostForm("message")

4.4 Bind

内置的有json, xml, protobuf, form, query, yaml. 这些Bind极大的减少我们自己去解析各种个样的数据格式, 提高我们的开发速度

  • context.BindJSON() 支持MIME为application/json的解析

  • context.BindXML() 支持MIME为application/xml的解析

5.gin的路由算法

在预处理阶段

  1. 在调用中间件的时候, 是将某个路由的handler处理函数和中间件的处理函数都放在了Handlers的数组中

  2. 在调用Group的时候, 是将路由的path上面拼上Group的值. 也就是/user/:name, 会变成v1/user:name

gin路由树简单介绍

gin的路由树算法是一棵前缀树. 不过并不是只有一颗树,而是每种方法(POST, GET ...)都有自己的一颗树

image.png

Gin基数树路由的局限性

在编写Api接口时,为了表意清晰方便调用,大都尽量遵循RESTFul的风格

出现这个问题主要是因为 golang 很多框架使用 httprouter 作为他们的路由模块。httprouter 为了性能优化使用了显式匹配,这就导致了很多通配符与具体路由产生冲突

如下列路由就会冲突:


GET /articles/info 

GET /articles/:articleID  // articleID == info ,就会冲突

由于在RESTFul接口的实现中,路由中存在大量的变量,因此有非常大的概率会造成路由冲突。

/users/:id 就是一种路由匹配模式,也是一个通配符,其中:id就是一个路由参数,我们可以通过c.Param("id")获取定义的路由参数的值,然后用来做事情,比如打印出来。

/users/:id这种匹配模式是精确匹配的,只能匹配一个


Pattern: /users/:id

/users/123          匹配

/users/哈哈        匹配

/users/123/go      不匹配

/users/            不匹配

Gin路径中的匹配都是字符串,它是不区分数字、字母和汉字的,都匹配。

解决方法#

1. 修改路由规则

对于路由规则不是很复杂的项目,可以使用规避传统路由规则的方法,如:


GET /articles/info

GET /article/:articleId

2. 特定路由参数分支

一种简单的解决方法就是在通配符外面包裹一层判断语句。


router.GET("/v1/users/:userID", func(c *gin.Context) {

    if c.Param("userID") == "info" {

        ...

    } else {

        ...

    }

3. 使用 * 号进行通配

httprouter 提供 * 号匹配,匹配效果如下:


/users/*action

    /users/                    action = ""

    /users/somefile.go          action = "somefile.go"

6. Restful风格的API

  • gin支持Restful风格的API

  • 即Representational State Transfer的缩写。直接翻译的意思是"表现层状态转化",是一种互联网应用程序的API设计理念:URL定位资源,用HTTP描述操作

1.获取文章 /blog/getXxx Get blog/Xxx

2.添加 /blog/addXxx POST blog/Xxx

3.修改 /blog/updateXxx PUT blog/Xxx

4.删除 /blog/delXxxx DELETE blog/Xxx

gin的中间件是什么

net/http的requeset, response都提供了哪些有用的东西


部分内容摘自

https://www.liwenzhou.com/posts/Go/read_gin_sourcecode/

https://www.jianshu.com/p/ef8217daf6f7


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:WAMiii

查看原文:Gin源码学习

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

437 次点击  
加入收藏 微博
上一篇:Docker 概述
下一篇:go grpc入门
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传