【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: 原文:【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流【.NET Core项目实战-统一认证平台】开篇及目录索引 上篇文章我介绍了如何在网关上增加自定义客户端授权功能,从设计到编码实现,一步一步详细讲解,相信大家也掌握了自定义中间件的开发技巧了,本篇我们将介绍如何实现自定义客户端的限流功能,来进一步完善网关的基础功能。
原文: 【.NET Core项目实战-统一认证平台】第七章 网关篇-自定义客户端限流

【.NET Core项目实战-统一认证平台】开篇及目录索引

上篇文章我介绍了如何在网关上增加自定义客户端授权功能,从设计到编码实现,一步一步详细讲解,相信大家也掌握了自定义中间件的开发技巧了,本篇我们将介绍如何实现自定义客户端的限流功能,来进一步完善网关的基础功能。

.netcore项目实战交流群(637326624),有兴趣的朋友可以在群里交流讨论。

一、功能描述

限流就是为了保证网关在高并发或瞬时并发时,在服务能承受范围内,牺牲部分请求为代价,保证系统的整体可用性而做的安全策略,避免单个服务影响整体网关的服务能力。
比如网关有商品查询接口 ,能接受的极限请求是每秒100次查询,如果此时不限流,可能因为瞬时请求太大,造成服务卡死或崩溃的情况,这种情况可以使用Ocelot客户端全局限流即可满足需求,现在又有一个需求,我需要把接口开放给A公司,他们也要查询这个商品接口,这时A公司请求频率也是我们设置的每秒100次请求,显然我们不希望A公司有这么高的请求频率,我只会给A公司最大每秒一次的请求,那怎么实现呢?这时我们就无法通过Ocelot配置限流来进行自定义控制了,这块就需要我们增加自定义限流管道来实现功能。

下面我们就该功能如何实现展开讲解,希望大家先理解下功能需求,然后在延伸到具体实现。

二、数据库设计

限流这块设计表结构和关系如下。
img_b51df88e5c3d17e81de9759d9950b43a.png

主要有限流规则表、路由限流规则表、限流组表、限流组策略表、客户端授权限流组表、客户端白名单表组成,设计思想就是客户端请求时先检查是否在白名单,如果白名单不存在,就检查是否在限流组里,如果在限流组里校验限流的规则是什么,然后比对这个规则和当前请求次数看是否能够继续访问,如果超过限流策略直接返回429状态,否则路由到下端请求。

梳理下后发现流程不是很复杂,最起码实现的思路非常清晰,然后我们就运用上篇自定义授权中间件的方式来开发我们第二个中间件,自定义限流中间件。

三、功能实现

1、功能开启配置

网关应该支持自定义客户端限流中间件是否启用,因为一些小型项目是不需要对每个客户端进行单独限流的,中型和大型项目才有可能遇到自定义配置情况,所以我们需要在配置文件增加配置选项。在AhphOcelotConfiguration.cs配置类中增加属性,默认不开启。

/// <summary>
/// 金焰的世界
/// 2018-11-18
/// 是否开启自定义限流,默认不开启
/// </summary>
public bool ClientRateLimit { get; set; } = false;

/// <summary>
/// 金焰的世界
/// 2018-11-18
/// 客户端限流缓存时间,默认30分钟
/// </summary>
public int ClientRateLimitCacheTime { get; set; } = 1800;

那我们如何把自定义的限流增加到网关流程里呢?这块我们就需要订制自己的限流中间件。

2、实现客户端限流中间件

首先我们定义一个自定义限流中间件AhphClientRateLimitMiddleware,需要继承OcelotMiddleware,然后我们要实现Invoke方法,详细代码如下。

using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.Errors;
using Ocelot.Logging;
using Ocelot.Middleware;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Ctr.AhphOcelot.RateLimit.Middleware
{

    /// <summary>
    /// 金焰的世界
    /// 2018-11-18
    /// 自定义客户端限流中间件
    /// </summary>
    public class AhphClientRateLimitMiddleware : OcelotMiddleware
    {
        private readonly IClientRateLimitProcessor _clientRateLimitProcessor;
        private readonly OcelotRequestDelegate _next;
        private readonly AhphOcelotConfiguration _options;
        public AhphClientRateLimitMiddleware(OcelotRequestDelegate next,
            IOcelotLoggerFactory loggerFactory,
            IClientRateLimitProcessor clientRateLimitProcessor,
            AhphOcelotConfiguration options)
            : base(loggerFactory.CreateLogger<AhphClientRateLimitMiddleware>())
        {
            _next = next;
            _clientRateLimitProcessor = clientRateLimitProcessor;
            _options = options;
        }

        public async Task Invoke(DownstreamContext context)
        {
            var clientId = "client_cjy"; //使用默认的客户端
            if (!context.IsError)
            {
                if (!_options.ClientRateLimit)
                {
                    Logger.LogInformation($"未启用客户端限流中间件");
                    await _next.Invoke(context);
                }
                else
                {
                    //非认证的渠道
                    if (!context.DownstreamReRoute.IsAuthenticated)
                    {
                        if (context.HttpContext.Request.Headers.Keys.Contains(_options.ClientKey))
                        {
                            clientId = context.HttpContext.Request.Headers[_options.ClientKey].First();
                        }
                    }
                    else
                    {//认证过的渠道,从Claim中提取
                        var clientClaim = context.HttpContext.User.Claims.FirstOrDefault(p => p.Type == _options.ClientKey);
                        if (!string.IsNullOrEmpty(clientClaim?.Value))
                        {
                            clientId = clientClaim?.Value;
                        }
                    }
                    //路由地址
                    var path = context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue;
                    //1、校验路由是否有限流策略
                    //2、校验客户端是否被限流了
                    //3、校验客户端是否启动白名单
                    //4、校验是否触发限流及计数
                    if (await _clientRateLimitProcessor.CheckClientRateLimitResultAsync(clientId, path))
                    {
                        await _next.Invoke(context);
                    }
                    else
                    {
                        var error = new RateLimitOptionsError($"请求路由 {context.HttpContext.Request.Path}触发限流策略");
                        Logger.LogWarning($"路由地址 {context.HttpContext.Request.Path} 触发限流策略. {error}");
                        SetPipelineError(context, error);
                    }
                }
            }
            else
            {
                await _next.Invoke(context);
            }
        }
    }
}

首先我们来分析下我们的代码,为了知道是哪个客户端请求了我们网关,需要提取clientId,分别从无需授权接口和需要授权接口两个方式提取,如果提取不到值直接给定默认值,放到全局限流里,防止绕过限流策略。然后根据客户端通过4步检验下是否允许访问(后面会介绍这4步怎么实现),如果满足限流策略直接返回限流错误提醒。

有了这个中间件,那么如何添加到Ocelot的管道里呢?上一篇介绍的非常详细,这篇我就不介绍了,自定义限流中间件扩展AhphClientRateLimitMiddlewareExtensions,代码如下。

using Ocelot.Middleware.Pipeline;
using System;
using System.Collections.Generic;
using System.Text;

namespace Ctr.AhphOcelot.RateLimit.Middleware
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-18
    /// 限流中间件扩展
    /// </summary>
    public static class AhphClientRateLimitMiddlewareExtensions
    {
        public static IOcelotPipelineBuilder UseAhphAuthenticationMiddleware(this IOcelotPipelineBuilder builder)
        {
            return builder.UseMiddleware<AhphClientRateLimitMiddleware>();
        }
    }
}

有了这个中间件扩展后,我们就在管道的合适地方加入我们自定义的中间件。我们添加我们自定义的管道扩展OcelotPipelineExtensions,然后把自定义限流中间件加入到认证之后。

//添加自定义限流中间件 2018-11-18 金焰的世界
builder.UseAhphClientRateLimitMiddleware();

现在我们完成了网关的扩展和应用,是时候把定义的IClientRateLimitProcessor接口实现了 ,是不是感觉做一个中间件很简单呢?而且每一步都是层层关联,只要一步一步按照自己的想法往下写就能实现。

3、结合数据库实现校验及缓存

首先我们新建AhphClientRateLimitProcessor类来实现接口,中间增加必要的缓存和业务逻辑,详细代码如下。

using Ctr.AhphOcelot.Configuration;
using Ocelot.Cache;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace Ctr.AhphOcelot.RateLimit
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-19
    /// 实现客户端限流处理器
    /// </summary>
    public class AhphClientRateLimitProcessor : IClientRateLimitProcessor
    {
        private readonly AhphOcelotConfiguration _options;
        private readonly IOcelotCache<ClientRoleModel> _ocelotCache;
        private readonly IOcelotCache<RateLimitRuleModel> _rateLimitRuleCache;
        private readonly IOcelotCache<AhphClientRateLimitCounter?> _clientRateLimitCounter;
        private readonly IClientRateLimitRepository _clientRateLimitRepository;
        private static readonly object _processLocker = new object();
        public AhphClientRateLimitProcessor(AhphOcelotConfiguration options,IClientRateLimitRepository clientRateLimitRepository, IOcelotCache<AhphClientRateLimitCounter?> clientRateLimitCounter, IOcelotCache<ClientRoleModel> ocelotCache, IOcelotCache<RateLimitRuleModel> rateLimitRuleCache)
        {
            _options = options;
            _clientRateLimitRepository = clientRateLimitRepository;
            _clientRateLimitCounter = clientRateLimitCounter;
            _ocelotCache = ocelotCache;
            _rateLimitRuleCache = rateLimitRuleCache;
        }

        /// <summary>
        /// 校验客户端限流结果
        /// </summary>
        /// <param name="clientid">客户端ID</param>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        public async Task<bool> CheckClientRateLimitResultAsync(string clientid, string path)
        {

            var result = false;
            var clientRule = new List<AhphClientRateLimitOptions>();
            //1、校验路由是否有限流策略
            result = !await CheckReRouteRuleAsync(path);
            if (!result)
            {//2、校验客户端是否被限流了
                var limitResult = await CheckClientRateLimitAsync(clientid, path);
                result = !limitResult.RateLimit;
                clientRule = limitResult.rateLimitOptions;
            }
            if (!result)
            {//3、校验客户端是否启动白名单
                result = await CheckClientReRouteWhiteListAsync(clientid, path);
            }
            if (!result)
            {//4、校验是否触发限流及计数
                result = CheckRateLimitResult(clientRule);
            }
            return result;

        }

        /// <summary>
        /// 检验是否启用限流规则
        /// </summary>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        private async Task<bool> CheckReRouteRuleAsync(string path)
        {
            var region = _options.RedisKeyPrefix + "CheckReRouteRuleAsync";
            var key = region + path;
            var cacheResult = _ocelotCache.Get(key, region);
            if (cacheResult != null)
            {//提取缓存数据
                return cacheResult.Role;
            }
            else
            {//重新获取限流策略
                var result = await _clientRateLimitRepository.CheckReRouteRuleAsync(path);
                _ocelotCache.Add(key, new ClientRoleModel() { CacheTime = DateTime.Now, Role = result }, TimeSpan.FromSeconds(_options.ClientRateLimitCacheTime), region);
                return result;
            }
            
        }

        /// <summary>
        /// 校验客户端限流规则
        /// </summary>
        /// <param name="clientid">客户端ID</param>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        private async Task<(bool RateLimit, List<AhphClientRateLimitOptions> rateLimitOptions)> CheckClientRateLimitAsync(string clientid, string path)
        {
            var region = _options.RedisKeyPrefix + "CheckClientRateLimitAsync";
            var key = region + clientid + path;
            var cacheResult = _rateLimitRuleCache.Get(key, region);
            if (cacheResult != null)
            {//提取缓存数据
                return (cacheResult.RateLimit, cacheResult.rateLimitOptions);
            }
            else
            {//重新获取限流策略
                var result = await _clientRateLimitRepository.CheckClientRateLimitAsync(clientid, path);
                _rateLimitRuleCache.Add(key, new RateLimitRuleModel() { RateLimit=result.RateLimit, rateLimitOptions=result.rateLimitOptions }, TimeSpan.FromSeconds(_options.ClientRateLimitCacheTime), region);
                return result;
            }
        }

        /// <summary>
        /// 校验是否设置了路由白名单
        /// </summary>
        /// <param name="clientid">客户端ID</param>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        private async Task<bool> CheckClientReRouteWhiteListAsync(string clientid, string path)
        {
            var region = _options.RedisKeyPrefix + "CheckClientReRouteWhiteListAsync";
            var key = region +clientid+ path;
            var cacheResult = _ocelotCache.Get(key, region);
            if (cacheResult != null)
            {//提取缓存数据
                return cacheResult.Role;
            }
            else
            {//重新获取限流策略
                var result = await _clientRateLimitRepository.CheckClientReRouteWhiteListAsync(clientid,path);
                _ocelotCache.Add(key, new ClientRoleModel() { CacheTime = DateTime.Now, Role = result }, TimeSpan.FromSeconds(_options.ClientRateLimitCacheTime), region);
                return result;
            }
        }

        /// <summary>
        /// 校验完整的限流规则
        /// </summary>
        /// <param name="rateLimitOptions">限流配置</param>
        /// <returns></returns>
        private bool CheckRateLimitResult(List<AhphClientRateLimitOptions> rateLimitOptions)
        {
            bool result = true;
            if (rateLimitOptions != null && rateLimitOptions.Count > 0)
            {//校验策略
                foreach (var op in rateLimitOptions)
                {
                    AhphClientRateLimitCounter counter = new AhphClientRateLimitCounter(DateTime.UtcNow, 1);
                    //分别对每个策略校验
                    var enablePrefix = _options.RedisKeyPrefix + "RateLimitRule";
                    var key = AhphOcelotHelper.ComputeCounterKey(enablePrefix, op.ClientId, op.Period, op.RateLimitPath);
                    var periodTimestamp = AhphOcelotHelper.ConvertToSecond(op.Period);
                    lock (_processLocker)
                    {
                        var rateLimitCounter = _clientRateLimitCounter.Get(key, enablePrefix);
                        if (rateLimitCounter.HasValue)
                        {//提取当前的计数情况
                         // 请求次数增长
                            var totalRequests = rateLimitCounter.Value.TotalRequests + 1;
                            // 深拷贝
                            counter = new AhphClientRateLimitCounter(rateLimitCounter.Value.Timestamp, totalRequests);
                        }
                        else
                        {//写入限流策略
                            _clientRateLimitCounter.Add(key, counter,TimeSpan.FromSeconds(periodTimestamp), enablePrefix);
                        }
                    }
                    if (counter.TotalRequests > op.Limit)
                    {//更新请求记录,并标记为失败
                        result = false;
                    }
                    if (counter.TotalRequests > 1 && counter.TotalRequests <= op.Limit)
                    {//更新缓存配置信息
                        //获取限流剩余时间
                        var cur = (int)(counter.Timestamp.AddSeconds(periodTimestamp) - DateTime.UtcNow).TotalSeconds;
                        _clientRateLimitCounter.Add(key, counter, TimeSpan.FromSeconds(cur), enablePrefix);
                    }
                }
            }
            return result;
        }
    }
}

我们来分析下这块代码,里面涉及了限流的提取和实现规则,首先我们注入了数据库实体接口和缓存信息,实现步骤是参照之前的流程。

主要流程如下:

1、路由是否启用限流,如果未启用直接完成校验,如果进行第2步判断.
2、客户端对应的路由是否设置了限流规则,如果未设置,直接完成校验,否则进入第3步判断.
3、客户端是否开启了路由白名单功能,如果开启了直接完成校验,否则进入第4步。
4、使用Redis来进行限流的判断。使用的就是计数器方法,结合redis设置key的过期时间来实现的。

为了减少后端请求,在数据库提取的方法前都加入了缓存,现在我们需要把用到的接口添加到入口进行注入。

builder.Services.AddSingleton<IOcelotCache<RateLimitRuleModel>, InRedisCache<RateLimitRuleModel>>();
builder.Services.AddSingleton<IOcelotCache<AhphClientRateLimitCounter?>, InRedisCache<AhphClientRateLimitCounter?>>();

现在我们还剩下IClientRateLimitRepository接口未实现,现在只要实现这个接口,然后注入下,我们就完成了限流中间件的开发了,我们根据限流的流程,梳理了实现,现在有3个方法需要进行实现。

新建SqlServerClientRateLimitRepository类,来开始实现我们与数据库的操作,有了上面的分析思路,现在就是把一个一个详细确定的方法实现而已,太简单了,只要花了几分钟后,就可以瞬间写出如下代码。

using Ctr.AhphOcelot.Configuration;
using Ctr.AhphOcelot.RateLimit;
using Dapper;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
using System.Threading.Tasks;

namespace Ctr.AhphOcelot.DataBase.SqlServer
{
    /// <summary>
    /// 金焰的世界
    /// 2018-11-19
    /// 客户端限流信息提取
    /// </summary>
    public class SqlServerClientRateLimitRepository : IClientRateLimitRepository
    {
        private readonly AhphOcelotConfiguration _option;
        public SqlServerClientRateLimitRepository(AhphOcelotConfiguration option)
        {
            _option = option;
        }

        /// <summary>
        /// 校验客户端限流规则
        /// </summary>
        /// <param name="clientid">客户端ID</param>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        public async Task<(bool RateLimit, List<AhphClientRateLimitOptions> rateLimitOptions)> CheckClientRateLimitAsync(string clientid, string path)
        {
            using (var connection = new SqlConnection(_option.DbConnectionStrings))
            {
                string sql = @"SELECT DISTINCT UpstreamPathTemplate AS RateLimitPath,LimitPeriod AS Period,LimitNum AS Limit,ClientId FROM AhphReRoute T1 INNER JOIN AhphReRouteLimitRule T2 ON T1.ReRouteId=T2.ReRouteId
                INNER JOIN AhphLimitRule T3 ON T2.RuleId=T3.RuleId INNER JOIN AhphLimitGroupRule T4 ON
                T2.ReRouteLimitId=T4.ReRouteLimitId INNER JOIN AhphLimitGroup T5 ON T4.LimitGroupId=T5.LimitGroupId
                INNER JOIN AhphClientLimitGroup T6 ON T5.LimitGroupId=T6.LimitGroupId INNER JOIN
                AhphClients T7 ON T6.Id=T7.Id
                WHERE T1.InfoStatus=1 AND T1.UpstreamPathTemplate=@path AND T3.InfoStatus=1 AND T5.InfoStatus=1
                AND ClientId=@clientid AND Enabled=1";
                var result = (await connection.QueryAsync<AhphClientRateLimitOptions>(sql, new { clientid, path }))?.AsList();
                if (result != null && result.Count > 0)
                {
                    return (true, result);
                }
                else
                {
                    return (false, null);
                }
            }
        }

        /// <summary>
        /// 校验是否设置了路由白名单
        /// </summary>
        /// <param name="clientid">客户端ID</param>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        public async Task<bool> CheckClientReRouteWhiteListAsync(string clientid, string path)
        {
            using (var connection = new SqlConnection(_option.DbConnectionStrings))
            {
                string sql = @"SELECT COUNT(1) FROM AhphReRoute T1 INNER JOIN AhphClientReRouteWhiteList T2 ON T1.ReRouteId=T2.ReRouteId
                            INNER JOIN AhphClients T3 ON T2.Id=T3.Id WHERE T1.InfoStatus=1 AND UpstreamPathTemplate=@path AND
                            ClientId=@clientid AND Enabled=1";
                var result = await connection.QueryFirstOrDefaultAsync<int>(sql, new { clientid,path });
                return result > 0;
            }
        }

        /// <summary>
        /// 校验是否启用限流规则
        /// </summary>
        /// <param name="path">请求地址</param>
        /// <returns></returns>
        public async Task<bool> CheckReRouteRuleAsync(string path)
        {
            using (var connection = new SqlConnection(_option.DbConnectionStrings))
            {
                string sql = @"SELECT COUNT(1) FROM AhphReRoute T1 INNER JOIN AhphReRouteLimitRule T2 ON T1.ReRouteId=T2.ReRouteId
                        INNER JOIN AhphLimitRule T3 ON T2.RuleId=T3.RuleId WHERE T1.InfoStatus=1 AND UpstreamPathTemplate=@path
                        AND T3.InfoStatus=1";
                var result = await connection.QueryFirstOrDefaultAsync<int>(sql, new { path });
                return result > 0;
            }
        }
    }
}

主要就是注意下表之间的关系,把实现注入到AddAhphOcelot里,现在就可以测试开始自定义客户端限流中间件。

builder.Services.AddSingleton<IClientRateLimitRepository, SqlServerClientRateLimitRepository>();

4、测试限流中间件

为了把把所有情况都测试一遍,先从开启限流,什么都不写入看是否能够正常运行。

option.ClientRateLimit = true;

还记得我们上篇的两个客户端和能访问的页面吗?就用它们来测试,结果显示正常,说明不开启限流没有影响。

开启/cjy/values2个限流规则,一个每1分钟访问1次,一个每1分钟访问60次。

--1、插入限流规则
INSERT INTO AhphLimitRule VALUES('每1分钟访问1次','1m',1,1);
INSERT INTO AhphLimitRule VALUES('每1分钟访问60次','1m',60,1);

--2、应用到/cjy/values路由
INSERT INTO AhphReRouteLimitRule VALUES(1,1);
INSERT INTO AhphReRouteLimitRule VALUES(2,1);

因为还未给客户端应用规则,所以应该也是可以正常访问,可以使用PostMan测试下,测试时需要注意下缓存,因为所有的访问都启用的默认缓存策略,经测试得到预期效果。

现在开始把限流分别应用到客户端1和客户端2,看下限流效果。

--3、插入测试分组
INSERT INTO AhphLimitGroup VALUES('限流分组1','',1);
INSERT INTO AhphLimitGroup VALUES('限流分组2','',1);
--4、分组应用策略
INSERT INTO AhphLimitGroupRule VALUES(1,1);
INSERT INTO AhphLimitGroupRule VALUES(2,2);

--5、客户端应用限流分组
INSERT INTO AhphClientLimitGroup VALUES(2,1);
INSERT INTO AhphClientLimitGroup VALUES(3,2);

然后使用PostMan测试客户端1和客户端2,结果如下,超过设置的频率后不返回结果,达到预期目的,但是返回的是404错误,强迫症患者表示这不优雅啊,应该是429 Too Many Requests,那我们如何修改呢?
img_cca3c0b3d1001eb32374bdd3b0c7eff0.png

img_7f701152efe388242e5b5fa54f0e8a2c.png

这里就需要了解下错误信息是如何输出的,需要查看Ocelot源码,您会发现IErrorsToHttpStatusCodeMapper接口和ErrorsToHttpStatusCodeMapper实现,代码如下,

using System.Collections.Generic;
using System.Linq;
using Ocelot.Errors;

namespace Ocelot.Responder
{
    public class ErrorsToHttpStatusCodeMapper : IErrorsToHttpStatusCodeMapper
    {
        public int Map(List<Error> errors)
        {
            if (errors.Any(e => e.Code == OcelotErrorCode.UnauthenticatedError))
            {
                return 401;
            }

            if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError 
                || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError
                || e.Code == OcelotErrorCode.ScopeNotAuthorisedError
                || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError
                || e.Code == OcelotErrorCode.CannotFindClaimError))
            {
                return 403;
            }

            if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError))
            {
                return 503;
            }

            if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError))
            {
                return 404;
            }

            if (errors.Any(e => e.Code == OcelotErrorCode.UnableToCompleteRequestError))
            {
                return 500;
            }

            return 404;
        }
    }
}

可以发现因为未定义RateLimitOptionsError错误的状态码,增加一个判断即可,那我们重写下把,然后集成在我们自己的中间件里,这块在后期有很多扩展能够用到,增加如下代码。

if (errors.Any(e => e.Code == OcelotErrorCode.RateLimitOptionsError))
{
    return 429;
}

然后重新注入下。

builder.Services.AddSingleton<IErrorsToHttpStatusCodeMapper, AhphErrorsToHttpStatusCodeMapper>();

在重新测试下访问限流地址。
img_39cda6a1a8fab183acd66d076af4ca68.png

奈斯,达到了我们预期的效果,.netcore 开发魅力体现出来了吗?

我们增加客户端1的路由白名单,然后再继续测试看是否解除限流限制?

--6、设置客户端1/cjy/values路由白名单
INSERT INTO AhphClientReRouteWhiteList VALUES(1,2);

注意测试时清除缓存

经测试不受限流控制,达到了我们最终目的,到此限流功能全部实现。

5、增加mysql支持

直接重写IClientRateLimitRepository实现,然后注入实现。

builder.Services.AddSingleton<IClientRateLimitRepository, MySqlClientRateLimitRepository>();

四、总结及预告

本篇我们讲解的是网关如何实现自定义客户端限流功能,从设计到实现一步一步详细讲解,虽然只用一篇就写完了,但是涉及的知识点还是非常多的,希望大家认真理解实现的思想,看我是如何从规划到实现的,为了更好的帮助大家理解。大家可以根据博客内容自己手动实现下,有利于消化,如果在操作中遇到什么问题,可以加.NET Core项目实战交流群(QQ群号:637326624)咨询作者。

从下一篇开始介绍IdentityServer4的相关应用,并配合我们的网关实现认证,在跟我教程学习的朋友,可以自己先预习下。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
|
2月前
|
数据采集 传感器 监控
.NET 工控网关 轻量级组态软件
【10月更文挑战第10天】.NET 工控网关是一种基于 .NET 平台开发的设备,用于连接工业控制系统中的不同网络和设备,实现数据传输和协议转换。它能统一处理多种协议(如 Modbus、Profibus)的数据,便于后续系统处理。.NET 平台的优势包括开发效率高、跨平台能力强及安全性高,适用于工业物联网环境。此外,轻量级组态软件具备体积小、资源占用少的特点,可在资源受限的设备上运行,提供数据采集、监控、报警及数据分析等功能,简化工业自动化过程。
|
3月前
|
存储 安全 物联网
.NET 跨平台工业物联网网关解决方案
【9月更文挑战第28天】本文介绍了利用 .NET 构建跨平台工业物联网网关的解决方案。通过 .NET Core 和多种通信协议(如 MQTT 和 Modbus),实现工业设备的高效接入和数据采集。系统架构包括设备接入层、数据处理层、通信层、应用层和数据库层,确保数据的准确采集、实时处理和安全传输。此外,还详细阐述了设备身份认证、数据加密及安全审计等机制,确保系统的安全性。该方案适用于不同操作系统和工业环境,具备高度灵活性和扩展性。
|
2月前
|
Windows
.NET 隐藏/自定义windows系统光标
【10月更文挑战第20天】在.NET中,可以使用`Cursor`类来控制光标。要隐藏光标,可将光标设置为`Cursors.None`。此外,还可以通过从文件或资源加载自定义光标来更改光标的样式。例如,在表单加载时设置`this.Cursor = Cursors.None`隐藏光标,或使用`Cursor.FromFile`方法加载自定义光标文件,也可以将光标文件添加到项目资源中并通过资源管理器加载。这些方法适用于整个表单或特定控件。
|
3月前
|
前端开发 关系型数据库 MySQL
ThingsGateway:一款基于.NET8开源的跨平台高性能边缘采集网关
ThingsGateway:一款基于.NET8开源的跨平台高性能边缘采集网关
105 2
|
4月前
|
开发框架 .NET Docker
【Azure 应用服务】App Service .NET Core项目在Program.cs中自定义添加的logger.LogInformation,部署到App Service上后日志不显示Log Stream中的问题
【Azure 应用服务】App Service .NET Core项目在Program.cs中自定义添加的logger.LogInformation,部署到App Service上后日志不显示Log Stream中的问题
|
4月前
|
存储 开发框架 安全
ASP.NET WebApi 如何使用 OAuth2.0 认证
ASP.NET WebApi 如何使用 OAuth2.0 认证
|
5月前
|
应用服务中间件 nginx 缓存
高并发架构设计三大利器:缓存、限流和降级问题之Nginx作为前置网关进行限流问题如何解决
高并发架构设计三大利器:缓存、限流和降级问题之Nginx作为前置网关进行限流问题如何解决
|
6月前
|
数据采集 边缘计算 UED
必知的技术知识:iNeuOS工业互联平台,机床&PLC硬件网关与平台无缝对接,进行数据交互
必知的技术知识:iNeuOS工业互联平台,机床&PLC硬件网关与平台无缝对接,进行数据交互
41 0
|
6月前
|
安全 程序员 Shell
老程序员分享:NSIS自定义界面,下载并安装Net.Framework4.8
老程序员分享:NSIS自定义界面,下载并安装Net.Framework4.8

热门文章

最新文章