Files
iot-dc3/docs/zh/drivers/http.md

175 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: HTTP 驱动
---
# HTTP 驱动
`dc3-driver-http` 把任意 HTTP/REST 接口当作数据源接入 IoT DC3——周期性调用 REST 端点、从 JSON
响应里抽一个字段作为[位号值](../introduction/concepts/point-value),并支持用请求体模板向接口写值。读完你能判断哪些设备/平台适合用它接入、每个属性该填什么、接不通时从哪里查。
## 协议背景
HTTP(HyperText Transfer Protocol)与建立在它之上的 REST 风格接口,是互联网最通用的请求/响应协议:客户端用一个**方法**(
`GET`/`POST`/`PUT`/`DELETE`)对一个**资源路径**发起请求,服务端返回状态码与一段报文(在物联网场景里通常是
JSON)。它无连接语义简单、几乎所有语言和工具都原生支持、调试方便,因此成为系统间集成的"最大公约数"。
在物联网四层参考架构里,HTTP 属于**网络层**中的**应用层消息协议**一类(与 MQTT、CoAP、LwM2M 并列)——它定义"
消息长什么样、怎么投递",而不关心底层走的是 Wi-Fi 还是蜂窝。但要诚实地说:HTTP 报文头臃肿、保活成本高、不为受限设备设计,**并不适合
**电池供电终端的高频上报。它在 IoT 里的真正位置是**对接现成接口**——第三方平台开放的 REST API、设备自带的 RESTful
接口、把现场数据聚合成 HTTP 端点的数据网关。这类"上游已经讲 REST、只需周期取数"的场景,正是本驱动的用武之地。关于 HTTP 与
MQTT/CoAP/LwM2M 的取舍,见[物联网网络层章节](../foundations/iot-protocols)。
本驱动作为 HTTP 客户端([驱动](../introduction/concepts/driver)类型 `DRIVER_CLIENT`),用 Spring WebFlux 的 `WebClient`
按[位号](../introduction/concepts/point)上配置的路径与方法去调接口,从 JSON 响应中按路径抽出一个值。两个本驱动特有的概念后面会反复出现:
- **响应路径(Response Path)**:从响应 JSON 里定位某字段的简单点号路径,如 `$.data.temperature` 表示取 `data` 对象下的
`temperature`。留空则把整段响应原文作为值。
- **请求体模板(Body Template)**:写值时用的请求体模板,里面的 `${value}` 占位符会被命令参数替换成实际写入值。
## 属性配置
属性分两层填写:**驱动属性**(`driver-attribute`,设备级,决定连到哪个服务)、**位号属性**(`point-attribute`
,决定每个位号调哪个路径、用什么方法、取哪个字段——读和写都用这一份)。`application.yml` 里还声明了**命令属性**(
`command-attribute`),但当前 `write()`
不消费它(见下文命令属性小节)。三类属性的来历与覆盖关系见[属性与配置](../introduction/concepts/attribute-config)
。所有默认值均取自驱动的 `application.yml`。
### 驱动属性(设备级 `driver-attribute`)
接入一个 HTTP 数据源时,在[设备](../introduction/concepts/device)上填这些属性。它们决定连到哪个服务、带什么头、超时多久——同一台设备下所有位号共享这套连接参数:
| 属性 | code | 类型 | 默认值 | 说明 |
|----------|-----------|--------|--------|----------------------------------------------------|
| Base URL | `baseUrl` | STRING | (空) | API 请求的基础地址(如 `https://api.example.com`) |
| Method | `method` | STRING | `GET` | 声明的默认 HTTP 方法,但当前实现未读取该属性(见下方告警);实际方法只由位号属性决定 |
| Headers | `headers` | STRING | (空) | 自定义请求头,JSON 形式(如 `{"Authorization":"Bearer xxx"}`) |
| Timeout | `timeout` | INT | `5000` | 请求超时(毫秒),作用于响应超时 `responseTimeout` |
`baseUrl` 是必填项——驱动用它构造 `WebClient` 的 base URL,所有位号路径都拼在它之后;缺了它驱动的 `validate()` 会直接判定校验不通过。
`timeout` 默认 `5000` 毫秒,落到 Reactor Netty `HttpClient` 的 `responseTimeout` 上。
::: warning Headers 属性目前不生效
`headers` 在 `application.yml` 里已声明,但当前实现的 `getConnector()` 只为 `WebClient` 设置了固定的
`Content-Type: application/json`,**没有**读取并应用 `headers` 属性。需要带 `Authorization` 等自定义头鉴权的接口,暂时无法仅靠该属性接入。
:::
::: warning 驱动级 Method 属性目前不生效
`method` 在 `application.yml` 里声明为驱动级属性,但 `getConnector()` 只读取 `baseUrl` 与 `timeout`,从不读取驱动级
`method`;`read()`/`write()` 的方法只取自位号属性 `method`,缺省回退到硬编码的 `GET`。因此在设备上设 `method=POST` 不会生效——它与
`headers` 同属"已声明但未应用"的属性。HTTP 方法实际只由位号属性 `method` 决定。
:::
### 位号属性(`point-attribute`)
每个采集[位号](../introduction/concepts/point)上填:调哪个路径、用什么方法、(写时)发什么请求体、从响应里取哪个字段。读/写实际使用的
HTTP 方法只来自位号的 `method`,缺省时回退到硬编码的 `GET`,与驱动级 `method` 属性无关(驱动级 `method` 不被实现读取,见上):
| 属性 | code | 类型 | 默认值 | 说明 |
|---------------|----------------|--------|-------|----------------------------------------|
| Path | `path` | STRING | (空) | API 路径(如 `/api/v1/sensor/{id}`) |
| Method | `method` | STRING | `GET` | 本位号的 HTTP 方法,缺省回退到硬编码 `GET` |
| Body Template | `bodyTemplate` | STRING | (空) | 带 `${value}` 占位符的请求体模板 |
| Response Path | `responsePath` | STRING | (空) | 从 JSON 响应抽值的路径(如 `$.data.temperature`) |
::: tip path 拼在 baseUrl 之后
实际请求 URL 是 `baseUrl + path`。例如 `baseUrl=https://api.example.com`、`path=/api/v1/sensor/1`
,驱动就请求 `https://api.example.com/api/v1/sensor/1`。
:::
::: warning Response Path 是简单点号路径,不是完整 JSONPath
驱动把 `$.` 前缀去掉后按 `.` 逐段从根对象往下钻,只支持 `$.a.b.c` 这样的逐层取字段写法。**不支持**数组下标(`[0]`
)、过滤器、通配符等完整 JSONPath 语法。取数组元素、或路径取不到对应字段时,会退回返回整段响应原文——这往往就是位号值"
看起来不对"的根因。响应本身就是裸值(纯数字/字符串)时,`responsePath` 留空即可,驱动把整段响应当作位号值。
:::
::: warning 写值要靠 Body Template 渲染
写命令不会自动把值塞进请求体。必须在位号的 `bodyTemplate` 里写好带 `${value}` 的模板(如 `{"value":${value}}`
),驱动才会把命令参数替换进去发出;模板为空时发的是空请求体。
:::
### 命令属性(`command-attribute`)
::: warning 命令属性目前不被写入路径消费
`application.yml` 在 `command-attribute` 下声明了 `path` 与 `method`(`default-value: POST`),但驱动的 SPI `write()` 签名只接收
`driverConfig` 与 `pointConfig`,不传入命令属性映射;`write()` 实际从位号属性(`pointConfig`)读取 `path`/`method`/
`bodyTemplate`,且 `method` 缺省回退到硬编码的 `GET`(不是 `POST`)。因此下表列出的 `command-attribute`
当前是声明但未生效的死配置,写操作的路径与方法请填在位号属性上。
:::
下表为 `application.yml` 中的声明值(当前 `write()` 未读取):
| 属性 | code | 类型 | 默认值 | 说明 |
|--------|----------|--------|--------|--------------------------------|
| Path | `path` | STRING | (空) | 命令的 API 路径(当前未被 `write()` 读取) |
| Method | `method` | STRING | `POST` | 命令的 HTTP 方法(当前未被 `write()` 读取) |
### 采集与健康检查
这些参数定在 `application.yml` 的 `dc3.driver.schedule` 与 `health` 下,无需在设备上填:
- **采集周期**:`application.yml` 基线定时读 cron `0/30 * * * * ?`(每 30 秒读一轮);默认激活的 `dev` profile 在
`application-dev.yml` 把 read cron 覆盖为 `0/5 * * * * ?`(每 5 秒),故开箱即用的实际采集间隔是 5 秒。
- **健康检查**:设备健康检查 cron `0/15 * * * * ?`,租约超时 `45 秒`
——在线状态机制见[设备](../introduction/concepts/device)。
- **在线判定**:`health()` 以"`clientMap` 里是否为该设备建过 `WebClient`"判断在线——首次成功读/写后建立连接即视为在线;读/写抛异常时驱动会
`clientMap.remove(deviceId)` 摘除连接,下一轮重建。
## 故障排查
::: warning 连不上 / 一直 offline
先确认 `baseUrl` 可达:用 `curl <baseUrl><path>` 在驱动所在主机直接验证网络与端口是否通。驱动以"是否建过 `WebClient`"
判在线,首次读/写失败会立即摘除连接,因此 `baseUrl` 错、DNS 解析不了、或目标端口被防火墙挡,都会表现为设备始终 offline。
:::
::: warning 请求超时
默认 `timeout=5000` 毫秒落在响应超时上。目标接口本身慢、或网络抖动时会触发超时并把本轮读/写判为失败、摘除连接。先用
`curl -w '%{time_total}'` 量一下真实耗时,确认是接口慢还是链路慢,再据此调大 `timeout`。
:::
::: warning 位号值看起来不对 / 总是整段 JSON
多半是 `responsePath` 没匹配上。该驱动只认 `$.a.b.c` 的逐层点号路径,路径写错、字段名大小写不符、或想取数组元素(
`$.list[0].v`),都会因取不到字段而**退回返回整段响应原文**。对照接口真实响应逐层核对字段名;要取数组里的元素,当前实现做不到。
:::
::: warning 鉴权接口 401/403
当前实现未应用 `headers` 属性(见上文驱动属性表的告警)。需要 `Authorization`、`X-Api-Key` 等请求头的接口,暂时无法仅凭设备属性接入,会被服务端拒为
401/403。
:::
::: warning 写命令报错或没生效
写值经由位号的 `bodyTemplate` 渲染:模板为空时发的是**空请求体**,接口若要求 JSON body 会拒绝。确认 `bodyTemplate` 里含
`${value}` 占位符、且渲染后是接口接受的合法 JSON;写失败时驱动抛 `WritePointException` 并摘除连接。
:::
## 在 IoT DC3 中如何落地
- **dc3.driver.code**:`HttpDriver`(驱动名 `HTTP REST Client Driver`,类型 `DRIVER_CLIENT`)。该 code 是稳定路由标识,不可随意改。
- **读**:✓ 已实现。定时按位号 `path`/`method` 调接口,`responsePath` 从 JSON 抽值。
- **写**:✓ 已实现。把位号 `bodyTemplate` 中 `${value}` 替换为命令参数后发请求。
- **订阅/上报**:— 不支持。HTTP 是请求/响应模型,驱动主动发起,无被动推送通道。
以上读/写能力与[驱动能力矩阵](./matrix)的 `HTTP (HttpDriver)` 行一致。
::: info 实现状态:可用
`HttpDriverCustomServiceImpl` 的 `initial()`/`read()`/`write()`/`health()`/`validate()` 均已完整实现,是可投入采集的成熟驱动。两处实现边界须知:①
`responsePath` 仅支持简单点号路径,不支持数组/过滤器(见上);② `headers` 属性已声明但尚未在连接中应用,自定义请求头当前无法生效。
:::
### 最小接入示例
把一个返回 `{"data":{"temperature":25.6}}` 的接口接进来:
1. 选 `HTTP REST Client Driver` 创建[设备](../introduction/concepts/device),驱动属性填
`baseUrl=https://api.example.com`、`method=GET`、`timeout=5000`。
2. 给设备绑定的[物模型](../introduction/concepts/profile)加一个温度[位号](../introduction/concepts/point)(
`pointTypeFlag=FLOAT`、`READ_ONLY`),位号属性填 `path=/api/v1/sensor/1`、`method=GET`、`responsePath=$.data.temperature`。
3. 启动驱动,数秒内就能在[位号值](../introduction/concepts/point-value)里看到抽取出的 `25.6`(默认 `dev` profile 每 5
秒采一轮)。
## 延伸阅读
- [驱动总览](./index) — 全部驱动分组与选型入口
- [驱动能力矩阵](./matrix) — 各驱动读/写/订阅能力一览
- [设备接入](../operation/device-onboarding) — 一次完整的接入流程
- [物联网网络层章节](../foundations/iot-protocols) — HTTP 与 MQTT/CoAP/LwM2M 在网络层的定位与取舍