HTTP 驱动
dc3-driver-http 把任意 HTTP/REST 接口当作数据源接入 IoT DC3——周期性调用 REST 端点、从 JSON 响应里抽一个字段作为位号值,并支持用请求体模板向接口写值。读完你能判断哪些设备/平台适合用它接入、每个属性该填什么、接不通时从哪里查。
协议背景
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 的取舍,见物联网网络层章节。
本驱动作为 HTTP 客户端(驱动类型 DRIVER_CLIENT),用 Spring WebFlux 的 WebClient 按位号上配置的路径与方法去调接口,从 JSON 响应中按路径抽出一个值。两个本驱动特有的概念后面会反复出现:
- 响应路径(Response Path):从响应 JSON 里定位某字段的简单点号路径,如
$.data.temperature表示取data对象下的temperature。留空则把整段响应原文作为值。 - 请求体模板(Body Template):写值时用的请求体模板,里面的
${value}占位符会被命令参数替换成实际写入值。
属性配置
属性分两层填写:驱动属性(driver-attribute,设备级,决定连到哪个服务)、位号属性(point-attribute ,决定每个位号调哪个路径、用什么方法、取哪个字段——读和写都用这一份)。application.yml 里还声明了命令属性( command-attribute),但当前 write() 不消费它(见下文命令属性小节)。三类属性的来历与覆盖关系见属性与配置 。所有默认值均取自驱动的 application.yml。
驱动属性(设备级 driver-attribute)
接入一个 HTTP 数据源时,在设备上填这些属性。它们决定连到哪个服务、带什么头、超时多久——同一台设备下所有位号共享这套连接参数:
| 属性 | 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 上。
Headers 属性目前不生效
headers 在 application.yml 里已声明,但当前实现的 getConnector() 只为 WebClient 设置了固定的 Content-Type: application/json,没有读取并应用 headers 属性。需要带 Authorization 等自定义头鉴权的接口,暂时无法仅靠该属性接入。
驱动级 Method 属性目前不生效
method 在 application.yml 里声明为驱动级属性,但 getConnector() 只读取 baseUrl 与 timeout,从不读取驱动级 method;read()/write() 的方法只取自位号属性 method,缺省回退到硬编码的 GET。因此在设备上设 method=POST 不会生效——它与 headers 同属"已声明但未应用"的属性。HTTP 方法实际只由位号属性 method 决定。
位号属性(point-attribute)
每个采集位号上填:调哪个路径、用什么方法、(写时)发什么请求体、从响应里取哪个字段。读/写实际使用的 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) |
path 拼在 baseUrl 之后
实际请求 URL 是 baseUrl + path。例如 baseUrl=https://api.example.com、path=/api/v1/sensor/1 ,驱动就请求 https://api.example.com/api/v1/sensor/1。
Response Path 是简单点号路径,不是完整 JSONPath
驱动把 $. 前缀去掉后按 . 逐段从根对象往下钻,只支持 $.a.b.c 这样的逐层取字段写法。不支持数组下标([0] )、过滤器、通配符等完整 JSONPath 语法。取数组元素、或路径取不到对应字段时,会退回返回整段响应原文——这往往就是位号值" 看起来不对"的根因。响应本身就是裸值(纯数字/字符串)时,responsePath 留空即可,驱动把整段响应当作位号值。
写值要靠 Body Template 渲染
写命令不会自动把值塞进请求体。必须在位号的 bodyTemplate 里写好带 ${value} 的模板(如 {"value":${value}} ),驱动才会把命令参数替换进去发出;模板为空时发的是空请求体。
命令属性(command-attribute)
命令属性目前不被写入路径消费
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基线定时读 cron0/30 * * * * ?(每 30 秒读一轮);默认激活的devprofile 在application-dev.yml把 read cron 覆盖为0/5 * * * * ?(每 5 秒),故开箱即用的实际采集间隔是 5 秒。 - 健康检查:设备健康检查 cron
0/15 * * * * ?,租约超时45 秒——在线状态机制见设备。 - 在线判定:
health()以"clientMap里是否为该设备建过WebClient"判断在线——首次成功读/写后建立连接即视为在线;读/写抛异常时驱动会clientMap.remove(deviceId)摘除连接,下一轮重建。
故障排查
连不上 / 一直 offline
先确认 baseUrl 可达:用 curl <baseUrl><path> 在驱动所在主机直接验证网络与端口是否通。驱动以"是否建过 WebClient" 判在线,首次读/写失败会立即摘除连接,因此 baseUrl 错、DNS 解析不了、或目标端口被防火墙挡,都会表现为设备始终 offline。
请求超时
默认 timeout=5000 毫秒落在响应超时上。目标接口本身慢、或网络抖动时会触发超时并把本轮读/写判为失败、摘除连接。先用 curl -w '%{time_total}' 量一下真实耗时,确认是接口慢还是链路慢,再据此调大 timeout。
位号值看起来不对 / 总是整段 JSON
多半是 responsePath 没匹配上。该驱动只认 $.a.b.c 的逐层点号路径,路径写错、字段名大小写不符、或想取数组元素( $.list[0].v),都会因取不到字段而退回返回整段响应原文。对照接口真实响应逐层核对字段名;要取数组里的元素,当前实现做不到。
鉴权接口 401/403
当前实现未应用 headers 属性(见上文驱动属性表的告警)。需要 Authorization、X-Api-Key 等请求头的接口,暂时无法仅凭设备属性接入,会被服务端拒为 401/403。
写命令报错或没生效
写值经由位号的 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 是请求/响应模型,驱动主动发起,无被动推送通道。
以上读/写能力与驱动能力矩阵的 HTTP (HttpDriver) 行一致。
实现状态:可用
HttpDriverCustomServiceImpl 的 initial()/read()/write()/health()/validate() 均已完整实现,是可投入采集的成熟驱动。两处实现边界须知:① responsePath 仅支持简单点号路径,不支持数组/过滤器(见上);② headers 属性已声明但尚未在连接中应用,自定义请求头当前无法生效。
最小接入示例
把一个返回 {"data":{"temperature":25.6}} 的接口接进来:
- 选
HTTP REST Client Driver创建设备,驱动属性填baseUrl=https://api.example.com、method=GET、timeout=5000。 - 给设备绑定的模板加一个温度位号(
pointTypeFlag=FLOAT、READ_ONLY),位号属性填path=/api/v1/sensor/1、method=GET、responsePath=$.data.temperature。 - 启动驱动,数秒内就能在位号值里看到抽取出的
25.6(默认devprofile 每 5 秒采一轮)。