关于 extension-http-endpoints
此技能指导 Caffeine AI Motoko 后端通过 `caffeineai-http-endpoints` 接收外部 HTTP 请求,适用于需要提供 Webhook 或其他入站 HTTP 路由的开发者。内容重点说明查询与更新请求的分工、网关 URL 及认证注意事项。
主要功能
- 01Motoko 后端入站 HTTP 处理
- 02查询与更新路径分离
- 03使用预制 HTTP 模块与 mixin
- 04Webhook URL 与 GET 主机规则
- 05签名和密钥验证提示
适用场景
- 01为 canister 接收外部 Webhook
- 02提供健康检查等 GET 路由
- 03实现需修改状态的 POST 路由
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ caffeinelabs/ skills --skill extension-http-endpoints安装后在 Claude Code 中输入/extension-http-endpoints确认已加载。
详细介绍
HTTP 端点(入站)
通过 mo:caffeineai-http-endpoints 为 Caffeine AI Motoko 后端提供入站 HTTP。
何时使用哪种技能
| 需求 | 技能 |
|---|---|
外部客户端(curl、webhook、bot)通过 HTTPS 访问 canister | 本技能 |
| Canister 调用外部 HTTPS API | extension-http-outcalls |
| 类型化的 OpenAI / Google / X / Stripe 客户端 | 对应的领域技能(这些技能封装出站调用) |
不要使用管理 canister 的 http_request / Call.httpRequest 出站调用 API 实现入站处理程序——方向正好相反。
入站 HTTP 的工作方式
- 客户端通过 HTTPS 访问 Motoko 后端 canister(不是
*.caffeine.xyz、自定义域名或前端/资源 canister)。 - HTTP Gateway 调用
http_request(查询)。 - 修改数据的路由返回
Http.upgrade(),使网关以http_request_update(更新)重新发起请求。
URL
- Webhook / POST:
https://<backend-canister-id>.icp0.io/...(或.ic0.app) - 未认证的 GET(健康检查 / 纯文本):
https://<backend-canister-id>.raw.icp0.io/...——非 raw 网关要求查询响应体经过认证(不在本技能范围内)
后端
使用预制模块(不要修改):
module {
public type Request = { method : Text; url : Text; headers : [HeaderField]; body : Blob; certificate_version : ?Nat16 };
public type Response = { status_code : Nat16; headers : [HeaderField]; body : Blob; streaming_strategy : ?StreamingStrategy; upgrade : ?Bool };
public func path(url : Text) : Text;
public func header(req : Request, name : Text) : ?Text; // case-insensitive
public func text(status : Nat16, body : Text) : Response;
public func upgrade() : Response; // query path asks gateway to call http_request_update
public func notFound() : Response;
public func methodNotAllowed() : Response;
public func bodyText(req : Request) : ?Text; // null if body is not UTF-8; HMAC must use req.body
};
mixin (
onQuery : Http.Request -> Http.Response,
onUpdate : Http.Request -> async Http.Response,
) {
public query func http_request(req : Http.Request) : async Http.Response;
public func http_request_update(req : Http.Request) : async Http.Response;
};
运行 mops add caffeineai-http-endpoints。在 main.mo 中现有的 actor 上接入处理程序——保留 MixinAuthorization,不要替换 actor 或重新声明 http_request*。
onQuery 必须保持查询安全:不得使用 await,不得写入状态。修改数据的工作应在查询路径返回 Http.upgrade() 后,于 onUpdate 中执行。
import Http "mo:caffeineai-http-endpoints/Http";
import MixinHttpEndpoints "mo:caffeineai-http-endpoints/MixinHttpEndpoints";
import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";
actor {
let accessControlState : AccessControl.AccessControlState;
include MixinAuthorization(accessControlState, null);
func onHttpQuery(req : Http.Request) : Http.Response {
let path = Http.path(req.url);
if (req.method == "POST" and path == "/webhook") {
return Http.upgrade();
};
if (req.method == "GET" and (path == "/" or path == "/health")) {
return Http.text(200, "ok");
};
Http.notFound();
};
func onHttpUpdate(req : Http.Request) : async Http.Response {
let path = Http.path(req.url);
if (req.method == "POST" and path == "/webhook") {
// Signature checks: Http.header(req, "stripe-signature"), HMAC over req.body
ignore Http.bodyText(req);
return Http.text(200, "received");
};
Http.notFound();
};
include MixinHttpEndpoints(onHttpQuery, onHttpUpdate);
};
迁移链头:
import AccessControl "mo:caffeineai-authorization/access-control";
module {
type NewActor = {
accessControlState : AccessControl.AccessControlState;
};
public func migration(_old : {}) : NewActor {
{ accessControlState = AccessControl.initState() };
};
};
经验法则
- 仅在
onUpdate中执行修改操作。 对会写入数据的 POST/PUT/PATCH/DELETE 路由,查询应返回Http.upgrade()。 - Webhook URL 只能使用后端 canister ID——项目/前端源不会到达 Motoko
http_request。 - 未认证的 GET →
.raw.icp0.io。 不要自行伪造响应认证信息。 - 应用 UI 优先使用 Candid + React;仅在面向外部 HTTP 客户端时使用入站 HTTP。
- 身份验证: Webhook 发送方没有 IC 调用方 principal——请自行验证签名/密钥(使用
Http.header、原始req.body)。 - 大型 JSON: 保持负载较小、架构简单,或存储数据块并在其他地方处理。
如何使用 curl
在非 raw 主机上执行未认证的 GET 会导致网关硬失败(503 / backend_response_verification / Certification values not found),而不是处理程序返回 404。返回 Http.upgrade() 的 POST 会跳过该检查。
# Reads — must be raw
curl https://<backend-canister-id>.raw.icp0.io/health
# Writes — non-raw is fine (update path)
curl -X POST https://<backend-canister-id>.icp0.io/webhook \
-H 'content-type: application/json' \
-H 'x-webhook-secret: …' \
-d '{"event":"…"}'
https://icp0.io/... 上的 ?canisterId=<id> 是另一种主机形式。建议使用 canister 子域名。IP/localhost 上仅带 ?canisterId= 的 GET 可能返回空的 204,而不是你的响应体——应将其视为 URL 形式不正确,而不是处理程序返回了空内容。
相关内容
extension-http-outcalls— canister → 互联网(模块辅助函数,无入站 mixin)extension-stripe— 通过出站调用完成结账;入站 Stripe webhook 仍使用本技能- icskills#337 — 上游技能缺口讨论
本地 PocketIC(限制)
PocketIC 适合验证升级流程,但不能直接替代 curl。
pocket-ic二进制是一个控制服务器。在客户端(@dfinity/pic)创建实例(包含一个 NNS 子网)并调用makeLive()(该调用会返回一个端口)之前,它不会监听curl请求。- 没有能打印出 curl URL 的
mops单行命令。网关会随该 Node/pic 进程一起退出。 - 网关启动后,规则相同:
http://<canister-id>.raw.localhost:<port>/health— 未认证的 GET 请求http://127.0.0.1:<port>/notes?canisterId=<id>— POST / 升级(已在运行中的网关测试中验证可用)http://<canister-id>.localhost:<port>/health(没有.raw)—503认证失败
- 不要将 PocketIC 描述为用户访问生产环境 Webhook 的方式。生产环境 URL 为上面的
*.icp0.io/*.raw.icp0.io形式。