/// <summary> /// Creates a new <see cref="HandshakeRequestMessage"/> from the specified serialized representation. /// </summary> /// <param name="buffer">The serialized representation of the message.</param> /// <param name="requestMessage">When this method returns, contains the parsed message.</param> /// <returns>A value that is <c>true</c> if the <see cref="HandshakeRequestMessage"/> was successfully parsed; otherwise, <c>false</c>.</returns> public static bool TryParseRequestMessage(ref ReadOnlySequence <byte> buffer, out HandshakeRequestMessage requestMessage) { if (!TextMessageParser.TryParseMessage(ref buffer, out var payload)) { requestMessage = null; return(false); } var textReader = Utf8BufferTextReader.Get(payload); try { using (var reader = JsonUtils.CreateJsonTextReader(textReader)) { JsonUtils.CheckRead(reader); JsonUtils.EnsureObjectStart(reader); string protocol = null; int? protocolVersion = null; var completed = false; while (!completed && JsonUtils.CheckRead(reader)) { switch (reader.TokenType) { case JsonToken.PropertyName: var memberName = reader.Value.ToString(); switch (memberName) { case ProtocolPropertyName: protocol = JsonUtils.ReadAsString(reader, ProtocolPropertyName); break; case ProtocolVersionPropertyName: protocolVersion = JsonUtils.ReadAsInt32(reader, ProtocolVersionPropertyName); break; default: reader.Skip(); break; } break; case JsonToken.EndObject: completed = true; break; default: throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading handshake request JSON."); } } if (protocol == null) { throw new InvalidDataException($"Missing required property '{ProtocolPropertyName}'."); } if (protocolVersion == null) { throw new InvalidDataException($"Missing required property '{ProtocolVersionPropertyName}'."); } requestMessage = new HandshakeRequestMessage(protocol, protocolVersion.Value); } } finally { Utf8BufferTextReader.Return(textReader); } return(true); }
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; string[]? streamIds = null; JArray?argumentsToken = null; ExceptionDispatchInfo? argumentBindingException = null; Dictionary <string, string>?headers = null; var completed = false; var allowReconnect = 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 StreamIdsPropertyName: JsonUtils.CheckRead(reader); if (reader.TokenType != JsonToken.StartArray) { throw new InvalidDataException($"Expected '{StreamIdsPropertyName}' to be of type {JTokenType.Array}."); } var newStreamIds = new List <string>(); reader.Read(); while (reader.TokenType != JsonToken.EndArray) { newStreamIds.Add(reader.Value?.ToString() ?? throw new InvalidDataException($"Null value for '{StreamIdsPropertyName}' is not valid.")); reader.Read(); } streamIds = newStreamIds.ToArray(); break; case TargetPropertyName: target = JsonUtils.ReadAsString(reader, TargetPropertyName); break; case ErrorPropertyName: error = JsonUtils.ReadAsString(reader, ErrorPropertyName); break; case AllowReconnectPropertyName: allowReconnect = JsonUtils.ReadAsBoolean(reader, AllowReconnectPropertyName); break; case ResultPropertyName: hasResult = true; if (string.IsNullOrEmpty(invocationId)) { JsonUtils.CheckRead(reader); // 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); if (!JsonUtils.ReadForType(reader, returnType)) { throw new JsonReaderException("Unexpected end when reading JSON"); } result = PayloadSerializer.Deserialize(reader, returnType); } break; case ItemPropertyName: JsonUtils.CheckRead(reader); hasItem = true; string?id = null; if (!string.IsNullOrEmpty(invocationId)) { id = invocationId; } else { // If we don't have an id yet then we need to store it as a JToken to parse later itemToken = JToken.Load(reader); break; } try { var itemType = binder.GetStreamItemType(id); item = PayloadSerializer.Deserialize(reader, itemType); } catch (Exception ex) { return(new StreamBindingFailureMessage(id, ExceptionDispatchInfo.Capture(ex))); } break; case ArgumentsPropertyName: JsonUtils.CheckRead(reader); int initialDepth = reader.Depth; 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); // 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.Depth == initialDepth && reader.TokenType == JsonToken.StartArray || reader.Depth > initialDepth) { JsonUtils.CheckRead(reader); } } } 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 (target is null) { throw new InvalidDataException($"Missing required property '{TargetPropertyName}'."); } 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, streamIds, binder); } break; case HubProtocolConstants.StreamInvocationMessageType: { if (target is null) { throw new InvalidDataException($"Missing required property '{TargetPropertyName}'."); } 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, streamIds, binder); } break; case HubProtocolConstants.StreamItemMessageType: if (invocationId is null) { throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'."); } if (itemToken != null) { try { var itemType = binder.GetStreamItemType(invocationId); item = itemToken.ToObject(itemType, PayloadSerializer); } catch (Exception ex) { message = new StreamBindingFailureMessage(invocationId, ExceptionDispatchInfo.Capture(ex)); break; }; } message = BindStreamItemMessage(invocationId, item, hasItem, binder); break; case HubProtocolConstants.CompletionMessageType: if (invocationId is null) { throw new InvalidDataException($"Missing required property '{InvocationIdPropertyName}'."); } 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, 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 (JsonReaderException jrex) { throw new InvalidDataException("Error reading JSON.", jrex); } }
/// <summary> /// Creates a new <see cref="HandshakeResponseMessage"/> from the specified serialized representation. /// </summary> /// <param name="buffer">The serialized representation of the message.</param> /// <param name="responseMessage">When this method returns, contains the parsed message.</param> /// <returns>A value that is <c>true</c> if the <see cref="HandshakeResponseMessage"/> was successfully parsed; otherwise, <c>false</c>.</returns> public static bool TryParseResponseMessage(ref ReadOnlySequence <byte> buffer, out HandshakeResponseMessage responseMessage) { if (!TextMessageParser.TryParseMessage(ref buffer, out var payload)) { responseMessage = null; return(false); } var textReader = Utf8BufferTextReader.Get(payload); try { using (var reader = JsonUtils.CreateJsonTextReader(textReader)) { JsonUtils.CheckRead(reader); JsonUtils.EnsureObjectStart(reader); int? minorVersion = null; string error = null; var completed = false; while (!completed && JsonUtils.CheckRead(reader)) { switch (reader.TokenType) { case JsonToken.PropertyName: var memberName = reader.Value.ToString(); switch (memberName) { case TypePropertyName: // a handshake response does not have a type // check the incoming message was not any other type of message throw new InvalidDataException("Handshake response should not have a 'type' value."); case ErrorPropertyName: error = JsonUtils.ReadAsString(reader, ErrorPropertyName); break; case MinorVersionPropertyName: minorVersion = JsonUtils.ReadAsInt32(reader, MinorVersionPropertyName); break; default: reader.Skip(); break; } break; case JsonToken.EndObject: completed = true; break; default: throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading handshake response JSON."); } } ; responseMessage = new HandshakeResponseMessage(minorVersion, error); return(true); } } finally { Utf8BufferTextReader.Return(textReader); } }
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) { try { var paramTypes = binder.GetParameterTypes(target); arguments = BindArguments(argumentsToken, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } message = BindInvocationMessage(invocationId, target, argumentBindingException, arguments, hasArguments, binder); } break; case HubProtocolConstants.StreamInvocationMessageType: { if (argumentsToken != null) { try { var paramTypes = binder.GetParameterTypes(target); arguments = BindArguments(argumentsToken, paramTypes); } catch (Exception ex) { argumentBindingException = ExceptionDispatchInfo.Capture(ex); } } message = BindStreamInvocationMessage(invocationId, target, argumentBindingException, 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); } }