跨源 SignalR 连接在协商后停止
Cross origin SignalR connection stops after negotiate
我有一个 MVC 5 应用程序提供视图,还有一个 Web API 2 应用程序作为服务层 (.NET 4.5)。 Web API 应用程序使用 SignalR 2.1.2 来 return 处理对服务 API 的 POST。两者部署到不同的域,因此我根据 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
}
}
}
我定义了一个简单的集线器class,没有server-side API(它只是让服务器推送给客户端,而不是让客户端调用) .
namespace MyApp.Service.Hubs
{
[HubName("testresult")]
public class TestResultHub : Hub
{
}
}
因为我要去 cross-domain 并且集线器没有暴露任何服务器端 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);
});
}
像这样 cross-origin,Firefox 网络流量显示(我已经确认 Chrome 显示相同的内容)GET 到
http://****service.azurewebsites.net/signalr/negotiate?clientProtocol=1.5&connectionData=[{"name":"testresult"}]&_=1424419288550
请注意,name
与我的集线器 class 上的 HubName
属性的值匹配。
这个 GET returns HTTP 200,响应给了我一个 JSON 有效载荷,其中包含一个 ConnectionId
、ConnectionToken
和一堆其他字段,表明一切都是行。 HTTP 响应还将 Access-Control-Allow-Origin:
header 设置为 GET 的来源域。一切看起来都不错,除了那是交通停止的地方。
但是 JS 控制台打印 "Failed to connect to signalr hub at http://****service.azurewebsites.net/signalr"
为了验证我没有做任何愚蠢的事情,我在 MVC 应用程序中添加了信号器支持和基本集线器(因此不需要交叉源),并更改了 $.hubConnection()
和 hubConnection.createProxy()
相应地调用。当我这样做时,浏览器流量显示相同的 /signalr/negotiate?...
GET(显然不再跨源),但也显示 /signalr/connect?...
和 /signalr/start?...
。 JS 控制台也会打印一条成功消息。
综上所述;
- 服务层启用了CORS,signalr
/negotiate
GET returns 200,似乎是一个有效的连接id,预期的Access-Control-Allow-Origin:
header.这向我表明 server-side CORS 支持本身运行正常,但信号器连接不成功。
- 当我重新配置信号器连接不是跨源时,一切都按预期工作。
WTF 我是漏掉了还是做错了?! HttpConfiguration.EnableCors()
和 IAppBuilder.UseCors(CorsOption)
之间可能有些冲突?
解决了。我已经将 map.UseCors(CorsOptions.AllowAll)
更改为传入 CorsPolicy
object,并将 SupportsCredentials
设置为 false,在其他地方读到 Access-Control-Allow-Origin: *
与 [=15 不兼容=].
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
header 被重写为响应中的实际来源(而不是 *
)和 access-control-allow-credentials: true
。
现在可以使用了。
对我来说,以下设置效果很好
services.AddCors(c =>
{
c.AddPolicy("AllowCCORSOrigin", options => options
.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});
我有一个 MVC 5 应用程序提供视图,还有一个 Web API 2 应用程序作为服务层 (.NET 4.5)。 Web API 应用程序使用 SignalR 2.1.2 来 return 处理对服务 API 的 POST。两者部署到不同的域,因此我根据 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
}
}
}
我定义了一个简单的集线器class,没有server-side API(它只是让服务器推送给客户端,而不是让客户端调用) .
namespace MyApp.Service.Hubs
{
[HubName("testresult")]
public class TestResultHub : Hub
{
}
}
因为我要去 cross-domain 并且集线器没有暴露任何服务器端 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);
});
}
像这样 cross-origin,Firefox 网络流量显示(我已经确认 Chrome 显示相同的内容)GET 到
http://****service.azurewebsites.net/signalr/negotiate?clientProtocol=1.5&connectionData=[{"name":"testresult"}]&_=1424419288550
请注意,name
与我的集线器 class 上的 HubName
属性的值匹配。
这个 GET returns HTTP 200,响应给了我一个 JSON 有效载荷,其中包含一个 ConnectionId
、ConnectionToken
和一堆其他字段,表明一切都是行。 HTTP 响应还将 Access-Control-Allow-Origin:
header 设置为 GET 的来源域。一切看起来都不错,除了那是交通停止的地方。
但是 JS 控制台打印 "Failed to connect to signalr hub at http://****service.azurewebsites.net/signalr"
为了验证我没有做任何愚蠢的事情,我在 MVC 应用程序中添加了信号器支持和基本集线器(因此不需要交叉源),并更改了 $.hubConnection()
和 hubConnection.createProxy()
相应地调用。当我这样做时,浏览器流量显示相同的 /signalr/negotiate?...
GET(显然不再跨源),但也显示 /signalr/connect?...
和 /signalr/start?...
。 JS 控制台也会打印一条成功消息。
综上所述;
- 服务层启用了CORS,signalr
/negotiate
GET returns 200,似乎是一个有效的连接id,预期的Access-Control-Allow-Origin:
header.这向我表明 server-side CORS 支持本身运行正常,但信号器连接不成功。 - 当我重新配置信号器连接不是跨源时,一切都按预期工作。
WTF 我是漏掉了还是做错了?! HttpConfiguration.EnableCors()
和 IAppBuilder.UseCors(CorsOption)
之间可能有些冲突?
解决了。我已经将 map.UseCors(CorsOptions.AllowAll)
更改为传入 CorsPolicy
object,并将 SupportsCredentials
设置为 false,在其他地方读到 Access-Control-Allow-Origin: *
与 [=15 不兼容=].
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
header 被重写为响应中的实际来源(而不是 *
)和 access-control-allow-credentials: true
。
现在可以使用了。
对我来说,以下设置效果很好
services.AddCors(c =>
{
c.AddPolicy("AllowCCORSOrigin", options => options
.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});