协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
extension-http-endpoints【开发与工程】Serve inbound HTTP from a Motoko canister (webhooks, curl POST/GET, bot callbacks, ingest/health APIs) via the IC HTTP Gateway methods `http_request` / `http_request_update`. Load whenever the user or spec needs a canister URL that external HTTP clients can hit — Stripe/GitHub/Slack webhooks, Telegram bots, health checks, or any plain REST-ish endpoi…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
http_request / http_request_update. Load whenever the user or spec needs
a canister URL that external HTTP clients can hit — Stripe/GitHub/Slack
webhooks, Telegram bots, health checks, or any plain REST-ish endpoint.
NOT for calling external APIs from the canister (use
extension-http-outcalls); NOT for certified static asset serving.
version: 0.2.1
compatibility:
mops:
caffeineai-http-endpoints: "~0.1.0"
caffeineai-authorization: "~1.0.1"
caffeineai-subscription: [none]Inbound HTTP for Caffeine AI Motoko backends via mo:caffeineai-http-endpoints.
| Need | Skill |
|---|---|
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 client | the 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.
*.caffeine.xyz, custom domain, or frontend/asset canister).http_request (query).Http.upgrade() so the gateway re-issues as http_request_update (update).URLs
https://<backend-canister-id>.icp0.io/... (or .ic0.app)https://<backend-canister-id>.raw.icp0.io/... — non-raw gateways expect certified query bodies (out of scope)Use the prefabricated modules (do not modify them):
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;
};
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.
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:
import AccessControl "mo:caffeineai-authorization/access-control";
module {
type NewActor = {
accessControlState : AccessControl.AccessControlState;
};
public func migration(_old : {}) : NewActor {
{ accessControlState = AccessControl.initState() };
};
};
onUpdate. Query returns Http.upgrade() for POST/PUT/PATCH/DELETE routes that write.http_request..raw.icp0.io. Do not invent response certificates.Http.header, raw req.body).curlUncertified 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.
# 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":"…"}'
?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.
extension-http-outcalls — canister → internet (module helpers, no inbound mixin)extension-stripe — checkout via outcalls; inbound Stripe webhooks still use this skillPocketIC is useful for proving the upgrade dance, not as a drop-in curl replica.
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.mops one-liner that prints a curl URL. The gateway dies with that Node/pic process.http://<canister-id>.raw.localhost:<port>/health — uncertified GEThttp://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*.icp0.io / *.raw.icp0.io forms above.