在 SignalR 中使用 MessagePack 集线器协议 ASP.NET Core

作者: Brennan Conroy

本文假定读者熟悉入门中介绍的主题。

什么是 MessagePack?

MessagePack是一种快速且紧凑的二进制序列化格式。 当性能和带宽需要考虑时,它很有用,因为它会创建比JSON更小的消息。 由于它是二进制格式,因此在查看网络跟踪和日志时会无法读取消息,除非这些字节是通过 MessagePack 分析器传递的。 SignalR 提供对 MessagePack 格式的内置支持,并为客户端和服务器提供要使用的 Api。

在服务器上配置 MessagePack

若要在服务器上启用 MessagePack 集线器协议,请在应用程序中安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包。 在 Startup.cs 文件中,将 AddMessagePackProtocol 添加到 AddSignalR 调用,以在服务器上启用 MessagePack 支持。

备注

默认情况下启用 JSON。 添加 MessagePack 可支持 JSON 和 MessagePack 客户端。

services.AddSignalR()
    .AddMessagePackProtocol();

若要自定义 MessagePack 如何设置数据的格式,AddMessagePackProtocol 获取用于配置选项的委托。 在该委托中,FormatterResolvers 属性可用于配置 MessagePack 序列化选项。 有关解析程序工作方式的详细信息,请访问MessagePack-CSharp上的 MessagePack 库。 属性可用于要序列化的对象,以定义应如何处理它们。

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

警告

强烈建议查看CVE-2020-5234和应用建议的修补程序。 例如,将 MessagePackSecurity.Active 静态属性设置为 MessagePackSecurity.UntrustedData 设置 MessagePackSecurity.Active 需要手动安装MessagePack 的1.9 版。 安装 MessagePack 1.9. x 升级 SignalR 使用的版本。 如果 MessagePackSecurity.Active 未设置为 MessagePackSecurity.UntrustedData,则恶意客户端可能会导致拒绝服务。 MessagePackSecurity.Active 设置 Program.Main中,如下面的代码所示:

public static void Main(string[] args)
{
  MessagePackSecurity.Active = MessagePackSecurity.UntrustedData;

  CreateHostBuilder(args).Build().Run();
}

在客户端上配置 MessagePack

备注

默认情况下,为支持的客户端启用 JSON。 客户端只能支持一个协议。 添加 MessagePack 支持将替换任何以前配置的协议。

.NET 客户端

若要在 .NET 客户端中启用 MessagePack,请安装 Microsoft.AspNetCore.SignalR.Protocols.MessagePack 包,并 AddMessagePackProtocol HubConnectionBuilder上调用。

var hubConnection = new HubConnectionBuilder()
                        .WithUrl("/chatHub")
                        .AddMessagePackProtocol()
                        .Build();

备注

AddMessagePackProtocol 调用采用一个委托来配置与服务器类似的选项。

JavaScript 客户端

@microsoft/signalr-protocol-msgpack npm 包提供对 JavaScript 客户端的 MessagePack 支持。 通过在命令行界面中执行以下命令来安装包:

npm install @microsoft/signalr-protocol-msgpack

@aspnet/signalr-protocol-msgpack npm 包提供对 JavaScript 客户端的 MessagePack 支持。 通过在命令行界面中执行以下命令来安装包:

npm install @aspnet/signalr-protocol-msgpack

安装 npm 包后,可以通过 JavaScript 模块加载程序直接使用该模块,或通过引用以下文件将该模块导入到浏览器中:

node_modules\@microsoft\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

node_modules\@aspnet\signalr-protocol-msgpack\dist\browser\signalr-protocol-msgpack.js

在浏览器中,还必须引用 msgpack5 库。 使用 <script> 标记创建一个引用。 可在node_modules \msgpack5\dist\msgpack5.js找到库。

备注

使用 <script> 元素时,顺序很重要。 如果在msgpack5之前引用signalr-protocol-msgpack ,则在尝试与 MessagePack 连接时将出现错误。 signalrsignalr-protocol-msgpack之前也是必需的。

<script src="~/lib/signalr/signalr.js"></script>
<script src="~/lib/msgpack5/msgpack5.js"></script>
<script src="~/lib/signalr/signalr-protocol-msgpack.js"></script>

HubConnectionBuilder 中添加 .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol()) 会将客户端配置为在连接到服务器时使用 MessagePack 协议。

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())
    .build();

备注

目前,JavaScript 客户端上没有用于 MessagePack 协议的配置选项。

MessagePack 兼容

使用 MessagePack 集线器协议时,需要注意几个问题。

MessagePack 区分大小写

MessagePack 协议区分大小写。 例如,请看下面C#的类:

public class ChatMessage
{
    public string Sender { get; }
    public string Message { get; }
}

从 JavaScript 客户端发送时,必须使用 PascalCased 属性名称,因为大小写必须与C#类完全匹配。 例如:

connection.invoke("SomeMethod", { Sender: "Sally", Message: "Hello!" });

使用 camelCased 名称不会正确绑定到C#类。 可以通过使用 Key 特性为 MessagePack 属性指定一个不同的名称来解决此情况。 有关详细信息,请参阅MessagePack-CSharp 文档

序列化/反序列化时不保留 DateTime. Kind

MessagePack 协议不提供对 DateTimeKind 值进行编码的方法。 因此,在对日期进行反序列化时,MessagePack Hub 协议假设传入日期为 UTC 格式。 如果你在本地时间使用 DateTime 值,则建议在发送这些值之前将其转换为 UTC。 接收到本地时间时将它们从 UTC 转换为本地时间。

有关此限制的详细信息,请参阅 GitHub issue aspnet/SignalR#2632

JavaScript 中的 MessagePack 不支持 MinValue

SignalR JavaScript 客户端使用的msgpack5库不支持 MessagePack 中的 timestamp96 类型。 此类型用于编码非常大的日期值(在过去或未来很大程度上)。 DateTime.MinValue 的值 January 1, 0001 必须用 timestamp96 值进行编码。 因此,不支持向 JavaScript 客户端发送 DateTime.MinValue 当 JavaScript 客户端接收到 DateTime.MinValue 时,将引发以下错误:

Uncaught Error: unable to find ext type 255 at decoder.js:427

通常,DateTime.MinValue 用于对 "缺少" 或 null 值进行编码。 如果需要在 MessagePack 中对该值进行编码,请使用可以为 null 的 DateTime 值(DateTime?),或对表示日期是否存在的单独 bool 值进行编码。

有关此限制的详细信息,请参阅 GitHub issue aspnet/SignalR#2228

"提前" 编译环境中的 MessagePack 支持

.NET 客户端和服务器使用的MessagePack-CSharp库使用代码生成来优化序列化。 因此,默认情况下,在使用 "提前" 编译(如 Xamarin iOS 或 Unity)的环境中,默认情况下不支持此方法。 可以通过 "预生成" 序列化程序/反序列化程序代码,在这些环境中使用 MessagePack。 有关详细信息,请参阅MessagePack-CSharp 文档 预生成序列化程序后,可以使用传递给 AddMessagePackProtocol的配置委托注册它们:

services.AddSignalR()
    .AddMessagePackProtocol(options =>
    {
        options.FormatterResolvers = new List<MessagePack.IFormatterResolver>()
        {
            MessagePack.Resolvers.GeneratedResolver.Instance,
            MessagePack.Resolvers.StandardResolver.Instance
        };
    });

类型检查在 MessagePack 中更加严格

JSON 集线器协议将在反序列化过程中执行类型转换。 例如,如果传入的对象的属性值为数字({ foo: 42 }),但 .NET 类的属性为类型 string,则将转换该值。 但是,MessagePack 不会执行此转换,并将引发可在服务器端日志中显示的异常(在控制台中):

InvalidDataException: Error binding arguments. Make sure that the types of the provided values match the types of the hub method being invoked.

有关此限制的详细信息,请参阅 GitHub issue aspnet/SignalR#2937

上一篇:ASP.NET Core 中的安全注意事项 SignalR

下一篇:在 ASP.NET Core 中使用流式处理 SignalR

关注微信小程序
程序员编程王-随时随地学编程

扫描二维码
程序员编程王

扫一扫关注最新编程教程