一、net-http 1 介绍 1 2 3 4 5 6 7 . ├── ClientGet │ └── main.go ├── ClientPost │ └── main.go ├── Server │ └── main.go
Go语言内置的net/http包十分的优秀,提供了HTTP客户端和服务端的实现。
2 web服务 1、Server/main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package mainimport ( "encoding/json" "fmt" "io/ioutil" "net/http" ) type Data struct { Name string `json:"name"` } func dealPostRequestHandler (w http.ResponseWriter, r *http.Request) { bodyContent, _ := ioutil.ReadAll(r.Body) strData := string (bodyContent) var d Data json.Unmarshal([]byte (strData), &d) fmt.Printf("body content:[%s]\n" , string (bodyContent)) json.NewEncoder(w).Encode(fmt.Sprintf("收到名字:%s" , d.Name)) } func dealGetRequestHandler (w http.ResponseWriter, r *http.Request) { query := r.URL.Query() jsonStr, _ := json.Marshal(query) fmt.Println("get request param: " + string (jsonStr) + "\n" ) name, _ := query["name" ] json.NewEncoder(w).Encode(name) } func main () { http.HandleFunc("/req/post" , dealPostRequestHandler) http.HandleFunc("/req/get" , dealGetRequestHandler) http.ListenAndServe(":8005" , nil ) }
2、postman测试
{width=”6.02363188976378in” height=”6.1725853018372705in”}
3 请求数据 1、ClientGet/main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "fmt" "io/ioutil" "net/http" "net/url" ) func main () { requestGet() } func requestGet () { apiUrl := "http://127.0.0.1:8005/req/get" data := url.Values{} data.Set("name" , "root" ) u, _ := url.ParseRequestURI(apiUrl) u.RawQuery = data.Encode() fmt.Println("请求路由为:" , u.String()) resp, _ := http.Get(u.String()) b, _ := ioutil.ReadAll(resp.Body) fmt.Println("返回数据为:" , string (b)) }
2、ClientPost/main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "fmt" "io/ioutil" "net/http" "strings" ) func main () { requestPost() } func requestPost () { url := "http://127.0.0.1:8005/req/post" contentType := "application/json" data := `{"name":"rootPort"}` resp, _ := http.Post(url, contentType, strings.NewReader(data)) b, _ := ioutil.ReadAll(resp.Body) fmt.Println(string (b)) }
二、Gin Web框架基本使用-原理篇 2.1 Gin入门 1、介绍
Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点
对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错
借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范
2、安装 要安装Gin软件包,您需要安装Go并首先设置Go工作区。
1.首先需要安装Go(需要1.10+版本),然后可以使用下面的Go命令安装Gin。
1 go get -u github.com/gin-gonic/gin
2.将其导入您的代码中:
1 import "github.com/gin-gonic/gin"
3.(可选)导入net/http。例如,如果使用常量,则需要这样做http.StatusOK。
3、hello word 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // 1.创建路由 r := gin.Default() // 2.绑定路由规则,执行的函数 // gin.Context,封装了request和response r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "hello World!") }) // 3.监听端口,默认在8080 // Run("里面不指定端口号默认为8080") r.Run(":8000") }
2.2 Gin工作流程 1、gin核心概念
Engine 容器对象,整个框架的基础
Engine.trees 负责存储路由和handle方法的映射,采用类似字典树的结构
Engine.RouterGroup ,其中的Handlers存储着所有中间件
Context 上下文对象,负责处理请求和回应 ,其中的handlers 是存储处理请求时中间件和处理方法 的
2、请求生命周期
3.3 Gin源码 [gin源码](# gin源码)
三、Gin Web框架基本使用-基础篇 3.1 路由与传参 1、无参路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func HelloWorldHandler (c *gin.Context) { c.String(http.StatusOK, "hello World!" ) } func main () { r := gin.Default() r.GET("/hello" , HelloWorldHandler) fmt.Println("运行地址:http://127.0.0.1:8000" ) r.Run(":8000" ) }
2、API参数
可以通过Context的Param方法来获取API参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func GetBookDetailHandler (c *gin.Context) { bookId := c.Param("id" ) c.String(http.StatusOK, fmt.Sprintf("成功获取书籍详情:%s" , bookId)) } func main () { r := gin.Default() r.GET("/book/:id" , GetBookDetailHandler) fmt.Println("运行地址:http://127.0.0.1:8000/book/24/" ) r.Run(":8000" ) }
3、url参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func GetUserDetailHandler (c *gin.Context) { username := c.Query("name" ) c.String(http.StatusOK, fmt.Sprintf("姓名:%s" , username)) } func main () { r := gin.Default() r.GET("/user/" , GetUserDetailHandler) fmt.Println("运行地址:http://127.0.0.1:8000/user?name=root" ) r.Run(":8000" ) }
4、ShouldBind参数绑定
我们可以基于请求的Content-Type 识别请求数据类型并利用反射机制
自动提取请求中QueryString 、form表单 、JSON 、XML 等参数到结构体中
下面的示例代码演示了.ShouldBind() 强大的功能
它能够基于请求自动提取JSON 、form表单 和QueryString 类型的数据,并把值绑定到指定的结 构体对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( "fmt" "net/http" "github.com/gin-gonic/gin" ) // Binding from JSON type Login struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func LoginHandler(c *gin.Context) { var login Login if err := c.ShouldBind(&login); err != nil { // 如果数据校验不通过直接返回 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } c.String(http.StatusOK, fmt.Sprintf("姓名:%s -- 密码:%s", login.Username, login.Password)) } func main() { // 1.创建路由 r := gin.Default() // 2.绑定路由规则,执行的函数 // 基本路由 /login/ r.POST("/login/", LoginHandler) // 3.监听端口,默认在8080 fmt.Println("运行地址:http://127.0.0.1:8000/login/") r.Run(":8000") }
postman测试
3.2 响应返回 1、响应String 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() r.GET("/response/" , ResponseStringHandler) fmt.Println("http://127.0.0.1:8000/response/" ) r.Run(":8000" ) } func ResponseStringHandler (c *gin.Context) { c.String(http.StatusOK, "返回简单字符串" ) }
2、响应JSON 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() r.GET("/response/" , ResponseJsonHandler) fmt.Println("http://127.0.0.1:8000/response/" ) r.Run(":8000" ) } func ResponseJsonHandler (c *gin.Context) { type Data struct { Msg string `json:"msg"` Code int `json:"code"` } d := Data{ Msg: "Json数据" , Code: 1001 , } c.JSON(http.StatusOK, d) }
3、路由重定向 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "net/http" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() r.GET("/" , func (c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "https://tigerfivegit.github.io/" ) }) fmt.Println("http://127.0.0.1:8000" ) r.Run(":8000" ) }
3.3 路由分发 1、项目结构 1 2 3 4 5 6 7 . ├── go.mod ├── go.sum ├── main.go └── routers ├── books.go └── users.go
2、main.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport ( "demo_roue_layer/routers" "fmt" "github.com/gin-gonic/gin" ) func main () { r := gin.Default() routers.LoadBooks(r) routers.LoadUsers(r) fmt.Println("用户路由: http://127.0.0.1:8000/user" ) fmt.Println("书籍路由: http://127.0.0.1:8000/book" ) r.Run(":8000" ) }
3、routers/users.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package routersimport ( "net/http" "github.com/gin-gonic/gin" ) func LoadUsers (e *gin.Engine) { e.GET("/user" , UserHandler) } func UserHandler (c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message" : "User Router" , }) }
4、routers/books.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package routersimport ( "net/http" "github.com/gin-gonic/gin" ) func LoadBooks (e *gin.Engine) { e.GET("/book" , GetBookHandler) } func GetBookHandler (c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message" : "Book Router" , }) }
测试路由分发效果
{width=”6.383172572178478in” height=”2.0739752843394577in”}
END
gin源码 00.前置知识 0.1 基数树(Radix Tree)
Radix Tree
可以被认为是一棵简洁版的前缀树。
我们注册路由的过程就是构造前缀树的过程,具有公共前缀的节点也共享一个公共父节点
由于URL路径具有层次结构,并且只使用有限的一组字符(字节值),所以很可能有许多常见的前缀。
这使我们可以很容易地将路由简化为更小的问题。
gin中,路由器为每种请求方法管理一棵单独的树
。
0.2 net/http
Go语言内置的net/http
包十分的优秀,提供了HTTP客户端和服务端的实现
ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。
处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" "net/http" ) func sayHello (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello World!" ) } func main () { http.HandleFunc("/" , sayHello) err := http.ListenAndServe(":9090" , nil ) if err != nil { fmt.Printf("http server failed, err:%v\n" , err) return } }
0.3 运行gin服务
1 C:\aaa\gin_demo> go mod tidy
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport "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() }
0.4 gin核心概念
Engine
容器对象,整个框架的基础
Engine.trees
负责存储路由和handle方法的映射,采用类似字典树的结构
Engine.RouterGroup
,其中的Handlers存储着所有中间件
Context
上下文对象,负责处理请求和回应
,其中的handlers
是存储处理请求时中间件和处理方法的
01.第一步: 初始化容器
1)初始化容器流程
通过调用 gin.New()
方法来实例化Engine容器
.
虽然参数很多,但我们只需要注意 RouterGroup
,trees
和 engine.pool.New
即可
engine.pool.New
负责创建Context
对象,采用sync.Pool
减少频繁context实例化带来的资源消耗
2)Engine说明
在整个gin框架中最重要的一个struct就是Engine, 它包含路由, 中间件, 相关配置信息等.
Engine的代码主要就在gin.go中
Engine中比较重要的几个属性, 其他的属性暂时全部省略掉
```go type Engine struct {
RouterGroup // 路由
pool sync.Pool // context pool
trees methodTrees // 路由树
// html template及其他相关属性先暂时忽略
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - Engine有几个比较主要的函数 `New(), Default()` ### 1.1 先看 `gin.Default()` - Default()跟New()几乎一模一样, 就是调用了gin内置的Logger(), Recovery()中间件. ```go func Default() *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine }
1.2 engine := New()
初始化
通过调用 gin.New()
方法来实例化Engine容器
.
1.初始化了Engine
2.将RouterGroup的Handlers(数组)设置成nil, basePath设置成 /
3.为了使用方便, RouteGroup里面也有一个Engine指针, 这里将刚刚初始化的engine赋值给了RouterGroup的engine指针
4.为了防止频繁的context GC造成效率的降低, 在Engine里使用了sync.Pool, 专门存储gin的Context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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" }, TrustedProxies: []string {"0.0.0.0/0" }, AppEngine: defaultAppEngine, UseRawPath: false , RemoveExtraSlash: false , UnescapePathValues: true , MaxMultipartMemory: defaultMultipartMemory, trees: make (methodTrees, 0 , 9 ), delims: render.Delims{Left: "{{" , Right: "}}" }, secureJSONPrefix: "while(1);" , } engine.RouterGroup.engine = engine engine.pool.New = func () interface {} { return engine.allocateContext() } return engine }
02.第二步: 注册中间件 2.1 engine.Use()
gin框架中的中间件设计很巧妙,我们可以首先从我们最常用的r := gin.Default()
的Default
函数开始看
它内部构造一个新的engine
之后就通过Use()
函数注册了Logger
中间件和Recovery
中间件
Use()就是gin的引入中间件的入口了.
仔细分析这个函数, 不难发现Use()其实是在给RouteGroup引入中间件的.
具体是如何让中间件在RouteGroup上起到作用的, 等说到RouteGroup再具体说.
1 2 3 4 5 6 func Default () *Engine { debugPrintWARNINGDefault() engine := New() engine.Use(Logger(), Recovery()) return engine }
2.2 往下查看Use()函数代码
gin.use()
调用RouterGroup.Use()
往RouterGroup.Handlers
写入记录
1 2 3 4 5 6 7 8 9 10 11 12 func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes { engine.RouterGroup.Use(middleware...) engine.rebuild404Handlers() engine.rebuild405Handlers() return engine } func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append (group.Handlers, middleware...) return group.returnObj() }
而我们注册路由时会将对应路由的函数和之前的中间件函数结合到一起
1 2 3 4 5 6 func (group *RouterGroup) handle(httpMethod, relativePath string , handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
2.3 组成一条处理函数链条HandlersChain
其中结合操作的函数内容如下,注意观察这里是如何实现拼接两个切片得到一个新切片的
1 2 3 4 5 6 7 8 9 10 11 12 const abortIndex int8 = math.MaxInt8 / 2 func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len (group.Handlers) + len (handlers) if finalSize >= int (abortIndex) { panic ("too many handlers" ) } mergedHandlers := make (HandlersChain, finalSize) copy (mergedHandlers, group.Handlers) copy (mergedHandlers[len (group.Handlers):], handlers) return mergedHandlers }
也就是说,我们会将一个路由的中间件函数和处理函数结合到一起组成一条处理函数链条HandlersChain
而它本质上就是一个由HandlerFunc
组成的切片
1 type HandlersChain []HandlerFunc
2.4 中间件的执行
其中
就是很关键的一步,它的代码很简单
从下面的代码可以看到,这里通过索引遍历HandlersChain
链条
从而实现依次调用该路由的每一个函数(中间件或处理请求的函数)
1 2 3 4 5 6 7 func (c *Context) Next() { c.index++ for c.index < int8 (len (c.handlers)) { c.handlers[c.index](c) c.index++ } }
我们可以在中间件函数中通过再次调用c.Next()
实现嵌套调用(func1中调用func2;func2中调用func3)
或者通过调用c.Abort()
中断整个调用链条,从当前函数返回
1 2 3 func (c *Context) Abort() { c.index = abortIndex }
c.Set()/c.Get()
c.Set()
和c.Get()
这两个方法多用于在多个函数之间通过c
传递数据的
比如我们可以在认证中间件中获取当前请求的相关信息(userID等)通过c.Set()
存入c
然后在后续处理业务逻辑的函数中通过c.Get()
来获取当前请求的用户
c
就像是一根绳子,将该次请求相关的所有的函数都串起来了。
03.第三步: 注册路由
注册路由的逻辑主要有addRoute
函数和insertChild
方法
3.1 注册路由动图
1、第一次注册路由,例如注册search
2、继续注册一条没有公共前缀的路由,例如blog
3、注册一条与先前注册的路由有公共前缀的路由,例如support
路由树节点
路由树是由一个个节点构成的,gin框架路由树的节点由node
结构体表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 type node struct { path string indices string children []*node handlers HandlersChain priority uint32 nType nodeType maxParams uint8 wildChild bool fullPath string }
3.2 addRoute构造路由树
这段代码就是利用method, path, 将handlers注册到engine的trees中.
注意这里为什么是HandlersChain呢, 可以简单说一下, 就是将中间件和处理函数都注册到method, path的tree中了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 func (n *node) addRoute(path string , handlers HandlersChain) { fullPath := path n.priority++ numParams := countParams(path) if len (n.path) == 0 && len (n.children) == 0 { n.insertChild(numParams, path, fullPath, handlers) n.nType = root return } parentFullPathIndex := 0 walk: for { if numParams > n.maxParams { n.maxParams = numParams } i := longestCommonPrefix(path, n.path) if i < len (n.path) { child := node{ path: n.path[i:], wildChild: n.wildChild, indices: n.indices, children: n.children, handlers: n.handlers, priority: n.priority - 1 , fullPath: n.fullPath, } for _, v := range child.children { if v.maxParams > child.maxParams { child.maxParams = v.maxParams } } n.children = []*node{&child} n.indices = string ([]byte {n.path[i]}) n.path = path[:i] n.handlers = nil n.wildChild = false n.fullPath = fullPath[:parentFullPathIndex+i] } if i < len (path) { path = path[i:] if n.wildChild { parentFullPathIndex += len (n.path) n = n.children[0 ] n.priority++ if numParams > n.maxParams { n.maxParams = numParams } numParams-- if len (path) >= len (n.path) && n.path == path[:len (n.path)] { if len (n.path) >= len (path) || path[len (n.path)] == '/' { continue walk } } pathSeg := path if n.nType != catchAll { pathSeg = strings.SplitN(path, "/" , 2 )[0 ] } prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path panic ("'" + pathSeg + "' in new path '" + fullPath + "' conflicts with existing wildcard '" + n.path + "' in existing prefix '" + prefix + "'" ) } c := path[0 ] if n.nType == param && c == '/' && len (n.children) == 1 { parentFullPathIndex += len (n.path) n = n.children[0 ] n.priority++ continue walk } for i, max := 0 , len (n.indices); i < max; i++ { if c == n.indices[i] { parentFullPathIndex += len (n.path) i = n.incrementChildPrio(i) n = n.children[i] continue walk } } if c != ':' && c != '*' { n.indices += string ([]byte {c}) child := &node{ maxParams: numParams, fullPath: fullPath, } n.children = append (n.children, child) n.incrementChildPrio(len (n.indices) - 1 ) n = child } n.insertChild(numParams, path, fullPath, handlers) return } if n.handlers != nil { panic ("handlers are already registered for path '" + fullPath + "'" ) } n.handlers = handlers return } }
3.3 insertChild
insertChild
函数是根据path
本身进行分割,将/
分开的部分分别作为节点保存,形成一棵树结构。
参数匹配中的:
和*
的区别是,前者是匹配一个字段而后者是匹配后面所有的路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 func (n *node) insertChild(numParams uint8 , path string , fullPath string , handlers HandlersChain) { for numParams > 0 { wildcard, i, valid := findWildcard(path) if i < 0 { break } if !valid { panic ("only one wildcard per path segment is allowed, has: '" + wildcard + "' in path '" + fullPath + "'" ) } if len (wildcard) < 2 { panic ("wildcards must be named with a non-empty name in path '" + fullPath + "'" ) } if len (n.children) > 0 { panic ("wildcard segment '" + wildcard + "' conflicts with existing children in path '" + fullPath + "'" ) } if wildcard[0 ] == ':' { if i > 0 { n.path = path[:i] path = path[i:] } n.wildChild = true child := &node{ nType: param, path: wildcard, maxParams: numParams, fullPath: fullPath, } n.children = []*node{child} n = child n.priority++ numParams-- if len (wildcard) < len (path) { path = path[len (wildcard):] child := &node{ maxParams: numParams, priority: 1 , fullPath: fullPath, } n.children = []*node{child} n = child continue } n.handlers = handlers return } if i+len (wildcard) != len (path) || numParams > 1 { panic ("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'" ) } if len (n.path) > 0 && n.path[len (n.path)-1 ] == '/' { panic ("catch-all conflicts with existing handle for the path segment root in path '" + fullPath + "'" ) } i-- if path[i] != '/' { panic ("no / before catch-all in path '" + fullPath + "'" ) } n.path = path[:i] child := &node{ wildChild: true , nType: catchAll, maxParams: 1 , fullPath: fullPath, } if n.maxParams < 1 { n.maxParams = 1 } n.children = []*node{child} n.indices = string ('/' ) n = child n.priority++ child = &node{ path: path[i:], nType: catchAll, maxParams: 1 , handlers: handlers, priority: 1 , fullPath: fullPath, } n.children = []*node{child} return } n.path = path n.handlers = handlers n.fullPath = fullPath }
04.第四步: 启动
通过调用net/http
来启动服务,由于engine
实现了ServeHTTP
方法
只需要直接传engine
对象就可以完成初始化并启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 r.Run() func (engine *Engine) Run(addr ...string ) (err error ) { defer func () { debugPrintError(err) }() address := resolveAddress(addr) debugPrint("Listening and serving HTTP on %s\n" , address) err = http.ListenAndServe(address, engine) return } func ListenAndServe (addr string , handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } type Handler interface { ServeHTTP(ResponseWriter, *Request) } func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { c := engine.pool.Get().(*Context) c.writermem.reset(w) c.Request = req c.reset() engine.handleHTTPRequest(c) engine.pool.Put(c) }
05.第五步: 处理请求
这里只需要留意handleHTTPRequest(c *Context)
方法就好了
通过请求方法和路由找到相对应的树节点,获取储存的[]HandlerFunc
列表,通过调用c.Next()
处理请求
通过不停的移动下标递归,最后完成处理返回结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func (engine *Engine) handleHTTPRequest(c *Context) { ... t := engine.trees for i, tl := 0 , len (t); i < tl; i++ { ... 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 } ... } ... } func (c *Context) Next() { c.index++ for c.index < int8 (len (c.handlers)) { c.handlers[c.index](c) c.index++ } }
06.参考博客
Go源码阅读】(十):Go语言Web框架GIN源码阅读
https://www.bilibili.com/video/BV1ap4y197Qj?from=search&seid=5844526642048212379
https://jiajunhuang.com/articles/2018_03_16-gin_source_code.md.html
Go源码阅读】(十一):Golang Web框架GIN路由/httprouter源码阅读
https://www.bilibili.com/video/BV1xf4y1m7bW?from=search&seid=1034343815651857298
https://jiajunhuang.com/articles/2020_05_09-httprouter.md.html
https://www.processon.com/view/link/5f4b70c2079129356ec5cb70#map
https://www.liwenzhou.com/posts/Go/read_gin_sourcecode/#autoid-0-1-1