net/http 框架源码解读

news2024/9/25 13:15:22

一、Hello World

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

package main

import "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 = &defaultServeMux

var defaultServeMux ServeMux

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

type muxEntry struct {
	h       Handler
	pattern 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.es
	if 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 string

	Handler Handler // handler to invoke, http.DefaultServeMux if nil

	TLSConfig *tls.Config

	ReadTimeout time.Duration

	ReadHeaderTimeout time.Duration

	WriteTimeout 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 failure
    for {
        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 := ctx
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew, runHooks) // before Serve can return
        go 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.Handler
	if 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.coloradmin.cn/o/1517573.html

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

相关文章

探索非监督学习:解决聚类问题

目录 1 非监督学习的概念1.1 非监督学习的定义1.2 非监督学习的重要性 2 聚类问题的定义和意义2.1 聚类问题的定义2.2 聚类问题的意义2.3 聚类问题在非监督学习中的地位 3 聚类算法介绍3.1 K均值聚类3.2 层次聚类3.3 密度聚类 4 聚类问题的评估4.1 内部评估指标4.2 外部评估指标…

提升数据分析效率,选择IBM SPSS Statistics专业统计分析软件

在当今信息爆炸的时代,数据已经成为决策的重要依据。对于研究人员、学者、企业管理者等群体来说,如何高效地进行数据分析并得出准确结论至关重要。而IBM SPSS Statistics作为一款专业统计分析软件,为用户提供了强大的工具和功能,助…

Unreal发布Android在刘海屏手机上不能全屏显示问题

Unreal 4.27发布Android在刘海屏手机上不能全屏显示问题 Android设置全屏刘海屏全屏设置4.27设置刘海屏在部分手机不能显示问题 Android设置全屏 AndroidManifest.xml文件配置 ...<activity android:name"com.epicgames.ue4.GameActivity" android:label"st…

Claude 3 Haiku,它不仅是Claude系列中最快的成员,还在速度的赛道上领先一大步。

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

vr虚拟现实游戏世界介绍|数字文化展览|VR元宇宙文旅

虚拟现实&#xff08;VR&#xff09;游戏世界是一种通过虚拟现实技术创建的沉浸式游戏体验&#xff0c;玩家可以穿上VR头显&#xff0c;仿佛置身于游戏中的虚拟世界中。这种技术让玩家能够全方位、身临其境地体验游戏&#xff0c;与游戏中的环境、角色和物体互动。 在虚拟现实游…

一文解决Word中公式插入问题(全免费/latex公式输入/texsWord)

分文不花&#xff0c;搞定你的word公式输入/texsWord完全使用指南 背景 碎碎念&#xff1a;折折腾腾至少装了几个小时&#xff0c;遇到了若干大坑。遇到的问题网上都搜索不到答案&#xff01;&#xff01;&#xff01;就让我来当指路的小火柴吧。 本篇适用于在word中输入la…

微前端框架 qiankun 配置使用【基于 vue/react脚手架创建项目 】

qiankun官方文档&#xff1a;qiankun - qiankun 一、创建主应用&#xff1a; 这里以 vue 为主应用&#xff0c;vue版本&#xff1a;2.x // 全局安装vue脚手架 npm install -g vue/clivue create main-app 省略 vue 创建项目过程&#xff0c;若不会可以自行百度查阅教程 …

3D全景:为各行业提供更真实的交互体验

近年来&#xff0c;随着科技的不断发展&#xff0c;3D全景技术逐渐融入到了我们的日常生活中来。3D全景技术的应用落地&#xff0c;为广大用户提供了全新的视觉体验&#xff0c;让人们能够更加真实、直观地感受各行业的场景。 3D全景的优势就在于真实感和互动性&#xff0c;可以…

代码训练LeetCode(11)删除有序数组中的重复项II

代码训练(11)LeetCode之删除有序数组中的重复项II Author: Once Day Date: 2024年3月14日 漫漫长路&#xff0c;才刚刚开始… 全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客 参考文章: 80. 删除有序数组中的重复项 II - 力扣&#xff08;LeetCode&#xff…

AI 大模型赋能手机影像,小米14 Ultra 让真实有层次

2月22日&#xff0c;小米龙年第一场重磅发布会&#xff0c;正式发布专业影像旗舰小米14 Ultra。 此前小米发布的两代 Ultra&#xff0c;在不同维度&#xff0c;引领了移动影像行业的走向。最新的小米14 Ultra 在定义的时候&#xff0c;我们反复在思考&#xff1a;怎么才能把移动…

三维高斯是什么

最近3DGS的爆火&#xff0c;引发了一众对三维高斯表达场景的研究。这里的三维高斯是什么&#xff1f;本文用简答的描述和简单实验来呈现三维高斯的数学意义。本文没有公式推导&#xff0c;主打一个意会。 我们高中都学过高斯分布&#xff0c;即一个钟形曲线。它的特点是有一个…

OpenAI的GPT-4.5 Turbo:意外曝光且可能在六月份推出

网络媒体THE DECODER的联合创始人兼出版人Matthias认为&#xff0c;人工智能技术将彻底改变人类和计算机的互动方式。 最新消息显示&#xff0c;OpenAI的最新力作GPT-4.5 Turbo已经在网络上意外曝光。首批发现此信息的是Bing和DuckDuck Go等搜索引擎&#xff0c;它们在官方发布…

吴恩达deeplearning.ai:独热编码One-hot连续有价值的特征回归树

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 文章目录 One-hot编码连续有价值的特征回归树 在之前的决策树例子中&#xff0c;每个分裂都只有两种选择&#xff0c;但是今天我们将提到一种新的分裂方式叫做One-hot&#xff0c;可以解决以…

Redis开发规范与性能优化(二)

开发规范与性能优化 3.客户端使用 1.【推荐】避免多个应用使用一个Redis示例 正例:不相干的业务拆分&#xff0c;公共数据库做服务化 2.【推荐】使用带有连接池的数据库&#xff0c;可以有效控制链接&#xff0c;同时提高效率&#xff0c;标准使用方式如代码所示 public c…

Python Web开发记录 Day10:Django part4 靓号管理与优化

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、数据库准备2、靓号列表3、新建靓号4、编辑靓…

无人机自动返航算法部署与飞行控制实践

一、环境准备 无人机硬件&#xff1a;确保你有一台支持自定义飞行控制的无人机&#xff0c;通常配备有Pixhawk或其他类似的飞行控制器。 地面站软件&#xff1a;安装如Mission Planner或QGroundControl等地面站软件&#xff0c;用于配置无人机参数和上传飞行控制代码。 编程环…

Hadoop大数据应用:NFS网关 连接 HDFS集群

目录 一、实验 1.环境 2.NFS网关 连接 HDFS集群 3. NFS客户端挂载HDFS文件系统 二、问题 1.关闭服务报错 2.rsync 同步报错 3. mount挂载有哪些参数 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 主机架构软件版本IP备注hadoop NameNode &#xff08;…

ASP.NET

Web控件 Web控件-内部控件 ASP.NET引入一组称为”内部控件”的新控件&#xff0c;它们专门用于ASP.NET 内部控件的使用方法与HTML控件相同&#xff0c;它们映射到HTML元素并通过使用 runat”server”属性在服务器上执行 Web控件-列表控件 这些控件用于在Web页中创建数据列表…

Revit二次开发,tuple,valuetuple,anonymousType匿名类型的区别,笔记记录

Revit二次开发&#xff0c;tuple&#xff0c;valuetuple&#xff0c;anonymousType匿名类型的区别&#xff0c;笔记记录 Tuple<int, string> tuple new Tuple<int, string>(1, "hello");//tuple ValueTuple<int, string> valueTuple (1, "…

叶顺舟:手机SoC音频趋势洞察与端侧AI技术探讨 | 演讲嘉宾公布

后续将陆续揭秘更多演讲嘉宾&#xff01; 请持续关注&#xff01; 2024中国国际音频产业大会(GAS)将于2024年3.27 - 28日在上海张江科学会堂举办。大会将以“音无界&#xff0c;未来&#xff08;Audio&#xff0c; Future&#xff09;”为主题。大会由中国电子音响行业协会、上…