1. 程式人生 > 實用技巧 >[Dnc.Api.Throttle] 適用於. Net Core WebApi 介面限流框架

[Dnc.Api.Throttle] 適用於. Net Core WebApi 介面限流框架

[Dnc.Api.Throttle] 適用於. Net Core WebApi 介面限流框架

使用 Dnc.Api.Throttle 可以使您輕鬆實現 webApi 介面的限流管理. Dnc.Api.Throttle 支援 IP, 使用者身份, Request Header,Request QueryString 等多種限流策略, 支援黑名單和白名單功能, 支援全域性攔截和單獨 Api 攔截.

Dnc.Api.Throttle 暫時只支援 Redis 作為快取和儲存庫, 後續會進行擴充套件.

開始使用

安裝 Dnc.Api.Throttle

  1. NuGet:

  2. PM> Install-Package Dnc.Api.Throttle

基本配置

  1. Startup.cs:

  2. public void ConfigureServices(IServiceCollection services)

  3. {

  4. //Api 限流

  5. services.AddApiThrottle(options => {

  6. // 配置 redis

  7. // 如果 Cache 和 Storage 使用同一個 redis, 則可以按如下配置

  8. options.UseRedisCacheAndStorage(opts => {

  9. opts.ConnectionString = "localhost,connectTimeout=5000,allowAdmin=false,defaultDatabase=0";

  10. //opts.KeyPrefix = "apithrottle"; // 指定給所有 key 加上字首, 預設為 apithrottle

  11. });

  12. // 如果 Cache 和 Storage 使用不同 redis 庫, 可以按如下配置

  13. //options.UseRedisCache(opts => {

  14. // opts.ConnectionString = "localhost,connectTimeout=5000,allowAdmin=false,defaultDatabase=0";

  15. //});

  16. //options.UseRedisStorage(opts => {

  17. // opts.ConnectionString = "localhost,connectTimeout=5000,allowAdmin=false,defaultDatabase=1";

  18. //});

  19. });

  20. services.AddMvc(opts => {

  21. // 這裡新增 ApiThrottleActionFilter 攔截器

  22. opts.Filters.Add(typeof(ApiThrottleActionFilter));

  23. }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

  24. }

  25. public void Configure(IApplicationBuilder app, IHostingEnvironment env)

  26. {

  27. ...

  28. //Api 限流

  29. app.UseApiThrottle();

  30. app.UseMvc();

  31. }

給 Api 新增一個限流閥門(Valve)

  1. ValuesController.cs:

  2. // GET api/values

  3. [HttpGet]

  4. [RateValve(Policy = Policy.Ip, Limit = 10, Duration = 30)]

  5. public ActionResult<IEnumerable<string>> Get()

  6. {

  7. return new string[] { "value1", "value2" };

  8. }

以上特性代表給 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 中如下配置:

  1. //Api 限流

  2. services.AddApiThrottle(options => {

  3. ...

  4. options.OnUserIdentity = (httpContext) =>

  5. {

  6. // 這裡根據自己需求返回使用者唯一身份

  7. return httpContext.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;

  8. };

  9. ...

  10. });

  11. [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

  1. // POST api/values

  2. [HttpPost]

  3. [WhiteListValve(Policy = Policy.Ip, Priority = 3)]

  4. [BlackListValve(Policy = Policy.UserIdentity, Priority = 2)]

  5. [RateValve(Policy = Policy.Header, PolicyKey = "hkey", Limit = 1, Duration = 30, WhenNull = WhenNull.Pass)]

  6. public void Post([FromBody] string value)

  7. {

  8. }

多個 Valve 根據 Priority 值從大到小進行攔截, 如果被攔截, 則不進行後續 Valve 攔截檢查.

全侷限流配置

以上都是對單個 Api 進行限流管理的, 如果需要對全域性進行限流管理, 可在 Startup.cs 中進行如下配置:

  1. //Api 限流

  2. services.AddApiThrottle(options => {

  3. ...

  4. options.Global.AddValves(new BlackListValve

  5. {

  6. Policy = Policy.Ip,

  7. Priority = 99

  8. }, new WhiteListValve

  9. {

  10. Policy = Policy.UserIdentity,

  11. Priority = 88

  12. },

  13. new BlackListValve

  14. {

  15. Policy = Policy.Header,

  16. PolicyKey = "throttle"

  17. }, new RateValve

  18. {

  19. Policy = Policy.Ip,

  20. Limit = 5,

  21. Duration = 10,

  22. WhenNull = WhenNull.Pass

  23. });

  24. ...

  25. });

以上代表給全域性添加了 4 個 Valve 進行攔截, 如果被攔截, 則不進行後續操作.

白名單檢查通過時, 代表全域性攔截通過, 不進行後續全域性 Valve 檢查(後續單獨 Api 的檢查還會進行).

相同識別策略 (Policy+PolicyKey) 的 Valve 只能新增一個, 重複不會新增.

全侷限流攔截在 Middlewarez 中進行, 單獨 Api 限流攔截在 IAsyncActionFilter 中進行, 當然也支援 Razor Page, 在 IAsyncPageFilterz 中進行限流.

其他自定義配置項

自定義 IP 地址取得方法:

  1. //Api 限流

  2. services.AddApiThrottle(options => {

  3. ...

  4. // 以下是 Dnc.Api.Throttle 預設取得 Ip 地址的方法, 可進行自定義

  5. options.OnIpAddress = (context) => {

  6. var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();

  7. if (string.IsNullOrEmpty(ip))

  8. {

  9. ip = context.Connection.RemoteIpAddress.ToString();

  10. }

  11. return ip;

  12. };

  13. ...

  14. });

自定義攔截後處理:

  1. //Api 限流

  2. services.AddApiThrottle(options => {

  3. ...

  4. options.onIntercepted = (context, valve, where) =>

  5. {

  6. //valve: 引發攔截的 valve

  7. //where: 攔截髮生的地方, 有 ActionFilter,PageFilter,Middleware(全域性)

  8. if (where == IntercepteWhere.Middleware)

  9. {

  10. // 注意: Middleware 中返回的 ActionResult 無法在 ResultFilter 中攔截處理.

  11. return new JsonResult(new { code = 99, message = "訪問過於頻繁, 請稍後重試!" });

  12. }

  13. else

  14. {

  15. return new ApiThrottleResult { Content = "訪問過於頻繁, 請稍後重試!" };

  16. }

  17. };

  18. ...

  19. });

  20. IApiThrottleService

使用 IApiThrottleService 介面可實現黑名單, 白名單的管理維護等其他功能.

使用範例:

  1. /// <summary>

  2. /// Api 限流管理服務

  3. /// </summary>

  4. private readonly IApiThrottleService _service;

  5. public ValuesController(IApiThrottleService service)

  6. {

  7. _service = service;

  8. }

  9. [HttpPost]

  10. [BlackListValve(Policy = Policy.Ip)]

  11. public async Task AddBlackList()

  12. {

  13. var ip = GetIpAddress(HttpContext);

  14. // 新增 IP 黑名單

  15. await _service.AddRosterAsync(RosterType.BlackList,

  16. "WebApiTest.Controllers.ValuesController.AddBlackList",

  17. Policy.Ip, null, TimeSpan.FromSeconds(60), ip);

  18. }

  19. /// <summary>

  20. /// 取得客戶端 IP 地址

  21. /// </summary>

  22. private static string GetIpAddress(HttpContext context)

  23. {

  24. var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();

  25. if (string.IsNullOrEmpty(ip))

  26. {

  27. ip = context.Connection.RemoteIpAddress.ToString();

  28. }

  29. return ip;

  30. }

AddBlackList 中針對 WebApiTest.Controllers.ValuesController.AddBlackList 方法添加了一個有效期 60 的 IP 黑名單, 當前 IP 呼叫該介面會被 IP 黑名單攔截.

IApiThrottleService 現有介面:

  1. #region 黑名單 & 白名單

  2. /// <summary>

  3. /// 新增名單

  4. /// </summary>

  5. /// <param name="rosterType">名單型別</param>

  6. /// <param name="api">Api</param>

  7. /// <param name="policy">策略</param>

  8. /// <param name="policyKey">策略 Key</param>

  9. /// <param name="expiry">過期時間</param>

  10. /// <param name="item">專案</param>

  11. Task AddRosterAsync(RosterType rosterType, string api, Policy policy, string policyKey, TimeSpan? expiry, params string[] item);

  12. /// <summary>

  13. /// 刪除名單中資料

  14. /// </summary>

  15. /// <param name="rosterType">名單型別</param>

  16. /// <param name="api">API</param>

  17. /// <param name="policy">策略</param>

  18. /// <param name="policyKey">策略 Key</param>

  19. /// <param name="expiry">過期時間</param>

  20. /// <param name="item">專案</param>

  21. Task RemoveRosterAsync(RosterType rosterType, string api, Policy policy, string policyKey, params string[] item);

  22. /// <summary>

  23. /// 取得名單列表(分頁)

  24. /// </summary>

  25. /// <param name="rosterType">名單型別</param>

  26. /// <param name="api">API</param>

  27. /// <param name="policy">策略</param>

  28. /// <param name="policyKey">策略 Key</param>

  29. Task<(long count, IEnumerable<ListItem> items)> GetRosterListAsync(RosterType rosterType, string api, Policy policy, string policyKey, long skip, long take);

  30. /// <summary>

  31. /// 取得名單列表

  32. /// </summary>

  33. /// <param name="rosterType">名單型別</param>

  34. /// <param name="api">API</param>

  35. /// <param name="policy">策略</param>

  36. /// <param name="policyKey">策略 Key</param>

  37. Task<IEnumerable<ListItem>> GetRosterListAsync(RosterType rosterType, string api, Policy policy, string policyKey);

  38. /// <summary>

  39. /// 新增全域性名單

  40. /// </summary>

  41. /// <param name="rosterType">名單型別</param>

  42. /// <param name="policy">策略</param>

  43. /// <param name="policyKey">策略 Key</param>

  44. /// <param name="expiry">過期時間</param>

  45. /// <param name="item">專案</param>

  46. Task AddGlobalRosterAsync(RosterType rosterType, Policy policy, string policyKey, TimeSpan? expiry, params string[] item);

  47. /// <summary>

  48. /// 移除全域性名單

  49. /// </summary>

  50. /// <param name="policy">策略</param>

  51. /// <param name="item">專案</param>

  52. Task RemoveGlobalRosterAsync(RosterType rosterType, Policy policy, string policyKey, params string[] item);

  53. /// <summary>

  54. /// 取得全域性名單列表(分頁)

  55. /// </summary>

  56. /// <param name="rosterType">名單型別</param>

  57. /// <param name="policy">策略</param>

  58. /// <param name="policyKey">策略 Key</param>

  59. Task<(long count, IEnumerable<ListItem> items)> GetGlobalRosterListAsync(RosterType rosterType, Policy policy, string policyKey, long skip, long take);

  60. /// <summary>

  61. /// 取得全域性名單列表

  62. /// </summary>

  63. /// <param name="rosterType">名單型別</param>

  64. /// <param name="policy">策略</param>

  65. /// <param name="policyKey">策略 Key</param>

  66. Task<IEnumerable<ListItem>> GetGlobalRosterListAsync(RosterType rosterType, Policy policy, string policyKey);

  67. #endregion

  1. Github:https://github.com/kulend/Dnc.Api.Throttle

  2. NuGet:https://www.nuget.org/packages/Dnc.Api.Throttle