HTTP 接口
不用 WebSocket 的接入方式:POST /rpc 与 WebSocket 共用同一个端口和同一套报文,面向移动端、原生桌面与服务端。
最后更新
printlink.v1 有两条传输层,跑在同一个端口上:
| 传输 | 端点 | 适合谁 |
|---|---|---|
| WebSocket | ws://127.0.0.1:17888/ws | 网页,有事件推送 |
| HTTP | http://127.0.0.1:17888/rpc | 移动端、原生桌面、服务端任务,无状态 |
两者的报文完全一样:HTTP 的请求体就是 WebSocket 的请求帧。方法集、参数、权限、 限流与错误码全部共用一套,不存在「HTTP 专用接口」。
网页请用 WebSocket
/rpc 有意不返回 CORS 头。浏览器页面跨源调用它会被浏览器拦下——这是设计取舍:
网页请用 /sdk/printlink.js 走 WebSocket,那条路径有完整的来源校验、配对与事件推送。
HTTP 传输面向的是没有 CORS 语义、也不方便维持长连接的客户端。
路由
| 路由 | 说明 |
|---|---|
POST /rpc | 一次接口调用 |
PUT /rpc/assets/{uploadId} | 素材分片数据 |
GET /healthz | 存活探针,无需鉴权 |
GET /api/v1/printers/{queueId}/status | 打印机状态快照 |
GET /api/v1/printers/{queueId}/telemetry | 耗材 / 计数等遥测 |
GET /api/v1/scans/{scanId}/pages/{index} | 扫描成品下载 |
GET /rpc 返回 405。全部路由都在来源白名单之下,见安全。
报文
// 请求体
{ "type": "request", "v": 1, "id": "req_1", "method": "printer.list", "params": {} }
// 成功
{ "type": "response", "v": 1, "id": "req_1", "ok": true, "result": { "devices": [], "queues": [] } }
// 失败
{ "type": "response", "v": 1, "id": "req_1", "ok": false,
"error": { "code": "PERMISSION_DENIED", "message": "printer.read required", "retryable": false } }type可省略(省略即视为request)。id可省略,默认为"",响应原样回显。v必须是1,否则PROTOCOL_MISMATCH。
鉴权
HTTP 没有连接,会话从每个请求的头部重建,因此令牌撤销会立即生效。
HTTP 上必须带令牌(Authorization: Bearer plk_…):来源地址不构成 HTTP
凭据——每个请求都是独立的,把地址当凭据等于让同网段任何一台机器自动获得与
「用户批准过的程序」同等的调用能力。
| 调用方 | 头部 | 结果 |
|---|---|---|
| 本机原生程序 | 无 Origin,来自本机 | 受信本机会话,无需令牌 |
远程无 Origin | 无 Origin,来自网络 | 拒绝:REMOTE_NATIVE_CLIENT_DENIED |
| 已签发令牌的应用 | Authorization: Bearer plk_… | 授权会话,权限 = 令牌权限 |
| 无令牌的应用 | 仅 Origin | 只能调用握手与授权相关方法 |
v1.0.10 起:令牌由用户在控制台签发
auth.pair 不再返回令牌。它在 HTTP 上会以 PAIRING_REQUIRED 失败,并带上
details.remedy = "issue_token",提示你到 PrintLink 控制台「安全中心 → 令牌」
创建一把。
请把令牌作为配置项交给程序(环境变量、配置文件皆可),而不是在运行时申请:
curl -X POST http://127.0.0.1:17888/rpc \
-H "Authorization: Bearer plk_…" \
-H "Content-Type: application/json" \
-d '{"v":1,"type":"request","id":"1","method":"printer.list","params":{}}'令牌请安全保存,永远不要写进日志。
HTTP 接入总开关
控制台「安全中心 → 令牌」页有一个 HTTP 接入开关,默认开启。关闭后 HTTP
上不再承载任何受保护调用(静态路由 /sdk/printlink.js / /healthz / / 与
授权方法不受影响),WebSocket 完全不受牵连。
上传素材
WebSocket 用二进制帧携带分片信息,HTTP 把它搬到 URL 与请求头:
| 字段 | HTTP 对应 |
|---|---|
uploadId | 路径 /rpc/assets/{uploadId} |
offset | 请求头 X-PrintLink-Offset(必填,必须等于已收字节数) |
final | 请求头 X-PrintLink-Final(1 / true / yes,缺省即 true) |
| 载荷 | 原始请求体 |
分片成功的响应是传输层确认,不是完整响应封装:
{ "uploadId": "upl_…", "offset": 2726 }完整流程:asset.begin → 一次或多次 PUT(大文件中间片带
X-PrintLink-Final: false)→ asset.commit → 得到 AssetRef。
asset.begin 命中缓存时会直接返回 asset,可以跳过分片。
状态码只是便利层
响应体永远是完整的封装,ok / error.code 才是权威。 请按 code 分支:
| 状态码 | 对应错误码 |
|---|---|
| 200 | 成功 |
| 400 | 其余客户端错误(INVALID_REQUEST、DOCUMENT_INVALID、PROTOCOL_MISMATCH 等) |
| 401 | TOKEN_INVALID、PAIRING_REQUIRED |
| 403 | PERMISSION_DENIED(含 HTTP 接入已关闭)、ORIGIN_NOT_ALLOWED、PAIRING_DENIED、PAIRING_PENDING、REMOTE_NATIVE_CLIENT_DENIED 等 |
| 404 | PRINTER_NOT_FOUND、PROFILE_NOT_FOUND、JOB_NOT_FOUND、METHOD_NOT_FOUND 等 |
| 413 | PAYLOAD_TOO_LARGE |
| 429 | RATE_LIMITED |
| 503 | QUEUE_FULL |
| 500 | INTERNAL |
没有事件推送
HTTP 是纯请求 / 响应。任务进度用 job.get / job.list 轮询,并且要有上界;
终态只有 completed / failed / canceled。需要实时推送就回到 WebSocket——
两条传输在同一端口上共存,同一个应用可以按需混用。
完整示例
# 1) 列打印机(令牌来自控制台「安全中心 → 令牌」,明文只显示一次)
curl -s -X POST http://127.0.0.1:17888/rpc \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer plk_…' \
-d '{"v":1,"id":"2","method":"printer.list","params":{}}'
# 2) 上传 PDF
curl -s -X POST http://127.0.0.1:17888/rpc \
-H 'Origin: http://com.example.printapp' -H 'Authorization: Bearer plk_…' \
-d '{"v":1,"id":"3","method":"asset.begin",
"params":{"mime":"application/pdf","size":2726,"sha256":"<sha256>"}}'
curl -s -X PUT http://127.0.0.1:17888/rpc/assets/<uploadId> \
-H 'Origin: http://com.example.printapp' -H 'Authorization: Bearer plk_…' \
-H 'X-PrintLink-Offset: 0' -H 'X-PrintLink-Final: true' \
--data-binary @file.pdf
curl -s -X POST http://127.0.0.1:17888/rpc \
-H 'Origin: http://com.example.printapp' -H 'Authorization: Bearer plk_…' \
-d '{"v":1,"id":"4","method":"asset.commit","params":{"uploadId":"<uploadId>","sha256":"<sha256>"}}'
# 4) 打印,然后轮询终态
curl -s -X POST http://127.0.0.1:17888/rpc \
-H 'Origin: http://com.example.printapp' -H 'Authorization: Bearer plk_…' \
-d '{"v":1,"id":"5","method":"job.print","params":{"queueId":"prq_…","title":"demo",
"source":{"items":[{"type":"pdf","asset":{"id":"asl_…","sha256":"<sha256>","mime":"application/pdf","size":2726}}]}}}'
curl -s -X POST http://127.0.0.1:17888/rpc \
-H 'Origin: http://com.example.printapp' -H 'Authorization: Bearer plk_…' \
-d '{"v":1,"id":"6","method":"job.get","params":{"jobId":"job_…"}}'仓库里的 examples/http-print/print.mjs 是同一条链路的零依赖 Node 客户端:
node examples/http-print/print.mjs # 只校验,不出纸
node examples/http-print/print.mjs --print # 真实静默打印
node examples/http-print/print.mjs --local # 本机原生:不带 Origin,免配对想在浏览器里逐步看每个请求,用调试台的 HTTP 直连页
(http://127.0.0.1:17888/playground/http/)。
与 WebSocket 的差异
| 方面 | WebSocket | HTTP |
|---|---|---|
| 会话 | 连接期内保持 | 每请求重建(撤销立即生效) |
| 事件 | 推送 | 无,靠轮询 |
| 素材分片 | 二进制帧 | PUT /rpc/assets/{uploadId} |
| CORS | 不适用 | 不发放(网页请用 WebSocket) |
| 方法集 / 权限 / 错误码 | — | 完全相同 |
| 能力解析 | 强制 | 同样强制 |