HubPipelineModule.BuildRejoiningGroups方法不适用于websockets

时间:2014-10-20 12:57:03

标签: c# .net signalr owin

我使用signalR编写了一些代码。主要思想是我们支持几组用户。每个组都使用令牌(某些字符串值)进行标识。此令牌通过查询字符串从客户端传递到服务器。然后具有相同令牌的所有客户端从服务器获取更新。 这是我的代码的一个非常简化的版本。

我使用HubPipelineModule来支持signalR中的组。假设令牌始终从客户端传递,因此我们可以从Referer头中获取其值。

public class RejoingGroupPipelineModule : HubPipelineModule
{
    public override Func<HubDescriptor, IRequest, IList<string>, IList<string>> BuildRejoiningGroups(
        Func<HubDescriptor, IRequest, IList<string>, IList<string>> rejoiningGroups)
    {
        return (hb, r, l) =>
        {
            return new[] { GetTaskToken(r) };
        };
    }

    public static String GetGroupName(IRequest request)
    {
        var refererHeader = request.Headers.FirstOrDefault(h => h.Key == "Referer");
        var uriString = refererHeader.Value;
        if (String.IsNullOrWhiteSpace(uriString))
        {
            return String.Empty;
        }
        return "defaultToken";
    }
}

RejoinGroupPipelineModule在Startup类中注册

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.MapSignalR();
        GlobalHost.HubPipeline.AddModule(new RejoingGroupPipelineModule());
    }
}

然后Hub类更新客户端。

public class BaseHub : Hub
{
    public void Update()
    {
        context.Clients.Groups(new[]{"defaultToken"}).updateClient(DateTime.Now);
    }
}

在计时器上重复调用BaseHub.Update方法。因此,所有客户端都会刷新服务器时间。 当signalR使用长轮询时,一切正常,客户端也会更新。但是当signalR使用网络套接字时,就会出现问题:

1)将具有uri http://localhost/signalr/CONNECT?transport=webSockets&...的请求传递给方法BuildRejoiningGroups。请求不包含引用标头,因此我们无法确定正确的组。

2)然后将具有uri http://localhost/signalr/START?transport=webSockets&...的请求传递给该方法。现在Referer标头出现在请求中。我们可以使用令牌并返回正确的功能。但客户不会更新。我建议重新加入的组实际上没有更新。

我使用了下一个解决方法:

public override Func<HubDescriptor, IRequest, IList<string>, IList<string>> BuildRejoiningGroups(
        Func<HubDescriptor, IRequest, IList<string>, IList<string>> rejoiningGroups)
    {
        return (hb, r, l) =>
        {
            if (r.Headers.All(h => h.Key != "Referer"))
            {
                return null;
            }
            return new[] { GetTaskToken(r) };
        };
    }

现在客户端已更新,但正在抛出“System.ArgumentNullException:Value not not null”。

我的问题是:为什么不使用最后重新加入的群组?如何正确地从查询字符串中获取令牌?如何避免System.ArgumentNullException?

1 个答案:

答案 0 :(得分:3)

  1. 为什么不使用上次重新加入的群组?

    SignalR的设计使其可以在负载均衡器后面使用。这意味着可能会将不同的请求发送到不同的服务器以进行相同的SignalR“连接”。出于这个原因,SignalR尝试保留大多数状态,例如客户端以groupsToken的形式加入的组在客户端本身上。

  2. 如何正确地从查询字符串中获取令牌?

    SignalR已拥有自己的“groupsToken”,以确保客户端在重新连接时通过调用Groups.Add安全地重新添加到已加入的群组中。为什么不使用SignalR的内置组重新加入功能?如果您希望始终根据Referer标头的存在将连接添加到默认组,则可以在OnConnected中执行此操作。

    public override Task OnConnected()
    {
        var refererHeader = Context.Request.Headers.FirstOrDefault(h => h.Key == "Referer");
        var uriString = refererHeader.Value;
        if (!String.IsNullOrWhiteSpace(uriString))
        {
            Groups.Add(Context.ConnectionId, GetTaskToken(Context.Request));
        }
    
        return base.OnConnected();
    }
    

    从SignalR 2.1.0开始,总是调用OnConnected以响应Ajax / start请求,这意味着与WebSocket请求不同,Referer头应该是可访问的。

  3. 如何避免System.ArgumentNullException?

    不要从BuildRejoiningGroups中返回的Func返回null。相反,您应该使用paremeter来获取SignalR通常会重新加入客户端的组。

    public override Func<HubDescriptor, IRequest, IList<string>, IList<string>> BuildRejoiningGroups(
        Func<HubDescriptor, IRequest, IList<string>, IList<string>> rejoiningGroups)
    {
        return (hb, r, l) =>
        {
            // Get the groups SignalR would rejoin the client to by default
            var groupsToRejoin = rejoiningGroups(hb, r, l);
    
            // I would ensure that groupsToRejoin doesn't already contain the group
            // GetTaskToken would add, because SignalR will rejoin the group automatically
            // if the client has already be added to the group.
            if (r.Headers.Any(h => h.Key == "Referer"))
            {
                groupsToRejoin.Add(GetTaskToken(r));
            }
    
            return groupsToRejoin;
        };
    }
    
  4. P.S。如您所述,“Referer”标头不会随WebSocket请求一起发送。但是,会发送类似的“Origin”标题。