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
}
r := gin.Default()
初始化了相关的参数/ping
将路由及处理handler注册到路由树中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()
主要干的事情:
初始化了Engine对象
将RouterGroup的Handlers(数组)设置成nil, basePath设置成
/
为了使用方便, RouteGroup里面也有一个Engine指针, 这里将刚刚初始化的engine赋值给了RouterGroup的engine指针
为了防止频繁的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, 从Engine
的trees
中获取路由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的路由算法
在预处理阶段
在调用中间件的时候, 是将某个路由的handler处理函数和中间件的处理函数都放在了
Handlers
的数组中在调用Group的时候, 是将路由的path上面拼上Group的值. 也就是
/user/:name
, 会变成v1/user:name
gin路由树简单介绍
gin的路由树算法是一棵前缀树. 不过并不是只有一颗树,而是每种方法(POST, GET ...)都有自己的一颗树
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
有疑问加站长微信联系(非本文作者)