3、HTTP协议核心:HTTP/1.1与HTTP/2特性、请求/响应模型、RESTful API设计原则

好,咱们进入第三讲。HTTP协议,做物联网和边缘计算的人,几乎天天跟它打交道。但说实话,很多人对它的理解停留在「发个请求,拿个数据」的层面。这不够,远远不够。

我当年在做一个智能网关项目时,设备端上报数据用的是HTTP/1.1,结果并发一上来,连接池直接爆了。后来换成HTTP/2,问题迎刃而解。你想想看,选错协议版本,代价有多大?

3.1 HTTP/1.1 的核心特性

HTTP/1.1 是1997年定稿的,到现在二十多年了。它有几个关键特性,咱们得掰扯清楚。

持久连接(Keep-Alive)

HTTP/1.0 时代,每次请求都要新建一个TCP连接,完事就断开。这效率,简直了。HTTP/1.1 引入了持久连接,默认开启。什么意思?就是多个请求可以复用同一个TCP连接。

我在项目中遇到过,有些嵌入式设备内存极小,TCP连接数有限。用持久连接后,连接数从几十个降到了两三个,设备稳定性明显提升。

管道化(Pipelining)

这个特性,说实话,是个「理论很美,现实很骨感」的东西。它允许客户端在收到上一个响应之前,就发送下一个请求。听起来能提速对吧?但实际部署中,很多中间件和服务器实现得不好,甚至直接不支持。我个人习惯是:别依赖管道化,尤其是在边缘设备上。

分块传输编码(Chunked Transfer Encoding)

服务器不知道响应体有多大时,可以用分块传输。每块前面有长度标识,最后一块长度为0表示结束。这对流式数据很有用,比如摄像头实时画面。

核心要点:HTTP/1.1 的队头阻塞问题——同一个连接上的请求必须按顺序处理。前一个请求慢了,后面的都得等着。这是它最大的痛点。

3.2 HTTP/2 的改进与特性

HTTP/2 不是简单的版本升级,它几乎是重写了。2015年发布,核心目标是解决HTTP/1.1的性能瓶颈。

二进制分帧层

HTTP/1.1 是文本协议,HTTP/2 改成二进制了。说白了,就是把请求和响应拆成更小的帧,然后在同一个TCP连接上交错发送。这解决了队头阻塞问题。

举个例子:你同时请求a.html和b.css。在HTTP/1.1里,必须等a.html传完才能传b.css。HTTP/2呢?把a.html拆成帧1、帧2、帧3,b.css拆成帧4、帧5,然后混着发:帧1、帧4、帧2、帧5、帧3。接收方再按流ID重新组装。是不是很巧妙?

多路复用

一个TCP连接上可以同时跑多个流(stream)。每个流独立,互不干扰。这对边缘计算设备特别友好——连接数少,但并发能力不降。

我的经验:在资源受限的MCU上,HTTP/2的多路复用能显著减少内存占用。我曾经把一个网关设备的TCP连接数从50降到5,CPU占用反而下降了30%。

服务器推送

服务器可以主动给客户端推送资源,不用等客户端请求。比如客户端请求index.html,服务器可以顺手把style.css和app.js一起推过去。

嗯,这里要注意:服务器推送不是万能的。推多了反而浪费带宽。我在一个项目中就吃过亏,推了一堆客户端已经缓存过的资源,结果带宽爆了。后来加了缓存检查逻辑才解决。

头部压缩(HPACK)

HTTP/1.1的头部是纯文本,重复的字段每次都传。HTTP/2用HPACK算法压缩头部,能减少50%-90%的头部开销。对于物联网设备,每次上报数据可能就几十字节,头部占一大半,压缩后效果立竿见影。

特性 HTTP/1.1 HTTP/2
数据格式 文本 二进制
队头阻塞 无(流级别)
多路复用 不支持 支持
头部压缩 HPACK
服务器推送 不支持 支持
连接复用 支持(Keep-Alive) 原生支持

3.3 请求/响应模型

HTTP的请求/响应模型,说白了就是「一问一答」。客户端发请求,服务器给响应。但这里面有几个细节,做边缘计算时必须搞清楚。

请求结构

一个HTTP请求由三部分组成:请求行、请求头、请求体。

POST /api/v1/sensor/data HTTP/1.1
Host: edge-gateway.local
Content-Type: application/json
Content-Length: 67

{
  "device_id": "sensor-01",
  "temperature": 25.3,
  "humidity": 68.2
}

请求行包含方法(POST)、路径(/api/v1/sensor/data)和协议版本。请求头是键值对,传递元数据。请求体放实际数据。

响应结构

响应也类似:状态行、响应头、响应体。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 45

{"status": "success", "record_id": "abc123"}

状态码很重要。200表示成功,201表示创建成功,400是客户端错误,500是服务器错误。我建议你在边缘设备上,对状态码做精细化处理。别只判断「是不是200」,其他一概不管。我曾经见过一个设备,服务器返回429(限流)它还傻傻地重试,结果被拉黑了。

避坑指南:在边缘设备上,HTTP请求的超时设置要合理。我见过有人设了30秒超时,结果设备在弱网环境下一直阻塞,电池半天就耗光了。建议根据业务场景,设3-10秒。

3.4 RESTful API 设计原则

RESTful API,不是标准,而是一套设计风格。做物联网平台,API设计得好不好,直接影响设备接入的效率和可维护性。

资源导向

把一切抽象为资源。设备是资源,传感器数据是资源,命令也是资源。用URL表示资源,用HTTP方法表示操作。

  • GET /devices — 获取设备列表
  • GET /devices/{id} — 获取单个设备详情
  • POST /devices — 注册新设备
  • PUT /devices/{id} — 更新设备信息
  • DELETE /devices/{id} — 删除设备

我个人习惯:URL用名词复数,不要用动词。比如 /getDeviceData 这种写法,一看就是新手。

无状态

每个请求都包含所有必要信息,服务器不保存客户端状态。这对边缘计算很重要——网关设备可能随时断连,无状态设计让恢复变得简单。

统一接口

用标准的HTTP方法,不要自己发明。比如更新资源用PUT或PATCH,不要用POST加个?action=update参数。

资源表述

客户端和服务器通过资源的表述(representation)交互。常用JSON或XML。物联网场景,我强烈推荐JSON,轻量、易解析。

我的建议:设计API时,给每个资源一个唯一的ID。设备ID、数据点ID、命令ID,都用字符串或UUID。别用自增整数,否则分布式环境下你会哭的。

HATEOAS(超媒体作为应用状态引擎)

这个原则,说实话,在物联网场景下用得不多。它要求响应中包含相关资源的链接,让客户端可以「发现」API。比如:

{
  "device_id": "sensor-01",
  "temperature": 25.3,
  "links": [
    {"rel": "self", "href": "/devices/sensor-01"},
    {"rel": "data", "href": "/devices/sensor-01/data"},
    {"rel": "commands", "href": "/devices/sensor-01/commands"}
  ]
}

这样做的好处是,客户端不用硬编码URL。但代价是响应体变大,对带宽有限的边缘设备不太友好。我一般只在管理类API中用,数据上报类API不用。

3.5 边缘计算场景下的选型建议

讲了这么多,到底怎么选?我给出几条实战建议:

  1. 资源极度受限(MCU、几十KB内存):用HTTP/1.1,配合Keep-Alive。HTTP/2的二进制帧解析开销大,不适合。
  2. 中等资源(Linux网关、几百MB内存):优先HTTP/2。多路复用能减少连接数,头部压缩能省带宽。
  3. 高并发场景(成百上千设备同时上报):必须HTTP/2。HTTP/1.1的队头阻塞会让你崩溃。
  4. 弱网环境(NB-IoT、卫星链路):建议考虑CoAP(后面章节会讲),HTTP的握手开销太大。

好了,这一章的内容就到这。HTTP协议看似简单,但里面的门道不少。下一章咱们聊聊CoAP协议,看看它凭什么在物联网领域跟HTTP叫板。