Angular/SignalR错误:无法完成与服务器的协商

42

我在我的服务器上使用SignalR,在客户端使用Angular... 当我运行客户端时,我收到以下错误:

zone.js:2969 OPTIONS https://localhost:27967/chat/negotiate 0 ()

Utils.js:148 Error: Failed to complete negotiation with the server: Error

Utils.js:148 Error: Failed to start the connection: Error

我猜测这是CORS的问题... 我正在尝试实现一个简单的聊天应用程序。我正在使用最新版本的SignalR:

这里是包含我正在遵循教程代码的GitHub链接。 SignalR聊天教程

这是我的启动代码

    using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace signalrChat
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
            {
                builder
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .WithOrigins("http://localhost:4200");
            }));

            services.AddSignalR();
        }

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

            app.UseCors("CorsPolicy");

            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatHub>("/chat");
            });
        }
    }
}

这是我的客户:

    import { Component, OnInit } from '@angular/core';
import { HubConnection, HubConnectionBuilder } from '@aspnet/signalr';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  private hubConnection: HubConnection;

  nick = '';
  message = '';
  messages: string[] = [];

  ngOnInit() {
    this.nick = window.prompt('Your name:', 'John');

    this.hubConnection = new HubConnectionBuilder().withUrl('https://localhost:27967/chat').build();

    this.hubConnection
    .start()
    .then(() => console.log("Connection Started!"))
    .catch(err => console.log("Error while establishing a connection :( "));

    this.hubConnection.on('sendToAll', (nick: string, receiveMessage: string) => {
      const text = `${nick}: ${receiveMessage}`;
      this.messages.push(text);
    })
  }

  public sendMessage(): void {
    this.hubConnection
    .invoke('sendToAll', this.nick, this.message)
    .catch(err => console.log(err));
  }

}

我猜可能是与cors有关。谢谢!

编辑:我刚刚在 Visual Studio 中重新创建了 SignalR 实现,它可以正常工作。我相信我在启动时选择了错误的设置。


是的,使用 withOrigins(); 不会改变任何东西。 - John
我创建了一个带有起始版本的存储库:https://github.com/WillieFitzPatrick/ng6-signalR.git - wFitz
请检查代理服务器设置,设置Internet选项连接选项卡Lan设置->选择自动检测设置并取消勾选代理配置。 - Ramkumar Sethumurugaun
@John,你说的“我只是重新创建了SignalR实现”是什么意思?请提供详细信息。这可能很有用,因为我也遇到了同样的问题。你是从头开始创建解决方案,还是只重写了客户端/服务器端代码?或者你重新安装/更新了一些软件包/库(哪些)等等? - ael
这有点晚了,但请确保您的URL是正确的,我的问题在于http与https。 - Braden Brown
8个回答

67
connection = new signalR.HubConnectionBuilder()
    .configureLogging(signalR.LogLevel.Debug)
    .withUrl("http://localhost:5000/decisionHub", {
      skipNegotiation: true,
      transport: signalR.HttpTransportType.WebSockets
    })
    .build();

21
请说明这如何解决 OP 的问题。 - Andrzej Ziółek
9
在这里给出了一个解释:https://learn.microsoft.com/en-us/javascript/api/%40aspnet/signalr/ihttpconnectionoptions?view=signalr-js-latest#skipnegotiation “一个布尔值,指示是否应跳过协商。只有在将transport属性设置为“HttpTransportType.WebSockets”时才能跳过协商。 这就是为什么@Claims将传输类型设置为WebSockets的原因。我认为这种方法的一个缺点是,它不适用于不支持WebSockets的旧浏览器。” - elfico
谢谢,他们要在本地调试时包括选项.SkipNegotiation = true; 在发布时没问题。 - Nick Kovalsky

27

我曾面临类似的问题,我通过添加

解决了它。

skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets

如@Caims所提到的,在客户端使用。但我不认为这是正确的解决方案,更像是一种hack。

你需要在服务器端添加AllowCredentials。无论如何,当涉及到Azure时,你不能依赖于该修复。因此,没有必要仅在客户端启用WSS。

这是我的ConfigureServices方法:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(o => o.AddPolicy("CorsPolicy", builder => {
        builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials()
        .WithOrigins("http://localhost:4200");
    }));

    services.AddSignalR();

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

这是我的配置方法:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors("CorsPolicy");
    app.UseSignalR(routes =>
    {
        routes.MapHub<NotifyHub>("/notify");
    });

    app.UseMvc();
}

最后,这是我如何从客户端连接的:

const connection = new signalR.HubConnectionBuilder()
      .configureLogging(signalR.LogLevel.Debug)
      .withUrl("http://localhost:5000/notify", {
        //skipNegotiation: true,
        //transport: signalR.HttpTransportType.WebSockets
      }).build();

connection.start().then(function () {
    console.log('Connected!');
}).catch(function (err) {
    return console.error(err.toString());
});

connection.on("BroadcastMessage", (type: string, payload: string) => {
    this.msgs.push({ severity: type, summary: payload });
});

3
Startup.csconfigure方法中使用app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials());可以让我成功使用AllowCredentials(允许凭据)选项! - Ansharja
哎呀,我无法让这个运作起来。 如果我尝试使用skip和websocket,那么我会得到一个307错误(重定向),而没有skip的话,我会收到CORS错误。 - Jesper Kristiansen
2
发现问题了,我在应用程序中使用了app.UseHttpsRedirection(),它将重定向到https://...5001。一旦我禁用了它,它就正常工作了。 - Jesper Kristiansen
2
我可以确认添加 AllowCredentials() 解决了这个问题。 - drocha87

7

我曾经遇到过同样的问题,后来发现在launchSettings.json文件中的signalRchatServer部分没有作用,适用于我的url是iisexpress的url。我这么说是因为有很多地方都说url是下面这个。

enter image description here


7

我指向了错误的端点。我使用了

https://localhost:5001/api/message-hub 而不是

https://localhost:5001/message-hub(多了一个 /api)

此外,如果您正在使用 Angular,则在修复此错误后,可能会立即收到“Websocket not OPEN”错误提示,因此这里提供了一个链接,以避免更多搜索。


2
我为此浪费了近两天时间,最终找到了解决方法。 这个错误是什么时候发生的?
  • 当您将现有的SignalR服务器项目升级到.Net Core但不升级客户端时
  • 当您使用.Net Core创建SignalR服务器,但使用传统的.Net框架作为客户端时
为什么会出现这个错误?
  • 新版SignalR不允许您使用旧服务器和新客户端,或者新服务器和旧客户端
  • 这意味着如果您使用.Net Core创建SignalR服务器,则必须使用.Net Core创建客户端
在我的情况下就是这个问题。

2

当我尝试连接到Azure SignalR服务Azure Function时,我的Angular应用程序遇到了同样的问题。

[FunctionName("Negotiate")]
public static IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, [SignalRConnectionInfo(HubName = "broadcast")] SignalRConnectionInfo info,
    ILogger log) {
    log.LogInformation("Negotiate trigger function processed a request.");
    return info != null ? (ActionResult) new OkObjectResult(info) : new NotFoundObjectResult("SignalR could not load");
}

以下是我的Angular服务中init()函数的代码。

init() {
    this.getSignalRConnection().subscribe((con: any) => {
        const options = {
            accessTokenFactory: () => con.accessKey
        };

        this.hubConnection = new SignalR.HubConnectionBuilder()
            .withUrl(con.url, options)
            .configureLogging(SignalR.LogLevel.Information)
            .build();

        this.hubConnection.start().catch(error => console.error(error));

        this.hubConnection.on('newData', data => {
            this.mxChipData.next(data);
        });
    });
}

我的问题出在con.accessKey上。我查看了SignalRConnectionInfo类的属性并理解需要使用accessToken而不是accessKey
public class SignalRConnectionInfo {
    public SignalRConnectionInfo();

    [JsonProperty("url")]
    public string Url {
        get;
        set;
    }
    [JsonProperty("accessToken")]
    public string AccessToken {
        get;
        set;
    }
}

所以在将代码更改为 accessTokenFactory: () => con.accessToken 后,一切都像往常一样工作。

2
在我的情况下,这些东西都不是必需的,我错过了https而不是http,它运行得很好。
const connection = new signalR.HubConnectionBuilder()
  .configureLogging(signalR.LogLevel.Debug)
  .withUrl('https://localhost:44308/message')
  .build();

-1

我曾因为跨域而遇到了同样的错误。对于我来说,解决方法是在 program.cs (dotnet 6) 或 startup.cs (dotnetcore < 6) 中进行处理。

app.UseCors(builder => builder
            .AllowAnyHeader()
            .AllowAnyMethod()
            .SetIsOriginAllowed(_ => true)
            .AllowCredentials()
        );

请注意,如果不是开发环境或特殊情况,您不应该打开所有来源。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接