gin源码解析(1) - gin 与 net/http 的关系
gin
是目前Go
里面使用最广泛的框架之一了,弄清楚gin
框架的原理,有助于我们更好的使用gin
。这个系列gin
源码阅读会逐步讲明白 gin
的原理,欢迎关注后续文章。
gin 概览
想弄清楚 gin, 需要弄明白以下几个问题:
request数据是如何流转的
gin框架到底扮演了什么角色
请求从gin流入net/http, 最后又是如何回到gin中
gin的context为何能承担起来复杂的需求
gin的路由算法
gin的中间件是什么
gin的Engine具体是个什么东西
net/http的requeset, response都提供了哪些有用的东西
从gin
的官方第一个demo入手.
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() // listen and serve on 0.0.0.0:8080
}
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
}
看到开始调用的是 http.ListenAndServe(address, engine), 这个函数是net/http
的函数, 然后请求数据就在net/http
开始流转.
Request 数据是如何流转的
先不使用gin
, 直接使用net/http
来处理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是如何建立起来的
简单的说一下http请求是如何建立起来的(需要有基本的网络基础, 可以找相关的书籍查看, 推荐看UNIX网络编程卷1:套接字联网API
)
TCP/IP 五层模型
socket建立过程
在TCP/IP五层模型下
, HTTP
位于应用层
, 需要有传输层
来承载HTTP
协议. 传输层比较常见的协议是TCP
,UDP
, SCTP
等. 由于UDP
不可靠, SCTP
有自己特殊的运用场景, 所以一般情况下HTTP
是由TCP
协议承载的(可以使用wireshark抓包然后查看各层协议)
使用TCP
协议的话, 就会涉及到TCP
是如何建立起来的. 面试中能够常遇到的名词三次握手
, 四次挥手
就是在这里产生的. 具体的建立流程就不在陈述了, 大概流程就是图中左半边
所以说, 要想能够对客户端http请求进行回应的话, 就首先需要建立起来TCP连接, 也就是socket
. 下面要看下net/http
是如何建立起来socket
?
net/http 是如何建立 socket 的
从图上可以看出, 不管server代码如何封装, 都离不开bind
,listen
,accept
这些函数. 就从上面这个简单的demo入手查看源码.
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)}
}
注册路由
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))
})
这段代码是在注册一个路由及这个路由的handler到DefaultServeMux
中
// server.go:L2366-2388
func (mux *ServeMux) Handle(pattern string, handler Handler) {mux.mu.Lock()defer mux.mu.Unlock()if pattern == "" {panic("http: invalid pattern")}if handler == nil {panic("http: nil handler")}if _, exist := mux.m[pattern]; exist {panic("http: multiple registrations for " + pattern)}if mux.m == nil {mux.m = make(map[string]muxEntry)}mux.m[pattern] = muxEntry{h: handler, pattern: pattern}if pattern[0] != '/' {mux.hosts = true}
}
可以看到这个路由注册太过简单了, 也就给gin
, iris
, echo
等框架留下了扩展的空间, 后面详细说这个东西
服务监听及响应
上面路由已经注册到net/http
了, 下面就该如何建立socket了, 以及最后又如何取到已经注册到的路由, 将正确的响应信息从handler中取出来返回给客户端
1.创建 socket
if err := http.ListenAndServe(":8000", nil); err != nil {fmt.Println("start http server fail:", err)
}
// net/http/server.go:L3002-3005
func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}
// net/http/server.go:L2752-2765
func (srv *Server) ListenAndServe() error {// ... 省略代码ln, err := net.Listen("tcp", addr) // <-----看这里listenif err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
2.Accept 等待客户端链接
// net/http/server.go:L2805-2853
func (srv *Server) Serve(l net.Listener) error {// ... 省略代码for {rw, e := l.Accept() // <----- 看这里acceptif e != nil {select {case <-srv.getDoneChan():return ErrServerCloseddefault:}if ne, ok := e.(net.Error); ok && ne.Temporary() {if tempDelay == 0 {tempDelay = 5 * time.Millisecond} else {tempDelay *= 2}if max := 1 * time.Second; tempDelay > max {tempDelay = max}srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)time.Sleep(tempDelay)continue}return e}tempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx) // <--- 看这里}
}
3. 提供回调接口 ServeHTTP
// net/http/server.go:L1739-1878
func (c *conn) serve(ctx context.Context) {// ... 省略代码serverHandler{c.server}.ServeHTTP(w, w.req)w.cancelCtx()if c.hijacked() {return}w.finishRequest()// ... 省略代码
}
// net/http/server.go:L2733-2742
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req)
}
// net/http/server.go:L2352-2362
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {if r.RequestURI == "*" {if r.ProtoAtLeast(1, 1) {w.Header().Set("Connection", "close")}w.WriteHeader(StatusBadRequest)return}h, _ := mux.Handler(r) // <--- 看这里h.ServeHTTP(w, r)
}
4. 回调到实际要执行的 ServeHTTP
// net/http/server.go:L1963-1965
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}
这基本是整个过程的代码了.
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, 然后将处理结果返回给客户端
从这里也能够看出来, net/http
基本上提供了全套的服务.
为什么会出现很多go框架
// net/http/server.go:L2218-2238
func (mux *ServeMux) match(path string) (h Handler, pattern string) {// Check for exact match first.v, ok := mux.m[path]if ok {return v.h, v.pattern}// Check for longest valid match.var n = 0for k, v := range mux.m {if !pathMatch(k, path) {continue}if h == nil || len(k) > n {n = len(k)h = v.hpattern = v.pattern}}return
}
从这段函数可以看出来, 匹配规则过于简单, 当能匹配到路由的时候就返回其对应的handler, 当不能匹配到时就返回/
. net/http
的路由匹配根本就不符合 RESTful 的规则,遇到稍微复杂一点的需求时,这个简单的路由匹配规则简直就是噩梦。
所以基本所有的go框架干的最主要的一件事情就是重写net/http
的route。我们直接说 gin
就是一个 httprouter 也不过分, 当然gin
也提供了其他比较主要的功能, 后面会一一介绍。
综述, net/http基本已经提供http
服务的70%的功能, 那些号称贼快的go框架, 基本上都是提供一些功能, 让我们能够更好的处理客户端发来的请求. 如果你有兴趣的话,也可以基于 net/http 做一个 Go 框架出来。
以上内容转载自公众号HHFCodeRv,如需转载请关注公众号进行联系~!
- END -
扫码关注公众号「网管叨bi叨」
给网管个星标,第一时间吸我的知识
gin源码解析(1) - gin 与 net/http 的关系相关推荐
- gin 源码解析 - 详解http请求在gin中的流转过程
本篇文章是 gin 源码分析系列的第二篇,这篇文章我们主要弄清一个问题:一个请求通过 net/http 的 socket 接收到请求后, 是如何回到 gin 中处理逻辑的? 我们仍然以 net/htt ...
- Gin源码解析和例子——中间件(middleware)
在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...
- Java源码解析:hashCode与相同对象的关系
1.普通类对象 1. hashCode相同,不一定是同一个对象 2. 同一个对象的,hashCode值一定相同 2. 数值型的原始数据类型对应的包装类 只要值是一样的,hashCode就会是相同的.尽 ...
- Gin源码解析和例子——路由
Gin是一个基于golang的net包实现的网络框架.从github上,我们可以看到它相对于其他框架而言,具有优越的性能.本系列将从应用的角度来解析其源码.(转载请指明出于breaksoftware的 ...
- Go框架 gin 源码学习--路由的实现原理剖析
往期回顾: gin源码解析 - gin 与 net/http 的关系 gin 源码解析 - 详解http请求在gin中的流转过程 上面两篇文章基本讲清楚了 Web Server 如何接收客户端请求,以 ...
- Java 线程池ThreadPoolExecutor的应用与源码解析
ThreadPoolExecutor 工作原理 假设corePool=5,队列大小为100,maxnumPoolSize为10 向线程池新提交一个任务,会根据ThreadFactory创建一个新的线程 ...
- 详细讲解go web框架之gin框架源码解析记录及思路流程和理解
开篇 首先gin 框架是在 官方提供的net/http标准包进行的相应封装. 那么要想理解gin框架, 就要先懂一些 net/http标准包 的相关知识. 可以参考中文的 文档: https://st ...
- android tcp socket框架_最流行的 Web 框架 Gin 源码阅读
最近公司大部分项目开始往golang换, api的框架选定使用gin, 于是将 gin的源码看了一遍, 会用几篇文章将gin的流程及流程做一个梳理, 下面进入正题. gin框架预览 上图大概是 gin ...
- 谷歌BERT预训练源码解析(二):模型构建
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...
最新文章
- 网络通信应用开发利器!—— ESPlus —— ESFramework通信框架的增强库
- Latent dirichlet allocation note -- Prepare
- TFS 2008 中文版下载及安装完整图解
- 玩聚SR和FriendFeed的区别
- 验证DetailsView插入数据不为空
- Unity AI副总裁Danny Lange:如何用AI助推游戏行业?
- JEECG 命名规范
- 推荐几篇开源论文,包含人脸、目标检测跟踪、分割、去噪、超分辨率等
- 微服务 java9模块化_Java9系列第8篇-Module模块化编程
- php百度鹰眼,Android 百度鹰眼里程计算简单实列
- java arraylist 添加对象_如何在Java中将对象添加到ArrayList
- 2020最新淘宝等级表图及商品发布限制数量类目表
- HTML led字体包
- 合宙Air105 + GC032A摄像头驱动显示教程说明
- 母亲的牛奶 Mother's Milk(usaco)
- [安卓开发] 总结一些android的云测试平台
- 好玩的免费GM游戏整理汇总
- 买华为手机U8825D的体验
- 解决mac 10.11 以后 无法使用未签名第三驱动
- ROS系统安装 kinetic (超详细)