HTTP | 豆包MarsCode AI刷题

157 阅读3分钟

一、走进HTTP协议

HTTP超文本 传输 协议(Hypertext Transfer Protocol)

1.1、为什么需要协议

需要明确知道数据明确的边界。

一个常见的POST请求在协议层做了什么?

请求:

image-20241110163745405

image-20241110163745405.png 响应:

image-20241110163933461.png 请求行,是由请求方法 + URL +协议版本 组成。

状态行,由协议版本 + 状态码 + 状态码描述

请求/响应头:协议约定 + 业务相关

  • 协议相关:Content-TypeContent-Length
  • 业务相关:自己定义

请求流程:

image-20241110182359344.png

1.2、不足与展望:

image-20241110182411025.png

HTTP1

  • 队头阻塞:
  • 传输效率低
  • 明文传输不安全

HTTP2

  • 多路复用
  • 头部压缩
  • 二进制协议

QUIC

  • 基于UDP实现
  • 解决队头阻塞
  • 加密减少握手次数
  • 支持快速启动

二、HTTP 框架的设计与实现

image-20241110182507019.png

HTTP框架图:

image-20241110183721576.png

2.1、应用层设计 Application

提供合理的API

  • 可理解性:如ctx.Body(), 不要ctx.BodyA()
  • 简单性:如ctx.Request.Header.Peek(key),包装一下:/ctx.GetHeader(key)
  • 冗余性
  • 兼容性
  • 可测性
  • 可见性

2.2、中间件需求

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

经典模型:洋葱模型

适用场景:

  • 日志记录
  • 性能统计
  • 安全控制
  • 事务处理
  • 异常处理

image-20241110184838484.png

举例:打印每个请求的requestresponse

image-20241110185416459.png

优化:

image-20241110185431595.png

2.3、路由设计

框架路由实际上是为了匹配URL对应的处理函数(Handlers)

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

一般路由使用前缀树来设计:

image-20241110185823709.png

那么,如何匹配HTTP方法呢? 其实可以通过构建多个前缀树:

image-20241110185918792.png

如何实现为一个路由添加多个处理函数?

在每个节点上使用一个list存储handler

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

2.4、协议层设计

需要抽象出来合适的接口:

type server interface{
    Serve(c context.Context, conn network.Conn) err
}

遵循下列规范:

  • 不要将Context存储在接口中,放在第一个参数中传递
  • Conn上读写数据

2.5、网络层设计

网络层分为阻塞式IO非阻塞式IO

阻塞式IO

传统的 Go net是阻塞式的

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    // ...
}
go func() {
    for {
        conn, _ := listener.Accept()
        go gunc() {
            conn.Read(request)
            
            // handler
            
            conn.Write(response);
        }()
    } 
}()

阻塞式IO的问题在于,Read的时候,没有数据会阻塞, Write的时候,没有要写的也会阻塞。

非阻塞式IO

Netpoll就实现了非阻塞式IO,

type Reader interface {
    Peek(n int) ([] byte, error)
    // ...
}
​
type Writer interface {
    Malloc(n int) (buf []byte, err error)
    Flush() error
    // ...
}
type Conn interface {
    net.Conn
    Reader,
    Writer
}
go func() {
    for {
        readableConns, _ := Monitor(conns) // 创建一个连接监视器,有数据了再去处理
        for conn := range readableConns {
            go gunc() {
                conn.Read(request)
​
                // handler
​
                conn.Write(response);
            }()
        }
    } 
}()

三、性能修炼之道

3.1、针对网络库的优化

go net -> Netpoll

为每一个连接都绑定一个buf

image-20241110203622362.png

采用链表实现无锁化

3.2、针对协议的优化

Header解析

image-20241110204954274.png

热点资源池化

有点类似于 线程池。