extension-http-endpoints について
`caffeineai-http-endpoints`を使って、Caffeine AIのMotokoバックエンドにHTTPエンドポイントを追加する開発者向けスキルです。クエリと更新リクエストの分離、ゲートウェイURL、外部呼び出し元の認証上の注意点を説明します。
主な機能
- 01MotokoバックエンドのHTTP受信処理
- 02クエリと更新パスの分離
- 03既成HTTPモジュールとmixin
- 04Webhook URLとGETホストの指針
- 05署名とシークレットの検証指針
活用シーン
- 01canisterで外部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、ボット)から 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()を返し、Gateway はhttp_request_update(アップデート)として再度リクエストを送信します。
URL
- Webhook/POST:
https://<backend-canister-id>.icp0.io/...(または.ic0.app) - 未認証 GET(ヘルスチェック/プレーンテキスト):
https://<backend-canister-id>.raw.icp0.io/...— raw ではない Gateway は認証済みクエリ本文を必要とします(対象外)
バックエンド
事前構築済みのモジュールを使用してください(変更しないでください)。
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 は、ハンドラーからの 404 ではなく、Gateway の致命的なエラー(503/backend_response_verification/Certification values not found)になります。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 のサブドメインを優先してください。?canisterId= のみを付けた IP/localhost への 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— 未認証の GEThttp://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形式です。