HTTP框架设计 | 青训营笔记

43 阅读3分钟

这是我参与「第五届青训营 」笔记创作活动的第17天

只不过是字节给我的任务罢了

HTTP

Hyper Text Transfer Protocol

image.png

  • 协议开始
  • 协议元数据
  • Text
  • 协议结束

POST请求

POST/sis HTTP/1.1
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
Sever: hertz
Data: Thu, 21 Apr 2022 11:46:32 GMT
Content-Type: text/plain; charset=utf-8
Content-length: 2
Upstream-Caught: 1650541592984580

OK

协议的组成

  • 请求行/状态行
    • 方法名
      • GET HAED POST PUT DELETE CONNECT OPTIONS TRACE PATCH
    • URL
    • 协议版本
    • 状态码
      • 1xx,信息类
      • 2xx,成功
      • 3xx,重定向
      • 4xx,客户端错误
      • 5xx,服务端错误
    • 状态码描述
  • 请求头/响应头
    • 协议相关
    • 业务相关
  • 请求体/响应体

请求流程

路由层根据url选择执行的handler

image.png

不同版本HTTP对比

  • HTTP1
    • 队头阻塞
    • 传输效率低
    • 明文传输不安全
  • HTTP2
    • 多路复用
    • 头部压缩
    • 二进制协议
  • QUIC
    • 基于UDP实现
    • 解决队头阻塞
    • 加密减少握手次数
    • 支持快速启动

分层设计

  • 高内聚,低耦合

  • 易复用

  • 高扩展性

    image.png 上图的Common层为通用层,其他层都会使用

一个切实可行的复杂系统应该由一个切实可行的简单系统发展而来,从头设计复杂系统是不切实可行的,无法修修补补让它切实可行,必须从切实可行的简单系统开始

应用层设计

提供合理的API

  • 可理解性:如ctx.Body(),ctx.GetBody()

  • 简单性:ctx.Request.Header.Peek(key) --> ctx.GetHeader(key)

  • 冗余性

  • 兼容性

  • 可测性

  • 可见性

    image.png

中间件设计

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

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

  • 可以注册多中间件

  • 对上层模块用户逻辑模块易用

    image.png

  • 洋葱模型

    image.png

  • 实现预处理和后处理,可以将中间件和业务逻辑代码合并为Next()函数

    image.png

路由设计

框架路由实际上是为了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

  • 黄金:前缀匹配树

  • 匹配HTTP方法

    image.png

    路由映射表,外层Map:根据method进行初步筛选

  • 实现添加多处理函数:使用list存储handler

    node struct{
        prefix string
        parent *node
        children children
        handlers app.HandlerChain
    }
    

如何做设计

  • 明确需求:考虑清楚需要解决什么问题,有哪些需求
  • 业界调研:业界都有哪些解决方案可供参考
  • 方案权衡:思考不同方案的取舍
  • 方案评审:相关同学对不同方案进行评审
  • 确定开发:确定最合适的方案进行开发

协议层设计

  • 抽象出合适的接口

    type Server interface{
        Serve(c context.Context, conn network.conn)`error
    }
    

    不要把上下文参数封装到结构体中,应该把上下文作为函数参数传递

    需要在连接上读写数据

网络层设计

BIO:阻塞IO

go net

type Conn interface{
    Read(b []byte)(n int, err error)
    Write(b []byte)(n int, err error)
}

NIO:非阻塞IO

type Reader interface{
    Peek(n int)([]byte, error)
}
type Writer interface{
    Malloc(n int)(buf []byte, err error)
    Flush() error
}

性能优化

对于go net

  • 存下全部Header
  • 减少系统调用次数
  • 能够复用内存
  • 能够多次读

绑定一块缓冲区,使用完后对其进行回收,保证下一次请求可以复用这块空间

对于net poll

为了减少锁竞争,使用链表实现无锁化

  • 存下全部Header

  • 拷贝出完整的Body

分配足够大的buffer,限制最大buffer size

  • go net
    • 流式友好
    • 小包性能好
  • net poll
    • 中大包性能高
    • 时延低

协议优化

找到Header Line的边界\r\n,两个连续的\r\n说明Header读完了

参考

juejin.cn/course/byte…