Exemplo n.º 1
0
        public Task <MethodExecutionState> ReadStateAsync(
            ServiceId serviceId,
            PersistedMethodId methodId,
            CancellationToken ct)
        {
            MethodExecutionState executionState;
            string serializedFlowContext;
            string serializedContinuation;
            string continuationStateFormat;

            byte[] continuationStateData;

            lock (_entryMap)
            {
                if (!_entryMap.TryGetValue(methodId.IntentId, out var entry))
                {
                    throw new StateNotFoundException(serviceId, methodId);
                }

                executionState = new MethodExecutionState
                {
                    Service     = serviceId,
                    Method      = methodId,
                    Caller      = entry.TryGetValue("Caller", out var callerObj) ? callerObj as CallerDescriptor : null,
                    MethodState = new SerializedValueContainer((string)entry["State"], _serializer)
                };
                executionState.Method.ETag = entry.ETag;

                serializedFlowContext   = entry.TryGetValue("FlowContext", out var flowContextObj) ? flowContextObj as string : null;
                serializedContinuation  = entry.TryGetValue("Continuation", out var continuationObj) ? continuationObj as string : null;
                continuationStateFormat = entry.TryGetValue("Continuation:Format", out var continuationStateFormatObj) ? continuationStateFormatObj as string : null;
                continuationStateData   = entry.TryGetValue("Continuation:State", out var continuationStateDataObj) ? continuationStateDataObj as byte[] : null;
            }

            if (!string.IsNullOrEmpty(serializedFlowContext))
            {
                executionState.FlowContext = _serializer.Deserialize <Dictionary <string, string> >(serializedFlowContext);
            }

            if (!string.IsNullOrEmpty(serializedContinuation))
            {
                executionState.Continuation = _serializer.Deserialize <ContinuationDescriptor>(serializedContinuation);
            }

            if (continuationStateData?.Length > 0)
            {
                executionState.ContinuationState = new SerializedMethodContinuationState
                {
                    Format = continuationStateFormat,
                    State  = continuationStateData
                };
            }

            return(Task.FromResult(executionState));
        }
Exemplo n.º 2
0
        public void Read(IObjectReconstructor reconstructor, ISerializer serializer)
        {
            var   stateStack = new Stack <State>();
            State state      = null;

            while (_jsonReader.Read())
            {
                switch (_jsonReader.TokenType)
                {
                case JsonToken.StartObject:
                {
                    if (state?.Info.Name != null && reconstructor.GetExpectedValueType(state.Info.Name) == typeof(IValueContainer))
                    {
                        var content    = new StringBuilder();
                        var textWriter = new StringWriter(content);
                        var jsonWriter = new JsonTextWriter(textWriter);
                        jsonWriter.WriteToken(_jsonReader, writeChildren: true);

                        var serializedContainer = new SerializedValueContainer(
                            content.ToString(), serializer);

                        reconstructor.OnValueStart(state.Info);
                        reconstructor.OnValue(serializedContainer);
                        reconstructor.OnValueEnd();

                        if (stateStack.Count > 0)
                        {
                            state = stateStack.Pop();

                            if (state.Info.IsCollection)
                            {
                                stateStack.Push(state);
                                state = new State();
                            }
                        }
                        else
                        {
                            state = null;
                        }
                    }
                    else
                    {
                        if (state == null)
                        {
                            state = new State();
                        }
                        state.HasMetadata = true;
                    }
                }
                break;

                case JsonToken.EndObject:
                {
                    if (state.ValueStarted && !state.ValueEnded)
                    {
                        reconstructor.OnValueEnd();
                        state.ValueEnded = true;
                    }
                    else if (!state.ValueStarted && (state.Info.ReferenceId != null || state.Info.SpecialId != null))
                    {
                        reconstructor.OnValueStart(state.Info);
                        reconstructor.OnValueEnd();
                    }

                    if (stateStack.Count > 0)
                    {
                        state = stateStack.Pop();

                        if (state.Info.IsCollection)
                        {
                            stateStack.Push(state);
                            state = new State();
                        }
                    }
                    else
                    {
                        state = null;
                    }
                }
                break;

                case JsonToken.PropertyName:
                {
                    var name = (string)_jsonReader.Value;
                    if (name[0] == '$')
                    {
                        if (name == "$id")
                        {
                            ReadObjectId(ref state.Info);
                        }
                        else if (name == "$itemCount")
                        {
                            ReadItemCount(ref state.Info);
                        }
                        else if (name == "$type")
                        {
                            state.Info.Type = ReadTypeInfo();
                        }
                        else if (name == "$itemType")
                        {
                            state.Info.IsCollection = true;

                            state.Info.ItemType = ReadTypeInfo();
                        }
                        else if (name == "$items")
                        {
                            state.Info.IsCollection = true;

                            if (!state.ValueStarted)
                            {
                                reconstructor.OnValueStart(state.Info);
                                state.ValueStarted = true;
                            }
                        }
                        else if (name == "$value")
                        {
                            if (!state.ValueStarted)
                            {
                                reconstructor.OnValueStart(state.Info);
                                state.ValueStarted = true;
                            }
                        }
                        else if (name == "$typeIsValue")
                        {
                            if (ReadTypeIsValue())
                            {
                                var value = state.Info.Type;
                                if (!state.ValueStarted)
                                {
                                    state.Info.Type = TypeSerializationInfo.Self;
                                    reconstructor.OnValueStart(state.Info);
                                    state.ValueStarted = true;
                                }
                                reconstructor.OnValue(value);
                            }
                        }
                        else
                        {
                            throw new InvalidOperationException($"Unknown directive '{name}'");
                        }
                    }
                    else
                    {
                        if (!state.ValueStarted)
                        {
                            reconstructor.OnValueStart(state.Info);
                            state.ValueStarted = true;
                        }
                        stateStack.Push(state);
                        state = new State
                        {
                            Info = new ValueInfo
                            {
                                Name = name
                            }
                        };
                    }
                }
                break;

                case JsonToken.StartArray:
                {
                    state.Info.IsCollection = true;

                    if (!state.ValueStarted)
                    {
                        reconstructor.OnValueStart(state.Info);
                        state.ValueStarted = true;
                    }
                    stateStack.Push(state);

                    state = new State();
                }
                break;

                case JsonToken.EndArray:
                {
                    state = stateStack.Pop();
                    if (!state.HasMetadata && state.ValueStarted)
                    {
                        reconstructor.OnValueEnd();
                        state.ValueEnded = true;
                    }
                }
                break;

                case JsonToken.Boolean:
                case JsonToken.Bytes:
                case JsonToken.Date:
                case JsonToken.Float:
                case JsonToken.Integer:
                case JsonToken.Null:
                case JsonToken.String:
                {
                    if (!state.HasMetadata)
                    {
                        reconstructor.OnValueStart(state.Info);
                    }

                    reconstructor.OnValue(_jsonReader.Value);

                    if (!state.HasMetadata)
                    {
                        reconstructor.OnValueEnd();

                        if (state.Info.Name != null)
                        {
                            state = stateStack.Pop();
                        }
                    }
                }
                break;

                default:
                    throw new InvalidOperationException(
                              $"Unexpected JSON token '{_jsonReader.TokenType}'.");
                }
            }
        }
Exemplo n.º 3
0
        public async Task HandleAsync(PathString basePath, HttpContext context, CancellationToken ct)
        {
            var isQueryRequest   = context.Request.Method == "GET";
            var isCommandRequest = context.Request.Method == "POST";

            if (!isQueryRequest && !isCommandRequest)
            {
                await ReplyWithTextError(context.Response, 405, "Only GET and POST verbs are allowed");

                return;
            }

            var headers = context.Response.Headers;

            headers.Add(DasyncHttpHeaders.PoweredBy, "D-ASYNC");

            var basePathSegmentCount = basePath.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length;
            var pathSegments         = context.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Skip(basePathSegmentCount).ToArray();

            if (pathSegments.Length == 0)
            {
                await ReplyWithTextError(context.Response, 404, "Empty request URL");

                return;
            }

            var serviceId = new ServiceId {
                Name = pathSegments[0]
            };

            if (!_serviceResolver.TryResolve(serviceId, out var serviceReference))
            {
                await ReplyWithTextError(context.Response, 404, $"Service '{serviceId.Name}' is not registered");

                return;
            }

            if (serviceReference.Definition.Type == ServiceType.External)
            {
                await ReplyWithTextError(context.Response, 404, $"Cannot invoke external service '{serviceReference.Definition.Name}' on your behalf");

                return;
            }

            if (pathSegments.Length == 1)
            {
                await ReplyWithTextError(context.Response, 404, "The request URL does not contain a service method");

                return;
            }

            var methodId = new MethodId {
                Name = pathSegments[1]
            };
            string methodIntentId = null;

            if (pathSegments.Length == 3)
            {
                methodIntentId = pathSegments[2];

                if (isQueryRequest)
                {
                    await HandleResultPoll(context, serviceReference.Id, methodId, methodIntentId);

                    return;
                }
            }

            if (pathSegments.Length > 3)
            {
                await ReplyWithTextError(context.Response, 404, "The request URL contains extra segments");

                return;
            }

            var         contentType = context.Request.GetContentType();
            ISerializer serializer;

            try
            {
                serializer = GetSerializer(contentType, isQueryRequest);
            }
            catch (ArgumentException)
            {
                await ReplyWithTextError(context.Response, 406, $"The Content-Type '{contentType.MediaType}' is not supported");

                return;
            }

            if (!_methodResolver.TryResolve(serviceReference.Definition, methodId, out var methodReference))
            {
                await ReplyWithTextError(context.Response, 404, $"The service '{serviceReference.Definition.Name}' does not have method '{methodId.Name}'");

                return;
            }

            if (isQueryRequest && !methodReference.Definition.IsQuery)
            {
                await ReplyWithTextError(context.Response, 404, $"The method '{methodId.Name}' of service '{serviceReference.Definition.Name}' is a command, but not a query, thus must be invoked with the POST verb");

                return;
            }

            MethodInvocationData   invokeData   = null;
            MethodContinuationData continueData = null;
            IValueContainer        parametersContainer;
            bool compressResponse    = false;
            bool respondWithEnvelope = false;

            if (isQueryRequest)
            {
                var inputObject = new JObject();

                foreach (var kvPair in context.Request.Query)
                {
                    if (kvPair.Value.Count == 0)
                    {
                        continue;
                    }

                    var parameterName = kvPair.Key;

                    if (kvPair.Value.Count == 1)
                    {
                        var parameterValue = kvPair.Value[0];
                        inputObject.Add(parameterName, parameterValue);
                    }
                    else
                    {
                        var values = new JArray();
                        foreach (var value in kvPair.Value)
                        {
                            values.Add(value);
                        }
                        inputObject.Add(parameterName, values);
                    }
                }

                var parametersJson = inputObject.ToString();

                parametersContainer = new SerializedValueContainer(parametersJson, _jsonSerializer);

                invokeData = new MethodInvocationData
                {
                    Service    = serviceId,
                    Method     = methodId,
                    Parameters = parametersContainer,
                    Caller     = GetCaller(context.Request.Headers)
                };
            }
            else
            {
                var payloadStream = context.Request.Body;

                var encoding = context.Request.GetContentEncoding();
                if (!string.IsNullOrEmpty(encoding))
                {
                    if ("gzip".Equals(encoding, StringComparison.OrdinalIgnoreCase))
                    {
                        compressResponse = true;
                        payloadStream    = new GZipStream(payloadStream, CompressionMode.Decompress, leaveOpen: true);
                    }
                    else if ("deflate".Equals(encoding, StringComparison.OrdinalIgnoreCase))
                    {
                        payloadStream = new DeflateStream(payloadStream, CompressionMode.Decompress, leaveOpen: true);
                    }
                    else
                    {
                        await ReplyWithTextError(context.Response, 406, $"The Content-Encoding '{encoding}' is not supported");

                        return;
                    }
                }

                var envelopeType = context.Request.Headers.GetValue(DasyncHttpHeaders.Envelope);

                if (string.IsNullOrEmpty(envelopeType))
                {
                    var payload = await payloadStream.ToBytesAsync();

                    parametersContainer = new SerializedValueContainer(payload, serializer);

                    if (string.IsNullOrEmpty(methodIntentId))
                    {
                        invokeData = new MethodInvocationData
                        {
                            Service    = serviceId,
                            Method     = methodId,
                            Parameters = parametersContainer,
                            Caller     = GetCaller(context.Request.Headers)
                        };
                    }
                    else
                    {
                        continueData = new MethodContinuationData
                        {
                            Service = serviceId,
                            Method  = methodId.CopyTo(new PersistedMethodId()),
                            Result  = parametersContainer,
                            Caller  = GetCaller(context.Request.Headers)
                        };
                        continueData.Method.IntentId = methodIntentId;
                        // TODO: get ETag from the query string
                    }
                }
                else if (envelopeType.Equals("invoke", StringComparison.OrdinalIgnoreCase))
                {
                    respondWithEnvelope = true;
                    invokeData          = await DeserializeAsync <MethodInvocationData>(serializer, payloadStream);
                }
                else if (envelopeType.Equals("continue", StringComparison.OrdinalIgnoreCase))
                {
                    respondWithEnvelope = true;
                    continueData        = await DeserializeAsync <MethodContinuationData>(serializer, payloadStream);
                }
                else
                {
                    await ReplyWithTextError(context.Response, 406, $"Unknown envelope type '{envelopeType}'");

                    return;
                }
            }

            var intentId              = context.Request.Headers.GetValue(DasyncHttpHeaders.IntentId) ?? _idGenerator.NewId();
            var externalRequestId     = context.Request.Headers.GetValue(DasyncHttpHeaders.RequestId);
            var externalCorrelationId = context.Request.Headers.GetValue(DasyncHttpHeaders.CorrelationId);
            var isRetry = context.Request.Headers.IsRetry();

            var rfc7240Preferences             = context.Request.Headers.GetRFC7240Preferences();
            var isHttpRequestBlockingExecution = !(rfc7240Preferences.RespondAsync == true);
            var waitTime = rfc7240Preferences.Wait;

            if (waitTime > MaxLongPollTime)
            {
                waitTime = MaxLongPollTime;
            }
            var waitForResult = isHttpRequestBlockingExecution || waitTime > TimeSpan.Zero;

            var communicatorMessage = new HttpCommunicatorMessage
            {
                IsRetry       = isRetry,
                RequestId     = externalRequestId,
                WaitForResult = waitForResult
            };

            if (invokeData != null)
            {
                if (invokeData.IntentId == null)
                {
                    invokeData.IntentId = intentId;
                }

                var invokeTask = _localTransitionRunner.RunAsync(invokeData, communicatorMessage);

                if (isHttpRequestBlockingExecution)
                {
                    var invocationResult = await invokeTask;

                    if (invocationResult.Outcome == InvocationOutcome.Complete)
                    {
                        await RespondWithMethodResult(context.Response, invocationResult.Result, serializer, respondWithEnvelope, compressResponse);

                        return;
                    }

                    if (waitForResult)
                    {
                        var taskResult = await TryWaitForResultAsync(
                            serviceReference.Id,
                            methodId, intentId, waitTime);

                        if (taskResult != null)
                        {
                            await RespondWithMethodResult(context.Response, taskResult, serializer, respondWithEnvelope, compressResponse);

                            return;
                        }
                    }
                }
                else
                {
                    // TODO: continue 'invokeTask' and handle exceptions in background
                }

                communicatorMessage.WaitForResult = false;

                var location = string.Concat(context.Request.Path, "/", intentId);
                context.Response.Headers.Add("Location", location);
                context.Response.Headers.Add(DasyncHttpHeaders.IntentId, intentId);
                context.Response.StatusCode = DasyncHttpCodes.Scheduled;
            }
            else if (continueData != null)
            {
                if (continueData.IntentId == null)
                {
                    continueData.IntentId = intentId;
                }

                var continueTask = _localTransitionRunner.ContinueAsync(continueData, communicatorMessage);
                // TODO: continue 'continueTask' in backgraound to handle exceptions

                context.Response.Headers.Add("Location", context.Request.Path.ToString());
                context.Response.Headers.Add(DasyncHttpHeaders.IntentId, continueData.Method.IntentId);
                context.Response.StatusCode = DasyncHttpCodes.Scheduled;
            }
        }