脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Gin 源码阅读之 Gin 与 Net/Http 的关系

Gin 源码阅读之 Gin 与 Net/Http 的关系

2021-09-10 00:46HHFCodeRvhaohongfan Golang

gin 是目前 Go 里面使用最广泛的框架之一了,弄清楚 gin 框架的原理,有助于我们更好的使用 gin。这个系列 gin 源码阅读会逐步讲明白 gin 的原理,欢迎关注后续文章。

Gin 源码阅读之 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入手.

  1. package main 
  2.  
  3. import "github.com/gin-gonic/gin" 
  4.  
  5. func main() { 
  6.     r := gin.Default() 
  7.     r.GET("/ping", func(c *gin.Context) { 
  8.         c.JSON(200, gin.H{ 
  9.           "message""pong"
  10.         }) 
  11.     }) 
  12.     r.Run() // listen and serve on 0.0.0.0:8080 

r.Run() 的源码:

  1. func (engine *Engine) Run(addr ...string) (err error) { 
  2.     defer func() { debugPrintError(err) }() 
  3.  
  4.     address := resolveAddress(addr) 
  5.     debugPrint("Listening and serving HTTP on %s\n", address) 
  6.     err = http.ListenAndServe(address, engine) 
  7.     return 

看到开始调用的是 http.ListenAndServe(address, engine), 这个函数是net/http的函数, 然后请求数据就在net/http开始流转.

Request 数据是如何流转的

先不使用gin, 直接使用net/http来处理http请求

  1. func main() { 
  2.     http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 
  3.         w.Write([]byte("Hello World")) 
  4.     }) 
  5.  
  6.     if err := http.ListenAndServe(":8000", nil); err != nil { 
  7.         fmt.Println("start http server fail:", err) 
  8.     } 

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

HTTP是如何建立起来的

简单的说一下http请求是如何建立起来的(需要有基本的网络基础, 可以找相关的书籍查看, 推荐看UNIX网络编程卷1:套接字联网API)

Gin 源码阅读之 Gin 与 Net/Http 的关系

TCP/IP 五层模型

Gin 源码阅读之 Gin 与 Net/Http 的关系

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入手查看源码.

  1. func main() { 
  2.     http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 
  3.         w.Write([]byte("Hello World")) 
  4.     }) 
  5.  
  6.     if err := http.ListenAndServe(":8000", nil); err != nil { 
  7.         fmt.Println("start http server fail:", err) 
  8.     } 

注册路由

  1. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 
  2.     w.Write([]byte("Hello World")) 
  3. }) 

这段代码是在注册一个路由及这个路由的handler到DefaultServeMux中

  1. // server.go:L2366-2388 
  2. func (mux *ServeMux) Handle(pattern string, handler Handler) { 
  3.     mux.mu.Lock() 
  4.     defer mux.mu.Unlock() 
  5.  
  6.     if pattern == "" { 
  7.         panic("http: invalid pattern"
  8.     } 
  9.     if handler == nil { 
  10.         panic("http: nil handler"
  11.     } 
  12.     if _, exist := mux.m[pattern]; exist { 
  13.         panic("http: multiple registrations for " + pattern) 
  14.     } 
  15.  
  16.     if mux.m == nil { 
  17.         mux.m = make(map[string]muxEntry) 
  18.     } 
  19.     mux.m[pattern] = muxEntry{h: handler, pattern: pattern} 
  20.  
  21.     if pattern[0] != '/' { 
  22.         mux.hosts = true 
  23.     } 

可以看到这个路由注册太过简单了, 也就给gin, iris, echo等框架留下了扩展的空间, 后面详细说这个东西

服务监听及响应

上面路由已经注册到net/http了, 下面就该如何建立socket了, 以及最后又如何取到已经注册到的路由, 将正确的响应信息从handler中取出来返回给客户端

1.创建 socket

  1. if err := http.ListenAndServe(":8000", nil); err != nil { 
  2.     fmt.Println("start http server fail:", err) 
  1. // net/http/server.go:L3002-3005 
  2. func ListenAndServe(addr string, handler Handler) error { 
  3.     server := &Server{Addr: addr, Handler: handler} 
  4.     return server.ListenAndServe() 
  1. // net/http/server.go:L2752-2765 
  2. func (srv *Server) ListenAndServe() error { 
  3.     // ... 省略代码 
  4.     ln, err := net.Listen("tcp", addr) // <-----看这里listen 
  5.     if err != nil { 
  6.       return err 
  7.     } 
  8.     return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) 

2.Aaccept 等待客户端链接

  1. // net/http/server.go:L2805-2853 
  2. func (srv *Server) Serve(l net.Listener) error { 
  3.     // ... 省略代码 
  4.     for { 
  5.       rw, e := l.Accept() // <----- 看这里accept 
  6.       if e != nil { 
  7.         select { 
  8.         case <-srv.getDoneChan(): 
  9.           return ErrServerClosed 
  10.         default
  11.         } 
  12.         if ne, ok := e.(net.Error); ok && ne.Temporary() { 
  13.           if tempDelay == 0 { 
  14.             tempDelay = 5 * time.Millisecond 
  15.           } else { 
  16.             tempDelay *= 2 
  17.           } 
  18.           if max := 1 * time.Second; tempDelay > max { 
  19.             tempDelay = max 
  20.           } 
  21.           srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) 
  22.           time.Sleep(tempDelay) 
  23.           continue 
  24.         } 
  25.         return e 
  26.       } 
  27.       tempDelay = 0 
  28.       c := srv.newConn(rw) 
  29.       c.setState(c.rwc, StateNew) // before Serve can return 
  30.       go c.serve(ctx) // <--- 看这里 
  31.     } 

3. 提供回调接口 ServeHTTP

  1. // net/http/server.go:L1739-1878 
  2. func (c *conn) serve(ctx context.Context) { 
  3.     // ... 省略代码 
  4.     serverHandler{c.server}.ServeHTTP(w, w.req) 
  5.     w.cancelCtx() 
  6.     if c.hijacked() { 
  7.       return 
  8.     } 
  9.     w.finishRequest() 
  10.     // ... 省略代码 
  1. // net/http/server.go:L2733-2742 
  2. func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { 
  3.     handler := sh.srv.Handler 
  4.     if handler == nil { 
  5.       handler = DefaultServeMux 
  6.     } 
  7.     if req.RequestURI == "*" && req.Method == "OPTIONS" { 
  8.       handler = globalOptionsHandler{} 
  9.     } 
  10.     handler.ServeHTTP(rw, req) 
  1. // net/http/server.go:L2352-2362 
  2. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { 
  3.     if r.RequestURI == "*" { 
  4.       if r.ProtoAtLeast(1, 1) { 
  5.         w.Header().Set("Connection""close"
  6.       } 
  7.       w.WriteHeader(StatusBadRequest) 
  8.       return 
  9.     } 
  10.     h, _ := mux.Handler(r) // <--- 看这里 
  11.     h.ServeHTTP(w, r) 

4. 回调到实际要执行的 ServeHTTP

  1. // net/http/server.go:L1963-1965 
  2. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { 
  3.    f(w, r) 

这基本是整个过程的代码了.

  1. ln, err := net.Listen("tcp", addr)做了初试化了socket, bind, listen的操作.
  2. rw, e := l.Accept()进行accept, 等待客户端进行连接
  3. go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发操作
  4. h, _ := mux.Handler(r) 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端

从这里也能够看出来, net/http基本上提供了全套的服务.

为什么会出现很多go框架

  1. // net/http/server.go:L2218-2238 
  2. func (mux *ServeMux) match(path string) (h Handler, pattern string) { 
  3.     // Check for exact match first
  4.     v, ok := mux.m[path] 
  5.     if ok { 
  6.         return v.h, v.pattern 
  7.     } 
  8.  
  9.     // Check for longest valid match. 
  10.     var n = 0 
  11.     for k, v := range mux.m { 
  12.       if !pathMatch(k, path) { 
  13.           continue 
  14.       } 
  15.       if h == nil || len(k) > n { 
  16.           n = len(k) 
  17.           h = v.h 
  18.           pattern = v.pattern 
  19.       } 
  20.     } 
  21.     return 

从这段函数可以看出来, 匹配规则过于简单, 当能匹配到路由的时候就返回其对应的handler, 当不能匹配到时就返回/. net/http的路由匹配根本就不符合 RESTful 的规则,遇到稍微复杂一点的需求时,这个简单的路由匹配规则简直就是噩梦。

所以基本所有的go框架干的最主要的一件事情就是重写net/http的route。我们直接说 gin就是一个 httprouter 也不过分, 当然gin也提供了其他比较主要的功能, 后面会一一介绍。

综述, net/http基本已经提供http服务的70%的功能, 那些号称贼快的go框架, 基本上都是提供一些功能, 让我们能够更好的处理客户端发来的请求. 如果你有兴趣的话,也可以基于 net/http 做一个 Go 框架出来。

延伸 · 阅读

精彩推荐
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08