一個小插曲,最近研究 netcore 微服務網關,在使用AddStoreOcelotConfigurationInConsul將配置存到consul後,任何經過網關的請求都出現404,並且沒有任何有用的異常信息列印。這裡先簡單講講這個問題是如何發生的,及如何解決。 之前在 ASP.NET Core ...
一個小插曲,最近研究 netcore 微服務網關,在使用AddStoreOcelotConfigurationInConsul將配置存到consul後,任何經過網關的請求都出現404,並且沒有任何有用的異常信息列印。這裡先簡單講講這個問題是如何發生的,及如何解決。
之前在 ASP.NET Core 2 學習筆記(三)中間件 提到過大部分擴展的Middleware都會用一個靜態方法包裝,如:UseMvc()
、UseRewriter()
等,而微服務網關Ocelot 包裝了兩個靜態的非同步方法 UseOcelot。
OcelotMiddlewareExtensions.cs
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder)
{
await builder.UseOcelot(new OcelotPipelineConfiguration());
return builder;
}
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration)
{
var configuration = await CreateConfiguration(builder);
CreateAdministrationArea(builder, configuration);
if(UsingRafty(builder))
{
SetUpRafty(builder);
}
if (UsingEurekaServiceDiscoveryProvider(configuration))
{
builder.UseDiscoveryClient();
}
ConfigureDiagnosticListener(builder);
var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices);
pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration);
var firstDelegate = pipelineBuilder.Build();
/*
inject first delegate into first piece of asp.net middleware..maybe not like this
then because we are updating the http context in ocelot it comes out correct for
rest of asp.net..
*/
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware";
builder.Use(async (context, task) =>
{
var downstreamContext = new DownstreamContext(context);
await firstDelegate.Invoke(downstreamContext);
});
return builder;
}
為什麼會封裝成非同步的方法,可能是因為從底層一步一步寫上來的。但是我個人認為 UseOcelot 是完全可以封裝成同步方法,以避免誤用。
誤用情形,如下:
Startup.cs
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
await app.UseOcelot();
}
這裡將 Startup.Configure() 方法寫成了一個不規範的非同步方法,其實是不被支持的。但是由於不規範,繞過了檢測。
規範寫法,如下
Startup.cs
public async Task Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
await app.UseOcelot();
}
檢測到Configure返回類型非Void,直接異常:
System.InvalidOperationException:“The 'Configure' method in the type 'OcelotConsul.ApiGateway.Core.Startup' must have a return type of 'Void'.”
這裡可以參考aspnet/Hosting關於Configure 非同步問題的討論:
由於用到了不規範的非同步方法,使得線程並沒有等待 UseOcelot 內部初始化完成就Host了起來,而造成了一些奇怪的異常,諸如:AddStoreOcelotConfigurationInConsul後請求404等問題。
解決方案就是用 Task.Wait() 方法 阻塞線程,等待 UseOcelot 執行完成,再Host。如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseOcelot().Wait();
}
也許 UseOcelot 封裝成同步方法會更好。已經給作者提了Issue