gin 源码阅读(1) - gin 与 net/http 的关系

gin 概览

想弄清楚 gin, 需要弄明白以下几个问题:

  • request数据是如何流转的
  • gin框架到底扮演了什么角色
  • 请求从gin流入net/http, 最后又是如何回到gin中
  • gin的context为何能承担起来复杂的需求
  • gin的路由算法
  • gin的中间件是什么
  • gin的Engine具体是个什么东西
  • net/http的requeset, response都提供了哪些有用的东西

gin的官方第一个demo入手.

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.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-2388func (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-3005func 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) // <-----看这里listen
    if err != nil {
      return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

2.Accept 等待客户端链接

// net/http/server.go:L2805-2853func (srv *Server) Serve(l net.Listener) error {    // ... 省略代码    for {      rw, e := l.Accept() // <----- 看这里accept      if e != nil {        select {        case <-srv.getDoneChan():          return ErrServerClosed        default:        }        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 = 0      c := srv.newConn(rw)      c.setState(c.rwc, StateNew) // before Serve can return      go 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-2742func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {    handler := sh.srv.Handler    if 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-1965func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {   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框架

// 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 = 0
    for k, v := range mux.m {
      if !pathMatch(k, path) {
          continue
      }
      if h == nil || len(k) > n {
          n = len(k)
          h = v.h
          pattern = v.pattern
      }
    }
    return
}

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

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

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

当然,如果你觉得本篇文章还不错,请大家帮忙点击

(0)

相关推荐

  • Golang 单元测试:有哪些误区和实践?

    阿里妹导读:单元测试作为开发的有力武器,应该在软件开发的各个流程中发挥它的价值.原始的开发模式(开发完毕,交给测试团队进行端到端测试)的流程,应该逐步向 devops 的方向转变.本文是一个转型的具体 ...

  • 通过Consul Raft库打造自己的分布式系统

    通用的CP系统有etcd和consul, 通用的对立面就是专用系统. 所以在某些场合是有这种需求的. 然而etcd embed的可用性极差, Windows上面跑会出现各种问题, 而且不能定制协议, ...

  • Go Fiber 框架系列教程 03:中间件

    2021-10-18 阅读本文大概需要 10 分钟. 大家好,我是 polarisxu. Middleware(中间件) 是一个 Web 框架重要的组成部分,通过这种模式,可以方便的扩展框架的功能.目 ...

  • 极速精简 Go 版 Logstash

    hxl Go语言中文网 今天 前言 今天来介绍 go-zero 生态的另一个组件 go-stash.这是一个 logstash 的 Go 语言替代版,我们用 go-stash 相比原先的 logsta ...

  • golang API开发过程的中的自动重启(基于gin框架)

    概要 实现方式 补充 syscall.Exec 概要 基于 golang Gin 框架开发 web 服务时, 需要时不时的 go build , 然后重启服务查看运行结果. go build 的过程集 ...

  • 一个超级实用的源码阅读小技巧

    在学习编程的路途漫漫,优秀的源码是非常珍贵的学习资源,阅读源码也是有效提高自己的一个好方法. 工欲善其事必先利其器: 我发现函数调用图可以让我们更加直观地了解到源码函数直接的调用和层次关系,提高阅读源 ...

  • spark源码阅读--shuffle过程分析 ShuffleManager(一)

    ShuffleManager(一) 本篇,我们来看一下spark内核中另一个重要的模块,Shuffle管理器ShuffleManager.shuffle可以说是分布式计算中最重要的一个概念了,数据的j ...

  • dubbo源码阅读之服务目录

    服务目录 服务目录对应的接口是Directory,这个接口里主要的方法是 List<Invoker<T>> list(Invocation invocation) throws ...

  • 手把手教学APK反编译实现源码阅读

    手把手教学APK反编译实现源码阅读

  • Vue2 源码阅读(三) 双向绑定原理

    Vue2 源码阅读(三) 双向绑定原理

  • Vue2.x 响应式部分源码阅读记录

    之前也用了一段时间Vue,对其用法也较为熟练了,但是对各种用法和各种api使用都是只知其然而不知其所以然.最近利用空闲时间尝试的去看看Vue的源码,以便更了解其具体原理实现,跟着学习学习. Proxy ...

  • Mybatis源码阅读套路,一次性打包发您~

    很多人看源码都不知道如何看,今天来教教大家如何看源码. 前提是我们需要对整个Mybatis的原理.工作流程和模块进行一个整体的直知晓,另外还要有使用经验. 建议先看这两篇文章: 本文主要内容: 源码下 ...

  • Vue2 源码阅读(二) new Vue()做了什么

    Vue2 源码阅读(二) new Vue()做了什么

  • redis 5.0.7 源码阅读——动态字符串sds

    redis中动态字符串sds相关的文件为:sds.h与sds.c 一.数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef ...