简体   繁体   English

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

[英]Connect to SignalR Hubs using Ocelot API Gateway

I am trying to connect Blazor client to SignalR hub in a simple microservice through the Ocelot API gateway.我正在尝试通过 Ocelot API 网关在一个简单的微服务中将 Blazor 客户端连接到 SignalR 集线器。 I am using SSL for all the ASP.NET Core projects.我将 SSL 用于所有 ASP.NET 核心项目。

The gateway works fine when calling https endpoints and I get "Connection Id Required" when I call the signalR hub endpoint directly from the gateway browser (which shows Ocelot routes correctly).网关在调用 https 端点时工作正常,当我直接从网关浏览器(正确显示 Ocelot 路由)调用 signalR 集线器端点时,我得到“需要连接 ID”。

Unfortunately, I get the following error when I try to connect to the hub from the blazor client application不幸的是,当我尝试从 blazor 客户端应用程序连接到集线器时出现以下错误

fail: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] requestId: 0HM4U0GLR9ACR:00000001, previousRequestId: no previous request id, message: Exception caught in global error handler, exception message: Only Uris starting with 'ws://' or 'wss://' are supported.失败:Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0] requestId:0HM4U0GLR9ACR:00000001,previousRequestId:没有以前的请求 id,消息:在全局错误处理程序中捕获异常,异常消息:仅 Uris 以“ws://”或“wss”开头://' 支持。 (Parameter 'uri'), exception stack: at System.Net.WebSockets.ClientWebSocket.ConnectAsync(Uri uri, CancellationToken cancellationToken) at Ocelot.WebSockets.Middleware.WebSocketsProxyMiddleware.Proxy(HttpContext context, String serverEndpoint) at Ocelot.WebSockets.Middleware.WebSocketsProxyMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) at Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) at Ocelot.LoadBalancer.Middleware.LoadBalancingMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) at Ocelot.Request.Middleware.DownstreamRequestInitialiserMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpCont (参数'uri'),异常堆栈:在 Ocelot.WebSockets.Middleware.WebSocketsProxyMiddleware.Proxy(HttpContext context, String serverEndpoint) 在 Ocelot.WebSockets.Middleware 的 System.Net.WebSockets.ClientWebSocket.ConnectAsync(Uri uri, CancellationToken cancelToken) .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 httpCont ext) at Ocelot.Multiplexer.MultiplexingMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) at Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext) ext) 在 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 context) 在 Microsoft.AspNetCore.MiddlewareAnalysis.AnalysisMiddleware.Invoke(HttpContext httpContext)

The following are my code.以下是我的代码。

Ocelot API Startup file 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 Configuration 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"

  }
}

Microservice API Startup file微服务 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 client Razor Page 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");
    }

}

I have tried to follow Ocelot not passing websockets to microservice without avail.我试图跟随Ocelot 没有将 websockets 传递给微服务而无济于事。 Can some one guide me in the right direction?有人可以指导我正确的方向吗?

In ocelot.json, try to remove the second route and leave just the first one but using "wss" as DownstreamScheme, like this:在 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"

  }
}

I solve the problem between Ocelot and SignalR Hub with NGinx Finally,我用NGinx解决了Ocelot和SignalR集线器之间的问题最后,

My Develepment configuration of Nginx is我的 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;
  }
}

NOTA: Sorry, I've lost the references for hub configuration (/devicehub) but I can update later.注意:抱歉,我丢失了集线器配置 (/devicehub) 的参考资料,但我可以稍后更新。

More detail solution (You can skip if you know about docker/ocelot)更详细的解决方案(如果您了解 docker/ocelot,您可以跳过)

This is the docker-compose file to start nginx这是启动 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

To start nginx启动 nginx

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

or或者

docker-compose up -d

Best regards此致

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM