extension-http-endpoints
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHTTP Endpoints (inbound)
HTTP端点(入站)
Inbound HTTP for Caffeine AI Motoko backends via .
mo:caffeineai-http-endpoints通过为Caffeine AI的Motoko后端提供入站HTTP能力。
mo:caffeineai-http-endpointsWhen to use which skill
何时使用哪种技能
| Need | Skill |
|---|---|
External client ( | this skill |
| Canister calls an external HTTPS API | |
| Typed OpenAI / Google / X / Stripe client | the matching domain skill (those wrap outbound calls) |
Do not implement inbound handlers with the management-canister / outcall API — that is the opposite direction.
http_requestCall.httpRequest| 需求 | 技能 |
|---|---|
外部客户端( | 本技能 |
| canister调用外部HTTPS API | |
| 类型化的OpenAI / Google / X / Stripe客户端 | 对应的领域技能(这些封装了出站调用) |
请勿使用management-canister的 / 出站调用API实现入站处理器——二者方向完全相反。
http_requestCall.httpRequestHow inbound HTTP works
入站HTTP的工作原理
- Client hits the backend Motoko canister over HTTPS (not , custom domain, or frontend/asset canister).
*.caffeine.xyz - The HTTP Gateway calls (query).
http_request - Mutating routes return so the gateway re-issues as
Http.upgrade()(update).http_request_update
URLs
- Webhooks / POST: (or
https://<backend-canister-id>.icp0.io/...).ic0.app - Uncertified GET (health / plaintext): — non-raw gateways expect certified query bodies (out of scope)
https://<backend-canister-id>.raw.icp0.io/...
- 客户端通过HTTPS访问后端Motoko canister(而非、自定义域名或前端/资源canister)。
*.caffeine.xyz - HTTP Gateway调用(查询)。
http_request - 有状态变更的路由返回,网关会重新发起
Http.upgrade()请求(更新)。http_request_update
URL格式
- Webhooks / POST: (或
https://<backend-canister-id>.icp0.io/...).ic0.app - 未认证GET(健康检查/纯文本): — 非raw网关要求查询响应体经过认证(不在本技能范围内)
https://<backend-canister-id>.raw.icp0.io/...
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 . Wire handlers on the existing actor in — keep , do not replace the actor or redeclare .
mops add caffeineai-http-endpointsmain.moMixinAuthorizationhttp_request*onQueryonUpdateHttp.upgrade()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;
};执行安装依赖。在的现有actor上连接处理器——保留,请勿替换actor或重新声明方法。
mops add caffeineai-http-endpointsmain.moMixinAuthorizationhttp_request*onQueryHttp.upgrade()onUpdatemotoko
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 . Query returns
onUpdatefor POST/PUT/PATCH/DELETE routes that write.Http.upgrade() - Backend canister id only for webhook URLs — project/frontend origins never reach Motoko .
http_request - Uncertified GET → . Do not invent response certificates.
.raw.icp0.io - 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 (, raw
Http.header).req.body - Large JSON: keep payloads small/schema-simple, or store the blob and process elsewhere.
- 仅在中执行变更操作。 对于会写入数据的POST/PUT/PATCH/DELETE路由,查询方法返回
onUpdate。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如何使用curl
curlUncertified GET on a non-raw host is a hard gateway failure ( / / ), not a 404 from your handler. POSTs that return skip that check.
503backend_response_verificationCertification values not foundHttp.upgrade()bash
undefined在非raw主机上发起未认证GET请求会直接导致网关错误( / / ),而非处理器返回的404。返回的POST请求会跳过该检查。
503backend_response_verificationCertification values not foundHttp.upgrade()bash
undefinedReads — 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":"…"}'
-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":"…"}'
-H 'content-type: application/json'
-H 'x-webhook-secret: …'
-d '{"event":"…"}'
`https://icp0.io/...` 上的`?canisterId=<id>`是另一种主机形式。优先使用canister子域名。仅带`?canisterId=`的IP/`localhost` GET请求可能返回空的`204`而非你的响应体——这是URL格式错误,不是处理器返回空内容。Related
相关内容
- — canister → internet (module helpers, no inbound mixin)
extension-http-outcalls - — checkout via outcalls; inbound Stripe webhooks still use this skill
extension-stripe - icskills#337 — upstream skill-gap discussion
- — canister → 互联网(模块辅助工具,无入站mixin)
extension-http-outcalls - — 通过出站调用实现结账;Stripe入站webhook仍使用本技能
extension-stripe - icskills#337 — 上游技能缺口讨论
Local PocketIC (limitations)
本地PocketIC(限制)
PocketIC is useful for proving the upgrade dance, not as a drop-in replica.
curl- The binary is a control server. It does not listen for
pocket-icuntil a client (curl) creates an instance (include an NNS subnet) and calls@dfinity/pic, which returns a port.makeLive() - There is no one-liner that prints a curl URL. The gateway dies with that Node/pic process.
mops - Once live, the same rules apply:
- — uncertified GET
http://<canister-id>.raw.localhost:<port>/health - — POST / upgrade (worked in a live gateway test)
http://127.0.0.1:<port>/notes?canisterId=<id> - (no
http://<canister-id>.localhost:<port>/health) —.rawcertification failure503
- Do not document PocketIC as the way users hit production webhooks. Production URLs are the /
*.icp0.ioforms above.*.raw.icp0.io
PocketIC适用于验证升级流程,不能直接替代的运行环境。
curl- 二进制文件是一个控制服务器。只有当客户端(
pocket-ic)创建实例(包含NNS子网)并调用@dfinity/pic(返回一个端口)后,它才会监听makeLive()请求。curl - 没有能直接打印curl URL的单行命令。网关会随Node/pic进程一起终止。
mops - 启动后,规则与上述一致:
- — 未认证GET
http://<canister-id>.raw.localhost:<port>/health - — POST / 升级(在实时网关测试中可用)
http://127.0.0.1:<port>/notes?canisterId=<id> - (无
http://<canister-id>.localhost:<port>/health) —.raw认证失败503
- 请勿将PocketIC记录为用户访问生产环境webhook的方式。生产环境URL应使用上述的/
*.icp0.io格式。*.raw.icp0.io