解决微服务网关Ocelot使用AddStoreOcelotConfigurationInConsul后请求404问题 ASP.N
一个小插曲,最近研究 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 异步问题的讨论:
aspnet/Hosting#27
aspnet/Hosting#29
aspnet/Hosting#373
aspnet/Hosting#1088
由于用到了不规范的异步方法,使得线程并没有等待 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