Reputation: 101
I am trying to connect Blazor client to SignalR hub in a simple microservice through the Ocelot API gateway. I am using SSL for all the ASP.NET Core projects.
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).
Unfortunately, I get the following error when I try to connect to the hub from the blazor client application
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. (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 httpContext) 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)
The following are my code.
Ocelot API Startup file
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
{
"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
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
@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. Can some one guide me in the right direction?
Upvotes: 5
Views: 9169
Reputation: 107
This is the Ocelot configuration that finally allowed me to setup client => Ocelot => signalR server communications.
{
"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
},
The signalR server is configured to listen to /hub.
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHub>("hub");
});
I hope it helps someone.
Upvotes: 6
Reputation: 946
I solve the problem between Ocelot and SignalR Hub with NGinx Finally,
My Develepment configuration of Nginx is
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.
More detail solution (You can skip if you know about docker/ocelot)
This is the docker-compose file to start nginx
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
docker-compose -f "docker-compose.yml" up -d
or
docker-compose up -d
Best regards
Upvotes: 0
Reputation: 1711
In ocelot.json, try to remove the second route and leave just the first one but using "wss" as DownstreamScheme, like this:
{
"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"
}
}
Upvotes: 0