HTTP协议
HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,它是一个简单的请求-响应协议。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。
1.客户端请求消息的报文结构:
(1) 请求行(start line) (2) 请求头(header) (3) 空行 (4) 请求数据(entity)
2.服务器响应消息的报文结构:
(1) 响应行(状态行)(start line) (2) 响应头(header) (3) 空行 (4) 响应正文(entity)
3.请求行的报文格式
- 请求方法:表示对资源的操作,如 GET / HEAD / PUT / POST / DELETE 。
- 请求目标:通常是一个 URI ,标记了请求方法要操作的资源。
- 协议版本:表示报文使用的HTTP协议版本。
4.响应行报文格式
- 版本号:表示报文使用的HTTP协议版本。
- 状态码:一个三位数,用代码的形式表示处理的结果:如200是成功,500是服务错误。
- 原因:作为数字状态码补充,是更为详细的解释文字。
5.头部字段格式
(1) 字段名不区分大小写 (2) 字段名不允许出现空格,不可以出现下划线 _ (3) 字段名后紧跟 :
HTTP的请求方式
(1) GET:向指定的资源发出请求。
(2) POST:传输实体主体,向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
(3) PUT:传输文件,向指定资源位置上传其最新数据内容。
(4) PATCH:是对 PUT 方法的补充,用来对已知资源进行局部更新 。
(5) HEAD:获得报文首部,类似于 GET 请求,只不过返回的响应中没有具体的内容。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
(6) DELETE:删除文件,请求服务器删除指定的页面。
(7) OPTIONS:访问支持的方法,返回服务器针对特定资源所支持的HTTP请求方法
(8) TRACE:追踪路径,回显服务器收到的请求,主要用于测试或诊断。
(9) CONNECT:要求用隧道协议连接代理,HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
(10) LINK:建立和资源之间的联系
(11) UNLINE:断开连接关系
HTTP框架的设计与实现
1.分层设计
(1):
(2) :
应用层(Application):直接与用户打交道的一层,对用户的需求进行一个抽象。
中间件层(middleware):有预处理和后处理的逻辑
路由层(route):有一个原生的路由实现来提供类似于跟注册选址相关的操作
协议层(codec):用抽象的接口完成协议的扩展
网络层(transport):需要灵活替换网络库的能力
2.应用层设计
提供合理的API(应用程序接口):
- 冗余性:有
ctx.GetHeader(key)接口就不需ctx.Request.Header.Peek(key),两者功能相同。 - 兼容性:接口的实现不可改变。
- 可测性:保证写出的接口可测试。
- 可见性:(1) 为了安全性,不可让用户拿到框架内的核心代码 (2) 有些接口使用复杂,使用率低
- 不要试图在文档中说明,很多用户不看文档
3.中间件设计
-
配合 Handler(处理函数) 实现一个完整的请求处理生命周期。
-
拥有预处理逻辑和后处理逻辑
-
可以注册多中间件
-
对上层模块和用户逻辑模块易用
经典的中间件洋葱模型:
4.路由设计
框架路由实际是为 URL 匹配对应的处理函数(Handlers)
- 静态路由:/ a/ b/ c、/ a/ d/ c
- 参数路由:/ a/ : id/ c(/ a/ b/ c,/ a/ d/ c)、/*all
- 路由修复:/ a/ b < - > / a/ b/
- 冲突路由以及优先级 :/ a/ b、/ a/ :id/ c
- 匹配 HTTP 方法
- 多处理函数:方便添加中间件
(1) 处理带参数的路由:使用前缀匹配树:
(2) 匹配 HTTP 方法:路由映射表
(3) 添加多处理函数:在每个节点上使用一个list存储handler
node struct {
prefix string
parent *node
children children
handlers app.Handlers.Chain
}
5.协议层设计
抽象出合适的接口
type server interface {
Serve(c context.Context, conn network.Conn) error
}
6.网络层(传输层)设计
BIO:
在go function中维护一个listener,每次去accept一个连接后,单独开一个routine去处理,在该go routine中,先读取数据,再去处理业务逻辑,再把response写回去。但是读取数据时卡住,那它无法继续运行。
go func() {
for {
conn, := listener.Accept()
go func() {
conn.Read(request)
handle
conn.Write(reponse)
}
}
}
NIO:
注册一个监听器readableConns, _ := Monitor(conns),当监听器监听到足够数据后,再去唤醒下方的go functon(),这样conn.Read便可读取到足够数据,整个流程没有阻塞
go func() {
for {
readableConns, _ := Monitor(conns)
for conn := range readableConns {
go func() {
conn.Read(request)
handle
conn.Write(reponse)
}()
}
}
}()