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多路视频融…

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

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

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 原因分析 我实在两台虚拟机…

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

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

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 …

VsCode 使用密钥连接 Centos

在 centos 下生成密钥 ssh-keygen 执行上述命令后&#xff0c;一路回车&#xff0c;直到出现如下界面&#xff1a; 查看密钥生成情况 cd /root/.ssh ls 结果如下所示&#xff1a; 服务器上安装公钥 cd /root/.ssh cat id_rsa.pub >> authorized_keys ls >查看确…

Covalent Network(CQT)与 Celo 集成,推动 Web3 下一代现实资产解决方案的发展

Covalent Network&#xff08;CQT&#xff09;是一个统一的区块链 API 提供商&#xff0c;其已正式与 Celo 集成&#xff0c;Celo 是一个以移动优先的 EVM 兼容链。这一重要的里程碑旨在提升 Celo 生态系统中开发者的能力&#xff0c;通过授予他们访问关键链上数据的权限&#…

Python | Bootstrap图介绍

在进入Bootstrap 图之前&#xff0c;让我们先了解一下Bootstrap&#xff08;或Bootstrap 抽样&#xff09;是什么。 Bootstrap 抽样&#xff08;Bootstrap Sampling&#xff09;&#xff1a;这是一种方法&#xff0c;我们从一个数据集中重复地取一个样本数据来估计一个总体参数…

Qt教程 — 3.1 深入了解Qt 控件:Buttons按钮

目录 1 Buttons按钮简介 1.1 Buttons按钮简介 1.2 Buttons按钮如何选择 2 如何使用Buttons按钮 2.1 QPushButton使用-如何自定义皮肤 2.2 QToolButton使用-如何设置帮助文档 2.3 QRadioButton使用-如何设置开关效果 2.4 QRadioButton使用-如何设置三态选择框 2.5 QCom…

学习使用postman软件上传文件发起api接口请求

学习使用postman软件上传文件发起api接口请求 设置headers头信息设置body 设置headers头信息 如图设置&#xff1a; KEY&#xff1a;Content-Type VALUE&#xff1a;multipart/form-data 设置body 设置需要上传的key对应的类型为File&#xff0c;上传类型 设置需要上传的文件…

留学生课设|R语言|研究方法课设

目录 INSTRUCTIONS Question 1. Understanding Quantitative Research Question 2. Inputting data into Jamovi and creating variables (using the dataset) Question 3. Outliers Question 4. Tests for mean difference Question 5. Correlation Analysis INSTRUCTIO…