使用 Ocelot 匹配路由的方法匹配路由


使用 Ocelot 匹配路由的方法匹配路由

Intro

之前我們在 Ocelot 網關的基礎上自定義了一個認證授權的 Ocelot 中間件,根據請求的路徑和 Method 進行匹配,找到對應的權限配置,並判斷是否可以擁有訪問資源的角色,如果沒有則返回 401/403,如果有權限則轉發到下游服務。

原來的匹配方式是首先根據請求路徑和方法完全匹配,如果匹配不到則嘗試使用正則匹配。

我們這次要做的就是將原來的正則匹配替換成 Ocelot 內部的路由匹配方式,這樣我們在配置的時候就不再需要配置兩套了,一邊寫 Ocelot 路由的配置,一邊寫權限的配置,這樣能減少不少工作量

深入 Ocelot 路由匹配

我們想使用 Ocelot 的路由匹配,首先應該了解 Ocelot 的執行過程,然后找到對應的路由匹配的地方,看路由匹配使用到了哪一個服務,用這個服務在我們自己的業務邏輯里匹配即可。

先來看一下 Ocelot 的服務注冊,Ocelot 的服務注冊

可以看到主要的服務注冊代碼應該在 OcelotBuilder 中,查看 OcelotBuilder https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DependencyInjection/OcelotBuilder.cs

可以看到,Ocelot 的服務注冊都在這里, Ocelot 內部好多都是基於接口的,所以需要找對應的實現的話可以看它的服務注冊是注冊的哪一個服務即可。

簡單分析一下,Ocelot 的路由匹配過程一定在尋找下游地址的時候,根據上游的請求信息(直接請求網關的請求)匹配,所以我們首先找到 DownstreamRouteFinderMiddleware https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs

由上面的代碼,我們可以看到,下游路由地址是通過 IDownstreamRouteFinder 來找下游路由的,轉到對應的實現代碼: https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs

這里我們可以看到是通過 IUrlPathToUrlTemplateMatcher 來進行路由匹配的,所以我們需要用到這個服務,然后看這個 Match 方法的參數,前兩個參數比較明確,第一個參數是上游請求的地址,第二個參數是請求的 queryString,第三個參數則是 Ocelot 內部構建出來的路由模板信息 UpstreamPathTemplate,然后我們就需要知道怎么構建一個 UpstreamPathTemplate 對象,繼續探索

直接看 UpstreamPathTemplate,表示一臉懵逼,不知道怎么構建, 全局搜素了一下,發現有一個 IUpstreamTemplatePatternCreator 里面定義了一個 Create 的方法

這個方法看上去簡單了好多,查看 IReRoute 的定義 https://github.com/ThreeMammals/Ocelot/blob/13.5.2/src/Ocelot/Configuration/File/IReRoute.cs

我們只需要根據路徑模板構建一個 IReRoute 對象即可,Ocelot 中有一個實現了 IReRoute 的類 FileReRoute,但是感覺有些復雜,就沒有用,自定義了一個類型實現了 IReRoute

自此,我們就已經找到了要使用 Ocelot 路由匹配所需要的服務了:

  • IUrlPathToUrlTemplateMatcher
  • IUpstreamTemplatePatternCreator

使用 Ocelot 路由匹配

上面提到了我們沒有使用 FileReRoute 對象,所以我們就需要自定義一個 IReRoute 對象:

private class FakeReRoute : IReRoute
{
    public string UpstreamPathTemplate { get; set; }
    public bool ReRouteIsCaseSensitive { get; set; }
    public int Priority { get; set; }
}

使用 Ocelot 路由匹配示例:

public class UrlBasedAuthenticationMiddleware : Ocelot.Middleware.OcelotMiddleware
{
    private readonly GatewayOptions _gatewayOptions;
    private readonly IMemoryCache _memoryCache;
    private readonly OcelotRequestDelegate _next;

    private readonly IUrlPathToUrlTemplateMatcher _urlTemplateMatcher;
    private readonly IUpstreamTemplatePatternCreator _templatePatternCreator;

    public UrlBasedAuthenticationMiddleware(OcelotRequestDelegate next, IOptions<GatewayOptions> options, IMemoryCache memoryCache, IOcelotLoggerFactory loggerFactory,
        IUrlPathToUrlTemplateMatcher urlTemplateMatcher,
        IUpstreamTemplatePatternCreator templatePatternCreator)
        : base(loggerFactory.CreateLogger<UrlBasedAuthenticationMiddleware>())
    {
        _next = next;

        _gatewayOptions = options.Value;
        _memoryCache = memoryCache;

        _urlTemplateMatcher = urlTemplateMatcher;
        _templatePatternCreator = templatePatternCreator;
    }

    public async Task Invoke(DownstreamContext context)
    {
        var permissions = await _memoryCache.GetOrCreateAsync(_gatewayOptions.ApiPermissionsCacheKey, async entry =>
        {
            using (var conn = new SqlConnection(_gatewayOptions.PermissionsConnectionString))
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
                return (await conn.QueryAsync<ApiPermission>(@"")).Select(_ => _.GetViewModel()).ToArray();
            }
        });

        var request = context.HttpContext.Request;
        
        var permission = permissions.FirstOrDefault(p =>
                                request.Path.Value.Equals(p.PathPattern, StringComparison.OrdinalIgnoreCase) &&
                                p.Method == request.Method.ToUpper());
        if (null == permission)
        {
            permission = permissions.FirstOrDefault(p =>
                p.Method == request.Method.ToUpper() &&
                _urlTemplateMatcher.Match(request.Path.Value, request.QueryString.Value,
                    _templatePatternCreator.Create(new FakeReRoute()
                    {
                        UpstreamPathTemplate = p.PathPattern
                    })).Data.Match
            );
        }

        // ...
        await _next.Invoke(context);
    }

    private class FakeReRoute : IReRoute
    {
        public string UpstreamPathTemplate { get; set; }
        public bool ReRouteIsCaseSensitive { get; set; }
        public int Priority { get; set; }
    }
}

More

這樣,apiPermission 的 Path 配置基本可以使用和 Ocelot 配置一樣的路由,可以更方便的配置,避免 996 咯

Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM