測試你的應用程式如何處理 GitHub API 的速率限制

Tip

第一次接觸節流? 了解什麼是限速(throttling)以及如何處理它。

一目了然
目標:測試你的應用程式如何處理 GitHub REST API 的速率限制
時間: 15分鐘
插件:RateLimitingPlugin、 GenericRandomErrorPlugin、 RetryAfterPlugin
前置條件:設定開發代理

你的應用程式呼叫 GitHub API。 它在你的機器上運作正常,接著 CI 工作、大型組織,或忙碌時就會把它推過速率限制,然後開始失敗。 要用真實 API 測試,你需要用盡速率限制配額,然後等上一小時才能再試。 Dev Proxy 在本地模擬 GitHub 的速率限制,使用你選擇的上限和時間視窗。

了解 GitHub 回傳什麼

GitHub 對 REST API 有兩種速率限制。

主要速率限制 是限制你每小時提出的請求數量。 例如,未認證請求為 60,個人存取權杖請求為 5,000。 每個回覆都包含顯示你所在位置的標頭:

Header Meaning
x-ratelimit-limit 每小時最大請求數
x-ratelimit-remaining 目前視窗中剩餘的請求數量
x-ratelimit-reset 窗口重置的時間,以 UTC Unix 時間戳秒數為單位

當你超過主要限制時,GitHub 會回傳403或429,並將x-ratelimit-remaining設為0。 在 x-ratelimit-reset 中顯示的時間之前,請勿重試。

次級速率限制可保護 GitHub 免於突增流量,例如同時請求過多或過快建立過多內容。 當你超出限制時,GitHub 會回傳 403 或 429,並顯示有關次要速率限制的訊息。 如果回應有 retry-after 標頭,就等那麼多秒。 否則,至少等待一分鐘,如果請求持續失敗,再延長等待時間。

GitHub 可以禁止那些在速率受限期間持續發送請求的整合。 更多資訊請參閱 GitHub 文件中的 REST API 速率限制。

模擬主要速率限制

使用 RateLimitingPlugin 來計數請求並回傳 GitHub 的速率限制標頭。 要測試而不等一小時,可以用小限額和短時間窗口。

檔案: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "RetryAfterPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
    },
    {
      "name": "RateLimitingPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "githubRateLimit"
    }
  ],
  "urlsToWatch": [
    "https://api.github.com/*"
  ],
  "githubRateLimit": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/ratelimitingplugin.schema.json",
    "headerLimit": "x-ratelimit-limit",
    "headerRemaining": "x-ratelimit-remaining",
    "headerReset": "x-ratelimit-reset",
    "resetFormat": "UtcEpochSeconds",
    "costPerRequest": 1,
    "rateLimit": 5,
    "resetTimeWindowSeconds": 60,
    "warningThresholdPercent": 0,
    "whenLimitExceeded": "Custom",
    "customResponseFile": "github-rate-limit-exceeded.json"
  }
}

Caution

請將RetryAfterPlugin新增在RateLimitingPlugin之前的組態檔中。 如果你是在之後才加入,它的話,RateLimitingPlugin 會在 RetryAfterPlugin 檢查之前先處理該請求。

在自訂回應檔案中,定義 GitHub 在你超過主要速率限制時會回傳的回應。

檔案: github-rate-limit-exceeded.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/ratelimitingplugin.customresponsefile.schema.json",
  "statusCode": 429,
  "headers": [
    {
      "name": "content-type",
      "value": "application/json; charset=utf-8"
    }
  ],
  "body": {
    "message": "API rate limit exceeded for user ID 1.",
    "documentation_url": "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api"
  }
}

啟動 Dev Proxy 並執行你的應用程式。

devproxy --config-file devproxyrc.json

Dev Proxy 會在每分鐘內將前 5 個請求轉發到 GitHub,並在回應上設定 x-ratelimit-* 標頭。 從第 6 次請求開始,Dev Proxy 會回傳速率限制的回應,將 x-ratelimit-remaining 設為 0,並將 x-ratelimit-reset 設為視窗結束。 如果你的應用程式在視窗重置前再次呼叫 API,RetryAfterPlugin 會回報此情況並限制該請求。

請檢查你的應用程式:

  • 讀取 x-ratelimit-remaining 並放慢速度,在它到達 0 之前。
  • 在速率限制回應後停止呼叫 API,並等待 x-ratelimit-reset。
  • 它會告訴使用者發生了什麼,例如「GitHub 速率限制已達,將於 14:05 重試」,而不是默默失敗。

Note

當你超過速率限制時,GitHub 會回傳 429 或 403。 要測試你的應用程式是否也能 403 處理,請改 statusCode 成 403。 RetryAfterPlugin它只追蹤429回應,所以不會回報在403之後的提前重試。

Tip

開發代理會將請求轉發到 GitHub,直到達到模擬的上限。 這些請求也會計入你真正的 GitHub 速率限制。

模擬次級速率限制

次級速率限制以突發形式出現,並包含標 retry-after 頭。 使用 GenericRandomErrorPlugin 隨機回傳這些資料。

檔案: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "RetryAfterPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll"
    },
    {
      "name": "GenericRandomErrorPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "githubSecondaryRateLimit"
    }
  ],
  "urlsToWatch": [
    "https://api.github.com/*"
  ],
  "githubSecondaryRateLimit": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.schema.json",
    "errorsFile": "github-secondary-rate-limit.json",
    "rate": 50,
    "retryAfterInSeconds": 60
  }
}

檔案: github-secondary-rate-limit.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/genericrandomerrorplugin.errorsfile.schema.json",
  "errors": [
    {
      "request": {
        "url": "https://api.github.com/*"
      },
      "responses": [
        {
          "statusCode": 429,
          "headers": [
            {
              "name": "content-type",
              "value": "application/json; charset=utf-8"
            },
            {
              "name": "retry-after",
              "value": "@dynamic"
            }
          ],
          "body": {
            "message": "You have exceeded a secondary rate limit. Please wait a few minutes before you try again.",
            "documentation_url": "https://docs.github.com/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits"
          }
        }
      ]
    }
  ]
}

啟動 Dev Proxy 並執行你的應用程式。 檢查你的應用程式是否會等到 retry-after 標頭中指定的秒數後,才會再次呼叫 API。 如果沒有,RetryAfterPlugin 會回報。

如果你用 Octokit 搭配節流外掛,請檢查你 onRateLimit 和 onSecondaryRateLimit 處理器是否執行,且回傳的結果是否符合你的預期。

下一步

了解更多關於 RateLimitingPlugin。

也請參閱