mcpField
E
extension-http-outcalls

extension-http-outcalls

github.com/caffeinelabs开发228.6k 次安装1 周前更新
代码仓库

关于 extension-http-outcalls

为 Caffeine AI 后端开发提供 HTTP 外呼规范与模块用法,适用于需要调用外部 API 的开发者。重点是要求服务端响应有界,并通过 curl 和合规检查验证请求。

主要功能

  1. 01支持多种 HTTP 方法
  2. 02要求服务端限制响应范围
  3. 03限制响应大小和条目数
  4. 04提供 Motoko 外呼模块
  5. 05规定 curl 与合规检查流程

适用场景

  1. 01按用户提供的标识查询单个实体
  2. 02对用户分页浏览的列表发起请求
  3. 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 外部调用都不完整,且不得继续部署。

  1. 必须测试 Motoko 中实现的确切请求: 使用相同的 HTTP 方法、API 版本、端点路径、查询参数、标头和请求体。测试相关端点,或添加实现中未使用的参数,都不算有效验证。
  2. 必须端到端追踪一个具有代表性的用户输入,检查它经过每次转换后如何进入 API 请求,然后测试最终生成的确切请求。例如,如果用户输入一个标识符,而服务提供方要求另一种标识符格式,就要端到端验证这种映射。仅调用服务提供方的基础端点或列表端点并不充分。
  3. 必须验证应用实际使用的响应契约。 使用 curl --fail-with-body --silent --show-error 运行请求。验证成功状态码,以及应用使用的每个响应字段及其类型。响应仅为有效 JSON 并不足够。
  4. 必须对实现使用的确切 URL 和方法运行 check_canister_api_compliance。 该工具会根据外部调用的字节上限和条目数上限检查响应,并报告 URL 是否在服务端、以机械方式且通过部署门禁完成了有界性审核,从而阻止调用未经审核的主机的请求发布。如果它拒绝某个端点,请选择兼容的公共 API,或如实缩小功能范围;不要实现无界的“获取全部”请求,再在 canister 中进行筛选。

响应上限由该检查负责,而不是 curl。以上检查是它无法替你完成的:它探测的是端点,而非应用,因此无法得知应用读取了哪些响应字段,也无法得知用户输入了什么来生成 URL。

如果请求失败或以上任何检查未满足,请检查状态码和响应正文。使用网络搜索查看服务提供方当前的官方 API 文档,以及发布说明或迁移说明。确认当前 API 版本、端点、参数名称与格式、必需标头和请求体。不要试图猜测绕过故障。修正 Motoko 请求,使其与可正常工作的 REST 请求一致,然后重新运行 curl;重复此过程,直到所有检查都通过,再重新运行后端检查。只要任何必需检查缺失或未通过,就绝不能报告已完成。

常见故障线索:400 表示参数无效或请求体格式错误;404/410 通常表示端点或 API 版本已过时;405 表示方法错误;415 表示内容类型错误。只有在根据当前官方文档确认请求无误后,才将 429 和 5xx 响应视为配额或服务提供方可用性问题。

评论

支持 Markdown