第四节课之HTTP协议
HTTP协议
-
定义 HTTP的全称为超文本传输协议(Hypertext Transfer Protocol)。 其中,超文本表示的是文本数据的拓展,包括图片、视频等文件;协议则能明确数据流边界,并携带信息,这可以简化为四部分内容:协议开始、协议元数据、文本、协议结束。
-
协议内容 首先是请求行或状态行,请求行应包括方法名、URL和协议版本,状态行则有协议版本、状态码和状态描述。常见的方法有
GET,HEAD,POST等,状态码则有信息类,成功,重定向等。 其次是请求头或响应头,它们都应有协议约定或业务相关的内容。 最后则是请求体或响应体。 一个完整的请求协议示例如下。首行和末行分别为请求行和请求体,其余则为请求头。POST /a/b/c 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 Server: hertz Date: Sun, 20 Aug 2023 22:42:13 GMT Content-Type: text/plain; charset=utf-8 Content-Length: 2 Upstream-Caught: 1650541592984580 OK而Go语言开发中,实现这样一个响应可以采用如下代码。
package main import ( "context" "code.byted.org/middleware/hertz/pkg/app" "code.byted.org/middleware/hertz/pkg/app/server" ) func main() { h := server.New() h.POST("/a/b/c", func(c context.Context, ctx *app.RequestContext) { ctx.Data(200, "text/plain; charset=utf-8", []byte("OK")) }) h.Spin() } -
请求流程 流程一般经过五层,由上至下分别为:业务层、服务治理层或中间层、路由层、协议编解码层和传输层。
-
问题 HTTP1存在队头阻塞,传输效率低和明文传输导致的不安全问题。 HTTP2解决了部分问题,实现了多路复用,头部压缩和二进制协议。 QUIC则基于UDP实现,进一步解决了队头阻塞,并加密减少握手次数和支持快速启动。
HTTP框架设计
-
分层 分层设计的专注性、扩展性和复用性有利于开发,这样的高内聚低耦合系统可以更轻松地开发较大的项目。
-
应用层 应用层设计应注重提供合理的API。 该接口需要保持可理解性,简单性,冗余性,兼容性,可测性和可见性。 如
ctx.Body()就是一个简单易理解的函数;为了保持简单性,我们可以将ctx.Request.Header.Peek(key)的调用简化为ctx.GetHeader(key)。 -
中间件 中间件应满足需求包括:配合Handler实现一个完整的请求处理生命周期,拥有预处理逻辑与后处理逻辑,可以注册多中间件,对上层模块用户逻辑模块易用。 一个典型的模型设计为洋葱模型,请求分别经过日志、Metrics到达业务处理,响应再从中经过Metrics和日志传出,这实现了核心逻辑与通用逻辑分离。 一个典型的中间件就像调用一个函数。
func Middleware (some param) { // some logic for pre-handle Next() // some logic for after-handle }用户若不主动调用下一处理函数,则可以通过如下方式在任何场景下保证索引递增。
func (ctx *RequestContext) Next() { ctx.index++ for ctx.index < int8(len(ctx.handlers)) { ctx.handlers[ctx.index]() ctx.index++ } }当异常出现需要停止时,我们可以通过设置索引边界的方式作为异常。
func (ctx *RequestContext) Abort() { ctx.index = IndexMax }在调用时,需注意后处理逻辑或需要在同一调用栈的中间件都应调用Next,一般仅初始化逻辑且不需要在同一调用栈时才不调用Next。
-
路由 路由实际上就是为URL匹配对应的处理函数。 路由的形式包括形如
/a/b/c的静态路由和/a/:id/c或/*all的参数路由。 除此之外,路由还应有路由修复的功能,如将/a/b和/a/b/对应起来 为了处理多种路由,一般采用前缀匹配树的数据结构进行路由匹配。 为了匹配HTTP方法,则在外层使用Map结构,根据method进行初步筛选,再进入前缀树匹配。 -
协议层 在这一层,可以抽象出合适的接口如下。
type Server interface { Serve(c context.Context, conn network.Conn) error } -
网络层 网络层的设计一般有两种,首先是会阻塞的BIO。
go func() { for { conn, _ := listener.Accept() go func() { conn.Read(request) // handle... conn.Write(response) }() } }()NIO相比于前者,可以避免读取数据阻塞等待过久的问题。
go func() { for { readableConns, _ := Monitor(conns) for conn := range readableConns { go func() { conn.Read(request) // handle... conn.Write(response) }() } } }()