Blazor WebAssembly集成Ocelot网关顺带解决跨域问题
我们知道跨域请求通常会被浏览器所拦截。这是浏览器提供的一种安全机制,即不允许一个域名下的脚本去请求另一个域名的数据。
请注意此处使用的是Blazor WebAssembly 的ASP.NET Core hosted模式,即模板具有Client,Server,Share三个项目。
问题描述
在blazor应用开发中我便遇到了这样的问题,我的项目需要用到六七个不同的站点接口,有统一登录站点,订单接口站点,门店接口站点,三方渠道站点等等。而我希望通过HttpClientFactory来获取HttpClient进行请求。
众所周知, IHttpClientFactory有多种使用方式:
- 基本用法
- 命名客户端
- 类型化客户端
- 生成的客户端
假如使用命名客户端方法,我们可以很轻松的根据名称区分开具有不同BaseAddress的HttpClient,但是公司已有一个对原生HttpClient封装的类库,因此,我必须采用类型化客户端的方法。
在此给出一个示例(包含IOC容器中HttpMessageHandler的添加):
public static class ServiceClientExtension
{
public static WebAssemblyHostBuilder AddServiceClient(this WebAssemblyHostBuilder builder)
{
builder.Services.AddScoped<PlatformRequestHandler>();
builder.Services
.AddHttpClient<IServiceClient, ServiceClients.ServiceClient>((sp, client) =>
{
var option = sp.GetService<ModuleOption>();
if (string.IsNullOrWhiteSpace(option.BaseAddress))
{
option.BaseAddress = builder.HostEnvironment.BaseAddress;
}
client.BaseAddress = new Uri(option.BaseAddress);
})
.AddHttpMessageHandler();
return builder;
}
private static IHttpClientBuilder AddHttpMessageHandler(this IHttpClientBuilder builder)
{
builder.ConfigureHttpMessageHandlerBuilder(cfg =>
{
var handlers = cfg.Services.GetServices<DelegatingHandler>();
foreach (var delegatingHandler in handlers)
{
cfg.AdditionalHandlers.Add(delegatingHandler);
}
});
return builder;
}
}
因此,我需要有一个请求转发站点,统一帮助我将请求转发至不同的站点去。自然而然的想到了Ocelot。Ocelot是一个优秀的开源网关项目。
我的Blazor项目中的Server,主要有以下作用:
- 集成Ocelot,作为网关对请求进行转发
- 集成Azure ApplicationInsights,对接口请求异常进行记录
- 调起Blazor.Client程序
长话短说
这里我不再赘述Ocelot的应用,仅仅介绍其在Blazor中的使用。
集成Ocelot示例:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, builder) =>
{
builder
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile($"/Ocelot/Ocelot.json", true, true)
.AddJsonFile($"/Ocelot/ocelot.{hostingContext.HostingEnvironment.EnvironmentName.ToLower()}.json", true, true)
.AddEnvironmentVariables();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
客户端调用时,将HttpClientFactory的BaseAddress指定为builder.HostEnvironment.BaseAddress(见上述示例),通过在请求url中添加转发节点标识,如 /login,然后Ocelot根据你的配置文件,找到/login对应的站点,进行请求转发:
var request = new HttpRequestMessage(HttpMethod.Get, "/login/Auth/UserInfo");
怎样让Ocelot不阻塞中间件管道,当找不到转发路由路径时,进入下一个中间件
需要注意的是,Ocelot通常要求阻塞中间件管道,因此,会导致blazor页面导航失效,即正常的页面跳转也会被当成请求转发。因此,我们需要对Ocelot中间件进行改造,当其找不到转发路由时,自动进入下一个中间件。
示例如下:
public static IApplicationBuilder UseOcelotWhenRouteMatch(this IApplicationBuilder app)
{
app.MapWhen(context =>
{
var internalConfigurationResponse = context.RequestServices.GetRequiredService<IInternalConfigurationRepository>().Get();
if (internalConfigurationResponse.IsError || internalConfigurationResponse.Data.Routes.Count == 0)
{
return false;
}
var internalConfiguration = internalConfigurationResponse.Data;
var downstreamRouteFinder = context.RequestServices.GetRequiredService<IDownstreamRouteProviderFactory>().Get(internalConfiguration);
var response = downstreamRouteFinder.Get(context.Request.Path, context.Request.QueryString.ToString(),
context.Request.Method, internalConfiguration, context.Request.Host.ToString());
return !response.IsError && !string.IsNullOrEmpty(response.Data?.Route?.DownstreamRoute?.FirstOrDefault()?.DownstreamScheme);
}, appBuilder => appBuilder.UseOcelot().Wait());
return app;
}
Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebAssemblyDebugging();
}
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseOcelotWhenRouteMatch();
app.UseEndpoints(builder =>
{
builder.MapFallbackToFile("index.html");
});
}