3. HTTP RESTful API集成

3.1 系统集成原理

在 IoT 系统中,设备与云端的交互通常通过 HTTP RESTful API 完成。本章涵盖四个核心环节:接口注册(设备身份绑定)、请求构造与签名(身份与完整性验证)、响应解析(标准化数据提取)以及错误重试机制(非幂等与幂等操作的容错策略)。整体集成模型如下图所示:

REST API 集成流程 IoT 设备 POST /devices/register 云端设备注册服务 返回 device_id + api_secret 构造签名(HMAC-SHA256) Authorization: HMAC <签名> 发送 REST 请求(带签名) GET /data?ts=... 云端验证签名 & 处理 解析 JSON 响应 { "status": "ok", "data": {...} } 错误重试(指数退避) max_retries=3, backoff=2 ⚠ 注意 仅对 5xx 及网络超时重试 4xx 如 403 不重试 失败(超时/5xx)

3.2 云端接口注册流程

设备首次上线需通过注册接口获取唯一身份凭证。典型注册请求如下:

POST /api/v1/devices/register
Content-Type: application/json

{
  "device_name": "sensor_temp_01",
  "device_type": "temperature",
  "mac_address": "AA:BB:CC:DD:EE:FF",
  "firmware_version": "1.2.0"
}

成功响应返回包含 device_id 和 api_secret 的 JSON:

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

{
  "device_id": "d-abc12345",
  "api_secret": "sk_live_xxxxx",
  "token_expires_in": 86400
}
安全提示:api_secret 仅首次返回,设备务必持久化至安全存储(如 TPM 模块或加密 Flash)。云端侧应支持密钥轮换接口。

3.3 请求构造与签名

后续所有 API 请求必须携带 HMAC-SHA256 签名。签名生成步骤:

  1. 规范请求方法、路径、查询参数、时间戳、请求体。
  2. 使用 api_secret 计算 HMAC-SHA256。
  3. 通过 HTTP 头 Authorization 或自定义头传递签名。

以下为 Python 签名示例:

import hmac, hashlib, json, time, requests

def sign_request(method, path, body, secret):
    timestamp = str(int(time.time()))
    payload = f"{method}\n{path}\n{timestamp}\n{json.dumps(body, sort_keys=True)}"
    signature = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
    return signature, timestamp

secret = "sk_live_xxxxx"
method = "POST"
path = "/api/v1/devices/data"
body = {"temperature": 25.3, "humidity": 60.2}
sig, ts = sign_request(method, path, body, secret)

headers = {
    "X-Device-ID": "d-abc12345",
    "X-Timestamp": ts,
    "Authorization": f"HMAC {sig}",
    "Content-Type": "application/json"
}
response = requests.post(f"https://iot-api.example.com{path}",
                         headers=headers, json=body)

3.4 响应解析

云端返回统一错误码规范,示例响应格式:

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

{
  "code": 0,
  "message": "success",
  "data": {
    "temperature": 25.3,
    "humidity": 60.2,
    "recorded_at": "2025-03-21T10:30:00Z"
  }
}

错误码一览表:

HTTP 状态码业务 code含义是否重试
2000成功否
4011001签名无效否(检查时间戳/密钥)
4031002设备未注册 / 权限不足否
4292001请求频率超限是(指数退避)
5003001服务器内部错误是
502/5033002网关超时或服务不可用是

3.5 错误重试机制

实现指数退避重试策略:

import time, requests

def request_with_retry(url, headers, body, max_retries=3, base_delay=1):
    for attempt in range(max_retries + 1):
        try:
            resp = requests.post(url, headers=headers, json=body, timeout=5)
            if resp.status_code == 200:
                return resp.json()
            if resp.status_code in [429, 500, 502, 503]:
                if attempt == max_retries:
                    raise Exception(f"Max retries exceeded, last status {resp.status_code}")
                delay = base_delay * (2 ** attempt)
                time.sleep(delay)
                continue
            else:
                # 4xx 非重试错误直接抛出
                resp.raise_for_status()
        except requests.exceptions.Timeout:
            if attempt == max_retries:
                raise
            delay = base_delay * (2 ** attempt)
            time.sleep(delay)
    return None
重要:仅对幂等请求(如获取数据、上报已去重的传感器值)使用自动重试。非幂等操作(如创建设备、发送命令)应避免重试,或先查询确认。

3.6 调试方法

使用 curl 验证签名逻辑是否正确:

# 计算签名(使用 openssl 辅助)
TIMESTAMP=$(date +%s)
PAYLOAD="POST\n/api/v1/devices/data\n${TIMESTAMP}\n{\"temperature\":25.3}"
SIGNATURE=$(echo -e "$PAYLOAD" | openssl dgst -sha256 -hmac "sk_live_xxxxx" | awk '{print $NF}')

curl -X POST "https://iot-api.example.com/api/v1/devices/data" \
  -H "X-Device-ID: d-abc12345" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "Authorization: HMAC ${SIGNATURE}" \
  -H "Content-Type: application/json" \
  -d '{"temperature":25.3}'

3.7 部署步骤

  1. 云端侧:部署注册服务端点、签名验证中间件、设备状态数据库(Redis/MySQL)。
  2. 设备侧:集成 HTTP 客户端库,将签名逻辑封装为独立模块。
  3. 测试环境:使用 mock 服务器(如 pytest-httpserver)模拟注册和常规 API 响应。
  4. 生产环境:配置 TLS 1.2+,禁止明文 HTTP 传输。

3.8 常见错误排查

现象可能原因排查步骤
注册返回 403MAC 地址已被注册或格式错误检查 MAC 格式,联系管理后台释放
401 签名无效时间戳偏差超过 5 分钟,或密钥错误同步 NTP,打印签名串比对云端日志
请求一直超时网络不可达或 DNS 解析失败使用 ping 和 curl -v 测试
响应解析失败云端返回非 JSON 或字段不匹配打印原始响应体,启用严格 JSON 解析
重试陷入死循环未区分幂等操作在重试逻辑前添加幂等检查标志

3.9 性能与稳定性优化

  • 连接池:使用 requests.Session() 或 aiohttp 的连接复用,减少 TCP 握手。
  • 签名缓存:同一时刻的多个请求可复用时间戳(精度秒级),减少 HMAC 计算开销。
  • 异步重试:使用 tenacity 库或 asyncio 实现非阻塞重试,避免阻塞主循环。
  • 分布式限流:云端侧使用令牌桶算法,设备侧根据 Retry-After 头等待。