예제 #1
0
        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");
        }
예제 #2
0
        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);
            }
        }
예제 #3
0
        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);
            }
        }
예제 #4
0
        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.");
        }
예제 #5
0
        /// <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);
        }
예제 #6
0
        /// <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);
            }
        }
예제 #7
0
        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);
            }
        }