extension-http-endpoints

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

HTTP Endpoints (inbound)

HTTP端点(入站)

Inbound HTTP for Caffeine AI Motoko backends via
mo:caffeineai-http-endpoints
.
通过
mo:caffeineai-http-endpoints
为Caffeine AI的Motoko后端提供入站HTTP能力。

When to use which skill

何时使用哪种技能

NeedSkill
External client (
curl
, webhook, bot) hits the canister over HTTPS
this skill
Canister calls an external HTTPS API
extension-http-outcalls
Typed OpenAI / Google / X / Stripe clientthe matching domain skill (those wrap outbound calls)
Do not implement inbound handlers with the management-canister
http_request
/
Call.httpRequest
outcall API — that is the opposite direction.
需求技能
外部客户端(
curl
、webhook、机器人)通过HTTPS访问canister
本技能
canister调用外部HTTPS API
extension-http-outcalls
类型化的OpenAI / Google / X / Stripe客户端对应的领域技能(这些封装了出站调用)
请勿使用management-canister的
http_request
/
Call.httpRequest
出站调用API实现入站处理器——二者方向完全相反。

How inbound HTTP works

入站HTTP的工作原理

  1. Client hits the backend Motoko canister over HTTPS (not
    *.caffeine.xyz
    , custom domain, or frontend/asset canister).
  2. The HTTP Gateway calls
    http_request
    (query).
  3. Mutating routes return
    Http.upgrade()
    so the gateway re-issues as
    http_request_update
    (update).
URLs
  • Webhooks / POST:
    https://<backend-canister-id>.icp0.io/...
    (or
    .ic0.app
    )
  • Uncertified GET (health / plaintext):
    https://<backend-canister-id>.raw.icp0.io/...
    — non-raw gateways expect certified query bodies (out of scope)
  1. 客户端通过HTTPS访问后端Motoko canister(而非
    *.caffeine.xyz
    、自定义域名或前端/资源canister)。
  2. HTTP Gateway调用
    http_request
    (查询)。
  3. 有状态变更的路由返回
    Http.upgrade()
    ,网关会重新发起
    http_request_update
    请求(更新)。
URL格式
  • Webhooks / POST:
    https://<backend-canister-id>.icp0.io/...
    (或
    .ic0.app
    )
  • 未认证GET(健康检查/纯文本):
    https://<backend-canister-id>.raw.icp0.io/...
    — 非raw网关要求查询响应体经过认证(不在本技能范围内)

Backend

后端

Use the prefabricated modules (do not modify them):
mo
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
};
mo
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;
};
Add
mops add caffeineai-http-endpoints
. Wire handlers on the existing actor in
main.mo
— keep
MixinAuthorization
, do not replace the actor or redeclare
http_request*
.
onQuery
must stay query-safe: no awaits, no state writes. Mutating work belongs in
onUpdate
after
Http.upgrade()
from the query path.
motoko
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);
};
The migration chain head:
motoko
import AccessControl "mo:caffeineai-authorization/access-control";

module {
  type NewActor = {
    accessControlState : AccessControl.AccessControlState;
  };

  public func migration(_old : {}) : NewActor {
    { accessControlState = AccessControl.initState() };
  };
};
请使用预制模块(请勿修改):
mo
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
};
mo
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
中执行。
motoko
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);
};
迁移链头部:
motoko
import AccessControl "mo:caffeineai-authorization/access-control";

module {
  type NewActor = {
    accessControlState : AccessControl.AccessControlState;
  };

  public func migration(_old : {}) : NewActor {
    { accessControlState = AccessControl.initState() };
  };
};

Rules of thumb

实用规则

  • Mutations only in
    onUpdate
    .
    Query returns
    Http.upgrade()
    for POST/PUT/PATCH/DELETE routes that write.
  • Backend canister id only for webhook URLs — project/frontend origins never reach Motoko
    http_request
    .
  • Uncertified GET →
    .raw.icp0.io
    .
    Do not invent response certificates.
  • Prefer Candid + React for app UI; use inbound HTTP only for external HTTP clients.
  • Auth: no IC caller principal from webhook senders — verify signatures/secrets yourself (
    Http.header
    , raw
    req.body
    ).
  • Large JSON: keep payloads small/schema-simple, or store the blob and process elsewhere.
  • 仅在
    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: 尽量保持负载体积小、结构简单,否则请存储blob后在其他地方处理。

How to
curl

如何使用
curl

Uncertified GET on a non-raw host is a hard gateway failure (
503
/
backend_response_verification
/
Certification values not found
), not a 404 from your handler. POSTs that return
Http.upgrade()
skip that check.
bash
undefined
在非raw主机上发起未认证GET请求会直接导致网关错误(
503
/
backend_response_verification
/
Certification values not found
),而非处理器返回的404。返回
Http.upgrade()
的POST请求会跳过该检查。
bash
undefined

Reads — must be raw

Reads — must be raw

curl https://<backend-canister-id>.raw.icp0.io/health
curl https://<backend-canister-id>.raw.icp0.io/health

Writes — non-raw is fine (update path)

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":"…"}'

`?canisterId=<id>` on `https://icp0.io/...` is an alternate host form. Prefer the canister subdomain. An IP/`localhost` GET with only `?canisterId=` can return an empty `204` instead of your body — treat that as the wrong URL shape, not an empty handler.
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子域名。仅带`?canisterId=`的IP/`localhost` GET请求可能返回空的`204`而非你的响应体——这是URL格式错误,不是处理器返回空内容。

Related

相关内容

  • extension-http-outcalls
    — canister → internet (module helpers, no inbound mixin)
  • extension-stripe
    — checkout via outcalls; inbound Stripe webhooks still use this skill
  • icskills#337 — upstream skill-gap discussion
  • extension-http-outcalls
    — canister → 互联网(模块辅助工具,无入站mixin)
  • extension-stripe
    — 通过出站调用实现结账;Stripe入站webhook仍使用本技能
  • icskills#337 — 上游技能缺口讨论

Local PocketIC (limitations)

本地PocketIC(限制)

PocketIC is useful for proving the upgrade dance, not as a drop-in
curl
replica.
  • The
    pocket-ic
    binary is a control server. It does not listen for
    curl
    until a client (
    @dfinity/pic
    ) creates an instance (include an NNS subnet) and calls
    makeLive()
    , which returns a port.
  • There is no
    mops
    one-liner that prints a curl URL. The gateway dies with that Node/pic process.
  • Once live, the same rules apply:
    • http://<canister-id>.raw.localhost:<port>/health
      — uncertified GET
    • http://127.0.0.1:<port>/notes?canisterId=<id>
      — POST / upgrade (worked in a live gateway test)
    • http://<canister-id>.localhost:<port>/health
      (no
      .raw
      ) —
      503
      certification failure
  • Do not document PocketIC as the way users hit production webhooks. Production URLs are the
    *.icp0.io
    /
    *.raw.icp0.io
    forms above.
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
    格式。