Tunnel 设备隧道

多路复用流式 WebSocket 反向隧道,用标准 HTTP 访问设备本机 / 局域网主机

模块类型:service | 基础路径:/api/v2/tunnel | 鉴权:设备 APIKey(noQuota)

应用须在后台启用 tunnel 服务(否则 403)。

一条多路复用 + 流式的 WebSocket 反向隧道:一台运行在 Windows / Linux / macOS / Android 上的网关程序(协议客户端,使用方按本协议自行实现)主动 WS 外连、鉴权后获得 6 位设备码,成为所在局域网的入口。调用方只用标准 HTTP,服务端经 WS 把请求流式并发转发到设备本机,或它所在局域网内的其它主机。

支持:任意方法、二进制、并发(多 streamId)、实时流式(SSE / 大文件边下边发)、跨主机(X-Tunnel-Target)。


网关程序接入(WebSocket)

项目内容
协议WebSocket(外连后业务帧均为二进制,多路复用)
连接 URL见下,name、ip 必填
鉴权查询参数 key=<设备apiKey>(路径带 :appname)
设备码连接成功后由服务端在首帧下发(6 位)

连接(ip 为设备内网 IPv4/IPv6,缺失 / 非法返回 400):

wss://api.b4qaq.cn/api/v2/tunnel/connectDevice/<appname>?key=<设备apikey>&name=<设备名>&ip=<内网IP>[&lanBaseUrl=...]

连接成功服务端发首帧(JSON 文本):

{ "ready": true, "deviceID": "384921", "protocol": "mux-v1" }

之后所有业务帧都是 ws binary,一条连接上并发多路(每请求一个偶数 streamId)。

帧格式(首字节类型 + 4 字节大端 streamId)

类型字节方向载荷
REQUEST0x01S→Du16 methodLen|method|u16 pathLen|path|u16 headerCount|(u16 kLen|k|u16 vLen|v)*;body 随后由 DATA 承载
DATA0x02双向原始字节(请求/响应体分块,流式)
HEADERS0x03D→Su16 status|u16 headerCount|(u16 kLen|k|u16 vLen|v)*
ERROR0x04D→Su16 len|message(utf8)
END0x05双向无(body 结束)
CANCEL0x06双向无(调用方放弃/超时)
PING/PONG0x07/0x08双向无

u16 = 2 字节大端长度前缀;头 key/value 为 utf8。请求体已剔除调用方鉴权用的 Key 字段;设备按「读到 END」取体,不依赖 content-length。

设备端处理流程(伪代码)

on REQUEST(sid, method, path, headers):
    target = headers["x-tunnel-target"]   // host:port,缺省=设备本机
    up = http.request(target ?? 本机, method, path, headers去掉x-tunnel-target)
    up.on('response', r => send HEADERS(sid, r.status, r.headers); r.on('data', d=>send DATA(sid,d)); r.on('end',()=>send END(sid)))
on DATA(sid, d):  up.write(d)             // 请求体分块
on END(sid):      up.end()
on CANCEL(sid):   up.destroy()
on error:         send ERROR(sid, msg)
  • 局域网其它设备:请求头 X-Tunnel-Target: 192.168.1.20:8080(或 [fe80::1]:9000、nas.local:80);缺省目标是设备本机。
  • 不同 sid 同时在途、互不阻塞;HEADERS 后可持续 DATA 直到 END(响应流式)。

查询自己的设备码

项目内容
APIGET /api/v2/tunnel/getDevice/<appname>?key=<设备apikey>
鉴权设备 APIKey(noQuota)

成功 200:

{
  "status": 200,
  "result": {
    "online": true,
    "deviceID": "384921",
    "deviceName": "office-pc",
    "ip": "192.168.1.10",
    "lanBaseUrl": null,
    "connectedAt": "...",
    "lastSeenAt": "..."
  }
}

设备离线时 online 为 false。


调用方透传(标准 HTTP,四方法一致)

<GET|POST|PUT|DELETE> https://<域名>/api/v2/tunnel/<6位设备码>/<目标路径>
Header: X-Tunnel-Target: <可选,局域网 host:port>

Key 放哪都行(优先级):?key= query > Authorization: Bearer > x-api-key 头 > JSON body 的 Key 字段(POST/PUT/DELETE,转发前自动剔除)。

// GET
axios.get(`/api/v2/tunnel/${CODE}/ResonaUI`, { params: { ...p, Key: APIKey } });
// POST / PUT
axios.post(`/api/v2/tunnel/${CODE}/ResonaUI`, { ...p, Key: APIKey });
axios.put (`/api/v2/tunnel/${CODE}/api/items/1`, { ...p, Key: APIKey });
// DELETE
axios.delete(`/api/v2/tunnel/${CODE}/api/items/1`, { params: { Key: APIKey } });

设备收到的 path 为剥掉 /tunnel/<设备码> 后的部分(query / body 中的 Key 已剔除)。

错误码:设备码不存在 404 | 设备不在线 503 | key 无效 401 | 未开服务 / 封禁 403 | 等待响应头 15s 超时 504 | 断线 / 帧错误 502。全部端点 noQuota。

大纲