private HubMessage CreateStreamInvocationMessage(ref MessagePackReader reader, IInvocationBinder binder, int itemCount) { var headers = ReadHeaders(ref reader); var invocationId = ReadInvocationId(ref reader); var target = ReadString(ref reader, "target"); object[] arguments; try { var parameterTypes = binder.GetParameterTypes(target); arguments = BindArguments(ref reader, parameterTypes); } catch (Exception ex) { return(new InvocationBindingFailureMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex))); } string[]? streams = null; // Previous clients will send 5 items, so we check if they sent a stream array or not if (itemCount > 5) { streams = ReadStreamIds(ref reader); } return(ApplyHeaders(headers, new StreamInvocationMessage(invocationId, target, arguments, streams))); }
private static InvocationMessage CreateInvocationMessage(Unpacker unpacker, IInvocationBinder binder) { var headers = ReadHeaders(unpacker); var invocationId = ReadInvocationId(unpacker); // For MsgPack, we represent an empty invocation ID as an empty string, // so we need to normalize that to "null", which is what indicates a non-blocking invocation. if (string.IsNullOrEmpty(invocationId)) { invocationId = null; } var target = ReadString(unpacker, "target"); var parameterTypes = binder.GetParameterTypes(target); try { var arguments = BindArguments(unpacker, parameterTypes); return(ApplyHeaders(headers, new InvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments))); } catch (Exception ex) { return(ApplyHeaders(headers, new InvocationMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex)))); } }
private HubMessage CreateInvocationMessage(ref MessagePackReader reader, IInvocationBinder binder, int itemCount) { var headers = ReadHeaders(ref reader); var invocationId = ReadInvocationId(ref reader); // For MsgPack, we represent an empty invocation ID as an empty string, // so we need to normalize that to "null", which is what indicates a non-blocking invocation. if (string.IsNullOrEmpty(invocationId)) { invocationId = null; } var target = ReadString(ref reader, "target"); object[]? arguments; try { var parameterTypes = binder.GetParameterTypes(target); arguments = BindArguments(ref reader, parameterTypes); } catch (Exception ex) { return(new InvocationBindingFailureMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex))); } string[]? streams = null; // Previous clients will send 5 items, so we check if they sent a stream array or not if (itemCount > 5) { streams = ReadStreamIds(ref reader); } return(ApplyHeaders(headers, new InvocationMessage(invocationId, target, arguments, streams))); }
private InvocationMessage BindInvocationMessage(JObject json, IInvocationBinder binder) { var invocationId = JsonUtils.GetRequiredProperty <string>(json, InvocationIdPropertyName, JTokenType.String); var target = JsonUtils.GetRequiredProperty <string>(json, TargetPropertyName, JTokenType.String); var nonBlocking = JsonUtils.GetOptionalProperty <bool>(json, NonBlockingPropertyName, JTokenType.Boolean); var args = JsonUtils.GetRequiredProperty <JArray>(json, ArgumentsPropertyName, JTokenType.Array); var paramTypes = binder.GetParameterTypes(target); var arguments = new object[args.Count]; if (paramTypes.Length != arguments.Length) { throw new FormatException($"Invocation provides {arguments.Length} argument(s) but target expects {paramTypes.Length}."); } for (var i = 0; i < paramTypes.Length; i++) { var paramType = paramTypes[i]; // TODO(anurse): We can add some DI magic here to allow users to provide their own serialization // Related Bug: https://github.com/aspnet/SignalR/issues/261 arguments[i] = args[i].ToObject(paramType, _payloadSerializer); } return(new InvocationMessage(invocationId, nonBlocking, target, arguments)); }
private static StreamInvocationMessage CreateStreamInvocationMessage(Unpacker unpacker, IInvocationBinder binder) { var invocationId = ReadInvocationId(unpacker); var target = ReadString(unpacker, "target"); var parameterTypes = binder.GetParameterTypes(target); try { var arguments = BindArguments(unpacker, parameterTypes); return(new StreamInvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments)); } catch (Exception ex) { return(new StreamInvocationMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex))); } }
private StreamInvocationMessage BindStreamInvocationMessage(JObject json, IInvocationBinder binder) { var invocationId = JsonUtils.GetRequiredProperty <string>(json, InvocationIdPropertyName, JTokenType.String); var target = JsonUtils.GetRequiredProperty <string>(json, TargetPropertyName, JTokenType.String); var args = JsonUtils.GetRequiredProperty <JArray>(json, ArgumentsPropertyName, JTokenType.Array); var paramTypes = binder.GetParameterTypes(target); try { var arguments = BindArguments(args, paramTypes); return(new StreamInvocationMessage(invocationId, target, argumentBindingException: null, arguments: arguments)); } catch (Exception ex) { return(new StreamInvocationMessage(invocationId, target, ExceptionDispatchInfo.Capture(ex))); } }
private InvocationMessage BindInvocationMessage(JObject json, IInvocationBinder binder) { var invocationId = JsonUtils.GetOptionalProperty <string>(json, InvocationIdPropertyName, JTokenType.String); var metadata = JsonUtils.GetOptionalMetadataDictionary(json, MetadataPropertyName); var target = JsonUtils.GetRequiredProperty <string>(json, TargetPropertyName, JTokenType.String); var args = JsonUtils.GetRequiredProperty <JArray>(json, ArgumentsPropertyName, JTokenType.Array); var paramTypes = binder.GetParameterTypes(target); try { var arguments = BindArguments(args, paramTypes); return(new InvocationMessage(invocationId, metadata, target, arguments: arguments)); } catch (Exception ex) { return(new InvocationMessage(invocationId, metadata, target, ExceptionDispatchInfo.Capture(ex))); } }
private static InvocationMessage CreateInvocationMessage(Unpacker unpacker, IInvocationBinder binder) { var invocationId = ReadInvocationId(unpacker); var nonBlocking = ReadBoolean(unpacker, "nonBlocking"); var target = ReadString(unpacker, "target"); var argumentCount = ReadArrayLength(unpacker, "arguments"); var parameterTypes = binder.GetParameterTypes(target); if (parameterTypes.Length != argumentCount) { throw new FormatException( $"Target method expects {parameterTypes.Length} arguments(s) but invocation has {argumentCount} argument(s)."); } var arguments = new object[argumentCount]; for (var i = 0; i < argumentCount; i++) { arguments[i] = DeserializeObject(unpacker, parameterTypes[i], "argument"); } return(new InvocationMessage(invocationId, nonBlocking, target, arguments)); }
private HubMessage ParseMessage(Utf8BufferTextReader textReader, IInvocationBinder binder) { try { // We parse using the JsonTextReader directly but this has a problem. Some of our properties are dependent on other properties // and since reading the json might be unordered, we need to store the parsed content as JToken to re-parse when true types are known. // if we're lucky and the state we need to directly parse is available, then we'll use it. int? type = null; string invocationId = null; string target = null; string error = null; var hasItem = false; object item = null; JToken itemToken = null; var hasResult = false; object result = null; JToken resultToken = null; var hasArguments = false; object[] arguments = null; JArray argumentsToken = null; ExceptionDispatchInfo argumentBindingException = null; Dictionary <string, string> headers = null; var completed = false; using (var reader = JsonUtils.CreateJsonTextReader(textReader)) { reader.DateParseHandling = DateParseHandling.None; JsonUtils.CheckRead(reader); // We're always parsing a JSON object JsonUtils.EnsureObjectStart(reader); do { switch (reader.TokenType) { case JsonToken.PropertyName: var memberName = reader.Value.ToString(); switch (memberName) { case TypePropertyName: var messageType = JsonUtils.ReadAsInt32(reader, TypePropertyName); if (messageType == null) { throw new InvalidDataException($"Missing required property '{TypePropertyName}'."); } type = messageType.Value; break; case InvocationIdPropertyName: invocationId = JsonUtils.ReadAsString(reader, InvocationIdPropertyName); break; case TargetPropertyName: target = JsonUtils.ReadAsString(reader, TargetPropertyName); break; case ErrorPropertyName: error = JsonUtils.ReadAsString(reader, ErrorPropertyName); break; case ResultPropertyName: JsonUtils.CheckRead(reader); hasResult = true; if (string.IsNullOrEmpty(invocationId)) { // If we don't have an invocation id then we need to store it as a JToken so we can parse it later resultToken = JToken.Load(reader); } else { // If we have an invocation id already we can parse the end result var returnType = binder.GetReturnType(invocationId); result = PayloadSerializer.Deserialize(reader, returnType); } break; case ItemPropertyName: JsonUtils.CheckRead(reader); hasItem = true; if (string.IsNullOrEmpty(invocationId)) { // If we don't have an invocation id then we need to store it as a JToken so we can parse it later itemToken = JToken.Load(reader); } else { var returnType = binder.GetReturnType(invocationId); item = PayloadSerializer.Deserialize(reader, returnType); } break; case ArgumentsPropertyName: JsonUtils.CheckRead(reader); if (reader.TokenType != JsonToken.StartArray) { throw new InvalidDataException($"Expected '{ArgumentsPropertyName}' to be of type {JTokenType.Array}."); } hasArguments = true; if (string.IsNullOrEmpty(target)) { // We don't know the method name yet so just parse an array of generic JArray argumentsToken = JArray.Load(reader); } else { try { var paramTypes = binder.GetParameterTypes(target); arguments = BindArguments(reader, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } break; case HeadersPropertyName: JsonUtils.CheckRead(reader); headers = ReadHeaders(reader); break; default: // Skip read the property name JsonUtils.CheckRead(reader); // Skip the value for this property reader.Skip(); break; } break; case JsonToken.EndObject: completed = true; break; } }while (!completed && JsonUtils.CheckRead(reader)); } HubMessage message; switch (type) { case HubProtocolConstants.InvocationMessageType: { if (argumentsToken != null) { // We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything. try { var paramTypes = binder.GetParameterTypes(target); arguments = BindArguments(argumentsToken, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } message = argumentBindingException != null ? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException) : BindInvocationMessage(invocationId, target, arguments, hasArguments, binder); } break; case HubProtocolConstants.StreamInvocationMessageType: { if (argumentsToken != null) { // We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything. try { var paramTypes = binder.GetParameterTypes(target); arguments = BindArguments(argumentsToken, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } message = argumentBindingException != null ? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException) : BindStreamInvocationMessage(invocationId, target, arguments, hasArguments, binder); } break; case HubProtocolConstants.StreamItemMessageType: if (itemToken != null) { var returnType = binder.GetReturnType(invocationId); item = itemToken.ToObject(returnType, PayloadSerializer); } message = BindStreamItemMessage(invocationId, item, hasItem, binder); break; case HubProtocolConstants.CompletionMessageType: if (resultToken != null) { var returnType = binder.GetReturnType(invocationId); result = resultToken.ToObject(returnType, PayloadSerializer); } message = BindCompletionMessage(invocationId, error, result, hasResult, binder); break; case HubProtocolConstants.CancelInvocationMessageType: message = BindCancelInvocationMessage(invocationId); break; case HubProtocolConstants.PingMessageType: return(PingMessage.Instance); case HubProtocolConstants.CloseMessageType: return(BindCloseMessage(error)); case null: throw new InvalidDataException($"Missing required property '{TypePropertyName}'."); default: // Future protocol changes can add message types, old clients can ignore them return(null); } return(ApplyHeaders(message, headers)); } catch (JsonReaderException jrex) { throw new InvalidDataException("Error reading JSON.", jrex); } }
private HubMessage?ParseMessage(ReadOnlySequence <byte> input, IInvocationBinder binder) { try { // We parse using the Utf8JsonReader directly but this has a problem. Some of our properties are dependent on other properties // and since reading the json might be unordered, we need to store the parsed content as JsonDocument to re-parse when true types are known. // if we're lucky and the state we need to directly parse is available, then we'll use it. int? type = null; string? invocationId = null; string? target = null; string? error = null; var hasItem = false; object? item = null; var hasResult = false; object? result = null; var hasArguments = false; object?[]? arguments = null; string[]? streamIds = null; bool hasArgumentsToken = false; Utf8JsonReader argumentsToken = default; bool hasItemsToken = false; Utf8JsonReader itemsToken = default; bool hasResultToken = false; Utf8JsonReader resultToken = default; ExceptionDispatchInfo? argumentBindingException = null; Dictionary <string, string>?headers = null; var completed = false; var allowReconnect = false; var reader = new Utf8JsonReader(input, isFinalBlock: true, state: default); reader.CheckRead(); // We're always parsing a JSON object reader.EnsureObjectStart(); do { switch (reader.TokenType) { case JsonTokenType.PropertyName: if (reader.ValueTextEquals(TypePropertyNameBytes.EncodedUtf8Bytes)) { type = reader.ReadAsInt32(TypePropertyName); if (type == null) { throw new InvalidDataException($"Expected '{TypePropertyName}' to be of type {JsonTokenType.Number}."); } } else if (reader.ValueTextEquals(InvocationIdPropertyNameBytes.EncodedUtf8Bytes)) { invocationId = reader.ReadAsString(InvocationIdPropertyName); } else if (reader.ValueTextEquals(StreamIdsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); if (reader.TokenType != JsonTokenType.StartArray) { throw new InvalidDataException( $"Expected '{StreamIdsPropertyName}' to be of type {SystemTextJsonExtensions.GetTokenString(JsonTokenType.StartArray)}."); } var newStreamIds = new List <string>(); reader.Read(); while (reader.TokenType != JsonTokenType.EndArray) { newStreamIds.Add(reader.GetString() ?? throw new InvalidDataException($"Null value for '{StreamIdsPropertyName}' is not valid.")); reader.Read(); } streamIds = newStreamIds.ToArray(); } else if (reader.ValueTextEquals(TargetPropertyNameBytes.EncodedUtf8Bytes)) { target = reader.ReadAsString(TargetPropertyName); } else if (reader.ValueTextEquals(ErrorPropertyNameBytes.EncodedUtf8Bytes)) { error = reader.ReadAsString(ErrorPropertyName); } else if (reader.ValueTextEquals(AllowReconnectPropertyNameBytes.EncodedUtf8Bytes)) { allowReconnect = reader.ReadAsBoolean(AllowReconnectPropertyName); } else if (reader.ValueTextEquals(ResultPropertyNameBytes.EncodedUtf8Bytes)) { hasResult = true; reader.CheckRead(); if (string.IsNullOrEmpty(invocationId)) { // If we don't have an invocation id then we need to value copy the reader so we can parse it later hasResultToken = true; resultToken = reader; reader.Skip(); } else { // If we have an invocation id already we can parse the end result var returnType = binder.GetReturnType(invocationId); result = BindType(ref reader, returnType); } } else if (reader.ValueTextEquals(ItemPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); hasItem = true; string?id = null; if (!string.IsNullOrEmpty(invocationId)) { id = invocationId; } else { // If we don't have an id yet then we need to value copy the reader so we can parse it later hasItemsToken = true; itemsToken = reader; reader.Skip(); continue; } try { var itemType = binder.GetStreamItemType(id); item = BindType(ref reader, itemType); } catch (Exception ex) { return(new StreamBindingFailureMessage(id, ExceptionDispatchInfo.Capture(ex))); } } else if (reader.ValueTextEquals(ArgumentsPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); int initialDepth = reader.CurrentDepth; if (reader.TokenType != JsonTokenType.StartArray) { throw new InvalidDataException($"Expected '{ArgumentsPropertyName}' to be of type {SystemTextJsonExtensions.GetTokenString(JsonTokenType.StartArray)}."); } hasArguments = true; if (string.IsNullOrEmpty(target)) { // We don't know the method name yet so just value copy the reader so we can parse it later hasArgumentsToken = true; argumentsToken = reader; reader.Skip(); } else { try { var paramTypes = binder.GetParameterTypes(target); arguments = BindTypes(ref reader, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); // Could be at any point in argument array JSON when an error is thrown // Read until the end of the argument JSON array while (reader.CurrentDepth == initialDepth && reader.TokenType == JsonTokenType.StartArray || reader.CurrentDepth > initialDepth) { reader.CheckRead(); } } } } else if (reader.ValueTextEquals(HeadersPropertyNameBytes.EncodedUtf8Bytes)) { reader.CheckRead(); headers = ReadHeaders(ref reader); } else { reader.CheckRead(); reader.Skip(); } break; case JsonTokenType.EndObject: completed = true; break; } }while (!completed && reader.CheckRead()); HubMessage message; switch (type) { case HubProtocolConstants.InvocationMessageType: { if (target is null) { throw new InvalidDataException($"Missing required property '{TargetPropertyName}'."); } if (hasArgumentsToken) { // We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything. try { var paramTypes = binder.GetParameterTypes(target); arguments = BindTypes(ref argumentsToken, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } message = argumentBindingException != null ? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException) : BindInvocationMessage(invocationId, target, arguments, hasArguments, streamIds); } break; case HubProtocolConstants.StreamInvocationMessageType: { if (target is null) { throw new InvalidDataException($"Missing required property '{TargetPropertyName}'."); } if (hasArgumentsToken) { // We weren't able to bind the arguments because they came before the 'target', so try to bind now that we've read everything. try { var paramTypes = binder.GetParameterTypes(target); arguments = BindTypes(ref argumentsToken, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } message = argumentBindingException != null ? new InvocationBindingFailureMessage(invocationId, target, argumentBindingException) : BindStreamInvocationMessage(invocationId, target, arguments, hasArguments, streamIds); } break; case HubProtocolConstants.StreamItemMessageType: if (invocationId is null) { throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'."); } if (hasItemsToken) { try { var returnType = binder.GetStreamItemType(invocationId); item = BindType(ref itemsToken, returnType); } catch (JsonException ex) { message = new StreamBindingFailureMessage(invocationId, ExceptionDispatchInfo.Capture(ex)); break; } } message = BindStreamItemMessage(invocationId, item, hasItem); break; case HubProtocolConstants.CompletionMessageType: if (invocationId is null) { throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'."); } if (hasResultToken) { var returnType = binder.GetReturnType(invocationId); result = BindType(ref resultToken, returnType); } message = BindCompletionMessage(invocationId, error, result, hasResult); break; case HubProtocolConstants.CancelInvocationMessageType: message = BindCancelInvocationMessage(invocationId); break; case HubProtocolConstants.PingMessageType: return(PingMessage.Instance); case HubProtocolConstants.CloseMessageType: return(BindCloseMessage(error, allowReconnect)); case null: throw new InvalidDataException($"Missing required property '{TypePropertyName}'."); default: // Future protocol changes can add message types, old clients can ignore them return(null); } return(ApplyHeaders(message, headers)); } catch (JsonException jrex) { throw new InvalidDataException("Error reading JSON.", jrex); } }