繁体   English   中英

SignalR js 客户端无法启动连接,即使日志显示正在建立连接(仅 LongPooling 有效)

[英]SignalR js client is not able to start connection, even if logs are showing that connection is being made (only LongPooling works)

我正在努力配置和使用 signalR 和 .net 核心 mvc 6。signalR 集线器的目的是在调用 C# controller 中的方法后向 js 客户端发送消息(js 客户端是在 MVC 中配置为 ClientApp 的 React 应用程序)。

我为客户端 signalR 实例和 asp.net 启用了调试

这是来自 ASP.NET 的日志:

      SPA proxy is ready. Redirecting to https://localhost:44440.
dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionManager[1]
      New connection ctA6QHwS4fvVGcufYvtlAA created.
dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionDispatcher[10]
      Sending negotiation response.
dbug: Microsoft.AspNetCore.Http.Connections.Internal.HttpConnectionDispatcher[4]
      Establishing new connection.
dbug: Microsoft.AspNetCore.SignalR.HubConnectionHandler[5]
      OnConnectedAsync started.
dbug: Microsoft.AspNetCore.SignalR.Internal.DefaultHubProtocolResolver[2]
      Found protocol implementation for requested protocol: json.
dbug: Microsoft.AspNetCore.SignalR.HubConnectionContext[1]
      Completed connection handshake. Using HubProtocol 'json'.
connected!! ctA6QHwS4fvVGcufYvtlAA

并与他们对应的js客户端日志:

Utils.ts:194 [2022-02-03T18:40:17.568Z] Debug: Starting HubConnection.
Utils.ts:194 [2022-02-03T18:40:17.568Z] Debug: Starting connection with transfer format 'Text'.
Utils.ts:194 [2022-02-03T18:40:17.576Z] Debug: Sending negotiation request: https://localhost:44440/hubs/order/negotiate?negotiateVersion=1.
Utils.ts:194 [2022-02-03T18:40:21.741Z] Debug: Skipping transport 'WebSockets' because it was disabled by the client.
Utils.ts:194 [2022-02-03T18:40:21.742Z] Debug: Selecting transport 'ServerSentEvents'.
Utils.ts:194 [2022-02-03T18:40:21.742Z] Trace: (SSE transport) Connecting.
Utils.ts:190 [2022-02-03T18:40:25.857Z] Information: SSE connected to https://localhost:44440/hubs/order?id=fxqgKpJnF5Dq5MX-RCfXcg
Utils.ts:194 [2022-02-03T18:40:25.857Z] Debug: The HttpConnection connected successfully.
Utils.ts:194 [2022-02-03T18:40:25.857Z] Debug: Sending handshake request.
Utils.ts:194 [2022-02-03T18:40:25.858Z] Trace: (SSE transport) sending data. String data of length 32.
Utils.ts:194 [2022-02-03T18:40:29.969Z] Trace: (SSE transport) request complete. Response status: 200.
Utils.ts:190 [2022-02-03T18:40:29.978Z] Information: Using HubProtocol 'json'.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: HttpConnection.stopConnection(undefined) called while in state Disconnecting.
index.js:1 [2022-02-03T18:40:59.997Z] Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message from the server.'.
console.<computed> @ index.js:1
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: HubConnection.connectionClosed(Error: Server timeout elapsed without receiving a message from the server.) called while in state Connecting.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: Hub handshake failed with error 'Error: Server timeout elapsed without receiving a message from the server.' during start(). Stopping HubConnection.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: Call to HttpConnection.stop(Error: Server timeout elapsed without receiving a message from the server.) ignored because the connection is already in the disconnected state.
Utils.ts:194 [2022-02-03T18:40:59.997Z] Debug: HubConnection failed to start successfully because of error 'Error: Server timeout elapsed without receiving a message from the server.'.

这是来自 js 客户端应用程序的示例代码:

    console.log("hub attached");

    const hubConnection = new HubConnectionBuilder()
      .withUrl(OrderHubUrl, {
        transport: HttpTransportType.ServerSentEvents,
        accessTokenFactory: () => user.accessToken ?? "",
      })
      .withAutomaticReconnect()
      .configureLogging(LogLevel.Trace)
      .build();

    this.dispatcher.state.saveState("hubConnection", hubConnection);

    const startConnection = async () => {
      try {
        await hubConnection.start();
        console.log("connected");
      } catch (e) {
        this.dispatcher.dispatch(Event.ShowModal, {
          actionName: "OK",
          header: "Error",
          content: e,
        });
      }
    };
    hubConnection.on("ReceiveMessage", (user, message) => {
      console.log("message received");
      console.log(user);
      console.log(message);
    });

    hubConnection.onreconnecting((e) => console.log("reconnecting", e));
    hubConnection.onreconnected((e) => console.log("reconnected", e));
    startConnection();
  } 

从顶部的日志可以看出,signalR js 客户端无法通过启动方法。 相反,一段时间后它会抛出一条错误消息。

下面是我的 Program.cs 文件,我在其中配置了 signalR(我怀疑这里可能有问题)

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using OrderMaker.Authentication.Helpers;
using OrderMaker.Authentication.Services;
using OrderMaker.Entities;
using OrderMaker.Modules.Common.Services;
using OrderMaker.Modules.Order.Hubs;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllersWithViews();
builder.Services.AddSignalR(c =>
{
    c.EnableDetailedErrors = true;
    c.ClientTimeoutInterval = TimeSpan.MaxValue;
    c.KeepAliveInterval = TimeSpan.MaxValue;
});

ConfigureConfiguration(builder.Configuration);
ConfigureServices(builder.Services, builder.Configuration);

var app = builder.Build();

app.UseAuthentication();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCors(
          x => x
          .AllowAnyMethod()
          .AllowAnyHeader()
          .AllowCredentials());

app.UseRouting();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");

app.MapHub<OrderHub>("/hubs/order");

app.Run();


void ConfigureConfiguration(ConfigurationManager configuration)
{
    using var client = new OrderMakerContext();
    client.Database.EnsureCreated();
}

void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
    services.AddControllers();
    // configure strongly typed settings objects
    var appSettingsSection = configuration.GetSection("AppSettings");
    services.Configure<AppSettings>(appSettingsSection);
    // configure jwt authentication
    var appSettings = appSettingsSection.Get<AppSettings>();
    var key = Encoding.ASCII.GetBytes(appSettings.Secret);
    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
        x.Events = new JwtBearerEvents
        {
            OnMessageReceived = context =>
            {
                context.Token = context.Request.Cookies["order_maker_token"];
                // If the request is for our hub...
                var path = context.HttpContext.Request.Path;
                var accessToken = context.Request.Query["access_token"];
                if (!string.IsNullOrEmpty(accessToken) &&
                    (path.StartsWithSegments("/hubs/")))
                {
                    // Read the token out of the query string
                    context.Token = accessToken;
                }
                return Task.CompletedTask;
            }
        };
    }
    );

    services.AddScoped<IUserService, UserService>();
    services.AddSingleton<ILogService, LogService>();
}


我的集线器的定义:

public class OrderHub : Hub
    {
        public async Task SendNotification(List<OrderModel> message)
        {
            await Clients.All.SendAsync("ReceiveMessage", message);
        }
        public override async Task OnConnectedAsync()
        {
            await base.OnConnectedAsync();
            Console.WriteLine("connected!! " + Context.ConnectionId);
        }
        public override async Task OnDisconnectedAsync(Exception exception)
        {
            await base.OnDisconnectedAsync(exception);
            Console.WriteLine("disconnected!!");
        }
    }

并从我想向客户发送消息的地方采样 controller:

  private readonly IHubContext<OrderHub> _orderHubContext;
        public OrdersController(IHubContext<OrderHub> orderHubContext)
        {
            _orderHubContext = orderHubContext;
        }

       
        [Route("api/[controller]/")]
        [HttpPost]
        //[Authorize(Roles = $"{Role.Admin},{Role.Manager},{Role.Employee}")]
        public async Task<IActionResult> Post([FromBody] List<OrderModel> model)
        {
               /// some code for creating entities etc
               db.SaveChanges();
               /// notification to all clients that something new was added
               Console.Write(_orderHubContext.Clients.All.ToString());
               await _orderHubContext.Clients.All.SendAsync("ReceiveMessage", "hi there");
                }           
            return Ok(new MessageResponse("Order added succesfully."));
    }

基本上我迷路了,我已经花了两天时间找出可能导致问题的原因,但我就是无法使它正常工作。 我真的很感激任何建议或帮助。 我尝试禁用防火墙、使用不同的浏览器等,但均未成功。 正在建立与集线器的连接,c# 应用程序看到新连接,但 js 客户端只是停留在 start() 方法中一段时间,并抛出错误消息“错误:服务器超时已过,未收到来自服务器的消息。”。

更新:当我在 js clint 中将传输类型显式设置为 LongPolling hub 时,它按预期工作,但这不是理想的解决方案。

--- 更新 --- 所有这些问题都只发生在本地机器上。 我试图检查我的运气并部署到生产应用程序,传输固定到 SSE,它没有任何问题,以及 WebSocket 传输。 我唯一的线索是,当我托管我的应用程序时,在本地主机应用程序上使用 kestrel,而在服务器上使用 IIS。

您可以尝试在 program.cs 中设置 serverTimeout 和 KeepAliveInterval:

builder.Services.AddSignalR(c =>
{
    c.EnableDetailedErrors = true;
    c.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
    c.KeepAliveInterval = TimeSpan.FromSeconds(15);
});

原因:

如果服务器在此间隔内未发送消息,则会自动发送 ping 消息以保持连接打开。 更改 KeepAliveInterval 时,更改客户端上的 ServerTimeout 或 serverTimeoutInMilliseconds 设置。 推荐的 ServerTimeout 或 serverTimeoutInMilliseconds 值是 KeepAliveInterval 值的两倍 所以,最好不要将 KeepAliveInterval 的值设置为最大值。

这与.NET设置的SPA Proxy有关。

我收到的消息如下:Utils.ts:193

      [2022-09-29T17:09:19.866Z] Error: Failed to complete negotiation with the server: Error: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /hub/negotiate</pre>
</body>
</html>
: Status code '404' Either this is not a SignalR endpoint or there is a proxy blocking the connection.

发生的情况很可能是代理将流量从您的 spa 路由到您的 csproj 文件中指定的端口,例如。 https://localhost:44423 你不应该启动一个 websocket 到这个端口,这是 -not- 代理。

在我的例子中,do.net 服务端口实际上是 https://localhost:7088 (properties/launchsettings.json)

所以修改你的 Cors 设置...如下所示,如果 spaproxy 例如在 44423 和 .net 服务端口在 7088。

 services.AddCors(options =>
        {
            options.AddPolicy("ClientPermission", policy =>
            {
                policy.AllowAnyHeader()
                    .AllowAnyMethod()
                    .WithOrigins("https://localhost:44423", "https://localhost:7088")
                    .AllowCredentials();
            });
        });

接下来是您的 js 代码,如下所示:

 const newConnection = new HubConnectionBuilder()
        .withUrl('https://localhost:7088/hub/myhub')
        .withAutomaticReconnect()
        .build();

对我来说,这解决了它。

暂无
暂无

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

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