mcpField
E
extension-http-endpoints

extension-http-endpoints

github.com/caffeinelabs开发23.8k 次安装今天更新
代码仓库

关于 extension-http-endpoints

此技能指导 Caffeine AI Motoko 后端通过 `caffeineai-http-endpoints` 接收外部 HTTP 请求,适用于需要提供 Webhook 或其他入站 HTTP 路由的开发者。内容重点说明查询与更新请求的分工、网关 URL 及认证注意事项。

主要功能

  1. 01Motoko 后端入站 HTTP 处理
  2. 02查询与更新路径分离
  3. 03使用预制 HTTP 模块与 mixin
  4. 04Webhook URL 与 GET 主机规则
  5. 05签名和密钥验证提示

适用场景

  1. 01为 canister 接收外部 Webhook
  2. 02提供健康检查等 GET 路由
  3. 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 APIextension-http-outcalls
类型化的 OpenAI / Google / X / Stripe 客户端对应的领域技能(这些技能封装出站调用)

不要使用管理 canister 的 http_request / Call.httpRequest 出站调用 API 实现入站处理程序——方向正好相反。

入站 HTTP 的工作方式

  1. 客户端通过 HTTPS 访问 Motoko 后端 canister(不是 *.caffeine.xyz、自定义域名或前端/资源 canister)。
  2. HTTP Gateway 调用 http_request(查询)。
  3. 修改数据的路由返回 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 形式不正确,而不是处理程序返回了空内容。

相关内容

本地 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 形式。

评论

支持 Markdown