HTTP 框架修炼之道 | 青训营

107 阅读7分钟

Part 01 再谈HTTP协议

HTTP 协议是什么

  HTTP 即 超文本传输协议 (英语:HyperText Transfer Protocol,缩写:HTTP

  设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。

  ?为什么需要协议

  1. 需要明确边界

    • 开始

    • 结束

  2. 元数据进行信息的描述

    能够携带信息

    • 什么消息
    • 消息类型

协议里有什么

  发送的HTTP报文

GET / HTTP/1.1
Host: developer.mozilla.org
Accept-Language: zh

  读取接收到的HTTP报文

HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
ETag: "51142bc1-7449-479b075b2891b"
Accept-Ranges: bytes
Content-Length: 114
Content-Type: text/html

<!DOCTYPE html>…(此处是所请求网页的 114 字节)

  根据上面所给出的发送和接收的 HTTP 报文,我们可以把协议内包含的内容分为3个方面

  1. 请求行 / 状态行

    • 请求行

      • 方法名

        HTTP 定义了一组请求方法,以表明要对给定资源执行的操作。指示针对给定资源要执行的期望动作

        常见 HTTP 方法介绍

        常见方法名:GET​​ POST​​ HEAD​​ PUT​​ DELETE​​ CONNECT​​ OPTIONS​​ TRACE​​ PATCH​​

        其中 GET​​ (HTTP/0.9)POST​​ 最常见 ,PUT​​ DELETE​​ CONNECT​​ OPTIONS​​ TRACE​​ (HTTP/1.1),PATCH​​ 为1.1后新增但使用广泛,与 PUT​​ 语义类似

        PUT​​:是幂等的——调用一次与连续调用多次效果是相同的(即没有作用),不像 POST​​ 重复提交数据会造成异常或冲突。并且是完整更新

        PATCH​​:不同PUT​​ 的全部更新,PATCH 是部分更新。

        实际上我们在浏览器浏览网页时按下 F12​​ 打开开发人员工具,在正常浏览网页时会出现一系列的请求

        在这些请求中主要以 GET​​ POST​​ 为主,当然有时候还会看到 OPTIONS​​ PATCH​​ 等等

        OPTIONS​​ 是用来请求给定的 URL 或服务器的允许通信选项,可以通过 curl​​ 发出该请求方法来判断服务器支持哪些请求方法,也可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受。在这个示例中,我们会为这些参数请求权限:

        GET​​:请求应该只用于请求数据,而不应该包含数据

        POST​:发送数据给服务器。请求主体的类型由 Content-Type​​ 标头指定

      • URL

      • 协议版本

    • 状态行

      • 协议版本

      • 响应状态码及其描述

        HTTP 响应状态码用来表明特定 HTTP 请求是否成功完成。 正常网络请求以 200​ 状态码居多。

        其核心作用是 Web Server服务器用来告诉客户端,当前的网页请求发生了什么事,或者说当前Web服务器的响应状态。如果没有HTTP状态码的返回,出现问题时不方便定位问题是由服务端还是客户端发生的。

        响应码被归为以下五大类:

        1. 信息响应 (100​​–199​​)
        2. 成功响应 (200​​–299​​)
        3. 重定向消息 (300​​–399​​)
        4. 客户端错误响应 (400​​–499​​)
        5. 服务端错误响应 (500​​–599​​)

        限于篇幅😢,就不再贴出完整的状态码返回的各个含义,可前往 HTTP 状态代码 | Microsoft Learn 查询

  2. 请求头 / 响应头

    • 请求头 Request header

      请求标头是一种 HTTP 标头,它可在 HTTP 请求中使用,其提供有关请求上下文的信息,以便服务器可以定制响应。例如,Accept-* 标头表示响应允许的条件和首选的格式。其他标头可以提供身份验证凭据(例如 Authorization),控制缓存,或者获取有关用户代理(user agent)或 referrer 的信息等。

    • 响应头 Response header

      其可以用于 HTTP 响应,且与响应消息主体无关。像 Age​、Location​ 或 Server​ 都属于响应标头,它们被用于提供更详细的响应上下文。

  3. 请求体 / 响应体

Demo 构建——一个服务器的实现

package main

import (
	"context"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
)

func main() {
	h := server.Default()

	h.GET("/ping", func(c context.Context, ctx *app.RequestContext) {
		ctx.JSON(consts.StatusOK, utils.H{"message": "success"})
	})
	h.POST("/test", func(c context.Context, ctx *app.RequestContext) {
		ctx.Data(200, "text/plain; charset=utf-8", []byte("Are u OK?"))
	})
	h.Spin()
}

  在这个代码中 /ping/test​ 为 URL ,GETPOST​ 为方法,而通过这个代码我们可以在本地计算机上访问本机IP地址加上8888端口号可以显示我们想要的内容

  例如上面的代码中 当你在本机运行程序时,浏览器打开并访问 127.0.0.1:8888/ping 屏幕上就会出现 {"message": "success"}

  请求流程由业务层、服务治理层、中间件层、路由层、协议编(解)码程、传输层起作用

  强烈推荐Postman,这是一款流行的API测试工具,它可以帮助测试和开发API。可以轻松地创建和发送HTTP请求,以及查看响应。

不足与展望

  1. HTTP1 基于TCP

    • 队头阻塞——前几个请求没结束前

    • 传输效率低

      引用上文所提到的HTTP报文 这个报文头部信息多,并且大多都是无关信息,而真正用户所需要的是第10行的网页

      HTTP/1.1 200 OK
      Date: Sat, 09 Oct 2010 14:28:02 GMT
      Server: Apache
      Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT
      ETag: "51142bc1-7449-479b075b2891b"
      Accept-Ranges: bytes
      Content-Length: 114
      Content-Type: text/html
      
      <!DOCTYPE html>…(此处是所请求网页的 114 字节)
      
    • 明文传输不安全

  2. HTTP2

    • 多路复用

    • 头部压缩

      请求头缓存

    • 二进制请求

      解析变得流畅

    • 仍然基于TCP,并没有彻底解决对头阻塞的问题

  3. QUIC

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

Part 02 HTTP框架的设计与实现*

分层设计

  分层设计的目的是为了简化系统的设计。

  试想一下如果编写一个程序,需要同时考虑OSI的7层网络模型,实在是过于麻烦。

  诞生分层设计我们只需关注特定层的开发即可,其余层使用接口,接口本身的实现无需关心。

  分层设计的特性

  1. 专注性
  2. 拓展性
  3. 复用性

  设计一个HTTP框架我们需要考虑让框架高内聚、低耦合、易复用、高拓展性的特性

一个切实可行的复杂系统势必是从一个切实可行的简单系统发展而来的。从头开始设计的复杂系统根本不切实可行,无法修修补补让它切实可行。你必须由一个切实可行的简单系统重新开始。——盖尔定律

  ​image转存失败,建议直接上传图片文件

应用层 Application

  提供合理的API

  • 可理解性

    ctx.Body()​ , ctx.GetBody()​ ,
    ctx.BodyA()

  • 简单性

    ctx.Request.Header.Peek(key)
    /ctx.GetHeader(key)

  • 兼容性

  • 可见性

  • 可测性

  • 冗余性

中间件层

  中间件需求:

  1. 配合Handler 实现一个完整的请求处理生命周期
  2. 拥有预处理逻辑与后处理逻辑
  3. 可以注册多中间件
  4. 对上层模块用户逻辑模块易用

  经典中间件模型——洋葱模型

  可以将核心逻辑与通用逻辑分离,因此适用于日志记录、性能统计、安全控制、事务处理、异常处理等不同的使用场景