net/http 框架源码解读

一、Hello World

使用net/http编写一个简单的web服务器, 定义了一个UserHandler的处理函数,通过HandleFunc来将路由和handler进行绑定,最后通过ListenAndServe启动web服务,后面我将handler统称为视图函数

package mainimport "net/http"func UserHandler(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World!"))
}func main() {http.HandleFunc("/", UserHandler)http.ListenAndServe(":8080", nil)
}

二、http.HandlFunc 源码解析

HandleFunc 接受两个参数,第一个是路由地址,第二个是一个视图函数,该函数接受两个参数 ResponseWriter 和 *Request ,前者是用来处理http请求的响应,后者是用来处理http请求的参数。

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)
}

DefaultServeMux 是一个结构体变量,真实的结构体是ServeMux

var DefaultServeMux = &defaultServeMuxvar defaultServeMux ServeMuxtype ServeMux struct {mu    sync.RWMutexm     map[string]muxEntryes    []muxEntry // slice of entries sorted from longest to shortest.hosts bool       // whether any patterns contain hostnames
}type muxEntry struct {h       Handlerpattern string
}type Handler interface {ServeHTTP(ResponseWriter, *Request)
}

ServeMux 有4个字段

  • 第一个字段是读写锁,
  • 第二个字段是一个map,key其实是路由地址,val是一个muxEntry结构体,该结构体由路由地址Handler接口组成。
  • 第三个字段是muxEntry切片,这个字段是用来处理存储路由地址是/结尾的,比如/abc/d/,并且将最长的pattern从长到短进行排序,后面的代码会做解释。

ServeMux (DefaultServeMux)定义了一个HandleFunc方法,该方法就是将用户的路由地址视图函数继续传递给Handle方法,并判断视图函数是否为空。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {if handler == nil {panic("http: nil handler")}mux.Handle(pattern, HandlerFunc(handler))
}

mux.Handle(pattern, HandlerFunc(handler)) 中HandlerFunc() 是将用户传递的视图函数转为 HandlerFunc 类型, 并不是函数调用。

type HandlerFunc func(ResponseWriter, *Request)// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}

这里为什么要将视图函数转为HandlerFunc呢?在上面的代码中muxEntry结构体存储一个Handler接口,这个接口的定义了一个ServerHTTP方法,你会发现HandlerFunc恰好实现一个ServeHTTP方法,所以HandlerFunc相当于实现了Handler接口,这样用户定义的视图函数就可以赋值给muxEntry结构体的h字段了
HandlerFunc 类型是一种适配器,允许将普通函数用作 HTTP 处理程序。

我们接着往下看mux.Handle内部,给ServeMux结构体初始化,并将视图函数和路由地址加载到m字段和es字段中。

func (mux *ServeMux) Handle(pattern string, handler Handler) {// 校验路由地址是否已经定义过了。if _, exist := mux.m[pattern]; exist {panic("http: multiple registrations for " + pattern)}// 初始化 m 字段if mux.m == nil {mux.m = make(map[string]muxEntry)}// 构造muxEntry结构体,并将视图函数和路由地址传入。e := muxEntry{h: handler, pattern: pattern}mux.m[pattern] = e// 如果路由地址最后是以 / 结尾,则将mux.es切片进行排序后重新赋值给mux.esif pattern[len(pattern)-1] == '/' {mux.es = appendSorted(mux.es, e)}if pattern[0] != '/' {mux.hosts = true}
}

appendSorted 函数内部主要是将路由地址从长到短进行排序后返回[]muxEntry,并重新赋值给ServeMux结构体的es字段。

以上就是HandlFunc的源码,还是挺简单的,主要做的事情就是将用户定义的路由地址和视图函数填充到ServeMux结构体

三、http.ListenAndServe 源码解析

func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}

ListenAndServe接受一个地址和一个Handler接口,然后实例化Server结构体,然后调用ServerListenAndServe方法。
Server 结构体由很多字段,包含TSL配置,读取超时时间,响应超时时间等。

type Server struct {Addr stringHandler Handler // handler to invoke, http.DefaultServeMux if nilTLSConfig *tls.ConfigReadTimeout time.DurationReadHeaderTimeout time.DurationWriteTimeout time.Duration......
}

ListenAndServe 对用户传递的addr进行校验,然后使用net.Listen创建监听对象,并传入Serve方法。

Serve 方法中我去掉了一些代码,保留了主要部分,baseCtx 主要用来保存整个请求生命周期的上下文,
这里有一个指数退避策略的用法。如果l.Accept()调用返回错误,我们判断该错误是不是临时性地(ne.Temporary())。如果是临时性错误,Sleep一小段时间后重试,每发生一次临时性错误,Sleep的时间翻倍,最多Sleep 1s。获得新连接后,将其封装成一个conn对象(srv.newConn(rw)),创建一个 goroutine 运行其serve()方法。省略无关逻辑的代码如下:

func (srv *Server) Serve(l net.Listener) error {ctx := context.WithValue(baseCtx, ServerContextKey, srv)var tempDelay time.Duration // how long to sleep on accept failurefor {rw, err := l.Accept()if err != nil {if srv.shuttingDown() {return ErrServerClosed}if ne, ok := err.(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", err, tempDelay)time.Sleep(tempDelay)continue}return err}connCtx := ctxtempDelay = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew, runHooks) // before Serve can returngo c.serve(connCtx)}
}

serve()方法其实就是不停地读取客户端发送地请求,创建serverHandler对象调用其ServeHTTP()方法去处理请求,然后做一些清理工作。serverHandler只是一个中间的辅助结构,关键点就在于 ServeHTTP方法。

func (c *conn) serve(ctx context.Context) {c.remoteAddr = c.rwc.RemoteAddr().String()ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())for {// 读取请求内容w, err := c.readRequest(ctx)// 执行用户的视图函数serverHandler{c.server}.ServeHTTP(w, w.req)// 返回响应w.finishRequest()}
}

ServeHTTP方法先判断HandlerServer结构体中的Handler,在http.ListenAndServe(":8080", nil)时,这个nil其实就是作为handler参数传入的)若为nil则使用默认的DefaultServeMux,这时就会发现通过http.HandlerFunc函数将路由和视图函数 塞进了DefaultServeMux结构体被使用到了。

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}handler.ServeHTTP(rw, req)
}

handler.ServeHTTP(rw, req) 就相当于调用了DefaultServeMux结构体的ServeHTTP方法,并将前面读取到的请求内存和封装的响应w传递进去
ServeMux结构体的ServeHTTP方法内部通过调用mux.Handler(r)方法来获得一个Handler接口,最后调用h接口的ServeHTTP方法。
那么h接口是由什么类型实现呢?我们往后看。

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {// 返回用户的视图函数,是HandleFunc类型。h, _ := mux.Handler(r)h.ServeHTTP(w, r)
}
  • mux.Handler(r ) 调用

mux.Handler(r) 方法最后调用handler方法,通过用户请求的地址,使用match方法来匹配视图函数并返回。

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {return mux.handler(host, r.URL.Path)
}func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {h, pattern = mux.match(path)return
}func (mux *ServeMux) match(path string) (h Handler, pattern string) {// Check for exact match first.// 检测path是否存在ServeMux的m中v, ok := mux.m[path]if ok {return v.h, v.pattern}// Check for longest valid match.  mux.es contains all patterns// that end in / sorted from longest to shortest.// 匹配路由地址是否为/结尾的路由中的一部分// 比如/ab 是满足 /ab/c/的前缀匹配。注意es中是已经按照路由从长到短排序过了的。for _, e := range mux.es {if strings.HasPrefix(path, e.pattern) {return e.h, e.pattern}}return nil, ""
}
  • h.ServeHTTP(w, r) 调用

前面 h, _ := mux.Handler(r)返回的h就是HandlerFunc 类型转换后的视图函数,所以就相当于调用HandlerFunc类型的ServeHTTP方法
ServeHTTP方法内部就是调用自身,也就是执行了用户的视图函数。

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {f(w, r)
}

最后在w.finishRequest()方法返回响应,至此从定义视图函数,到接受请求并执行视图函数,最后返回响应体的基本路线已经完成了。

附上两张调用流程图,我觉得图片做的挺好的。
图片来源:https://blog.csdn.net/s2603898260/article/details/121575056
image.png
image.png

参考文章:https://darjun.github.io/2021/07/13/in-post/godailylib/nethttp/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/745522.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发,提供3套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI接收OSD动态字符叠加输出应用本方案的SDI接收HLS多路视频融…

什么是RPC?RPC 和 HTTP 对比?RPC有什么缺点?市面上常用的RPC框架?

什么是RPC? RPC(Remote Procedure Call)是一种用于实现不同计算机间程序之间通信的协议,主要用于分布式系统中。它允许程序员编写客户端和服务器端代码,就像它们位于同一台机器上一样,而不需要显式地处理网…

uniapp中人脸识别图片并圈起人脸

效果如上,我用的是阿里云的人脸识别。首先,我们先封装一个阿里云的请求js文件 faceRecognition.js import CryptoJS from crypto-js//SignatureNonce随机数字 function signNRandom() {const Rand Math.random()const mineId Math.round(Rand * 1000…

C++ 多路音频pcm混音算法

1、均值化混音算法 不适合商用,声音的损失比较大,不建议用,建议用第二种声音混音 short remix(short pcm1,short pcm2){ int value pcm1 pcm2; return (short)(value/2) } 2、归一化混音算法 输入数据为48Khz-2-16bit音频数据 方法&#…

R语言lavaan结构方程模型(SEM)实践技术应用

基于R语言lavaan程序包,通过理论讲解和实际操作相结合的方式,由浅入深地系统介绍结构方程模型的建立、拟合、评估、筛选和结果展示的全过程。我们筛选大量经典案例,这些案例来自Nature、Ecology、Ecological Applications、Journal of Ecolog…

MySQL 数据库 下载地址 国内阿里云站点

mysql安装包下载_开源镜像站-阿里云 以 MySQL 5.7 为例 mysql-MySQL-5.7安装包下载_开源镜像站-阿里云

C#,图论与图算法,输出无向图(Un-directed Graph)全部环(cycle)的算法与源代码

1 无向图(Un-directed Graph)全部环 图算法中需要求解全部的环。 2 方法 使用图着色方法,用唯一的数字标记不同循环的所有顶点。图形遍历完成后,将所有类似的标记数字推送到邻接列表,并相应地打印邻接列表。 3 算法 将边插入到邻接列表中。调用DFS函数,该函数使用着色方…

C# wpf 使用GDI实现截屏

wpf截屏系列 第一章 使用GDI实现截屏(本章) 第二章 使用GDI实现截屏 第三章 使用DockPanel制作截屏框 第四章 实现截屏框热键截屏 第五章 实现截屏框实时截屏 第六章 使用ffmpeg命令行实现录屏 文章目录 wpf截屏系列前言一、导入gdi32方法一、NuGet获取…

解决无法登录到 ArcGIS Server Administrator

目录 问题复现原因分析解决办法 问题复现 今天在访问arcgisserver后台准备设置arcgis api for js请求路径时,登录之后出现500错误。Services Directoryhttp://xxx.xxx.xxx.xxx:6080/arcgis/admin/system/handlers/rest/servicesdirectory 原因分析 我实在两台虚拟机…

蚓链助新零售企业快速实现数字化转型

2024年3月11日星期一,今天来自广州市开利网络科技有限公司交付部传来喜讯!帮助新零售企业可以快速实现数字化转型的蚓链数字化生态系统速效解决方案正式面世!该蚓链速效数字化方案从如何决策、如何执行、如何落地三个主要问题入手给予了明确的…

【经验分享】Windows10无法通过SSHFS连接服务器

【经验分享】Windows10如何通过SSHFS连接服务器 前言问题分析解决方法 前言 现在很多公司出于成本考虑,不会为每一台电脑都提供高配置,所以需要通过访问云服务器来进行编译等操作。程序员如果配备的是一台windows电脑,那么需要访问linux服务…

Kamailio Debian安装

新方法是: apt install -y gnupg2 wget -O- https://deb.kamailio.org/kamailiodebkey.gpg | gpg --dearmor | tee /usr/share/keyrings/kamailio.gpg 老方法是: wget -O- http://deb.kamailio.org/kamailiodebkey.gpg | apt-key add - # apt-key已经淘…

软件测试的测试用例

一、测试用例的概念 测试用例(Test Case)是为了实施测试而向被测试的系统提供的一组集合,这组集合包含:测试环境、操作步骤、测试数据、预期结果等要素 二、测试用例设计方法 1.等价类 1.1思想 依据需求将输入,划分…

web蓝桥杯真题:成语学习

代码: //TODO 点击文字后,在idiom从左到右第一个空的位置加上改文字 getSingleWord(val) {let index this.idiom.indexOf() //从左往右查询空字符串this.$set(this.idiom, index, val) //响应式更新 },// TODO 校验成语是否输入正确答案 confirm…

vue iview 级联选择器遇到的坑

我们PC项目用到的前端技术栈是vue+iview,最近有个需求,要做个级联选择器,并且是懒加载动态加载后端返回的数据。效果如下: 如下图所示,在我们封装的公共组件form-box.vue里有我们级联选择器: 代码如下: <!--级联选择器--><template v-else-if="item.type…

基于Java的海南旅游景点推荐系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

在Django中使用PyJWT实现登录及验证功能

目录 1、安装PyJWT 2、对信息加密及解密 3、配置登录视图和及url 4、登录装饰器 5、在验证有登录权限的的视图中登录 PyJWT的使用 1、安装PyJWT pip isntall pyjwt 2、对信息加密及解密 import jwt import datetime from jwt import exceptions# 加密盐 JWT_SALT &qu…

腾讯云轻量服务器地域选择教程,2024最新地域选择攻略

腾讯云服务器地域怎么选择&#xff1f;不同地域之间有什么区别&#xff1f;腾讯云哪个地域好&#xff1f;地域选择遵循就近原则&#xff0c;访客距离地域越近网络延迟越低&#xff0c;速度越快。腾讯云百科txybk.com告诉大家关于地域的选择还有很多因素&#xff0c;地域节点选择…

Android 异常重启--踩坑归来--干货篇

如果你未对自己的app进行过处理&#xff0c;那么线上各种偶发莫名其妙的闪退、白屏、数据丢失&#xff0c;请检查一下是否因此而引发的。 起因 异常重建指的是非配置变更情况下导致的 Activity 重新创建。 常见场景大多是因为内存等资源不足&#xff0c;从而导致后台应用被系…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:XComponent)

可用于EGL/OpenGLES和媒体数据写入&#xff0c;并显示在XComponent组件。 说明&#xff1a; 该组件从API Version 8 开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 构造参数type为"surface"时不支持。 从API version …