协商后跨源 SignalR 连接停止

Posted

技术标签:

【中文标题】协商后跨源 SignalR 连接停止【英文标题】:Cross origin SignalR connection stops after negotiate 【发布时间】:2015-04-21 21:08:20 【问题描述】:

我有一个 MVC 5 应用程序提供视图,还有一个 Web API 2 应用程序作为服务层 (.NET 4.5)。 Web API 应用程序使用 SignalR 2.1.2 在处理 POST 到服务 API 时返回进度。两者部署到不同的域,所以我根据asp.net 教程文章设置了跨源支持。

[assembly: OwinStartup(typeof (Startup))]
namespace MyApp.Service

    public class Startup
    
        public void Configuration(IAppBuilder app)
        
            app.Map("/signalr", map =>
            
                //worry about locking it down to specific origin later
                map.UseCors(CorsOptions.AllowAll);
                map.RunSignalR(new HubConfiguration());
            );
            //now start the WebAPI app
            GlobalConfiguration.Configure(WebApiConfig.Register);
        
    

WebApiConfig.cs 还包含自己的 CORS 声明。

namespace MyApp.Service

    public static class WebApiConfig
    
        public static void Register(HttpConfiguration config)
        
            //controller invocations will come from the MVC project which is deployed to a
            //different domain, so must enable cross origin resource sharing
            config.EnableCors();
            // Web API routes
            config.MapHttpAttributeRoutes();

            //Snip other controller dependency initialisation
        
    

我定义了一个没有服务器端 API 的简单集线器类(它只是允许服务器推送给客户端,而不是让客户端调用)。

namespace MyApp.Service.Hubs

    [HubName("testresult")]
    public class TestResultHub : Hub
    
    

由于我要跨域,并且集线器没有公开任何服务器端 API,因此我不会费心使用生成的 JS 代理。

设置信号器集线器连接的 JS 的相关位是:(请记住,这是从 MVC 应用程序提供的,它没有任何信号器支持(当然除了 jquery-signalr-version.js ))

function TestScenarioHandler(signalrHubUrl) 
    var self = this;
//Snip irrelevant bits (mostly Knockout initialisation)

    self.signalrConnectionId = ko.observable();

    var hubConnection = $.hubConnection(signalrHubUrl,  useDefaultPath: false );

    var hubProxy = hubConnection.createHubProxy("testresult");
    hubProxy.on("progress", function(value) 
        console.log("Hooray! Got a new value from the server: " + value);
    );

    hubConnection.start()
        .done(function() 
            self.signalrConnectionId(hubConnection.id);
            console.log("Connected to signalr hub with connection id " + hubConnection.id);
        )
        .fail(function() 
            console.log("Failed to connect to signalr hub at " + hubConnection.url);
        );

像这样跨域,Firefox 网络流量显示(我已经确认 Chrome 显示相同的东西)一个 GET 到

http://****service.azurewebsites.net/signalr/negotiate?clientProtocol=1.5&connectionData=["name":"testresult"]&_=1424419288550

请注意,name 与我的集线器类上的 HubName 属性的值匹配。

这个 GET 返回 HTTP 200,响应给了我一个 JSON 有效负载,其中包含一个 ConnectionIdConnectionToken 和一堆其他表明一切正常的字段。 HTTP 响应还将 Access-Control-Allow-Origin: 标头设置为 GET 源自的域。总而言之,它看起来不错,除了交通停止的地方。

但是JS控制台打印"Failed to connect to signalr hub at http://****service.azurewebsites.net/signalr"

为了验证我没有做任何太愚蠢的事情,我在 MVC 应用程序中添加了信号器支持和基本集线器(因此不需要跨源),并相应地更改了 $.hubConnection()hubConnection.createProxy() 调用。当我这样做时,浏览器流量显示相同的 /signalr/negotiate?... GET(显然不再跨源),但随后也 GET 到 /signalr/connect?.../signalr/start?...。 JS 控制台也会打印成功消息。

总之;

在服务层启用了CORS,信号器/negotiate GET 返回200,这似乎是一个有效的连接ID,以及预期的Access-Control-Allow-Origin: 标头。这向我表明服务器端 CORS 支持的行为正确,但信号器连接不成功。 当我重新配置信号器连接不跨源时,一切正常。

我错过了还是做错了?! HttpConfiguration.EnableCors()IAppBuilder.UseCors(CorsOption) 之间可能有些冲突?

【问题讨论】:

Curioser 和 curioser... 似乎当信号器 JS 客户端确定它是一个跨域请求时,它会将 http Content-Type 从 "application/json; charset=UTF-8" 更改为 "application/x-www-form-urlencoded; charset=UTF-8"。我不知道它为什么会这样做,也不知道它为什么会有所作为。 【参考方案1】:

解决了。我已将map.UseCors(CorsOptions.AllowAll) 更改为传入CorsPolicy 对象,并将SupportsCredentials 设置为false,在其他地方读到Access-Control-Allow-Origin: *access-control-allow-credentials: true 不兼容。

private static readonly Lazy<CorsOptions> SignalrCorsOptions = new Lazy<CorsOptions>(() =>

    return new CorsOptions
    
        PolicyProvider = new CorsPolicyProvider
        
            PolicyResolver = context =>
            
                var policy = new CorsPolicy();
                policy.AllowAnyOrigin = true;
                policy.AllowAnyMethod = true;
                policy.AllowAnyHeader = true;
                policy.SupportsCredentials = false;
                return Task.FromResult(policy);
            
        
    ;
);

public void Configuration(IAppBuilder app)

    app.Map("/signalr", map =>
    
        map.UseCors(SignalrCorsOptions.Value);
        map.RunSignalR(new HubConfiguration());
     );
     //now start the WebAPI app
     GlobalConfiguration.Configure(WebApiConfig.Register);

SupportCredentials 设置为true 会导致Access-Control-Allow-Origin 标头被重写为响应中的实际来源(不是*)和access-control-allow-credentials: true

现在它可以工作了。

【讨论】:

我有同样的问题,但这个解决方案没有解决它,还有其他线索吗? 在我的情况下,我必须像上面那样创建自定义策略,而是使用SupportsCredentials = true 我的问题通过将默认 map.UseCors(CorsOptions.AllowAll) 替换为上面提供的解决方案得到了解决。大反响。现在 cors orgin 标头出现在所有 signalR 响应标头中。【参考方案2】:

对我来说,以下设置做得很好

        services.AddCors(c =>
        
            c.AddPolicy("AllowCCORSOrigin", options => options
                .WithOrigins("http://localhost:3000")
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials()
                ); 
        );

【讨论】:

添加 .AllowCredentials() 对我有用。谢谢

以上是关于协商后跨源 SignalR 连接停止的主要内容,如果未能解决你的问题,请参考以下文章

客户何时适合与 SignalR 服务重新协商?

SignalR:协商请求期间出错:未定义

在 SignalR 协商阶段发生 CORS 错误

SingnalR 开发到生产部署闭坑指南

如何终止 SignalR 连接?

SignalR 广播消息在停止/启动后重复