使用 Ocelot API 网关连接到 SignalR 集线器

DOJ*_*Jay 4 microservices asp.net-core asp.net-core-signalr ocelot blazor-client-side

我正在尝试通过 Ocelot API 网关将 Blazor 客户端连接到简单微服务中的 SignalR 集线器。我对所有 ASP.NET Core 项目都使用 SSL。

网关在调用 https 端点时工作正常,当我直接从网关浏览器调用 signalR hub 端点(正确显示 Ocelot 路由)时,我收到“需要连接 ID”。

不幸的是,当我尝试从 blazor 客户端应用程序连接到集线器时,出现以下错误

失败:Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] requestId:0HM4U0GLR9ACR:00000001,previousRequestId:没有以前的请求ID,消息:全局错误处理程序中捕获异常,异常消息:仅以“ws://”或“wss”开头的Uris支持 ://'。(参数'uri'),异常堆栈:在Ocelot.WebSockets.Middleware.WebSocketsProxyMiddleware.Proxy(HttpContext context,String serverEndpoint)在Ocelot.WebSockets.Middleware的System.Net.WebSockets.ClientWebSocket.ConnectAsync(Uri uri,CancellationToken CancellationToken) .WebSocketsProxyMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) 在 Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext)在 Ocelot.LoadBalancer.Middleware.LoadBalancingMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) 在 Ocelot.Request.Middleware.DownstreamRequestInitialiserMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware .Invoke(HttpContext httpContext) 在 Ocelot.Multiplexer.MultiplexingMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) 在 Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context)在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext)

以下是我的代码。

Ocelot API 启动文件

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {            
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy", policy =>
            {
                policy.SetIsOriginAllowed(x => true).AllowAnyMethod().AllowAnyHeader();
                //policy.SetIsOriginAllowed(x => true).AllowAnyMethod().AllowAnyHeader().AllowCredentials();
            });
        });
        services.AddSignalR();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        
        app.UseRouting();
        app.UseCors("CorsPolicy");
        app.UseWebSockets();
        app.UseOcelot().Wait();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Hello World!");
            });
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

ocelot.json 配置

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/currency/{everything}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7004
        }
      ],
      "UpstreamPathTemplate": "/api-currency/{everything}",
      "UpstreamHttpMethod": [ "Get" ]
    },
    {
      "DownstreamPathTemplate": "/api/currency/{everything}",
      "ReRouteIsCaseSensitive": false,
      "DownstreamScheme": "wss",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7004
        }
      ],
      "UpstreamPathTemplate": "/api-currencyhub/{everything}",
      "UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE", "OPTIONS" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:7000",
    "RequestIdKey": "OcRequestId"

  }
}
Run Code Online (Sandbox Code Playgroud)

微服务API启动文件

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy", policy =>
            {
                policy.SetIsOriginAllowed(x => true).AllowAnyMethod().AllowAnyHeader();
                //policy.SetIsOriginAllowed(x => true).AllowAnyMethod().AllowAnyHeader().AllowCredentials();
                //policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials();
            });
        });
        services.AddSignalR();
        services.AddControllers();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();
        app.UseCors("CorsPolicy");
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapHub<CurrencyHub>("/api/currency/maincurrencyhub");
            endpoints.MapControllers();
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

Blazor 客户端 Razor 页面

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
<h1>Hello, world!</h1>

<h1>Welcome to SignalR with Blazor</h1>
<button class="btn btn-success" @onclick="async () => await ConnectToServer()" disabled="@isConnected">Connect</button>
<button class="btn btn-success" @onclick="async () => await OnGateway()">Gateway</button>
<h3>Connection Status: @connectionStatus</h3>
<div class="row">
    <div class="col-4">
        @foreach (var item in notifications)
        {
            <div class="row">
                <h4>@item</h4>
            </div>
        }
    </div>
</div>

@code {

    //string gatewayUrl = "wss://localhost:7000/api-currency/maincurrencyhub";
    string gatewayUrl = "https://localhost:7000/api-currency/maincurrencyhub";
    HubConnection gatewayConnection = null;

    bool isConnected = false;
    string connectionStatus = "Closed";

    List<string> notifications = new List<string>();

    private async Task ConnectToServer()
    {
        gatewayConnection = new HubConnectionBuilder()
            //.WithUrl(gatewayUrl)

            .WithUrl(gatewayUrl, opt => { opt.SkipNegotiation = true; opt.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets; })
            .Build();

        try
        {
            await gatewayConnection.StartAsync();

            connectionStatus = "Connected :-)";

            gatewayConnection.Closed += async (s) =>
            {
                isConnected = false;
                connectionStatus = "Disconnected";
                await gatewayConnection.StartAsync();
                isConnected = true;
            };
            gatewayConnection.On<string>("ReceiveMessage", m =>
            {
                notifications.Add(m);
                StateHasChanged();
            });
        }
        catch (Exception ex)
        {

        }
    }

    async Task OnGateway()
    {
        await gatewayConnection.InvokeAsync("Send", "Na Gode");
    }

}
Run Code Online (Sandbox Code Playgroud)

我试图遵循Ocelot 不将 websocket 传递给微服务的做法,但没有成功。有人可以引导我走向正确的方向吗?

小智 5

这是 Ocelot 配置,最终允许我设置客户端 => Ocelot => signalR 服务器通信。

    {
      "DownstreamPathTemplate": "/hub/",
      "DownstreamScheme": "ws",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/hub/",
      "QoSOptions": {
        "TimeoutValue": 320000
      },
      "Priority": 50
    },
    {
      "DownstreamPathTemplate": "/hub/{path}",
      "DownstreamScheme": "ws",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/hub/{path}",
      "QoSOptions": {
        "TimeoutValue": 320000
      },
      "Priority": 50
    },
Run Code Online (Sandbox Code Playgroud)

signalR 服务器配置为监听 /hub。

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapHub<MyHub>("hub");
    });
Run Code Online (Sandbox Code Playgroud)

我希望它能帮助某人。