問題描述
使用Azure API Management, 想對一些固定的IP地址進行訪問次數的限制,如被限制的IP地址一分鐘可以訪問10次,而不被限制的IP地址則可以無限訪問?
ChatGPT 解答
最近ChatGPT爆火,所以也把這個問題讓ChatGPT來解答,然後人工驗證它的回答正確與否?
根據對APIM Policy的文件參考, choose 和 rate-limit 策略組合理論上的確可以實現要求, 接下來就讓我們實際驗證:
- choose策略:https://docs.azure.cn/zh-cn/api-management/api-management-advanced-policies#choose ,choose 策略根據布林表示式的求值結果應用括住的策略語句,類似於程式語言中的 if-then-else 或開關構造。
- rate-limit策略:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#LimitCallRate , rate-limit 策略可以對呼叫速率進行限制,使每個指定時段的呼叫不超出指定的數目,避免單個訂閱的 API 使用量暴增。 超過呼叫速率時,呼叫方會收到 429 Too Many Requests 響應狀態程式碼。
驗證步驟
1)在API的Inbound 策略中新增 choose策略
(策略具體內容,見文末)
2) 測試驗證,連續對該API訪問10次以上,得到429 Too Many Requests錯誤
3)以上證明,ChatGPT針對這個問題的解答是正確的!
工程師解答
在參考ChatGPT給出的 choose + rate limit 組合後,我們也發現另一個選項。使用 rate-limit-by-key 策略實現對特定IP的速率限制。
- rate-limit-by-key 策略:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#LimitCallRateByKey , 可以對呼叫速率進行限制,使指定時段的呼叫不超出指定的數目,避免單個金鑰的 API 使用量暴增。 金鑰的值可以是任意字串,通常使用策略表示式來提供金鑰。 可以新增可選增量條件,指定在決定是否到達限制值時應該進行計數的請求。 超過此呼叫速率時,呼叫方會收到
429 Too Many Requests
響應狀態程式碼。
在官方文件中給出的示例中,是針對所有的IP(context.Request.IpAddress)都進行了10次/60秒請求的限制,而本示例中則特指“某些固定IP”限制。那麼如何來完成這個需求呢?
答案 就在“rate-limit-by-key 策略”的說明中,”可以新增可選增量條件,指定在決定是否到達限制值時應該進行計數的請求”, 所以,只要可選增量條件(increment-condition) 的值根據輸入的IP地址動態賦值True/False, 就能完美匹配以上要求。
理論推斷,只需要實現如下邏輯,即可以實現終極需求“想對一些固定的IP地址進行訪問次數的限制,如被限制的IP地址一分鐘可以訪問10次,而不被限制的IP地址則可以無限訪問?”
只需兩步:
1)透過設定一個變數(set-variable) 值,用C#程式碼來計算變數值,在賦值語句中,預先定義一個IP限制列表,透過 contains 檢查當前請求IP是否在列表中,返回True or False 。True表示當前請求的IP需要速率限制, 否則,不需要。
2) 然後,在rate-limit-by-key 的 increment-condition條件中使用上一步引數值,進行判斷是否計入限制
驗證步驟
1)在API的 Inbound 策略中新增 rate-limit-by-key策略
(策略具體內容,見文末)
2)驗證在30秒,訪問5次以上後,同樣得到429 Too Many Requests錯誤
3) 當在請求Headers中新增Ocp-Apim-Trace: true 和 Ocp-Apim-Subscription-Key: {訂閱Key}後,可以檢視請求在APIM中執行的日誌跟蹤。可以檢視rate-limit-by-key 策略的執行情況.
總結
想實現固定IP地址訪問次數的限制,至少有如下兩種解決方案。
方案一:Choose + rate-limit 策略組合
<!-- IMPORTANT: - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements. - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element. - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element. - To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar. - To remove a policy, delete the corresponding policy statement from the policy document. - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope. - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope. - Policies are applied in the order of their appearance, from the top down. - Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope. --> <policies> <inbound> <base /> <set-variable name="IsCountIpLimit" value="@{ string ipAddress =context.Request.IpAddress; List<string> cidrList = new List<string>(){ "167.xxx. xxx.135", "167.xxx. xxx.136", "167.xxx. xxx.137" }; return cidrList.Contains(ipAddress); }" /> <choose> <when condition="@((bool)context.Variables["IsCountIpLimit"])"> <rate-limit calls="10" renewal-period="60" /> </when> </choose> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
方案二:rate-limit-by-key策略
<!-- IMPORTANT: - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements. - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element. - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element. - To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar. - To remove a policy, delete the corresponding policy statement from the policy document. - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope. - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope. - Policies are applied in the order of their appearance, from the top down. - Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope. --> <policies> <inbound> <base /> <set-variable name="IsCountIpLimit" value="@{ string ipAddress =context.Request.IpAddress; List<string> limitIPs = new List<string>(){ "167.xxx. xxx.135", "167.xxx. xxx.136", "167.xxx. xxx.137" }; return limitIPs.Contains(ipAddress); }" /> <rate-limit-by-key calls="5" renewal-period="30" counter-key="@(context.Request.IpAddress)" increment-condition="@(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300 && (bool)context.Variables["IsCountIpLimit"])" /> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
參考資料
choose策略:https://docs.azure.cn/zh-cn/api-management/api-management-advanced-policies#choose
rate-limit策略:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#LimitCallRate ,
rate-limit-by-key 策略:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#LimitCallRateByKey