【问题标题】:Connect to SignalR Hubs using Ocelot API Gateway使用 Ocelot API 网关连接到 SignalR 集线器
【发布时间】:2021-03-23 12:00:17
【问题描述】:

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

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

很遗憾,当我尝试从 blazor 客户端应用程序连接到集线器时出现以下错误

失败:Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] requestId:0HM4U0GLR9ACR:00000001,previousRequestId:没有以前的请求 id,消息:全局错误处理程序中捕获的异常,异常消息:仅支持以 'ws://' 或 'wss://' 开头的 Uris。 (参数“uri”),异常堆栈:在 System.Net.WebSockets.ClientWebSocket.ConnectAsync(Uri uri, CancellationToken cancelToken) 在 Ocelot.WebSockets.Middleware.WebSocketsProxyMiddleware.Proxy(HttpContext 上下文,字符串 serverEndpoint) 在 Ocelot.WebSockets.Middleware.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.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) 在 Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext 上下文) 在 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!");
            });
        });
    }
}

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"

  }
}

微服务 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();
        });
    }
}

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");
    }

}

我尝试关注Ocelot not passing websockets to microservice 无济于事。 有人能指引我正确的方向吗?

【问题讨论】:

  • 嗨,你能成功吗?我也面临同样的问题...
  • 你解决了吗?我正在考虑使用 nginx ... =/

标签: asp.net-core microservices blazor-client-side asp.net-core-signalr ocelot


【解决方案1】:

在 ocelot.json 中,尝试删除第二条路由,只保留第一个,但使用“wss”作为 DownstreamScheme,如下所示:

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

  }
}

【讨论】:

    【解决方案2】:

    我终于用NGinx解决了Ocelot和SignalR Hub之间的问题,

    我的Nginx开发配置是

    server {
      listen 2000; #Entry point and redirect to ocelot to 2005 to start site
    
      #Ocelot configuration in root
      location / {
        proxy_pass http://localhost.com:2005; 
      }
    
      #SignalR hub (devicehub)
      location /devicehub {
        proxy_pass http://localhost:2004;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
      }
    }
    

    注意:抱歉,我丢失了集线器配置 (/devicehub) 的参考,但我可以稍后更新。

    更详细的解决方案(了解docker/ocelot的可以跳过)

    这是启动 nginx 的 docker-compose 文件

    version: '3.4'
    
    services:
    # ... 
      nginx:
        image: nginx:latest
        volumes:
            #For windows development doesn't work without c:, but in linux you can change to / with other yaml file
          - c:/data/nginx/default.conf:/etc/nginx/conf.d/default.conf
        ports:
          - 2000:2000
    

    启动 nginx

    docker-compose -f "docker-compose.yml" up -d

    docker-compose up -d

    最好的问候

    【讨论】:

      猜你喜欢
      • 2022-01-24
      • 1970-01-01
      • 2021-05-04
      • 2017-03-11
      • 2019-10-09
      • 2021-03-23
      • 1970-01-01
      • 2015-09-09
      • 2016-10-29
      相关资源
      最近更新 更多