• Go For Web:Golang http 包详解(源码剖析)


    前言:

    本文作为解决如何通过 Golang 来编写 Web 应用这个问题的前瞻,对 Golang 中的 Web 基础部分进行一个简单的介绍。目前 Go 拥有成熟的 Http 处理包,所以我们去编写一个做任何事情的动态 Web 程序应该是很轻松的,接下来我们就去学习了解一些关于 Web 的相关基础,了解一些概念,以及 Golang 是如何运行一个 Web 程序的。
    文章预计分为四个部分逐步更新
    2023-04-13 星期四 一更 全文共计约 3800 字 阅读大约花费 5 分钟
    2023-04-14 星期五 二更(两篇) 全文共计约 2000 字 阅读大概花费 4 分钟
    2023-04-14 星期五 三更 全文共计约 2000 字 阅读大概花费 5 分钟


    文章目录:

    1. Web 的工作方式
    2. 用 Go 搭建一个最简单的 Web 服务
    3. 了解 Golang 运行 web 的原理
    4. Golang http 包详解(源码剖析)

    正文:

    Golang http 包详解(源码剖析)

    前面小节我们认识了 Web 的工作方式,也成功用 Go 搭建了一个最简单的 Web 服务了解了 Golang 运行 Web 的原理。现在我们详细地去解剖以下 http 包,看看它如何实现整个过程的

    Go 的 http 包中有两个核心功能:Conn 、ServeMux

    Conn 的 goroutine

    与我们使用其他语言编写 http 服务器不同, Go为了实现高并发和高性能,使用了 goroutines 来处理 Conn 的读写事件。这样让每个请求都能保持独立,相互不会阻塞,可以高效地响应网络事件,这是 Go 高效的保证。

    根据上一节,我们知道 Go 在等待客户端请求里面是这样写的:

    点击查看代码
    c, err := srv.newConn(rw)
    if ree != nil {
    	continue
    }
    go c.serve()

    这段代码中,客户端的每一次请求都会创建一个 Conn,这个 Conn 里面保存了这次请求的信息,然后再传递到对应的 handler,该handler中便可以读取到相应的 header 信息,这样保证了每个请求的独立性。

    ServeMux 的自定义

    在之前我们 使用 conn.server 的时候,其实内部是调用了 http 包默认的路由器也就是DefaultServeMux,通过这个路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?

    结构如下:

    • 首先是一个 自定义类型结构体 ServeMux 其中包含一个
      和一个 路由规则
      image

    • 路由规则中一个 string 对应一个 mux 实体,我们来看看 muxEntry 它也是一个自定义类型结构体,包含一个 布尔值,一个Handler 处理函数
      image

    • 最后再来看看 Handler 的定义,它其实是一个接口,实现了 ServeHTTP 这个函数
      image

    这个时候我们可以回过头来看我们之前自己写的 Web 服务器

    点击查看代码
    // Handler处理函数
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
    	r.ParseForm() // 解析参数,默认不会解析
    	fmt.Println(r.Form)// 以下这些信息是输出到服务端的打印信息:请求表单form、路径path、格式scheme
    	fmt.Println("path", r.URL.Path)
    	fmt.Println("scheme", r.URL.Scheme)
    	fmt.Println(r.Form["url_long"])
    	for k, v := range r.Form {
    		fmt.Println("key:", k)
    		fmt.Println("val:", strings.Join(v, ""))
    	}
    	fmt.Fprintln(w, "Hello astaxie!") // 输出到客户端
    }
    调用: `http.HandleFunc("/", sayhelloName) // 设置访问的路由`

    我们会发现,我们自己写的 sayhelloName 函数并没有实现 ServeHTTP 这个函数,也就是说按照常理我们并没有实现 Handler 这个接口,那我们是怎么添加的?

    原来, http 包里面还定义了一个自定义函数类型 HandlerFunc,而我们定义的函数 sayhelloName 就是这个 HandlerFunc 调用之后的结果,这个自定义函数类型默认会实现 ServeHTTP 这个方法,即我们调用了 HandlerFunc(f)强制类型转换 f 成为了 HandlerFunc 类型,这样 f 就拥有了ServeHTTP 方法
    image

    路由器里存储好了相应的路由规则(Response / Request)之后,那么具体的请求又是怎么分发的呢?
    路由器接收到请求之后调用 mux.handler(r).ServeHTTP(w,r)
    也就是调用对应路由的 handler 的 ServerHTTP 接口,让我们来看看
    mux.handler(r)是怎么处理的↓
    image

    我们可以看到它是根据用户请求的 URL 和路由器里面存储的 map 去匹配的,当匹配到之后返回存储的 handler,调用这个 handler 的 ServeHTTP 接口就可以执行相应的函数了

    通过上面的介绍,我们大致了解了整个构建路由的过程,Go其实支持外部实现的路由器 而 ListenAndServe 的第二个参数就是用来配置外部路由器的,它是一个 Handler 接口,所以我们的外部路由只要实现了 Handler 接口就可以发挥作用,因此我们可以在自己实现的路由器的 ServeHTTP 里面实现自定义的路由功能

    贴个代码↓

    点击查看代码
    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    type MyMux struct {
    }
    
    func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	if r.URL.Path == "/" {
    		sayhelloName2(w, r)
    		return
    	} else {
    		http.NotFound(w, r)
    		return
    	}
    }
    
    func sayhelloName2(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintf(w, "Hello myroute!")
    }
    
    func main() {
    	mux := &MyMux{}
    	http.ListenAndServe(":9090", mux)
    }
    

    实现效果:
    image

    Go 代码的执行流程

    最后我们来梳理一下整个代码的执行过程

    • 首先调用 Http.HandleFunc
      按照顺序做了这几件事:
    1. 调用了 DefaultServeMux 的 HandleFunc
    2. 调用了 DefaultServeMux 的 Handler
    3. 往 DefaultServeMux 的 map[string]muxEntry 中增加对应的handler 和 路由规则
    • 其次调用 http.ListenAndServe(":9090",nil)
      按顺序做了这几件事:
    1. 实例化 Server

    2. 调用 Server 的 ListenAndServer()

    3. 调用 net.Listen("tcp", addr)监听端口

    4. 启动一个 for 循环,在循环题中 Accept 请求

    5. 对每一个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求开一个 go.c.serve()

    6. 读取每个请求的内容 w, err := c.readRequest()

    7. 判断 handler 是否为空,如果没有就设置 handler(默认设置)

    8. 调用 handler 的ServeHTTP

    9. 进入到 DefaultServerMux.ServeHTTP

    10. 根据 request 选择 handler, 并且进去到这个 handler 的 ServerHTTP
      image

    11. 选择 handler
      A 判断是否有路由能满足这个 request (循环遍历 ServerMux 的 muxEntry)
      B 如果满足,则调用这个路由 handler 的 ServeHTTP
      C 如果不满足,则调用 NotFoundHandler 的 ServeHTTP

    总结

    到这里为止我们从第一章介绍了 HTTP 协议,DNS 解析过程,了解了 Web 的工作方式,第二章分别用 Go 搭建一个最简单的 Web 服务,并且了解 Golang 运行 web 的原理,在最后一章,我们还深入到 net/http 包中的源码里为大家揭开了更底层的原理

    既然对 Go 开发 Web 有了初步的了解,接下来我们就可以有十足的信心去学习更多 Go For Web 的后续内容了!

    关于 Golang 基础部分 以及 计算机网络部分读者可以参阅我的往期 blog👇
    Goalng:基础复习一遍过

    漫谈计算机网络:网络层 ------ 重点:IP协议与互联网路由选择协议

    以上

    看完记得留下一个👍


    __EOF__

  • 本文作者: slowlydance2me
  • 本文链接: https://www.cnblogs.com/slowlydance2me/p/17318092.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    全年冲刺200万辆!座舱域控制器进入「软硬升级」周期
    leetcode 210. 课程表 II 拓扑排序
    【七夕】是时候展现专属于程序员的“浪漫”了
    如何写好测试用例,看完即会
    JavaScript系列从入门到精通系列第十三篇:JavaScript中基本数据类型和引用数据类型,创建对象的两种方式
    12. 转义字符及print函数的参数
    PHP基础教程——总结W3school
    嘿,朋友,其实 CSS 动画超简单的 - 时间函数篇(贝塞尔曲线、steps,看完还不懂算我输)
    基于Flume+Kafka+Hbase+Flink+FineBI的实时综合案例(四)实时计算需求及技术方案
    讲解一下docker和docker常用命令
  • 原文地址:https://www.cnblogs.com/slowlydance2me/p/17318092.html