HTTP协议|豆包MarsCode AI刷题

43 阅读3分钟

一、走进HTTP协议 HTTP:超文本 传输 协议(Hypertext Transfer Protocol)

1.1、为什么需要协议 需要明确知道数据明确的边界。

一个常见的POST请求在协议层做了什么? 请求行,是由请求方法 + URL +协议版本 组成。

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

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

协议相关:Content-Type、Content-Length 业务相关:自己定义 HTTP1: 队头阻塞: 传输效率低 明文传输不安全 HTTP2: 多路复用 头部压缩 二进制协议 QUIC:

基于UDP实现 解决队头阻塞 加密减少握手次数 支持快速启动 二、HTTP 框架的设计与实现

2.1、应用层设计 Application 提供合理的API

可理解性:如ctx.Body(), 不要ctx.BodyA() 简单性:如ctx.Request.Header.Peek(key),包装一下:/ctx.GetHeader(key) 冗余性 兼容性 可测性 可见性 2.2、中间件需求 配合Handler实现一个完整的请求处理生命周期。 拥有预处理逻辑与后处理逻辑:可统计业务耗时 可以注册多个中间件 对上层模块用户逻辑块易用 经典模型:洋葱模型

适用场景:

日志记录 性能统计 安全控制 事务处理 异常处理 2.3、路由设计 框架路由实际上是为了匹配URL对应的处理函数(Handlers)

静态路由:/a/b/c、/a/b/d 参数路由:/a/:id/c、/*all 路由修复:/a/b -> /a/b/ 冲突路由以及优先级:/a/b、/:id/c 匹配HTTP方法 多处理函数:方便添加中间件 一般路由使用前缀树来设计:

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

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

在每个节点上使用一个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

采用链表实现无锁化

3.2、针对协议的优化 Header解析 热点资源池化

有点类似于 线程池。