private Dictionary <string, string> ReadHeaders(JsonTextReader reader) { var headers = new Dictionary <string, string>(StringComparer.Ordinal); if (reader.TokenType != JsonToken.StartObject) { throw new InvalidDataException($"Expected '{HeadersPropertyName}' to be of type {JTokenType.Object}."); } while (reader.Read()) { switch (reader.TokenType) { case JsonToken.PropertyName: var propertyName = reader.Value.ToString(); JsonUtils.CheckRead(reader); if (reader.TokenType != JsonToken.String) { throw new InvalidDataException($"Expected header '{propertyName}' to be of type {JTokenType.String}."); } headers[propertyName] = reader.Value?.ToString(); break; case JsonToken.Comment: break; case JsonToken.EndObject: return(headers); } } throw new JsonReaderException("Unexpected end when reading message headers"); }
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); } }
public static NegotiationResponse ParseResponse(Stream content) { try { using (var reader = JsonUtils.CreateJsonTextReader(new StreamReader(content))) { JsonUtils.CheckRead(reader); JsonUtils.EnsureObjectStart(reader); string connectionId = null; List <AvailableTransport> availableTransports = null; var completed = false; while (!completed && JsonUtils.CheckRead(reader)) { switch (reader.TokenType) { case JsonToken.PropertyName: var memberName = reader.Value.ToString(); switch (memberName) { case ConnectionIdPropertyName: connectionId = JsonUtils.ReadAsString(reader, ConnectionIdPropertyName); break; case AvailableTransportsPropertyName: JsonUtils.CheckRead(reader); JsonUtils.EnsureArrayStart(reader); availableTransports = new List <AvailableTransport>(); while (JsonUtils.CheckRead(reader)) { if (reader.TokenType == JsonToken.StartObject) { availableTransports.Add(ParseAvailableTransport(reader)); } else if (reader.TokenType == JsonToken.EndArray) { break; } } break; default: reader.Skip(); break; } break; case JsonToken.EndObject: completed = true; break; default: throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading negotiation response JSON."); } } if (connectionId == null) { throw new InvalidDataException($"Missing required property '{ConnectionIdPropertyName}'."); } if (availableTransports == null) { throw new InvalidDataException($"Missing required property '{AvailableTransportsPropertyName}'."); } return(new NegotiationResponse { ConnectionId = connectionId, AvailableTransports = availableTransports }); } } catch (Exception ex) { throw new InvalidDataException("Invalid negotiation response received.", ex); } }
private static AvailableTransport ParseAvailableTransport(JsonTextReader reader) { var availableTransport = new AvailableTransport(); while (JsonUtils.CheckRead(reader)) { switch (reader.TokenType) { case JsonToken.PropertyName: var memberName = reader.Value.ToString(); switch (memberName) { case TransportPropertyName: availableTransport.Transport = JsonUtils.ReadAsString(reader, TransportPropertyName); break; case TransferFormatsPropertyName: JsonUtils.CheckRead(reader); JsonUtils.EnsureArrayStart(reader); var completed = false; availableTransport.TransferFormats = new List <string>(); while (!completed && JsonUtils.CheckRead(reader)) { switch (reader.TokenType) { case JsonToken.String: availableTransport.TransferFormats.Add(reader.Value.ToString()); break; case JsonToken.EndArray: completed = true; break; default: throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading transfer formats JSON."); } } break; default: reader.Skip(); break; } break; case JsonToken.EndObject: if (availableTransport.Transport == null) { throw new InvalidDataException($"Missing required property '{TransportPropertyName}'."); } if (availableTransport.TransferFormats == null) { throw new InvalidDataException($"Missing required property '{TransferFormatsPropertyName}'."); } return(availableTransport); default: throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading available transport JSON."); } } throw new InvalidDataException("Unexpected end when reading JSON."); }
/// <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. Message content: {GetPayloadAsString()}"); } } if (protocol == null) { throw new InvalidDataException($"Missing required property '{ProtocolPropertyName}'. Message content: {GetPayloadAsString()}"); } if (protocolVersion == null) { throw new InvalidDataException($"Missing required property '{ProtocolVersionPropertyName}'. Message content: {GetPayloadAsString()}"); } requestMessage = new HandshakeRequestMessage(protocol, protocolVersion.Value); } } finally { Utf8BufferTextReader.Return(textReader); } // For error messages, we want to print the payload as text string GetPayloadAsString() { // REVIEW: Should we show hex for binary charaters? return(Encoding.UTF8.GetString(payload.ToArray())); } return(true); }
/// <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("Expected a handshake response from the server."); 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); } }
public static NegotiationResponse ParseResponse(Stream content) { try { using (var reader = JsonUtils.CreateJsonTextReader(new StreamReader(content))) { JsonUtils.CheckRead(reader); JsonUtils.EnsureObjectStart(reader); string connectionId = null; string url = null; string accessToken = null; List <AvailableTransport> availableTransports = null; var completed = false; while (!completed && JsonUtils.CheckRead(reader)) { switch (reader.TokenType) { case JsonToken.PropertyName: var memberName = reader.Value.ToString(); switch (memberName) { case UrlPropertyName: url = JsonUtils.ReadAsString(reader, UrlPropertyName); break; case AccessTokenPropertyName: accessToken = JsonUtils.ReadAsString(reader, AccessTokenPropertyName); break; case ConnectionIdPropertyName: connectionId = JsonUtils.ReadAsString(reader, ConnectionIdPropertyName); break; case AvailableTransportsPropertyName: JsonUtils.CheckRead(reader); JsonUtils.EnsureArrayStart(reader); availableTransports = new List <AvailableTransport>(); while (JsonUtils.CheckRead(reader)) { if (reader.TokenType == JsonToken.StartObject) { availableTransports.Add(ParseAvailableTransport(reader)); } else if (reader.TokenType == JsonToken.EndArray) { break; } } break; case ProtocolVersionPropertyName: throw new InvalidOperationException("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details."); default: reader.Skip(); break; } break; case JsonToken.EndObject: completed = true; break; default: throw new InvalidDataException($"Unexpected token '{reader.TokenType}' when reading negotiation response JSON."); } } if (url == null) { // if url isn't specified, connectionId and available transports are required if (connectionId == null) { throw new InvalidDataException($"Missing required property '{ConnectionIdPropertyName}'."); } if (availableTransports == null) { throw new InvalidDataException($"Missing required property '{AvailableTransportsPropertyName}'."); } } return(new NegotiationResponse { ConnectionId = connectionId, Url = url, AccessToken = accessToken, AvailableTransports = availableTransports }); } } catch (Exception ex) { throw new InvalidDataException("Invalid negotiation response received.", ex); } }