From 7bb7d9f21406ed3f11a6e6b713e4f35f280a7ba6 Mon Sep 17 00:00:00 2001 From: stdrickforce Date: Fri, 20 Sep 2019 16:13:41 +0800 Subject: [PATCH] Add MigrationLevel to HandshakeRequest --- .gitignore | 6 +-- specs/ServiceProtocol.md | 22 +++++++-- .../ServerConnectionMigrationLevel.cs | 9 ++++ .../ServiceMessage.cs | 12 +++++ .../ServiceProtocol.cs | 26 ++++------- .../ServiceProtocolConstants.cs | 2 +- ...osoft.Azure.SignalR.Protocols.Tests.csproj | 2 +- .../ServiceProtocolFacts.cs | 46 ++++++++++++++++++- 8 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServerConnectionMigrationLevel.cs diff --git a/.gitignore b/.gitignore index 1f3597a0d..4c5ea419b 100644 --- a/.gitignore +++ b/.gitignore @@ -292,8 +292,8 @@ __pycache__/ .publish/ -# vim -*.swp - # docker .docker/ + +# vim +*.swp diff --git a/specs/ServiceProtocol.md b/specs/ServiceProtocol.md index be60ea67a..d8d4d89a5 100644 --- a/specs/ServiceProtocol.md +++ b/specs/ServiceProtocol.md @@ -5,7 +5,7 @@ The Azure SignalR Service Protocol is a protocol between Azure SignalR Service a ## Terms - Service - Azure SignalR Service. It accepts connections from both clients and servers, acting as the abstract transport between them. It will internally maintain a one-to-one mapping between clients and servers, to make sure that messages are correctly routed to the recipients as if it is a physical transport. -- Server - Application server node, which is connected to the Azure SignalR Service, using this protocol to receive data from and send data to clients through Azure SignalR Service. +- Server - Application server node, which is connected to the Azure SignalR Service, using this protocol to receive data from and send data to clients through Azure SignalR Service. - Client - The SignalR client connected to the Azure SignalR Service. The Azure SignalR Service will look exactly the same as a self-hosted SignalR server from the client's perspective. ## Overview @@ -53,7 +53,7 @@ Ack | Service | Sent from Service to Server to return the operation result of Jo ## Communication Model -This protocol will be used between Service and Server. There will be one or a few physical connections between Service and Server. Data from/to multiple client connections will be multiplexed within these physical connections. Each client connection will be identified by a unique connection Id. +This protocol will be used between Service and Server. There will be one or a few physical connections between Service and Server. Data from/to multiple client connections will be multiplexed within these physical connections. Each client connection will be identified by a unique connection Id. The number of client connections will be far more (over 100 times) than the number of physical connections between Service and Server. @@ -67,15 +67,23 @@ Server will initiate a physical connection to Service, using WebSocket transport When a new client is connected to Service, a `OpenConnection` message will be sent by Service to Server. +#### Client migrate-in from another server + +When a new client is migrated from another server, a `OpenConnection` message will be sent by Service to Server, with an `Asrs-Migrated-In` header given. + ### Client Disconnect - When a client is disconnected from Service, a `CloseConnection` message will be sent by Service to Server. - When Server wants to disconnect a client, a `CloseConnection` message will be sent by Server to Service. Then Service will disconnect the phyical connection with the target client. +#### Client migrate-out to another server + +When a client is migrated to another server, a `CloseConnection` message will be sent by Service to Server, with an `Asrs-Migrated-Out` header given. + ### Client Data Pass Through - When a client sends data to Service, a `ConnectionData` message will be sent by Service to Server. -- When Server wants to send data to a client, a `ConnectionData` message will be sent by Server to Service. +- When Server wants to send data to a client, a `ConnectionData` message will be sent by Server to Service. ### SignalR scenarios @@ -100,6 +108,14 @@ MessagePack uses different formats to encode values. Refer to the [MessagePack F ``` - 1 - Message Type, indicating this is a `HandshakeRequest` message. - Version - A `Int32` encoding number of the protocol version. +- ConnectionType - A `Int32` encoding number of the connection type. + - 0, Default, it can carry clients, service runtime should always accept this kind of connection. + - 1, OnDemand, creating when service requested more connections, it can carry clients, but it may be rejected by service runtime. + - 2, Weak, it can not carry clients, but it can send message. +- MigrationLevel - A `Int32` encoding number indicates if further client connections associated with this server connection could be migrated. + - 0, Off, a client connection can not be migrated to another server. + - 1, ShutdownOnly, a client connection can be migrated only if the pairing server was shutdown gracefully. + - 2, Any, a client connection can be migrated even if the pairing server connection was dropped accidentally. (may cause data loss) #### Example: TODO diff --git a/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServerConnectionMigrationLevel.cs b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServerConnectionMigrationLevel.cs new file mode 100644 index 000000000..aea2280df --- /dev/null +++ b/src/Microsoft.Azure.SignalR.Common/ServiceConnections/ServerConnectionMigrationLevel.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Azure.SignalR +{ + public enum ServerConnectionMigrationLevel + { + Off = 0, + ShutdownOnly = 1, + Any = 2, + } +} diff --git a/src/Microsoft.Azure.SignalR.Protocols/ServiceMessage.cs b/src/Microsoft.Azure.SignalR.Protocols/ServiceMessage.cs index 7e2a9f6c5..94719d909 100644 --- a/src/Microsoft.Azure.SignalR.Protocols/ServiceMessage.cs +++ b/src/Microsoft.Azure.SignalR.Protocols/ServiceMessage.cs @@ -42,6 +42,18 @@ public class HandshakeRequestMessage : ServiceMessage /// public int ConnectionType { get; set; } + /// + /// Gets or sets the migratable flag. + /// + /// + /// 0, Off, a client connection can not be migrated to another server. + /// 1, ShutdownOnly, a client connection can be migrated only if the pairing server was shutdown gracefully. + /// 2, Any, a client connection can be migrated even if the pairing server connection was dropped accidentally. (may cause data loss) + /// + /// + /// + public int MigrationLevel { get; set; } + /// /// Gets or sets the target of service connection, only work for OnDemand connections. /// diff --git a/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocol.cs b/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocol.cs index 287fcc9ef..423140039 100644 --- a/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocol.cs +++ b/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocol.cs @@ -227,20 +227,12 @@ private static void WriteMessageCore(ServiceMessage message, Stream packer) private static void WriteHandshakeRequestMessage(HandshakeRequestMessage message, Stream packer) { - if (message.ConnectionType == 0) - { - MessagePackBinary.WriteArrayHeader(packer, 2); - MessagePackBinary.WriteInt32(packer, ServiceProtocolConstants.HandshakeRequestType); - MessagePackBinary.WriteInt32(packer, message.Version); - } - else - { - MessagePackBinary.WriteArrayHeader(packer, 4); - MessagePackBinary.WriteInt32(packer, ServiceProtocolConstants.HandshakeRequestType); - MessagePackBinary.WriteInt32(packer, message.Version); - MessagePackBinary.WriteInt32(packer, message.ConnectionType); - MessagePackBinary.WriteString(packer, message.Target ?? string.Empty); - } + MessagePackBinary.WriteArrayHeader(packer, 5); + MessagePackBinary.WriteInt32(packer, ServiceProtocolConstants.HandshakeRequestType); + MessagePackBinary.WriteInt32(packer, message.Version); + MessagePackBinary.WriteInt32(packer, message.ConnectionType); + MessagePackBinary.WriteString(packer, message.ConnectionType == 0 ? "" : message.Target ?? string.Empty); + MessagePackBinary.WriteInt32(packer, (int) message.MigrationLevel); } private static void WriteHandshakeResponseMessage(HandshakeResponseMessage message, Stream packer) @@ -516,7 +508,8 @@ private static HandshakeRequestMessage CreateHandshakeRequestMessage(byte[] inpu { result.ConnectionType = ReadInt32(input, ref offset, "connectionType"); result.Target = ReadString(input, ref offset, "target"); - } + } + result.MigrationLevel = arrayLength >= 5 ? ReadInt32(input, ref offset, "migratableStatus") : 0; return result; } @@ -553,7 +546,6 @@ private static OpenConnectionMessage CreateOpenConnectionMessage(int arrayLength { var headers = ReadHeaders(input, ref offset); var queryString = ReadString(input, ref offset, "queryString"); - return new OpenConnectionMessage(connectionId, claims, headers, queryString); } else @@ -575,7 +567,7 @@ private static ConnectionDataMessage CreateConnectionDataMessage(byte[] input, r var connectionId = ReadString(input, ref offset, "connectionId"); var payload = ReadBytes(input, ref offset, "payload"); - return new ConnectionDataMessage(connectionId, payload); + return new ConnectionDataMessage(connectionId, payload); } private static MultiConnectionDataMessage CreateMultiConnectionDataMessage(byte[] input, ref int offset) diff --git a/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocolConstants.cs b/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocolConstants.cs index 400560bcc..3bc0f2520 100644 --- a/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocolConstants.cs +++ b/src/Microsoft.Azure.SignalR.Protocols/ServiceProtocolConstants.cs @@ -25,5 +25,5 @@ public static class ServiceProtocolConstants public const int JoinGroupWithAckMessageType = 18; public const int LeaveGroupWithAckMessageType = 19; public const int AckMessageType = 20; - } + } } diff --git a/test/Microsoft.Azure.SignalR.Protocols.Tests/Microsoft.Azure.SignalR.Protocols.Tests.csproj b/test/Microsoft.Azure.SignalR.Protocols.Tests/Microsoft.Azure.SignalR.Protocols.Tests.csproj index a375191eb..fc6cfb85e 100644 --- a/test/Microsoft.Azure.SignalR.Protocols.Tests/Microsoft.Azure.SignalR.Protocols.Tests.csproj +++ b/test/Microsoft.Azure.SignalR.Protocols.Tests/Microsoft.Azure.SignalR.Protocols.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.0 diff --git a/test/Microsoft.Azure.SignalR.Protocols.Tests/ServiceProtocolFacts.cs b/test/Microsoft.Azure.SignalR.Protocols.Tests/ServiceProtocolFacts.cs index 807a5a820..e81221e54 100644 --- a/test/Microsoft.Azure.SignalR.Protocols.Tests/ServiceProtocolFacts.cs +++ b/test/Microsoft.Azure.SignalR.Protocols.Tests/ServiceProtocolFacts.cs @@ -26,8 +26,17 @@ public static IEnumerable TestDataNames } } - public static IDictionary TestData => new[] + public static IEnumerable TestCompatibilityDataNames { + get + { + foreach (var k in TestCompatibilityData.Keys) + { + yield return new object[] { k }; + } + } + } + public static IDictionary TestCompatibilityData => new[] { new ProtocolTestData( name: "HandshakeRequest", message: new HandshakeRequestMessage(1), @@ -36,6 +45,26 @@ public static IEnumerable TestDataNames name: "HandshakeRequestWithProperty", message: new HandshakeRequestMessage(1) { ConnectionType = 1, Target = "abc" }, binary: "lAEBAaNhYmM="), + new ProtocolTestData( + name: "ConnectionData", + message: new ConnectionDataMessage("conn5", new byte[] {1, 2, 3, 4, 5, 6, 7}), + binary: "kwalY29ubjXEBwECAwQFBgc="), + }.ToDictionary(t => t.Name); + + public static IDictionary TestData => new[] + { + new ProtocolTestData( + name: "HandshakeRequest", + message: new HandshakeRequestMessage(1), + binary: "lQEBAKAA"), + new ProtocolTestData( + name: "HandshakeRequestWithProperty", + message: new HandshakeRequestMessage(1) { ConnectionType = 1, Target = "abc" }, + binary: "lQEBAaNhYmMA"), + new ProtocolTestData( + name: "HandshakeRequestWithMigratableStatus", + message: new HandshakeRequestMessage(1) { MigrationLevel = 1}, + binary: "lQEBAKAB"), new ProtocolTestData( name: "HandshakeResponse", message: new HandshakeResponseMessage(), @@ -197,6 +226,21 @@ public static IEnumerable TestDataNames binary: "lBQCZblKb2luZWQgZ3JvdXAgc3VjY2Vzc2Z1bGx5"), }.ToDictionary(t => t.Name); + [Theory] + [MemberData(nameof(TestCompatibilityDataNames))] + public void ParseOldMessages(string testDataName) + { + var testData = TestData[testDataName]; + + // Verify that the input binary string decodes to the expected MsgPack primitives + var bytes = Convert.FromBase64String(testData.Binary); + + // Parse the input fully now. + bytes = Frame(bytes); + var message = ParseServiceMessage(bytes); + Assert.Equal(testData.Message, message, ServiceMessageEqualityComparer.Instance); + } + [Theory] [MemberData(nameof(TestDataNames))] public void ParseMessages(string testDataName)