[Dnc.Api.Throttle] 適用於. Net Core WebApi 介面限流框架
使用 Dnc.Api.Throttle 可以使您輕鬆實現
Dnc.Api.Throttle 暫時只支援 Redis 作為快取和儲存庫, 後續會進行擴充套件.
開始使用
安裝 Dnc.Api.Throttle
-
NuGet:
-
PM> Install-Package Dnc.Api.Throttle
-
Startup.cs:
-
public void ConfigureServices(IServiceCollection services)
-
{
-
//Api 限流
-
services.AddApiThrottle(options => {
-
// 配置 redis
-
// 如果 Cache 和 Storage 使用同一個 redis, 則可以按如下配置
-
options.UseRedisCacheAndStorage(opts => {
-
opts.ConnectionString = "localhost,connectTimeout=5000,allowAdmin=false,defaultDatabase=0";
-
//opts.KeyPrefix = "apithrottle"; // 指定給所有 key 加上字首, 預設為 apithrottle
-
});
-
// 如果 Cache 和 Storage 使用不同 redis 庫, 可以按如下配置
-
//options.UseRedisCache(opts => {
-
// opts.ConnectionString = "localhost,connectTimeout=5000,allowAdmin=false,defaultDatabase=0";
-
//});
-
//options.UseRedisStorage(opts => {
-
// opts.ConnectionString = "localhost,connectTimeout=5000,allowAdmin=false,defaultDatabase=1";
-
//});
-
});
-
services.AddMvc(opts => {
-
// 這裡新增 ApiThrottleActionFilter 攔截器
-
opts.Filters.Add(typeof(ApiThrottleActionFilter));
-
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
-
}
-
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
-
{
-
...
-
//Api 限流
-
app.UseApiThrottle();
-
app.UseMvc();
-
}
給 Api 新增一個限流閥門(Valve)
-
ValuesController.cs:
-
// GET api/values
-
[HttpGet]
-
[RateValve(Policy = Policy.Ip, Limit = 10, Duration = 30)]
-
public ActionResult<IEnumerable<string>> Get()
-
{
-
return new string[] { "value1", "value2" };
-
}
以上特性代表給 Get 介面新增一個速率閥門, 指定每個 IP,30 秒內最多呼叫 10 次該介面.
通過以上配置, 最簡單的一個介面限流就完成了.
當 Api 被攔截時, 介面不會執行, context.Result 會返回一個
new ApiThrottleResult { Content = "訪問過於頻繁, 請稍後重試!" }
, ApiThrottleResult 繼承於 ContentResult, 你可以不繼續處理, 也可以在自己的 ResultFilter 中攔截 ApiThrottleResult 並處理.
更多 Valve 範例
[RateValve(Policy = Policy.UserIdentity, Limit = 1, Duration = 60)]
代表根據使用者身份, 每 60 秒可訪問 1 次該介面. 關於使用者身份, 預設是如下取得的:
return context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
如果需要自定義, 則可以在 Startup.cs 中如下配置:
-
//Api 限流
-
services.AddApiThrottle(options => {
-
...
-
options.OnUserIdentity = (httpContext) =>
-
{
-
// 這裡根據自己需求返回使用者唯一身份
-
return httpContext.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
-
};
-
...
-
});
-
[RateValve(Policy = Policy.Header, PolicyKey = "hkey", Limit = 1, Duration = 30, WhenNull = WhenNull.Intercept)]
代表根據 Request Header 中 hkey 對應的值, 每 30 秒可訪問 1 次該介面. 如果無法取得 Header 中的值或取得的值為空, 則進行攔截.
關於 WhenNull:
WhenNull = WhenNull.Pass
: 對應策略取得的識別值為空時, 不進行攔截.
WhenNull = WhenNull.Intercept
: 對應策略取得的識別值為空時, 進行攔截.
[RateValve(Policy = Policy.Query, PolicyKey = "mobile", Limit = 1, Duration = 30, WhenNull = WhenNull.Pass)]
代表根據 Request Query 中 mobile 對應的值, 每 30 秒可訪問 1 次該介面. 如果無法取得識別值或取得的值為空, 則不進行攔截.
[BlackListValve(Policy = Policy.Query, PolicyKey = "mobile")]
黑名單攔截, 代表根據 Request Query 中 mobile 對應的值, 如果在黑名單中, 則進行攔截. 關於如何新增黑名單, 請參照後面關於 IApiThrottleService 部分.
[WhiteListValve(Policy = Policy.Ip)]
白名單攔截, 代表根據客戶端 IP 地址, 如果在白名單中, 則不進行攔截(如果同一個 Api 上有多個 Valve, 按序當檢查到白名單符合時, 則代表檢查通過, 不進行後續 Valve 的攔截檢查). 關於如何新增白名單, 請參照後面關於 IApiThrottleService 部分.
一個 Api 多個 Valve
-
// POST api/values
-
[HttpPost]
-
[WhiteListValve(Policy = Policy.Ip, Priority = 3)]
-
[BlackListValve(Policy = Policy.UserIdentity, Priority = 2)]
-
[RateValve(Policy = Policy.Header, PolicyKey = "hkey", Limit = 1, Duration = 30, WhenNull = WhenNull.Pass)]
-
public void Post([FromBody] string value)
-
{
-
}
多個 Valve 根據 Priority 值從大到小進行攔截, 如果被攔截, 則不進行後續 Valve 攔截檢查.
全侷限流配置
以上都是對單個 Api 進行限流管理的, 如果需要對全域性進行限流管理, 可在 Startup.cs
中進行如下配置:
-
//Api 限流
-
services.AddApiThrottle(options => {
-
...
-
options.Global.AddValves(new BlackListValve
-
{
-
Policy = Policy.Ip,
-
Priority = 99
-
}, new WhiteListValve
-
{
-
Policy = Policy.UserIdentity,
-
Priority = 88
-
},
-
new BlackListValve
-
{
-
Policy = Policy.Header,
-
PolicyKey = "throttle"
-
}, new RateValve
-
{
-
Policy = Policy.Ip,
-
Limit = 5,
-
Duration = 10,
-
WhenNull = WhenNull.Pass
-
});
-
...
-
});
以上代表給全域性添加了 4 個 Valve 進行攔截, 如果被攔截, 則不進行後續操作.
白名單檢查通過時, 代表全域性攔截通過, 不進行後續全域性 Valve 檢查(後續單獨 Api 的檢查還會進行).
相同識別策略 (Policy+PolicyKey) 的 Valve 只能新增一個, 重複不會新增.
全侷限流攔截在 Middlewarez 中進行, 單獨 Api 限流攔截在 IAsyncActionFilter 中進行, 當然也支援 Razor Page, 在 IAsyncPageFilterz 中進行限流.
其他自定義配置項
自定義 IP 地址取得方法:
-
//Api 限流
-
services.AddApiThrottle(options => {
-
...
-
// 以下是 Dnc.Api.Throttle 預設取得 Ip 地址的方法, 可進行自定義
-
options.OnIpAddress = (context) => {
-
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
-
if (string.IsNullOrEmpty(ip))
-
{
-
ip = context.Connection.RemoteIpAddress.ToString();
-
}
-
return ip;
-
};
-
...
-
});
自定義攔截後處理:
-
//Api 限流
-
services.AddApiThrottle(options => {
-
...
-
options.onIntercepted = (context, valve, where) =>
-
{
-
//valve: 引發攔截的 valve
-
//where: 攔截髮生的地方, 有 ActionFilter,PageFilter,Middleware(全域性)
-
if (where == IntercepteWhere.Middleware)
-
{
-
// 注意: Middleware 中返回的 ActionResult 無法在 ResultFilter 中攔截處理.
-
return new JsonResult(new { code = 99, message = "訪問過於頻繁, 請稍後重試!" });
-
}
-
else
-
{
-
return new ApiThrottleResult { Content = "訪問過於頻繁, 請稍後重試!" };
-
}
-
};
-
...
-
});
-
IApiThrottleService
使用 IApiThrottleService 介面可實現黑名單, 白名單的管理維護等其他功能.
使用範例:
-
/// <summary>
-
/// Api 限流管理服務
-
/// </summary>
-
private readonly IApiThrottleService _service;
-
public ValuesController(IApiThrottleService service)
-
{
-
_service = service;
-
}
-
[HttpPost]
-
[BlackListValve(Policy = Policy.Ip)]
-
public async Task AddBlackList()
-
{
-
var ip = GetIpAddress(HttpContext);
-
// 新增 IP 黑名單
-
await _service.AddRosterAsync(RosterType.BlackList,
-
"WebApiTest.Controllers.ValuesController.AddBlackList",
-
Policy.Ip, null, TimeSpan.FromSeconds(60), ip);
-
}
-
/// <summary>
-
/// 取得客戶端 IP 地址
-
/// </summary>
-
private static string GetIpAddress(HttpContext context)
-
{
-
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
-
if (string.IsNullOrEmpty(ip))
-
{
-
ip = context.Connection.RemoteIpAddress.ToString();
-
}
-
return ip;
-
}
AddBlackList 中針對 WebApiTest.Controllers.ValuesController.AddBlackList 方法添加了一個有效期 60 的 IP 黑名單, 當前 IP 呼叫該介面會被 IP 黑名單攔截.
IApiThrottleService 現有介面:
-
#region 黑名單 & 白名單
-
/// <summary>
-
/// 新增名單
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="api">Api</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
/// <param name="expiry">過期時間</param>
-
/// <param name="item">專案</param>
-
Task AddRosterAsync(RosterType rosterType, string api, Policy policy, string policyKey, TimeSpan? expiry, params string[] item);
-
/// <summary>
-
/// 刪除名單中資料
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="api">API</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
/// <param name="expiry">過期時間</param>
-
/// <param name="item">專案</param>
-
Task RemoveRosterAsync(RosterType rosterType, string api, Policy policy, string policyKey, params string[] item);
-
/// <summary>
-
/// 取得名單列表(分頁)
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="api">API</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
Task<(long count, IEnumerable<ListItem> items)> GetRosterListAsync(RosterType rosterType, string api, Policy policy, string policyKey, long skip, long take);
-
/// <summary>
-
/// 取得名單列表
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="api">API</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
Task<IEnumerable<ListItem>> GetRosterListAsync(RosterType rosterType, string api, Policy policy, string policyKey);
-
/// <summary>
-
/// 新增全域性名單
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
/// <param name="expiry">過期時間</param>
-
/// <param name="item">專案</param>
-
Task AddGlobalRosterAsync(RosterType rosterType, Policy policy, string policyKey, TimeSpan? expiry, params string[] item);
-
/// <summary>
-
/// 移除全域性名單
-
/// </summary>
-
/// <param name="policy">策略</param>
-
/// <param name="item">專案</param>
-
Task RemoveGlobalRosterAsync(RosterType rosterType, Policy policy, string policyKey, params string[] item);
-
/// <summary>
-
/// 取得全域性名單列表(分頁)
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
Task<(long count, IEnumerable<ListItem> items)> GetGlobalRosterListAsync(RosterType rosterType, Policy policy, string policyKey, long skip, long take);
-
/// <summary>
-
/// 取得全域性名單列表
-
/// </summary>
-
/// <param name="rosterType">名單型別</param>
-
/// <param name="policy">策略</param>
-
/// <param name="policyKey">策略 Key</param>
-
Task<IEnumerable<ListItem>> GetGlobalRosterListAsync(RosterType rosterType, Policy policy, string policyKey);
-
#endregion
-
NuGet: