解决微服务网关Ocelot使用AddStoreOcelotConfigurationInConsul后请求404问题
一个小插曲,最近研究 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