SignalR 和跨域问题 (ASP.NET MVC)

SignalR 有三块,

Server:
  Hub Server
  Custom Message Sender Service (Sender Proxy)

Client:
  Message Sender
  Message Receiver

1. Hub Server & Sender Proxy Service
Hub Server 和 Custom Message Sender Service 在一个app中,“Custom Message Sender Service” 通过api直接控制Hub Server行为。(connection filtering, status, deliver message, etc )

2. 跨域
Sender, Receiver 和 Hub Server 可能都不在同一个域里。 会有跨域访问的问题。

3. 发消息的客户端
可以有两种方式发消息给Hub Server
  3.1 通过hubproxy.server.func(), 走的是signalR的persistent connection. (这种情形下一般只发一次消息,persistent connection占用了资源,浪费。 当然也可以建立完释放,persistent connection -> send message -> close persistent connection)。 这种模式下,跨域由SignalR Hub框架控制。
  3.2 通过ajax调用 "Custom Message Sender Service (Proxy)", 由Proxy调用Hub的服务。 这种结构更干净,发送者和SignalR connection没有关系, 走单独Http请求。 跨域由 "Custom Message Sender Service (Proxy)", 一般由服务端暴露服务(MVC/WebAPI). 但是MVC和WebApi的跨域管理不太一样。(见“MVC和WebApi的跨域管理”)
  3.3 有一种情况可以考虑用signalR的persistent connection发消息。 就是发消息的人,同时又是收消息的人。 那么一个persistent connection可以同时用来收发消息。

4. 收消息的客户端
这个只有一种,“持久连接,保持监听”。
  4.1 SignalR保持连接的方式由协议本身定义,由服务器和浏览器支持。 WebSocket / iFrame / longPooling。 具体可google
  4.2 WebSocket的效率要比其他高很多,比较reliable,比如disconnect, 其他方式可能不能直接反应客户端关闭的动作,需要等待超时发生触发disconnect. 代码和排错上有时比较费解。
  4.3 【Hub Server服务端】微软的WebApp PaaS上, by default, "WebSocket"不enable。要去管理界面把它打开。

5. Hub Server
  5.1 Owin中map /signalr 作为SignalR routing的开始
     (https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server#tracing If you are adding SignalR functionality to an ASP.NET MVC application, make sure that the SignalR route is added before the other routes. For more information, see Tutorial: Getting Started with SignalR 2 and MVC 5.)
  5.2 跨域访问由Owin框架自己处理, 如果再由IIS web.config来控制CORS, 会产生冲突。
  5.3 Hub Server中的方法,只是个签名,具体实现不重要(无需写实现,除非客户端用hub.server.func()发消息)。 主要作用
    5.3.1 生成客户端代理类。
    5.3.2 让代理类绑定本地方法,以被服务端调用

 6. Sender Message Proxy
  6.1 如果用MVC做,有个很大的问题就是,跨域。 
    6.1.1 如果浏览器是IE, 问题不是很大。 貌似preflight request 被省略了。 request 被直接提交到MVC层, 可以自定义Action属性, 加入”Access-Control-Allow-Origin“。
    6.1.2 如果浏览器是Chrome, "preflight request"不会被忽略. http option verb会发过来。 这个时候MVC就有问题了, 无论从owin还是application_beginRequest,都无法获得这个request,无法修改http头,使得CORS通过。 (MVC 不能intercept http option request,而webapi能,webapi 是不是因为引用了system.web.http.webhost 才能intercept?)
    6.1.3 对于simple CORS, IE 和 Chrome (Chrome 无须preflight), 如果用Get/Post传数据,由于不走preflight, 自定义Action属性修改http header是可行的。 但是如果用ajax, 有content type header的话, 那就必须要走preflight.

7. IIS setting, web.config 
  实际使用当中,如果使用MVC 和 SignalR, 客户端用ajax post json (content type = application / json), 那么需要考虑直接使用直接从服务器设置应用CORS。 这个时候SignalR的CORS需要被取消(//map.UseCors(CorsOptions.AllowAll) )

    <add name="Access-Control-Allow-Origin" value="https://localhost:44300" />
    <add name="Access-Control-Allow-Headers" value="Content-Type" />
    <add name="Access-Control-Allow-Credentials" value="true" />

 

CORS 介绍 

  一篇很好的文章介绍CORS:https://staticapps.org/articles/cross-domain-requests-with-cors/

  注意Simple CORS / Complex CORS 的区别

备注
  尽量少用IE来做测试,用Chrome或firefox比较标准,会得到和预期一致的结果。 IE的结果经常莫名其妙,比如SignalR不enable CORS (map.UseCors(CorsOptions.AllowAll);)在localhost下,跨域的连接居然也能成功。

 

常用代码

var heartBeat = GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>();
var connectionAlive = heartBeat.GetConnections().FirstOrDefault(c=>c.ConnectionId == connection.ConnectionId);
if (connectionAlive.IsAlive)
{//Do whatever...}

 ======================================================

IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext(“MyHub”);

======================================================

public class EnableCorsAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
        base.OnActionExecuting(filterContext);
    }
}

 

posted @ 2017-05-09 14:16  chen.s  阅读(3147)  评论(0编辑  收藏  举报