HTTP框架设计与实现 | 青训营

64 阅读5分钟

一、走进HTTP协议

前后端分离的流程图

image.png

  • HTTP框架负责对HTTP请求的解析,根据对应的路由选择对应的后端逻辑

  • 1.1 HTTP协议是什么?

    • HTTP: 超文本传输协议(Hypertext Transfer Protocol)
  • 为什么需要协议?

    • 需要明确信息起始边界,需要描述信息类型
  • 1.2 一个POST请求发生了什么?协议里面有什么?

    • 请求行(方法名:GET、POST等、url、协议版本)/状态行(协议版本、状态码、状态码描述)
      • 状态码1xx:信息类;2xx:成功;3xx:重定向;4xx:客户端错误;5xx:服务端错
    • 请求头/响应头
    • 请求体/响应体
    POST /sis HTTP/1.1      // 请求行:POST url HTTP版本
    Who: Alex               //协议的源数据
    Content-Type: text/plain
    Host: 127.0.0.1.8888
    Content-Length: 28      //描述包里有几个字节
    
    Let's watch a movie together    //实际发送的数据
    
    HTTP/1.1 200 OK             // 200是状态码,OK是对该状态码的回复
    Server: hertz
    Data: Thu, 21 Apr 2022 11:46:32 GMT
    Content-Type: text/plain; charset=utf-8
    Content-Length: 2
    Upstream-Caught: *************
    
    OK
    
  • 1.3 请求流程

    • 在业务层,业务方使用框架提供的API去完成业务逻辑
    • 服务治理层(垄断、限流等),服务治理层依托于中间件层
    • 路由层
    • 协议编解码层
    • 传输层

image.png

  • HTTP不足与展望
    • HTTP1:基于TCP,存在队头阻塞问题;传输效率低;不支持多路复用;使用明文传输不安全
    • HTTP2:多路复用;头部压缩;二进制协议
    • QUIC: 基于UDP实现,解决队头阻塞;优化加密算法,加密减少握手次数;支持快速启动

二、HTTP框架的设计与实现

  • 2.1 分层设计(高内聚、低耦合、易复用、高扩展性),下面架构分成5层,层与层之间使用接口解耦,从上往下依次是(应用层:对请求进行抽象,提供丰富应用API;中间件层:对用户预处理和后处理逻辑;路由层:注册、寻址相关操作;协议层;网络层),common中存放通用逻辑。 image-2.png

  • 2.2 应用层设计
    核心是提供合理的API

    • 可理解性:使用主流的概念(方便能看出来接口的功能)
    • 简单性:对于高频结构在最外层写一个方法
    • 冗余性:不要出现实现同样功能的接口
    • 兼容性
    • 可测性:接口可测试
    • 可见性:核心部分不可见,防止被修改
  • 2.3 中间件设计
    中间件的需求如下:

    • 配合Handler实现一个完整的请求处理生命周期

    • 拥有预处理逻辑和后处理逻辑

    • 可以注册多中间件

    • 对上层模块用户逻辑模块易用
      举例:洋葱模型\

image-3.png
请求过来之后先经过日志中间件的预处理,之后经过Metrics中间件的预处理,最后去执行业户逻辑,在业务逻辑退出之后首先经过Metrics中间件的后处理,最后通过日志中间件的预处理,然后将真正的响应返回给用户。中间件的核心是将核心逻辑与通用逻辑分离。使用场景包括:日志记录、性能统计、安全控制、事务处理、异常处理。

*   怎样实现中间件设计?
    1.  实现预处理和后处理,很像调用了一个函数
    ```go
    func Middleware(some param){
        // some logic for pre-handle
        ...

        nextMiddleware() / bizlogic()

        // some logic after-handle
        ...
    }
    ```
    2.  路由上可以注册多Middleware,同时可以满足请求级别有效,只需要将Middleware设计为和业务和Handle相同即可。
    ```go
    func Middleware(some param){
        // some logic for pre-handle
        ...
        // 不需要区分业务逻辑和中间件
        Next()

        // some logic after-handle
        ...
    }
    ```
    3.  用户如果不主动调用下一个处理函数怎么办?
    ```go
    func Middleware(some param){
        // some logic
        ...
    }
    ```
    我们需要帮用户主动调用之后的中间件
    ```go
    func (ctx *RequestContext) Next(){
        ctx.index++
        for ctx.index < int8(len(ctx.handlers)){
            ctx.handlers[ctx.index]()
            ctx.index++
        }
    }
    // 核心是在任何场景下保证index递增
    ```
    4.  出现异常想停止怎么办?\
        我们可以把index设置为最大值让其跳出循环
    ```go
    func (ctx *RequestContext) Abort(){
        ctx.index = IndexMax
    }
    ```
    ![Alt text转存失败,建议直接上传图片文件](<转存失败,建议直接上传图片文件 image-4.png>)
  • 2.4 路由设计
    框架路由实际上就是为URL匹配对应的处理函数(Handlers)

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

    • 青铜: map[string]handlers
      只适合/a/b/c、/a/b/d这种静态路由,不适合/a/:id/c、/*all这种参数路由
    • 黄金:前缀匹配树 /a/b/c、/a/b/d,从前往后依次匹配,如果是参数路由,先匹配到:
    • 如何匹配·HTTP方法?\

image-5.png 外层Map:根据methord进行初步筛选 * 如何实现添加多处理函数?
在每个节点上使用一个list存储handler go node struct{ prefix string parent *node children children handlers app.HandlersChain ... }

  • 如何做设计?

    1. 明确需求:考虑清楚要解决什么问题,有哪些需求
    2. 业界调研:业界有哪些解决方案可供参考
    3. 方案权衡:思考不同方案的取舍
    4. 方案评审:相关同学对不同方案进行评审
    5. 确定开发:确定最合适的方案进行开发
  • 2.5 协议层设计
    抽象出合适的接口:

    type Server interface{
        Server(c context.Context, conn network.Conn) error
    }
    
    1. 不要将contexts放到一个struct里面,而应该将context作为第一个参数传进需要的函数中
    2. 需要在连接上读写数据
  • 2.6 网络层设计
    首先了解BIO(阻塞IO)

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

NIO(非阻塞IO)

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

    }
}()

网络层设计

type Conn interface{
    net.Conn
    Reader
    Writer
}