关于 extension-http-outcalls
为 Caffeine AI 后端开发提供 HTTP 外呼规范与模块用法,适用于需要调用外部 API 的开发者。重点是要求服务端响应有界,并通过 curl 和合规检查验证请求。
主要功能
- 01支持多种 HTTP 方法
- 02要求服务端限制响应范围
- 03限制响应大小和条目数
- 04提供 Motoko 外呼模块
- 05规定 curl 与合规检查流程
适用场景
- 01按用户提供的标识查询单个实体
- 02对用户分页浏览的列表发起请求
- 03在部署前验证外部 API 调用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ caffeinelabs/ skills --skill extension-http-outcalls安装后在 Claude Code 中输入/extension-http-outcalls确认已加载。
详细介绍
HTTP 外部调用
适用于 Caffeine AI 的 HTTP 外部调用扩展。
概述
本技能涵盖后端 canister 发出 HTTP 请求的要求,包括 GET、HEAD、POST、PUT、DELETE 和 PATCH。每当需要集成外部 API 或服务时,都应使用本技能。
关键要求
遵循以下规则:
- 必须使用由提供方强制执行的有界 API 请求。 请求必须按标识符、分页、时间范围、地理区域、结果数量或其他适用于该功能的服务端限制来约束响应。 每次响应最多包含 10_000 条记录且大小不超过 1 MB;如有需要,请分页。
- 必须选择与功能相匹配的限制方式。 这五种限制方式不可互换。如果功能需要根据用户提供的信息查询单个实体——例如航班号、订单 ID、股票代码或邮政编码——标识符限制是唯一正确的选择。分页浏览集合以查找该实体是反面模式,而非替代方案:无论每页多小,工作量仍会随整个集合增长。分页适用于用户主动翻阅的列表,而不是搜索。
- 必须在实际的 Motoko http 请求中设置每项限制。 只用于本地测试的限制无法保护 canister。
- 绝不能获取无界集合后再在 canister 中筛选。 预期响应大小、典型流量或当前较小的数据集都不算限制。
- 如果提供方无法强制限制响应并提供所需数据,必须拒绝该 API 或缩小功能范围。 不要实现明知不安全的回退方案。
- 必须使用 curl 实现进行本地测试。 确保一对一的 curl 调用成功,并返回应用会使用的所有字段。修改 Motoko 代码后,必须重新进行本地 curl 测试。
- 必须使用
check_canister_api_compliance检查限制。 它会探测端点并根据这些限制测量响应;同时,它在部署门禁中运行,因此会拒绝无法部署的端点。
上述 1 MB 和 10_000 条记录限制是本模块自身的上限——defaultMaxResponseBytes 为 1 MB——而 check_canister_api_compliance 恰好依据这两个数值进行测量。它会更早拒绝:当 URL 未包含服务端限制时,如果探测到的响应已超过字节上限的四分之一,或超过 1_000 条记录,就会阻止请求。这一区间很危险,因为请求可能通过一次性的 curl 测试,之后数据不断增长,直到 canister 在解析负载时超出每条消息的指令预算。请按更严格的限制设计:在服务端限制请求。
选择错误的限制方式
航班查询会接收航班号,并且必须返回一架飞机。获取 https://opensky-network.org/api/states/all——即全球所有正在飞行的飞机,约有 10-13k 行——然后在 Motoko 中扫描解析结果以查找某个呼号,是错误的做法。canister 为响应一次查询而下载并解析全球每条记录,随后消息会被拒绝:
IC0522: Canister exceeded the limit of 40000000000 instructions for single message execution。代码表面上看不出任何问题——URL 有效,解析代码可以编译,一次性的 curl 请求也返回 200。
这是指令限制,不是字节限制。改用更小的分页大小也无济于事:canister 仍需遍历整个集合才能找到一行。
提供方已经提供了正确的限制方式。同一端点上的 ?icao24=<hex> 就是标识符限制:响应只有几 KB,只包含一架飞机和一条待解析记录。
反过来,如果是外部客户端通过 HTTPS 访问 canister(webhook、curl、机器人回调),请使用 extension-http-endpoints。
后端
对于必须在后端执行的 HTTP 外部调用:
现有模块 mo:caffeineai-http-outcalls/outcall.mo 提供了一个通用的有界请求函数,支持 IC 支持的所有 HTTP 方法,并提供向后兼容的 GET 和 POST 辅助函数。
module {
public type TransformationInput = {
context : Blob;
response : IC.HttpRequestResult;
};
public type TransformationOutput = IC.HttpRequestResult;
public type Transform = query TransformationInput -> async TransformationOutput;
public type Header = {
name: Text;
value: Text;
};
public type Method = {
#get;
#head;
#post;
#put;
#delete;
#patch;
};
public type Request = {
url : Text;
method : Method;
headers : [Header];
body : ?Blob;
maxResponseBytes : Nat64;
transform : Transform;
};
public type Response = IC.HttpRequestResult;
public let defaultMaxResponseBytes : Nat64;
// Helper function for the transform callback used by the IC on HTTP outcalls.
public func transform(input : TransformationInput) : TransformationOutput;
// Generic bounded request supporting GET, HEAD, POST, PUT, DELETE, and PATCH.
public func httpRequest(request : Request) : async Response;
// HTTP GET request with a transform callback function.
public func httpGetRequest(url : Text, extraHeaders: [Header], transform : Transform) : async Text;
// HTTP POST request, specifying a transform callback.
public func httpPostRequest(url : Text, extraHeaders: [Header], body : Text, transform : Transform) : async Text;
};
需要状态码、标头、自定义响应上限或 GET、POST 以外的方法时,请使用 httpRequest。maxResponseBytes 可以设置更低的上限;该模块会将每个请求的上限设为 defaultMaxResponseBytes(1 MB)。该模块执行的所有 HTTP 外部调用都会设置 is_replicated = ?false;调用方无法启用复制执行。对于简单的 GET 请求,向后兼容的辅助函数仍是最简便的方式:
import Text "mo:core/Text";
import OutCall "mo:caffeineai-http-outcalls/outcall";
actor {
public query func transform(input : OutCall.TransformationInput) : async OutCall.TransformationOutput {
OutCall.transform(input);
};
func setItemActive() : async OutCall.Response {
await OutCall.httpRequest({
url = "https://api.example.com/items/123";
method = #patch;
headers = [{ name = "Content-Type"; value = "application/json" }];
body = ?("{\"active\":true}".encodeUtf8());
maxResponseBytes = 100_000;
transform;
});
};
func makeGetOutcall(url : Text) : async Text {
await OutCall.httpGetRequest(url, [], transform);
};
};
通过 httpPostRequest 发送 POST 请求的用法与此类似。
验证每个外部 API 请求
在认定 HTTP 外部调用已完成之前,必须使用 curl 在本地执行等效请求。不要只依赖记忆中的 API 文档,也不要只确认 Motoko 代码能够编译。以下检查全部是必需的。任何一项检查未通过,HTTP 外部调用都不完整,且不得继续部署。
- 必须测试 Motoko 中实现的确切请求: 使用相同的 HTTP 方法、API 版本、端点路径、查询参数、标头和请求体。测试相关端点,或添加实现中未使用的参数,都不算有效验证。
- 必须端到端追踪一个具有代表性的用户输入,检查它经过每次转换后如何进入 API 请求,然后测试最终生成的确切请求。例如,如果用户输入一个标识符,而服务提供方要求另一种标识符格式,就要端到端验证这种映射。仅调用服务提供方的基础端点或列表端点并不充分。
- 必须验证应用实际使用的响应契约。 使用
curl --fail-with-body --silent --show-error运行请求。验证成功状态码,以及应用使用的每个响应字段及其类型。响应仅为有效 JSON 并不足够。 - 必须对实现使用的确切 URL 和方法运行
check_canister_api_compliance。 该工具会根据外部调用的字节上限和条目数上限检查响应,并报告 URL 是否在服务端、以机械方式且通过部署门禁完成了有界性审核,从而阻止调用未经审核的主机的请求发布。如果它拒绝某个端点,请选择兼容的公共 API,或如实缩小功能范围;不要实现无界的“获取全部”请求,再在 canister 中进行筛选。
响应上限由该检查负责,而不是 curl。以上检查是它无法替你完成的:它探测的是端点,而非应用,因此无法得知应用读取了哪些响应字段,也无法得知用户输入了什么来生成 URL。
如果请求失败或以上任何检查未满足,请检查状态码和响应正文。使用网络搜索查看服务提供方当前的官方 API 文档,以及发布说明或迁移说明。确认当前 API 版本、端点、参数名称与格式、必需标头和请求体。不要试图猜测绕过故障。修正 Motoko 请求,使其与可正常工作的 REST 请求一致,然后重新运行 curl;重复此过程,直到所有检查都通过,再重新运行后端检查。只要任何必需检查缺失或未通过,就绝不能报告已完成。
常见故障线索:400 表示参数无效或请求体格式错误;404/410 通常表示端点或 API 版本已过时;405 表示方法错误;415 表示内容类型错误。只有在根据当前官方文档确认请求无误后,才将 429 和 5xx 响应视为配额或服务提供方可用性问题。