前言

整理了一下.net core 一些常见的库的源码阅读,共32个库,记100余篇。

以下只是个人的源码阅读,如有错误或者思路不正确,望请指点。

正文

github 地址为:

https://github.com/stefanprodan/AspNetCoreRateLimit

一般个人习惯先阅读readme的简介。

上面大概翻译是:

AspNetCoreRateLimit 是ASP.NET Core 访问速率限制的解决方案,设计基于ip地址和客户端id用于控制用于web api和 mvc app的客户端访问速率。

这个包包含了IpRateLimitMiddleware and a ClientRateLimitMiddleware两个中间件,用这两个中间件你根据不同的场景能设置几种不同的限制,

比如限制一个客户端或者一个ip在几秒或者15分钟内访问最大限制。您可以定义这些限制来处理对某个API的所有请求,也可以将这些限制限定在指定范围的每个API URL或HTTP请求路径上。

上面说了这么多就是用来限流的,针对客户端id和ip进行限流。

因为一般用的是ip限流,看下ip限制怎么使用的,毕竟主要还是拿来用的嘛。

本来还想根据文档先写个小demo,然后发现官方已经写了demo。

直接看demo(只看ip限制部分的)。

先来看下ConfigureServices:

services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
services.AddInMemoryRateLimiting();

从上面看,那么配置ip 限制的有两个配置,一个配置是IpRateLimitOptions,另外一个配置是IpRateLimitPolicies。

那么为什么要设计成两个配置呢?一个配置不是更香吗?

官方设计理念是这样的:

https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/IpRateLimitMiddleware#defining-rate-limit-rules

IpRateLimiting Configuration and general rules appsettings.json

IpRateLimitPolicies Override general rules for specific IPs appsettings.json

原来IpRateLimiting 是限制普遍的ip,而IpRateLimitPolicies 是限制一些特殊的ip。

比如说有些api对内又对外的,普遍的ip对外限制是1分钟300次,如果有个大客户特殊需求且固定ip的,需要限制是1分钟是10000次的,那么就可以这样特殊处理,而不用另外写code来维护,成本问题。

故而我们写中间件组件的时候也可以参考这个来做,特殊的怎么处理,普遍的怎么处理,当然也不能盲目的设计。

然后看:AddInMemoryRateLimiting

public static IServiceCollection AddInMemoryRateLimiting(this IServiceCollection services)
{
services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
services.AddSingleton<IClientPolicyStore, MemoryCacheClientPolicyStore>();
services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
services.AddSingleton<IProcessingStrategy, AsyncKeyLockProcessingStrategy>();
return services;
}

里面注入了MemoryCacheIpPolicyStore、MemoryCacheClientPolicyStore、MemoryCacheRateLimitCounterStore、AsyncKeyLockProcessingStrategy。

分别看下这几个东西。

MemoryCacheIpPolicyStore:

public class MemoryCacheIpPolicyStore : MemoryCacheRateLimitStore<IpRateLimitPolicies>, IIpPolicyStore
{
private readonly IpRateLimitOptions _options;
private readonly IpRateLimitPolicies _policies; public MemoryCacheIpPolicyStore(
IMemoryCache cache,
IOptions<IpRateLimitOptions> options = null,
IOptions<IpRateLimitPolicies> policies = null) : base(cache)
{
_options = options?.Value;
_policies = policies?.Value;
} public async Task SeedAsync()
{
// on startup, save the IP rules defined in appsettings
if (_options != null && _policies != null)
{
await SetAsync($"{_options.IpPolicyPrefix}", _policies).ConfigureAwait(false);
}
}
}

这个是用例存储IpRateLimitPolicies(ip限制)。

MemoryCacheIpPolicyStore 这个名字起的有点意思,MemoryCache 是内存缓存,IpPolicy ip策略,store 存储。

分别是存储空间、物品、功能的组合。所以这个库应该是外国人写的,一般来说中国人会这样改:IpPolicyMemoryCacheStore,估计是因为强调故而把MemoryCache放到前面去了。

这里我刚开始有点不理解,本来已经可以读取到了options,那么按照options操作就很方便了。

那么为啥要用缓存到内存中呢?后来大体的通读了一下,是因为_policies(特殊制定的ip规则)很多地方都要使用到,一方面是为了解耦,另外一方面呢,是因为下面这个。

[HttpPost]
public void Post()
{
var pol = _ipPolicyStore.Get(_options.IpPolicyPrefix); pol.IpRules.Add(new IpRateLimitPolicy
{
Ip = "8.8.4.4",
Rules = new List<RateLimitRule>(new RateLimitRule[] {
new RateLimitRule {
Endpoint = "*:/api/testupdate",
Limit = 100,
Period = "1d" }
})
}); _ipPolicyStore.Set(_options.IpPolicyPrefix, pol);
}

是可以动态设置特殊ip的一些配置的。 那么里面也考虑到了分布式的一些行为,比如把缓存放到redis这种隔离缓存中。

如果将_policies 封装到memory cache 中,那么和redis cache形成了一套适配器。个人认为是从设计方面考虑的。

然后看下这个方法,里面就是以IpRateLimiting的IpPolicyPrefix 作为key,然后存储了IpRateLimitPolicies。

public async Task SeedAsync()
{
// on startup, save the IP rules defined in appsettings
if (_options != null && _policies != null)
{
await SetAsync($"{_options.IpPolicyPrefix}", _policies).ConfigureAwait(false);
}
}

具体的SetAsync 如下:

public Task SetAsync(string id, T entry, TimeSpan? expirationTime = null, CancellationToken cancellationToken = default)
{
var options = new MemoryCacheEntryOptions
{
Priority = CacheItemPriority.NeverRemove
}; if (expirationTime.HasValue)
{
options.SetAbsoluteExpiration(expirationTime.Value);
} _cache.Set(id, entry, options); return Task.CompletedTask;
}

然后这里值得注意的是_options.IpPolicyPrefix,这个值如果是分布式那么应该值得关注一下,因为我们有不同应用服务,如果希望不同的应用服务用到不同的ip限制,那么IpPolicyPrefix 最好改成应用名,而不是使用默认值。

那么看下MemoryCacheClientPolicyStore:

public class MemoryCacheClientPolicyStore : MemoryCacheRateLimitStore<ClientRateLimitPolicy>, IClientPolicyStore
{
private readonly ClientRateLimitOptions _options;
private readonly ClientRateLimitPolicies _policies; public MemoryCacheClientPolicyStore(
IMemoryCache cache,
IOptions<ClientRateLimitOptions> options = null,
IOptions<ClientRateLimitPolicies> policies = null) : base(cache)
{
_options = options?.Value;
_policies = policies?.Value;
} public async Task SeedAsync()
{
// on startup, save the IP rules defined in appsettings
if (_options != null && _policies?.ClientRules != null)
{
foreach (var rule in _policies.ClientRules)
{
await SetAsync($"{_options.ClientPolicyPrefix}_{rule.ClientId}", new ClientRateLimitPolicy { ClientId = rule.ClientId, Rules = rule.Rules }).ConfigureAwait(false);
}
}
}
}

这个就是client id的限制的缓存的,和上面一样就不看了。

MemoryCacheRateLimitCounterStore:

public class MemoryCacheRateLimitCounterStore : MemoryCacheRateLimitStore<RateLimitCounter?>, IRateLimitCounterStore
{
public MemoryCacheRateLimitCounterStore(IMemoryCache cache) : base(cache)
{
}
}

这里面没有啥子。但是从名字上猜测,里面是缓存每个ip请求次数的当然还有时间,主要起缓存作用。

最后一个:AsyncKeyLockProcessingStrategy

public class AsyncKeyLockProcessingStrategy : ProcessingStrategy
{
private readonly IRateLimitCounterStore _counterStore;
private readonly IRateLimitConfiguration _config; public AsyncKeyLockProcessingStrategy(IRateLimitCounterStore counterStore, IRateLimitConfiguration config)
: base(config)
{
_counterStore = counterStore;
_config = config;
} /// The key-lock used for limiting requests.
private static readonly AsyncKeyLock AsyncLock = new AsyncKeyLock(); public override async Task<RateLimitCounter> ProcessRequestAsync(ClientRequestIdentity requestIdentity, RateLimitRule rule, ICounterKeyBuilder counterKeyBuilder, RateLimitOptions rateLimitOptions, CancellationToken cancellationToken = default)
{
var counter = new RateLimitCounter
{
Timestamp = DateTime.UtcNow,
Count = 1
}; var counterId = BuildCounterKey(requestIdentity, rule, counterKeyBuilder, rateLimitOptions); // serial reads and writes on same key
using (await AsyncLock.WriterLockAsync(counterId).ConfigureAwait(false))
{
var entry = await _counterStore.GetAsync(counterId, cancellationToken); if (entry.HasValue)
{
// entry has not expired
if (entry.Value.Timestamp + rule.PeriodTimespan.Value >= DateTime.UtcNow)
{
// increment request count
var totalCount = entry.Value.Count + _config.RateIncrementer?.Invoke() ?? 1; // deep copy
counter = new RateLimitCounter
{
Timestamp = entry.Value.Timestamp,
Count = totalCount
};
}
} // stores: id (string) - timestamp (datetime) - total_requests (long)
await _counterStore.SetAsync(counterId, counter, rule.PeriodTimespan.Value, cancellationToken);
} return counter;
}
}

估摸着是执行具体计数逻辑的,那么等执行中间件的时候在看。

后面有写入了一个:services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

那么这个RateLimitConfiguration 是做什么的呢?

public class RateLimitConfiguration : IRateLimitConfiguration
{
public IList<IClientResolveContributor> ClientResolvers { get; } = new List<IClientResolveContributor>();
public IList<IIpResolveContributor> IpResolvers { get; } = new List<IIpResolveContributor>(); public virtual ICounterKeyBuilder EndpointCounterKeyBuilder { get; } = new PathCounterKeyBuilder(); public virtual Func<double> RateIncrementer { get; } = () => 1; public RateLimitConfiguration(
IOptions<IpRateLimitOptions> ipOptions,
IOptions<ClientRateLimitOptions> clientOptions)
{
IpRateLimitOptions = ipOptions?.Value;
ClientRateLimitOptions = clientOptions?.Value;
} protected readonly IpRateLimitOptions IpRateLimitOptions;
protected readonly ClientRateLimitOptions ClientRateLimitOptions; public virtual void RegisterResolvers()
{
string clientIdHeader = GetClientIdHeader();
string realIpHeader = GetRealIp(); if (clientIdHeader != null)
{
ClientResolvers.Add(new ClientHeaderResolveContributor(clientIdHeader));
} // the contributors are resolved in the order of their collection index
if (realIpHeader != null)
{
IpResolvers.Add(new IpHeaderResolveContributor(realIpHeader));
} IpResolvers.Add(new IpConnectionResolveContributor());
} protected string GetClientIdHeader()
{
return ClientRateLimitOptions?.ClientIdHeader ?? IpRateLimitOptions?.ClientIdHeader;
} protected string GetRealIp()
{
return IpRateLimitOptions?.RealIpHeader ?? ClientRateLimitOptions?.RealIpHeader;
}
}

重点看:

public virtual void RegisterResolvers()
{
string clientIdHeader = GetClientIdHeader();
string realIpHeader = GetRealIp(); if (clientIdHeader != null)
{
ClientResolvers.Add(new ClientHeaderResolveContributor(clientIdHeader));
} // the contributors are resolved in the order of their collection index
if (realIpHeader != null)
{
IpResolvers.Add(new IpHeaderResolveContributor(realIpHeader));
} IpResolvers.Add(new IpConnectionResolveContributor());
}

这里只看ip部分:

protected string GetRealIp()
{
return IpRateLimitOptions?.RealIpHeader ?? ClientRateLimitOptions?.RealIpHeader;
}

那么这个IpHeaderResolveContributor是什么呢?

public class IpHeaderResolveContributor : IIpResolveContributor
{
private readonly string _headerName; public IpHeaderResolveContributor(
string headerName)
{
_headerName = headerName;
} public string ResolveIp(HttpContext httpContext)
{
IPAddress clientIp = null; if (httpContext.Request.Headers.TryGetValue(_headerName, out var values))
{
clientIp = IpAddressUtil.ParseIp(values.Last());
} return clientIp?.ToString();
}
}

原来是配置是从header的哪个位置获取ip。官网demo中给的是"RealIpHeader": "X-Real-IP"。从header部分的RealIpHeader获取。

同样,官方也默认提供了IpResolvers.Add(new IpConnectionResolveContributor());。

public class IpConnectionResolveContributor : IIpResolveContributor
{ public IpConnectionResolveContributor()
{ } public string ResolveIp(HttpContext httpContext)
{
return httpContext.Connection.RemoteIpAddress?.ToString();
}
}

从httpContext.Connection.RemoteIpAddress 中获取ip,那么问题来了,RemoteIpAddress 是如何获取的呢? 到底X-Real-IP 获取的ip准不准呢?会在.net core 细节篇中介绍。

回到原始。现在已经注入了服务,那么如何把中间件注入进去呢?

在Configure 中:

app.UseIpRateLimiting();

将会执行中间件:IpRateLimitMiddleware

public class IpRateLimitMiddleware : RateLimitMiddleware<IpRateLimitProcessor>
{
private readonly ILogger<IpRateLimitMiddleware> _logger; public IpRateLimitMiddleware(RequestDelegate next,
IProcessingStrategy processingStrategy,
IOptions<IpRateLimitOptions> options,
IRateLimitCounterStore counterStore,
IIpPolicyStore policyStore,
IRateLimitConfiguration config,
ILogger<IpRateLimitMiddleware> logger
)
: base(next, options?.Value, new IpRateLimitProcessor(options?.Value, counterStore, policyStore, config, processingStrategy), config)
{
_logger = logger;
} protected override void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule)
{
_logger.LogInformation($"Request {identity.HttpVerb}:{identity.Path} from IP {identity.ClientIp} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.Count - rule.Limit}. Blocked by rule {rule.Endpoint}, TraceIdentifier {httpContext.TraceIdentifier}. MonitorMode: {rule.MonitorMode}");
}
}

查看:RateLimitMiddleware

里面就是具体的invoke中间件代码了。

因为篇幅有限,后一节invoke逐行分析其如何实现的。

以上只是个人看源码的过程,希望能得到各位的指点,共同进步。

另外.net core 细节篇整理进度为40%。

重新整理 .net core 周边阅读篇————AspNetCoreRateLimit[一]的更多相关文章

  1. 重新整理 .net core 周边阅读篇————AspNetCoreRateLimit 之规则[二]

    前言 本文和上文息息相关. https://www.cnblogs.com/aoximin/p/15315102.html 是紧接着上文invoke来书写的,那么现在来逐行分析invoke到底干了啥. ...

  2. 重新整理 .net core 实践篇————配置应用[一]

    前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...

  3. .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html 本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新 ...

  4. .NET Core CSharp初级篇 1-1

    .NET Core CSharp初级篇 1-1 本节内容是对于C#基础类型的存储方式以及C#基础类型的理论介绍 基础数据类型介绍 例如以下这句话:"张三是一名程序员,今年15岁重50.3kg ...

  5. net core体系-web应用程序-4asp.net core2.0 项目实战(CMS)-第二章 入门篇-快速入门ASP.NET Core看这篇就够了

    .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了   原文链接:https://www.cnblogs.com/yilezhu/p/9985451.ht ...

  6. 重新整理 .net core 实践篇————依赖注入应用[二]

    前言 这里介绍一下.net core的依赖注入框架,其中其代码原理在我的另一个整理<<重新整理 1400篇>>中已经写了,故而专门整理应用这一块. 以下只是个人整理,如有问题, ...

  7. NET Core CSharp初级篇 1-3面向对象

    .NET Core CSharp初级篇 1-3 本节内容为面向对象初级教程 类 简介 面向对象是整个C#中最核心最有特色的一个模块了,它很好的诠释了程序与现实世界的联系. 面向对象的三大特征:继承.多 ...

  8. .NET Core CSharp初级篇 1-5 接口、枚举、抽象

    .NET Core CSharp初级篇 1-5 本节内容类的接口.枚举.抽象 简介 问题 如果你需要表示星期或者是某些状态,使用字符串或者数字是否不直观? 你是否发现,无论何种电脑,它的USB口的设计 ...

  9. .NET Core CSharp初级篇 1-6 类的多态与继承

    .NET Core CSharp初级篇 1-6 本节内容为类的多态与继承 简介 终于讲到了面向对象三大特性中的两大特性--继承与多态.通过继承与多态,我们能很好的将类的拓展性发挥到了极致.在下面的内容 ...

随机推荐

  1. Shell-14-常用命令和工具

    常用命令 有人说 Shell 脚本是命令堆积的一个文件, 按顺序去执行 还有人说想学好 Shell 脚本,要把 Linux 上各种常见的命令或工具掌握了,这些说法都没错 Shell 语言本身在语法结构 ...

  2. MATLAB—数组运算及数组化编程

    文章目录 前言 一.数组的结构和创建 1.数组及其结构 2.行数组的创建 3.对数组构造的操作 二.数组元素编址及寻访 1.数组元素的编址 2.二维数组元素的寻访 三.数组运算 非数的问题 前言 编程 ...

  3. STM32—驱动BT-06蓝牙模块传输数据

    文章目录 BT-06简介 数据透传 配置串口 USART1初始化函数 USART2初始化函数 USART2的NVIC配置 USART1串口重映射 BT-06简介 BT06蓝牙模块是专为智能无线数据传输 ...

  4. Longhorn,企业级云原生容器分布式存储 - K8S 资源配置示例

    内容来源于官方 Longhorn 1.1.2 英文技术手册. 系列 Longhorn 是什么? Longhorn 企业级云原生容器分布式存储解决方案设计架构和概念 Longhorn 企业级云原生容器分 ...

  5. Longhorn,企业级云原生容器分布式存储 - 备份与恢复

    内容来源于官方 Longhorn 1.1.2 英文技术手册. 系列 Longhorn 是什么? Longhorn 企业级云原生容器分布式存储解决方案设计架构和概念 Longhorn 企业级云原生容器分 ...

  6. liunx上安装nacos

    下载nacos wget https://github.com/alibaba/nacos/releases/download/1.4.1/nacos-server-1.4.1.tar.gz 启动服务 ...

  7. java实现全排列输出

    java实现全排列输出 转自:http://easonfans.iteye.com/blog/517286 最近在找工作,面试java程序员或者软件工程师,在笔试的时候常常见到这么一道题:全排列 的输 ...

  8. Python - 面向对象编程 - 类变量、实例变量/类属性、实例属性

    什么是对象和类 https://www.cnblogs.com/poloyy/p/15178423.html 什么是 Python 类.类对象.实例对象 https://www.cnblogs.com ...

  9. asp.NetCore3.1系统自带Imemcache缓存-滑动/绝对/文件依赖的缓存使用测试

    个人测试环境为:Asp.net coe 3.1 WebApi 1:封装自定义的cacheHelper帮助类,部分代码 1 public static void SetCacheByFile<T& ...

  10. 一种封装Retrofit的方法,可以自动解析Gson,回避Method return type must not include a type variable or wildcard: retrofit2.Call<T>的问题

    封装目的:屏蔽底层实现,提供统一接口,并支持Gson自动转化 最初封装: //请求方法 interface RequestListener { interface PostListener { @PO ...