[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

posted @ 2020-08-14 16:33  Areas  阅读(276)  评论(0编辑  收藏  举报