Note04 HTTP 框架修炼之道 | 青训营

51 阅读6分钟

HTTP协议是什么

HTTP(Hypertext Transfer Protocol)是一种用于传输超文本的协议,它是Web上数据传输的基础。HTTP协议是一种无状态的、应用层的协议,它通过客户端和服务器之间的请求和响应来进行通信。

协议里面有什么

HTTP协议包含以下几个主要部分:

  1. 请求方法:定义了客户端对服务器的操作类型,常见的方法有GET、POST、PUT、DELETE等。
  2. 请求头:包含了请求的一些附加信息,例如Content-Type、User-Agent等。
  3. 请求体:用于传输请求的数据,例如表单数据、JSON数据等。
  4. 状态码:服务器返回的三位数代码,用于表示请求的处理结果,例如200表示成功,404表示未找到等。
  5. 响应头:包含了响应的一些附加信息,例如Content-Type、Content-Length等。
  6. 响应体:用于传输响应的数据,例如HTML页面、JSON数据等。

image.png

请求流程

  1. 客户端发起HTTP请求,构建请求行,包括请求方法、请求URL和HTTP协议版本。
  2. 客户端发送请求头,包括一些附加信息,例如User-Agent、Accept等。
  3. 客户端发送请求体,如果有需要传输的数据。
  4. 服务器接收到请求,解析请求行和请求头,根据请求方法和请求URL确定需要执行的操作。
  5. 服务器处理请求,生成响应数据。
  6. 服务器发送响应头,包括一些附加信息,例如Content-Type、Content-Length等。
  7. 服务器发送响应体,包含了响应数据。
  8. 客户端接收响应,解析响应头和响应体,根据状态码判断请求的处理结果。

image.png

不足与展望

HTTP协议在传输效率、安全性和可扩展性方面存在一些不足之处:

  1. 传输效率:HTTP协议在每次请求和响应时都需要建立和关闭连接,造成了较大的开销。另外,HTTP/1.1在传输大文件时会存在队头阻塞的问题,影响了传输效率。
  2. 安全性:HTTP协议的通信内容是明文传输的,容易被窃听和篡改。虽然可以通过HTTPS来加密通信内容,但仍然存在中间人攻击等安全问题。
  3. 可扩展性:HTTP协议的功能相对较为简单,不支持灵活的扩展和自定义。在一些特殊需求下,需要额外的协议或技术来支持。
  • HTTP1:基于TCP,队头阻塞,传输效率低,明文传输不安全
  • HTTP2:基于TCP,多路复用,头部压缩,二进制协议
  • QUIC:基于UDP实现,解决队头阻塞,加密减少握手次数,支持快速启动

HTTP框架的设计与实现

分层设计

分层设计:专注性、扩展性、复用性。专注于特定层的设计,直接使用下层提供的接口

传输层:TCP,UDP

应用层:HTTP

TCP/IP四层概念模型:应用层,传输层,网络层,数据链路层

应用层(抽象请求) -- 中间件层(预处理/后处理逻辑) -- 路由层(注册/寻址) -- 协议层(通过接口,完成协议扩展) -- 网络层(灵活替换网络库)

image.png

  • 专注性
  • 扩展性
  • 复用性

image.png

  • 高内聚低耦合
  • 易复用
  • 高扩展性

image.png

盖尔定律

一个运转正常的复杂系统,总是从一个运转正常的简单系统演化而来。 反之也是一样的:一个从零开始设计的复杂系统永远不会起作用,也不可能让它起作用。你必须重新开始,从一个简单的系统开始。

应用层设计

image.png

提供合理的 API

  • 可理解性:如 ctx.Body(),ctx.GetBody()
    不要用 ctx.BodyA()
  • 简单性:如 ctx.Request.Header.Peek (key)
    /ctx. GetHeader (key)

中间件设计

中间件需求:

  • 配合 Handler 实现一个完整的请求处理生命周期
  • 拥有预处理逻辑与后处理逻辑
  • 可以注册多中间件
  • 对上层模块用户逻辑模块易用

洋葱模型: image.png 适用场景:

  • 日志记录
  • 性能统计
  • 安全控制
  • 事务处理
  • 异常处理
  1. 既然要实现预处理和后处理,那这个就很像调用了一个函数
func Middleware (some param) {
    // some logic for pre-handle
    ...
    Next( )
    //some Logic for after-handle
    ...
}
  1. 路由上可以注册多 Middleware,同时也可以满足请求级别有效,只需要将 Middleware 设计为和业务和 Handler 相同即可。
  2. 用户如果不主动调用下一个处理函数怎么办?
    核心:在任何场景下Index保证递增
  3. 出现异常想停止怎么办?
func (ctx *RequestContext) Abort(){
    ctx.index = IndexMax  //直接将Index设置为最大值
}

调用链:
图中调用链存在一个问题:不在同一调用栈上

图片.png 适用场景

  • 不调用 Next: 初始化逻辑且不需要在同一调用栈

  • 调用 Next: 后处理逻辑或需要在 同一调用栈上

Request -- 日志(预处理) -- Metrics(预处理) -- Biz Handler(执行业务逻辑) -- Metrics(后处理) -- 日志(后处理) -- Response

路由设计

框架路由实际上就是为URL匹配对应的处理函数

  • 静态路由:/a/b/c
  • 参数路由:/a/:id/c (/a/b/c、/a/d/c) 、/*all
  • 路由修复:/a/b <-> /a/b/
  • 冲突路由以及优先级:/a/b、/:id/c
  • 匹配HTTP方法
  • 多处理函数:方便添加中间件

青铜:map[string]handlers,只适配静态路由

黄金:前缀匹配树,可以满足静态路由/参数路由

image.png 匹配HTTP方法:路由映射表:Map -- Method(string) -- 前缀树 -- 头节点(*node)

外层Map:通过Method初步筛选HTTP方法

实现添加多处理函数:在每个节点上使用list存储handler

协议层设计

抽象出合适的接口;

go
复制代码
type Swever interface{
    Serve(c context.Context, conn network.Conn) error
}

传输层:网络层设计

BIO:blockIO,用户管理buffer,go.net

go func() {
        for {
        conn, - := listener.Accept()
        go func() {
            conn.Read(request)
            
            handle...
            
            conn.Write(response)
        }()
    }
}()

NIO:注册监听器,监听到足够数据后唤醒,解决堵塞,网络库管理buffer,go.netpoll

go func() {
        for {
        readableConns, _ := Monitor(conns)
        for conn:= range readableConns {
        go func() {
            conn.Read(request)
            
            handle...
            
            conn.Write(response)
        }()
    }
}()

性能修炼之道与企业实践

针对网络库的优化:

  1. Buffer设计:在网络传输中,使用缓冲区(Buffer)能够提高传输效率。通过合理设计缓冲区大小,减少读写次数,可以更高效地传输数据。

针对协议的优化:

  1. Headers解析:在HTTP协议中,请求头和响应头是以键值对形式存在的,但是没有固定的长度。为了解析这些变长的头部信息,可以采用高效的解析算法,例如使用有限状态机(FSM)来逐个解析头部字段,提高解析效率。

其他优化措施:

  1. 热点资源池化:对于需要频繁创建和销毁的资源,可以采用资源池化技术进行重复利用,减少资源动态分配和回收的开销。这可以有效减少系统负载,提高性能。

企业实践:

  1. 框架设计:在开发企业级应用时,框架设计起着关键作用。一个好的框架不仅能够提供高效的网络库,还应该考虑到扩展性、可维护性、稳定性和安全性等方面。通过设计合理的框架结构和模块划分,提供良好的接口和抽象层,能够满足企业级应用的各种需求。