Exemple #1
0
        public Task <ContinueRoutineResult> ContinueAsync(
            MethodContinuationData data,
            InvocationPreferences preferences)
        {
            var message = new Message
            {
                Type      = MessageType.Response,
                DeliverAt = data.ContinueAt
            };

            MethodContinuationDataTransformer.Write(message, data, _serializer);

            var result = new ContinueRoutineResult();

            if (preferences.LockMessage)
            {
                result.MessageHandle = new MessageHandle(message, _messageHub);
            }
            else
            {
                _messageHub.Schedule(message);
            }

            return(Task.FromResult(result));
        }
 public TransitionCarrier(
     MethodContinuationData routineContinuationData,
     ICommunicatorMessage message)
 {
     _methodContinuationData = routineContinuationData;
     Message = message;
 }
 public static void Write(Message message, MethodContinuationData data, ISerializer serializer)
 {
     message.Data["IntentId"]            = data.IntentId;
     message.Data["Service"]             = data.Service.Clone();
     message.Data["Method"]              = data.Method.Clone();
     message.Data["TaskId"]              = data.TaskId;
     message.Data["Continuation:Format"] = data.State?.Format;
     message.Data["Continuation:State"]  = data.State?.State;
     message.Data["Format"]              = serializer.Format;
     message.Data["Result"]              = serializer.SerializeToString(data.Result);
     message.Data["Caller"]              = data.Caller?.Clone();
 }
        public async Task <ContinueRoutineResult> ContinueAsync(
            MethodContinuationData data,
            InvocationPreferences preferences)
        {
            var queueName = _settings.QueueName
                            .Replace("{serviceName}", data.Service.Name)
                            .Replace("{methodName}", data.Method.Name);

            // TODO: declare once? what's the penalty?
            _channel.QueueDeclare(
                queueName,
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: null);

            var properties = CreateMessageProperties(_channel);

            properties.Type = MessageTypes.Continue;
            SetIntentId(properties, data.IntentId);
            SetFormat(properties, _serializer);
            properties.Headers.Add("X-Service-Name", data.Service.Name);
            properties.Headers.Add("X-Method-Name", data.Method.Name);

            // This requires the Message Delay plugin.
            if (data.ContinueAt.HasValue && (Traits & CommunicationTraits.ScheduledDelivery) != default)
            {
                var delay = data.ContinueAt.Value - DateTimeOffset.Now;
                if (delay > TimeSpan.Zero)
                {
                    properties.Headers.Add("x-delay", (int)delay.TotalMilliseconds);
                }
            }

            var payload = SerializePayload(data);

            _channel.BasicPublish(
                exchange: "",
                routingKey: queueName,
                basicProperties: properties,
                body: payload);

            _channel.WaitForConfirms(); // no async version :(

            return(new ContinueRoutineResult
            {
            });
        }
Exemple #5
0
        public async Task <ContinueRoutineResult> ContinueAsync(
            MethodContinuationData data,
            InvocationPreferences preferences)
        {
            HttpResponseMessage response;

            using (var requestContent = CreateContent(data))
            {
                requestContent.Headers.TryAddWithoutValidation(DasyncHttpHeaders.Envelope, "continue");
                requestContent.Headers.TryAddWithoutValidation(DasyncHttpHeaders.IntentId, data.IntentId);
                AddAsyncHeader(requestContent.Headers, preferAsync: true);
                AddCallerHeaders(requestContent.Headers, data.Caller);
                AddRetryHeader(requestContent.Headers, retryCount: 0);

                var url = _urlTemplate
                          .Replace("{serviceName}", data.Service.Name)
                          .Replace("{methodName}", data.Method.Name)
                          + "/" + data.Method.IntentId;

                if (!string.IsNullOrEmpty(data.Method.ETag))
                {
                    url += "?etag=" + data.Method.ETag;
                }

                response = await _httpClient.PostAsync(url, requestContent);
            }
            using (response)
            {
                if ((int)response.StatusCode == DasyncHttpCodes.Scheduled)
                {
                    return(new ContinueRoutineResult
                    {
                    });
                }

                if ((int)response.StatusCode == DasyncHttpCodes.Deduplicated)
                {
                    return(new ContinueRoutineResult
                    {
                    });
                }
                throw new Exception(); // TODO: add info
            }
        }
Exemple #6
0
        public async Task <ContinueRoutineResult> ContinueAsync(MethodContinuationData data, ICommunicatorMessage message)
        {
            var serviceReference = _serviceResolver.Resolve(data.Service);
            var methodReference  = _methodResolver.Resolve(serviceReference.Definition, data.Method);

            var behaviorSettings = _communicationSettingsProvider.GetMethodSettings(methodReference.Definition);

            //-------------------------------------------------------------------------------
            // MESSGE DE-DUPLICATION
            //-------------------------------------------------------------------------------

            if (behaviorSettings.Deduplicate &&
                !message.CommunicatorTraits.HasFlag(CommunicationTraits.MessageDeduplication) &&
                (message.IsRetry != false || string.IsNullOrEmpty(message.RequestId)))
            {
                // TODO: if has message de-dup'er, check if a dedup
                // return new InvokeRoutineResult { Outcome = InvocationOutcome.Deduplicated };
            }

            //-------------------------------------------------------------------------------
            // UNIT OF WORK
            //-------------------------------------------------------------------------------

            // TODO: Unit of Work - check if there is cached/stored data
            if (message.IsRetry != false)
            {
                // TODO: If has entities in transitions and they have been already committed,
                // skip method transition and re-try sending commands and events.
            }

            //-------------------------------------------------------------------------------
            // DELEGATION TO RESILIENT COMMUNICATOR
            //-------------------------------------------------------------------------------

            IMessageHandle messageHandle = null;

            if (behaviorSettings.Persistent && message.CommunicatorTraits.HasFlag(CommunicationTraits.Volatile))
            {
                // TODO: check if can poll for result! (think about continuation and sync invocation)

                var preferredCommunicator = _communicatorProvider.GetCommunicator(data.Service, data.Method);
                if (message.CommunicatorType != preferredCommunicator.Type &&
                    !preferredCommunicator.Traits.HasFlag(CommunicationTraits.Volatile))
                {
                    var preferences = new InvocationPreferences
                    {
                        LockMessage = behaviorSettings.RunInPlace &&
                                      preferredCommunicator.Traits.HasFlag(CommunicationTraits.MessageLockOnPublish)
                    };

                    var invocationResult = await preferredCommunicator.ContinueAsync(data, preferences);

                    if (!preferences.LockMessage || invocationResult.MessageHandle == null)
                    {
                        return(new ContinueRoutineResult {
                        });
                    }

                    // NOTE: will run synchronously below
                    messageHandle = invocationResult.MessageHandle;
                }
            }

            //-------------------------------------------------------------------------------
            // RUN METHOD TRANSITION
            //-------------------------------------------------------------------------------

            // A communication method may not support a scheduled message delivery.
            if (data.ContinueAt.HasValue && data.ContinueAt.Value > DateTimeOffset.Now)
            {
                var delay = data.ContinueAt.Value - DateTimeOffset.Now;
                if (delay > TimeSpan.Zero)
                {
                    await Task.Delay(delay);
                }
            }

            try
            {
@TryRun:
                var adapter = new TransitionCarrier(data, message);

                MethodExecutionState methodState = DecodeContinuationData(data.State);
                if (methodState == null)
                {
                    var stateStorage = _methodStateStorageProvider.GetStorage(data.Service, data.Method, returnNullIfNotFound: true);
                    if (stateStorage == null)
                    {
                        throw new InvalidOperationException($"Cannot resume method '{data.Service}'.{data.Method} due to absence of a persistence mechanism.");
                    }

                    // TODO: a method can be already completed or transitioned (duplicate messages?) - discard transition?
                    methodState = await stateStorage.ReadStateAsync(data.Service, data.Method, default);
                }

                adapter.SetMethodExecutionState(methodState, _valueContainerCopier);

                InvokeRoutineResult result;
                try
                {
                    var transitionDescriptor = new TransitionDescriptor {
                        Type = TransitionType.ContinueRoutine
                    };
                    result = await RunRoutineAsync(adapter, transitionDescriptor, default);
                }
                catch (ConcurrentTransitionException)
                {
                    goto TryRun;
                }

                if (result.Outcome == InvocationOutcome.Complete && !string.IsNullOrEmpty(data.Method.IntentId))
                {
                    _routineCompletionSink.OnRoutineCompleted(data.Service, data.Method, data.Method.IntentId, result.Result);
                }

                if (messageHandle != null)
                {
                    await messageHandle.Complete();
                }

                return(new ContinueRoutineResult
                {
                });
            }
            catch
            {
                messageHandle?.ReleaseLock();
                throw;
            }
        }
        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;
            }
        }