讲师介绍
伊旭然:
去年毕业于北京邮电大学计算机相关专业毕业,在校期间做过前端、后端、基础架构、游玩过全国2/3的省份
1.png
目录
本节介绍:HTTP协议是什么、协议里有什么、请求流程、不足与展望
HTTP:超文本传输协议(Hypertext Transfer Protocol)
2.png
为什么需要协议?
首先协议都是字符流,一定需要边界,有了边界我们需要知道内容的类型,就可以把内容放进消息对应的地方,之后进行传输,这就是简单的协议
一个常见的POST请求在协议层究竟做了什么?
3.png
讲师举了一个场景,你要和小姐姐交流去请她看电影。然后就是把请小姐姐看电影这句话,把它转换成一个真实的http协议。可以看到这个协议里面的第一行是从post开始打头,然后一个空格之后接上的URL然后接下来又是一个空格,然后接上一个HTTP1.1,这看着像是一个HD当前版本的一个描述。这就是协议里面的描述。然后再一个大空行之后,我们可以看到我们真实的一个想说的话,也就是body部分:lets watch a movie totogether tonight 那之后这个协议结束了。那我们可以看到那我们的协议的开始,那其实就是我们的这个post这一行开始,只要我们对端检测到这一行的内容后,就可以接收我们的协议了。那协议的结束就是我们这个Let‘s watch a movie together tonight 最后我们再加上那一个换行之后就结束掉了我们的这个协议。然后同时我们可以看到源数据里面有一个叫做content Length的一个描述,这个是协议的关键的一个header 它描述的是我们的body到底有多少个字节。所以我们的server 端就是我们的小姐姐端就能根据这个字节来指定去接收多少个字节的数据,这样就能拿到我们完整的一个消息了。OK这是我们请求的一个真实的场景。那我们小姐姐肯定会给我们一个回复对吧?那回复其实可以看到非常类似,我们回复其实也是有一个first line.first line 的话它是由我们的一个http 1.1是一个版本的开始,然后空格,然后200是一个状态码,然后再一个空格,然后又是一个OK OK的话就是一个我们对应200的一个状态码的一个文字描述,然后最后是一些源数据,然后最后是小姐姐回复的一个OK,这就是小姐姐响应的完整的协议了。
4.png
首先是一个请求行,状态码,然后就是一些元数据请求头响应头。最后就是一个请求响应体。那针对我们的请求行,它是由方法名、URL和协议版本组成,那方法名其实就是比较多了,我们常见的方法名就是get 就是http 0.9里面唯一的一个方法。然后1.0里面扩充了header和post然后1.1里面又陆陆续续扩充了5个,然后从put开始到trace然后最终到一个patch。patch是1.1之后额外增加的一个方法名,但是使用比较广泛。
pathc和put具体有些什么区别吗?
pathc就是部分更新,put它的语义就是完整的更新,所以它一个比较细微的一个区别在(PUT是幂等的,而PATCH不是幂等的。)
5.png
6.png
7.png
根据上述小姐姐案例的一个请求流程
8.png
9.png
从上往下总共分为五层,层与层之前使用接口解耦。
两种设计进行对比:
11.png
10.png
一个切实可行的复杂系统势必是从一个切实可行的简单系统发展而来的。从头开始设计的复杂系统根本不切实可行,无法修修补补让它切实可行。你必须由一个切实可行的简单系统重新开始。-- 盖尔定律
12.png
提供合理的 API
中间件需求:
洋葱模型:
13.png
适用场景:
核心逻辑与通用逻辑分离
举个例子:
打印每个请求的request 和 response
14.png
1. 既然要实现预处理和后处理,那这个就很像调用了一个函数
15.png
将nextMiddleware() 统称为Next()
16.png
2. 路由上可以注册多个Middleware,同时也可以满足请求级别有效只需要将Middleware 设计为和业务和Handler相同即可。
3. 用户如果不主动调用下一个处理函数怎么办?
17.png
18.png
核心:在任何场景下index保证递增
4. 出现异常想停止怎么办?
19.png
调用链:
20.png
有没有什么坑呢?
不在一个调用栈上
适用场景:
思考:有没有其他实现中间件的方式
框架路由实际上就是为了URL 匹配对应的处理函数(Handlers)
青铜:map[string]handlers
/a/b/c、/a/b/d /a/:id/c、/*all
黄金:前缀匹配树
/a/b/c、/a/b/d
21.png
/a/b/c
/a/d/d
/a/:b/d
/a/:c/f
22.png
如何匹配HTTP方法?
路由映射表
外层Map:根据method 进行初步筛选
如何实现添加多处理函数?
在每个节点上适用一个list存储handler
24.png
思考:如何查找路由
如何做设计
25.png
抽象出合适的接口:
26.png
官方说明:
BIO(阻塞IO)
27.png
NIO(非阻塞)
28.png
go net "BIO" 用户管理 buffer
29.png
netpoll NIO 网络管理 buffer
netpoll地址:https://github.com/cloudwego/netpoll
30.png
31.png
go net "BIO"
32.png
go net
go net with bufio
绑定一块缓冲区
33.png
netpoll
存下全部Header 拷贝出完整的Body
34.png
35.png
go net
netpoll
找到Header Line 边界: \r\n
对于这种情况也可以适用上课学习的知识例如kmp,当然这些算法也不是很快,对于边界处理也不是最优
所以我们先找到\n 再看它前一个是不是 \r
36.png
有没有更快的找到\n的方法呢?SIMD?
SIMD:全称Single Instruction Multiple Data,单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集。
相关代码链接:https://github.con/golang/go/blob/9abacc83c853c17700c44e336e2d3e2oOfe9a72b/ src/internal/bytealg/indexbyte_amd64. s#lL8
json解析库:Sonic,https://github.com/bytedance/sonic(由字节内部开源的,目前这个库是最快的)
适用SIMD加速和未使用比较
37.png
38.png
针对协议相关的Headers快速解析:
请求体中同样处理的Key:
User-Agent、Content-Type、Content-Length、Connection、Transfer-Encoding
取
舍
aaa-bbb --> Aaa-Bbb
39.png
40.png
取
舍
41.png
42.png
取
舍
讲师进入公司以来一直学习的
目前内部有HTTP框架:Hertz
1万+服务 3千万+QPS