在Ocelot中使用自定義的中介軟體(二)

在上文中《

在Ocelot中使用自定義的中介軟體(一)

》,我介紹瞭如何在Ocelot中使用自定義的中介軟體來修改下游服務的response body。今天,我們再擴充套件一下設計,讓我們自己設計的中介軟體變得更為通用,使其能夠應用在不同的Route上。比如,我們可以設計一個通用的替換response body的中介軟體,然後將其應用在多個Route上。

Ocelot的配置檔案

我們可以將Ocelot的配置資訊寫在appsettings。json中,當然也可以將其放在單獨的json檔案裡,然後透過ConfigureAppConfiguration的呼叫,將單獨的json檔案新增到配置系統中。無論如何,基於JSON檔案的Ocelot配置都是可以加入我們自定義的內容的,基於資料庫的或者其它儲存的配置檔案資訊或許擴充套件起來並不方便,因此,使用JSON檔案作為配置源還是一個不錯的選擇。比如,我們可以在ReRoute的某個配置中新增以下內容:

{

“DownstreamPathTemplate”: “/api/themes”,

“DownstreamScheme”: “http”,

“DownstreamHostAndPorts”: [

{

“Host”: “localhost”,

“Port”: 5010

}

],

“UpstreamPathTemplate”: “/themes-api/themes”,

“UpstreamHttpMethod”: [ “Get” ],

“CustomMiddlewares”: [

{

“Name”: “themeCssMinUrlReplacer”,

“Enabled”: true,

“Config”: {

“replacementTemplate”: “/themes-api/theme-css/{name}”

}

}

}

然後就需要有一個方法能夠解析這部分配置內容。為了方便處理,可以增加以下配置Model,專門存放CustomMiddlewares下的配置資訊:

public class CustomMiddlewareConfiguration

{

public string DownstreamPathTemplate { get; set; }

public string UpstreamPathTemplate { get; set; }

public int ReRouteConfigurationIndex { get; set; }

public string Name { get; set; }

public bool Enabled { get; set; }

public Dictionary Config { get; set; }

}

然後定義下面的擴充套件方法,用以從IConfiguration物件中解析出所有的CustomMiddleware的配置資訊:

public static IEnumerable GetCustomMiddlewareConfigurations(this IConfiguration config)

{

var reRoutesConfigSection = config。GetSection(“ReRoutes”);

if (reRoutesConfigSection。Exists())

{

var reRoutesConfigList = reRoutesConfigSection。GetChildren();

for (var idx = 0; idx < reRoutesConfigList。Count(); idx++)

{

var reRouteConfigSection = reRoutesConfigList。ElementAt(idx);

var upstreamPathTemplate = reRouteConfigSection。GetSection(“UpstreamPathTemplate”)。Value;

var downstreamPathTemplate = reRouteConfigSection。GetSection(“DownstreamPathTemplate”)。Value;

var customMidwareConfigSection = reRouteConfigSection。GetSection(“CustomMiddlewares”);

if (customMidwareConfigSection。Exists())

{

var customMidwareConfigList = customMidwareConfigSection。GetChildren();

foreach (var customMidwareConfig in customMidwareConfigList)

{

var customMiddlewareConfiguration = customMidwareConfig。Get();

customMiddlewareConfiguration。UpstreamPathTemplate = upstreamPathTemplate;

customMiddlewareConfiguration。DownstreamPathTemplate = downstreamPathTemplate;

customMiddlewareConfiguration。ReRouteConfigurationIndex = idx;

yield return customMiddlewareConfiguration;

}

}

}

}

yield break;

}

CustomMiddleware基類

為了提高程式設計師的開發體驗,我們引入CustomMiddleware基類,在Invoke方法中,CustomMiddleware物件會讀取所有的CustomMiddleware配置資訊,並找到屬於當前ReRoute的CustomMiddleware配置資訊,從而決定當前的CustomMiddleware是否應該被執行。相關程式碼如下:

public abstract class CustomMiddleware : OcelotMiddleware

{

#region Private Fields

private readonly ICustomMiddlewareConfigurationManager customMiddlewareConfigurationManager;

private readonly OcelotRequestDelegate next;

#endregion Private Fields

#region Protected Constructors

protected CustomMiddleware(OcelotRequestDelegate next,

ICustomMiddlewareConfigurationManager customMiddlewareConfigurationManager,

IOcelotLogger logger) : base(logger)

{

this。next = next;

this。customMiddlewareConfigurationManager = customMiddlewareConfigurationManager;

}

#endregion Protected Constructors

#region Public Methods

public async Task Invoke(DownstreamContext context)

{

var customMiddlewareConfigurations = from cmc in this

。customMiddlewareConfigurationManager

。GetCustomMiddlewareConfigurations()

where cmc。DownstreamPathTemplate == context

。DownstreamReRoute

。DownstreamPathTemplate

。Value &&

cmc。UpstreamPathTemplate == context

。DownstreamReRoute

。UpstreamPathTemplate

。OriginalValue

select cmc;

var thisMiddlewareName = this。GetType()。GetCustomAttribute(false)?。Name;

var customMiddlewareConfiguration = customMiddlewareConfigurations。FirstOrDefault(x => x。Name == thisMiddlewareName);

if (customMiddlewareConfiguration?。Enabled ?? false)

{

await this。DoInvoke(context, customMiddlewareConfiguration);

}

await this。next(context);

}

#endregion Public Methods

#region Protected Methods

protected abstract Task DoInvoke(DownstreamContext context, CustomMiddlewareConfiguration configuration);

#endregion Protected Methods

}

接下來就簡單了,只需要讓自定義的Ocelot中介軟體繼承於CustomMiddleware基類就行了,當然,為了解耦型別名稱與中介軟體名稱,使用一個自定義的CustomMiddlewareAttribute:

[CustomMiddleware(“themeCssMinUrlReplacer”)]

public class ThemeCssMinUrlReplacer : CustomMiddleware

{

private readonly Regex regex = new Regex(@“\w+://[a-zA-Z0-9]+(\:\d+)?/themes/(?[a-zA-Z0-9_]+)/bootstrap。min。css”);

public ThemeCssMinUrlReplacer(OcelotRequestDelegate next,

ICustomMiddlewareConfigurationManager customMiddlewareConfigurationManager,

IOcelotLoggerFactory loggerFactory)

: base(next, customMiddlewareConfigurationManager, loggerFactory。CreateLogger())

{

}

protected override async Task DoInvoke(DownstreamContext context, CustomMiddlewareConfiguration configuration)

{

var downstreamResponseString = await context。DownstreamResponse。Content。ReadAsStringAsync();

var downstreamResponseJson = JObject。Parse(downstreamResponseString);

var themesArray = (JArray)downstreamResponseJson[“themes”];

foreach(var token in themesArray)

{

var cssMinToken = token[“cssMin”];

var cssMinValue = cssMinToken。Value();

if (regex。IsMatch(cssMinValue))

{

var themeName = regex。Match(cssMinValue)。Groups[“theme_name”]。Value;

var replacementTemplate = configuration。Config[“replacementTemplate”]。ToString();

var replacement = $“{context。HttpContext。Request。Scheme}://{context。HttpContext。Request。Host}{replacementTemplate}”

。Replace(“{name}”, themeName);

cssMinToken。Replace(replacement);

}

}

context。DownstreamResponse = new DownstreamResponse(

new StringContent(downstreamResponseJson。ToString(Formatting。None), Encoding。UTF8, “application/json”),

context。DownstreamResponse。StatusCode, context。DownstreamResponse。Headers, context。DownstreamResponse。ReasonPhrase);

}

}

自定義中介軟體的註冊

在上文介紹的BuildCustomOcelotPipeline擴充套件方法中,加入以下幾行,就完成所有自定義中介軟體的註冊:

var customMiddlewareTypes = from type in typeof(Startup)。Assembly。GetTypes()

where type。BaseType == typeof(CustomMiddleware) &&

type。IsDefined(typeof(CustomMiddlewareAttribute), false)

select type;

foreach (var customMiddlewareType in customMiddlewareTypes)

{

builder。UseMiddleware(customMiddlewareType);

}

當然,app。UseOcelot的呼叫要調整為:

1

app。UseOcelot((b, c) => b。BuildCustomOcelotPipeline(c)。Build())。Wait();

執行

重新執行API閘道器,得到結果跟之前的一樣。所不同的是,我們可以將ThemeCssMinUrlReplacer在其它的ReRoute配置上重用了。

在Ocelot中使用自定義的中介軟體(二)