3. CoAP请求/响应方法与状态码

CoAP 把 HTTP 的“方法 + 状态码”语义压缩到 UDP 之上,但保留了几乎一致的资源操作模型。理解方法语义与响应码分层,是设计设备资源、编写客户端逻辑和排查通信故障的基础。本节先厘清四种方法的幂等性与副作用,再说明 2.xx、4.xx、5.xx 三类响应码的判断边界,最后给出一个可直接对照实现的设备资源操作示例。

一、四种方法的语义与幂等性

CoAP 请求方法定义在 RFC 7252 中,语义与 HTTP 对齐,但作用对象是 CoAP 资源(URI 路径)。判断方法是否可安全重传,关键看两点:是否改变资源状态(副作用),以及重复执行是否产生相同结果(幂等性)。

方法语义副作用幂等典型设备用途
GET读取资源当前表示无是读温度、读开关状态、读版本号
POST向资源提交数据,由服务端决定处理结果有否触发动作、追加日志、提交控制指令
PUT用请求负载整体替换目标资源有是写入配置、设置目标温度、覆盖阈值
DELETE删除目标资源有是删除定时任务、注销订阅、清除告警记录

幂等性直接决定重传策略。CoAP 运行在 UDP 上,报文可能丢失或重复。GET、PUT、DELETE 即使被重复送达,结果与送达一次相同,因此客户端在 CON 消息超时后可以安全重传;POST 不幂等,重复送达可能触发两次动作,例如“开阀”指令被下发两次。对 POST 类操作,设备端应结合 Token 或业务序号做去重,客户端也应避免盲目重传。

PUT 是“整体替换”而非“局部更新”。若只想改一个字段,要么先 GET 拿到完整表示、修改后再 PUT 回去,要么在设备端专门设计一个只接受该字段的子资源。CoAP 没有 PATCH 方法,局部更新需靠资源建模解决。

二、响应码分层:2.xx、4.xx、5.xx

CoAP 响应码为 8 位,高 3 位是类别(class),低 5 位是细节(detail),书写为 “c.dd”。类别决定客户端应采取的动作方向:

  • 2.xx 成功:请求已被理解、接受并处理。常见 2.01 Created(PUT/POST 新建资源成功)、2.02 Deleted(DELETE 成功)、2.04 Changed(PUT/POST 修改成功)、2.05 Content(GET 成功并携带负载)。
  • 4.xx 客户端错误:请求本身有问题,重发同样的请求不会成功。常见 4.00 Bad Request(负载格式错误)、4.01 Unauthorized(未通过认证)、4.03 Forbidden(无权限)、4.04 Not Found(资源不存在)、4.05 Method Not Allowed(该资源不支持此方法)、4.15 Unsupported Content-Format(负载媒体类型不支持)。
  • 5.xx 服务端错误:请求格式正确,但服务端当前无法完成。常见 5.00 Internal Server Error、5.01 Not Implemented(功能未实现)、5.03 Service Unavailable(设备忙或过载)。

区分 4.xx 与 5.xx 的实践意义在于重试决策:4.xx 应修正请求(改路径、改负载、补认证),重试无意义;5.03 属于暂时性故障,客户端可退避后重试。设备端返回 5.03 时通常还应在响应中携带 Max-Age,提示客户端多久后再来。

CoAP 方法语义与响应码判断路径 CoAP 客户端 发起请求 / 重传 选择方法 GET / PUT / DELETE 幂等 POST 非幂等,需去重 设备资源 /sensor/temp /config /tasks 响应码 2.xx / 4.xx / 5.xx 决定下一步动作 2.xx 成功 2.01 Created(PUT/POST 新建) 2.02 Deleted(DELETE 成功) 2.04 Changed(PUT/POST 修改) 2.05 Content(GET 返回负载) 4.xx 客户端错误 4.00 负载格式错误 4.04 资源不存在 4.05 方法不被支持 4.15 媒体类型不支持 5.xx 服务端错误 5.00 内部错误 5.01 功能未实现 5.03 设备忙 / 过载 可携带 Max-Age 提示重试 客户端决策 2.xx → 处理负载,结束 4.xx → 修正请求,不重试 5.03 → 退避后重试 POST 重传前须确认设备端已按 Token 去重,避免动作重复执行

三、设备资源操作示例

假设一台环境控制器暴露三个资源:/sensor/temp(只读温度)、/config/target(可读写目标温度)、/tasks/reboot(动作型资源)。下面给出一次完整的操作序列,包含请求方法、负载与预期响应码。

# 1. 读取当前温度(GET,幂等)
GET coap://[fd00::1]/sensor/temp
→ 2.05 Content  Payload: {"v":23.5,"u":"C"}

# 2. 修改目标温度(PUT,整体替换,幂等)
PUT coap://[fd00::1]/config/target
  Content-Format: application/json
  Payload: {"target":25.0}
→ 2.04 Changed

# 3. 触发重启(POST,非幂等,需去重)
POST coap://[fd00::1]/tasks/reboot
  Content-Format: application/json
  Payload: {"delay":5}
→ 2.01 Created  Location-Path: /tasks/reboot/17

# 4. 删除已完成的定时任务(DELETE,幂等)
DELETE coap://[fd00::1]/tasks/reboot/17
→ 2.02 Deleted

# 5. 错误示例:向只读资源写入
PUT coap://[fd00::1]/sensor/temp
  Payload: {"v":99}
→ 4.05 Method Not Allowed

# 6. 错误示例:设备过载
POST coap://[fd00::1]/tasks/reboot
→ 5.03 Service Unavailable  Max-Age: 10

从示例可以看到几个设计要点。第一,/sensor/temp 只支持 GET,对 PUT 返回 4.05 而不是 4.00,因为请求格式本身没错,是方法不被该资源允许。第二,POST 创建任务后返回 2.01 并带 Location-Path,客户端据此获得新资源路径,后续可用 DELETE 清理。第三,5.03 携带 Max-Age: 10,客户端应在约 10 秒后重试,而不是立即重发造成更大压力。

不要把 4.04 与 5.03 混用。资源确实不存在时返回 4.04;资源存在但设备暂时无法处理时返回 5.03。若把设备忙误报为 4.04,客户端会认为路径写错而放弃重试,导致控制指令永久丢失。

四、方法选择与资源建模的对应关系

资源设计应让方法语义自然成立。可读状态用 GET;可整体覆盖的配置用 PUT;需要服务端生成标识、追加记录或触发不可重复动作的,用 POST;可撤销的实体用 DELETE。若一个资源同时承担“读状态”和“触发动作”,建议拆成两个 URI,例如 /sensor/temp 与 /tasks/calibrate,避免 GET 与 POST 语义在同一路径上冲突。

响应码的选择同样要克制:能用 2.04 表达修改成功,就不要一律返回 2.05;能区分 4.00 与 4.15,就不要把所有负载问题都归为 4.00。清晰的响应码分层能让客户端用少量分支完成正确决策,也便于抓包时快速定位问题发生在请求侧还是设备侧。