コード例 #1
0
ファイル: EventPublisher.cs プロジェクト: simis00873/Dasync
        public async Task PublishAsync(EventPublishData data, PublishPreferences preferences)
        {
            var eventDesc = new EventDescriptor {
                Service = data.Service, Event = data.Event
            };
            var subscribers = _eventSubscriber.GetSubscribers(eventDesc).ToList();

            if (subscribers.Count == 0)
            {
                return;
            }

            foreach (var subscriber in subscribers)
            {
                var invokeData = new MethodInvocationData
                {
                    IntentId    = _idGenerator.NewId(),
                    Service     = subscriber.Service,
                    Method      = subscriber.Method,
                    Parameters  = data.Parameters,
                    FlowContext = data.FlowContext,
                    Caller      = new CallerDescriptor(data.Service, data.Event, data.IntentId)
                };

                InvokeInBackground(invokeData, preferences);
            }
        }
コード例 #2
0
        /// <summary>
        /// 保存事件
        /// 作者:郭明
        /// 日期:2017年11月15日
        /// </summary>
        /// <param name="events"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public async Task <List <EventLogEntry> > SaveEventAsync(List <EventLogEntry> LogEntrys, IDbTransaction transaction)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException("transaction", $"A {typeof(DbTransaction).FullName} is required as a pre-requisite to save the event.");
            }
            var sqlParamtersList = new List <DynamicParameters>();

            foreach (var eventLogEntry in LogEntrys)
            {
                if (eventLogEntry.EventId <= 0)
                {
                    eventLogEntry.EventId = _uniqueIdGenerator.NewId();
                }

                var sqlParamters = new DynamicParameters();
                sqlParamters.Add("EventId", eventLogEntry.EventId, System.Data.DbType.Int64, System.Data.ParameterDirection.Input, 6);
                sqlParamters.Add("MessageId", eventLogEntry.MessageId, System.Data.DbType.StringFixedLength, System.Data.ParameterDirection.Input, 50);
                sqlParamters.Add("TraceId", eventLogEntry.TraceId, System.Data.DbType.StringFixedLength, System.Data.ParameterDirection.Input, 50);
                sqlParamters.Add("EventTypeName", eventLogEntry.EventTypeName, System.Data.DbType.StringFixedLength, System.Data.ParameterDirection.Input, 500);
                sqlParamters.Add("State", eventLogEntry.State, System.Data.DbType.Int32, System.Data.ParameterDirection.Input, 4);
                sqlParamters.Add("TimesSent", 0, System.Data.DbType.Int32, System.Data.ParameterDirection.Input, 4);
                sqlParamters.Add("CreationTime", DateTime.UtcNow, System.Data.DbType.Date, System.Data.ParameterDirection.Input, 4);
                sqlParamters.Add("Content", eventLogEntry.Content, System.Data.DbType.StringFixedLength, System.Data.ParameterDirection.Input);
                sqlParamtersList.Add(sqlParamters);
            }

            await transaction.Connection.ExecuteAsync($"insert into {_mySqlConfiguration.TablePrefix}EventLogs(EventId,MessageId,TraceId,EventTypeName,State,TimesSent,CreationTime,Content) values(@EventId,@MessageId,@TraceId,@EventTypeName,@State,@TimesSent,@CreationTime,@Content)",
                                                      sqlParamtersList,
                                                      transaction : transaction
                                                      );

            return(LogEntrys);
        }
コード例 #3
0
        public bool TryRegisterNew(object taskCompletionSource, out TriggerReference triggerReference)
        {
            if (taskCompletionSource == null)
            {
                throw new ArgumentNullException(nameof(taskCompletionSource));
            }
            if (!TaskCompletionSourceAccessor.IsTaskCompletionSource(taskCompletionSource))
            {
                throw new ArgumentException($"Input object must be a TaskCompletionSource`1, but got {taskCompletionSource.GetType()}", nameof(taskCompletionSource));
            }

            var task = TaskCompletionSourceAccessor.GetTask(taskCompletionSource);

            triggerReference = task.AsyncState as TriggerReference;
            if (triggerReference != null)
            {
                return(false);
            }

            triggerReference = new TriggerReference
            {
                Id = _numericIdGenerator.NewId()
            };
            task.SetAsyncState(triggerReference);

            return(true);
        }
コード例 #4
0
        public Task Execute <TParameters>(IProxy proxy, MethodInfo methodInfo, ref TParameters parameters)
            where TParameters : IValueContainer
        {
            var serviceProxyContext = (ServiceProxyContext)proxy.Context;

            var intent = new ExecuteRoutineIntent
            {
                Id         = _numericIdGenerator.NewId(),
                ServiceId  = serviceProxyContext.Descriptor.Id,
                MethodId   = _routineMethodIdProvider.GetId(methodInfo),
                Parameters = parameters
            };

            var taskResultType =
                // Dispose() does not return a task, and is the only exception.
#warning check if it's really IDisposable.Dispose
                methodInfo.ReturnType == typeof(void)
                ? TaskAccessor.VoidTaskResultType
                : TaskAccessor.GetTaskResultType(methodInfo.ReturnType);

            var taskState = new RoutineReference
            {
                IntentId = intent.Id
#warning must have id of actual routine for dynamic subscription (subscribe after a routine already scheduled).
            };

            var proxyTask = TaskAccessor.CreateTask(taskState, taskResultType);

            bool executeInline = !_transitionScope.IsActive || !IsCalledByRoutine(
                _transitionScope.CurrentMonitor.Context,
                // Skip 2 stack frames: current method and dynamically-generated proxy.
                // WARNING! DO NOT PUT 'new StackFrame()' into a helper method!
                new StackFrame(skipFrames: 2, fNeedFileInfo: false));

            if (executeInline)
            {
                ExecuteAndAwaitInBackground(intent, proxyTask);
            }
            else
            {
                _transitionScope.CurrentMonitor.RegisterIntent(intent, proxyTask);
            }

            return(proxyTask);
        }
コード例 #5
0
        internal void ScheduleRoutineFromEvent(EventSubscriberDescriptor eventSubscriberDescriptor, RoutineEventData raisedEventData)
        {
            var intentId = _idGenerator.NewId();

            var pregeneratedRoutineId = intentId.ToString();

            var routineDescriptor = new RoutineDescriptor
            {
                IntentId  = intentId,
                MethodId  = eventSubscriberDescriptor.MethodId,
                RoutineId = pregeneratedRoutineId
            };

            var eventData = new RoutineEventData
            {
                ServiceId = eventSubscriberDescriptor.ServiceId,
                Routine   = routineDescriptor,
                Caller    = new CallerDescriptor
                {
                    ServiceId = raisedEventData.ServiceId
                },
                Parameters = raisedEventData.Parameters
            };

            var eventEnvelope = new RoutineEventEnvelope
            {
                CloudEventsVersion = CloudEventsEnvelope.Version,
                EventType          = DasyncCloudEventsTypes.InvokeRoutine.Name,
                EventTypeVersion   = DasyncCloudEventsTypes.InvokeRoutine.Version,
                Source             = "/" + (raisedEventData.ServiceId?.ServiceName ?? ""),
                EventID            = intentId.ToString(),
                EventTime          = DateTimeOffset.Now,
                ContentType        = "application/json",
                Data = CloudEventsSerialization.Serialize(eventData)
            };

            var fileName = intentId.ToString() + ".json";
            var filePath = Path.Combine(_transitionsDirectory, fileName);
            var content  = CloudEventsSerialization.Serialize(eventEnvelope);

            File.WriteAllText(filePath, content, Encoding.UTF8);
        }
コード例 #6
0
        public CancellationTokenSourceState Register(CancellationTokenSource source)
        {
            var state = (CancellationTokenSourceState)source.GetState();

            if (state == null)
            {
                state = new CancellationTokenSourceState
                {
                    Id         = _idGenerator.NewId(),
                    CancelTime = source.GetCancellationTime()
                };
                source.SetState(state);
                TryAdd(state.Id, source);
            }
            else if (!TryGet(state.Id, out var otherSource))
            {
                TryAdd(state.Id, source);
            }
            return(state);
        }
コード例 #7
0
 public async Task <long> Test()
 {
     return(await Task.FromResult(uniqueIdGenerator.NewId()));
 }
コード例 #8
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;
            }
        }
コード例 #9
0
        private async void RunMessageInBackground(Message message)
        {
            if (message.DeliverAt.HasValue && message.DeliverAt > DateTime.UtcNow)
            {
                await Task.Delay(message.DeliverAt.Value - DateTime.UtcNow);
            }
            else
            {
                await Task.Yield();
            }

            var ct = CancellationToken.None;

            if (message.IsEvent)
            {
                var serviceId = Serializer.Deserialize <ServiceId>(message[nameof(ServiceId)]);
                var eventId   = Serializer.Deserialize <EventId>(message[nameof(EventId)]);
                var eventDesc = new EventDescriptor {
                    EventId = eventId, ServiceId = serviceId
                };
                var subscribers = DataStore.GetEventSubscribers(eventDesc);

                foreach (var subscriber in subscribers)
                {
                    var routineId = Interlocked.Increment(ref DataStore.RoutineCounter);

                    var routineRecord = new RoutineStateRecord
                    {
                        ETag       = DateTime.UtcNow.Ticks.ToString("X16"),
                        Id         = routineId.ToString(),
                        Completion = new TaskCompletionSource <string>()
                    };

                    lock (DataStore.Routines)
                    {
                        DataStore.Routines.Add(routineRecord.Id, routineRecord);
                    }

                    var transitionDescriptor = new TransitionDescriptor
                    {
                        Type = TransitionType.InvokeRoutine,
                        ETag = routineRecord.ETag
                    };

                    var routineDescriptor = new RoutineDescriptor
                    {
                        MethodId  = subscriber.MethodId,
                        IntentId  = _uniqueIdGenerator.NewId(),
                        RoutineId = routineRecord.Id,
                        ETag      = routineRecord.ETag
                    };

                    var invokeRoutineMessage = new Message
                    {
                        //["IntentId"] = _serializer.Serialize(intent.Id),
                        [nameof(TransitionDescriptor)] = Serializer.SerializeToString(transitionDescriptor),
                        [nameof(ServiceId)]            = Serializer.SerializeToString(subscriber.ServiceId),
                        [nameof(RoutineDescriptor)]    = Serializer.SerializeToString(routineDescriptor),
                        ["Parameters"] = message["Parameters"]
                    };

                    DataStore.ScheduleMessage(invokeRoutineMessage);
                }
            }
            else
            {
                for (; ;)
                {
                    using (_serviceProviderScope.New())
                    {
                        var carrier = new TransitionCarrier(this, message);
                        carrier.Initialize();

                        //var transitionInfo = await data.GetTransitionDescriptorAsync(ct);
                        //if (transitionInfo.Type == TransitionType.InvokeRoutine ||
                        //    transitionInfo.Type == TransitionType.ContinueRoutine)
                        //{
                        //    var routineDescriptor = await data.GetRoutineDescriptorAsync(ct);

                        //    if (!string.IsNullOrEmpty(transitionInfo.ETag) &&
                        //        transitionInfo.ETag != routineDescriptor.ETag)
                        //    {
                        //        // Ignore - stale duplicate message
                        //        return;
                        //    }
                        //}

                        try
                        {
                            await _transitionRunner.RunAsync(carrier, ct);

                            break;
                        }
                        catch (ConcurrentRoutineExecutionException)
                        {
                            // re-try
                            continue;
                        }
                    }
                }
            }
        }
コード例 #10
0
 public async Task <long> Test()
 {
     return(uniqueIdGenerator.NewId());
 }
コード例 #11
0
        public Task Execute <TParameters>(IProxy proxy, MethodInfo methodInfo, ref TParameters parameters)
            where TParameters : IValueContainer
        {
            var serviceProxyContext = (ServiceProxyContext)proxy.Context;
            var methodDefinition    = serviceProxyContext.Definition.FindMethod(methodInfo);

            if (methodDefinition == null || methodDefinition.IsIgnored)
            {
                var invoker = _methodInvokerFactory.Create(methodInfo);
                return(invoker.Invoke(proxy, parameters));
            }

            var intent = new ExecuteRoutineIntent
            {
                Id         = _idGenerator.NewId(),
                Service    = serviceProxyContext.Descriptor.Id,
                Method     = _routineMethodIdProvider.GetId(methodInfo),
                Parameters = parameters
            };

            Type taskResultType =
                methodInfo.ReturnType == typeof(void)
                ? TaskAccessor.VoidTaskResultType
                : TaskAccessor.GetTaskResultType(methodInfo.ReturnType);

            var taskState = new RoutineReference
            {
                ServiceId = intent.Service,
                MethodId  = intent.Method,
                IntentId  = intent.Id
#warning must have id of actual routine for dynamic subscription (subscribe after a routine already scheduled).
            };

            var proxyTask = TaskAccessor.CreateTask(taskState, taskResultType);

            bool invokedByRunningMethod = _transitionScope.IsActive &&
                                          IsCalledByRoutine(
                _transitionScope.CurrentMonitor.Context,
                // Skip 2 stack frames: current method and dynamically-generated proxy.
                // WARNING! DO NOT PUT 'new StackFrame()' into a helper method!
                new StackFrame(skipFrames: 2, fNeedFileInfo: false));

            bool ignoreTransaction = !invokedByRunningMethod;

            if (!ignoreTransaction && _transitionScope.IsActive)
            {
                var runningMethodSettings = _communicationSettingsProvider.GetMethodSettings(
                    _transitionScope.CurrentMonitor.Context.MethodRef.Definition);

                if (!runningMethodSettings.Transactional)
                {
                    ignoreTransaction = true;
                }
            }

            if (!ignoreTransaction)
            {
                var methodSettings = _communicationSettingsProvider.GetMethodSettings(methodDefinition);
                if (methodSettings.IgnoreTransaction)
                {
                    ignoreTransaction = true;
                }
            }

            if (ignoreTransaction)
            {
                ExecuteAndAwaitInBackground(intent, proxyTask);
            }
            else
            {
                _transitionScope.CurrentMonitor.RegisterIntent(intent, proxyTask);
            }

            return(proxyTask);
        }
コード例 #12
0
        public async Task HandleAsync(PathString basePath, HttpContext context, CancellationToken ct)
        {
            var headers = context.Response.Headers;

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

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

            if (pathSegments.Length == 0 && context.Request.Query.ContainsKey("react"))
            {
                await HandleEventReactionAsync(null, context, ct);

                return;
            }

            if (pathSegments.Length == 0)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Empty request URL"));

                return;
            }

            var serviceName       = pathSegments[0];
            var serviceDefinition = _communicationModelProvider.Model.FindServiceByName(serviceName);

            if (serviceDefinition == null)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"Service '{serviceName}' is not registered"));

                return;
            }

            if (serviceDefinition.Type == ServiceType.External)
            {
                context.Response.StatusCode  = 403; // Forbidden
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"Cannot invoke external service '{serviceName}' on your behalf"));

                return;
            }

            if (_eventDispatcher != null && pathSegments.Length == 1 && context.Request.Query.ContainsKey("react"))
            {
                await HandleEventReactionAsync(serviceDefinition, context, ct);

                return;
            }

            if (pathSegments.Length == 1)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request URL does not contain a service method"));

                return;
            }

            if (_eventDispatcher != null && context.Request.Query.ContainsKey("subscribe"))
            {
                await HandleEventSubsciptionAsync(serviceDefinition, pathSegments[1], context, ct);

                return;
            }

            var methodName = pathSegments[1];

            if (pathSegments.Length == 3)
            {
                await HandleResultPoll(context, serviceName, methodName, intentId : pathSegments[2]);

                return;
            }

            if (pathSegments.Length > 3)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request URL contains extra segments"));

                return;
            }

            var isQueryRequest      = context.Request.Method == "GET";
            var isCommandRequest    = context.Request.Method == "PUT" || context.Request.Method == "POST";
            var contentType         = context.Request.GetContentType();
            var isJsonRequest       = string.Equals(contentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase);
            var isDasyncJsonRequest = string.Equals(contentType.MediaType, "application/dasync+json", StringComparison.OrdinalIgnoreCase);

            if (!isQueryRequest && !isJsonRequest && !isDasyncJsonRequest)
            {
                context.Response.StatusCode  = 406; // Not Acceptable
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request content type is either not specified or not supported"));

                return;
            }

            if (!isQueryRequest && !isCommandRequest)
            {
                context.Response.StatusCode  = 405; // Method Not Allowed
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The service method invocation must use one of these HTTP verbs: GET, POST, PUT"));

                return;
            }

            var routineMethodId = new RoutineMethodId
            {
                MethodName = methodName
            };

#warning Use model for method resolution instead
            MethodInfo routineMethod;
            try
            {
                routineMethod = _routineMethodResolver.Resolve(serviceDefinition, routineMethodId);
            }
            catch
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"The service '{serviceDefinition.Name}' does not have method '{methodName}'"));

                return;
            }

#warning Use model to determine if the method is command or query
            var isQueryMethod = GetWordAndSynonyms.Contains(GetFirstWord(methodName));

            if (isQueryRequest && !isQueryMethod)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"The method '{methodName}' of service '{serviceDefinition.Name}' is a command, but not a query, thus must be invoked with POST or PUT verb"));

                return;
            }

            string parametersJson = null;
            ContinuationDescriptor continuationDescriptor = null;

            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);
                    }
                }

                parametersJson = inputObject.ToString();
            }
            else if (isJsonRequest)
            {
                parametersJson = await context.Request.ReadBodyAsStringAsync(contentType);
            }
            else
            {
                var inputJson = await context.Request.ReadBodyAsStringAsync(contentType);

                var envelope = JsonConvert.DeserializeObject <CommandEnvelope>(inputJson, JsonSettings);
                parametersJson         = envelope.Parameters;
                continuationDescriptor = envelope.Continuation;
            }

            var methodInvoker      = _methodInvokerFactory.Create(routineMethod);
            var parameterContainer = methodInvoker.CreateParametersContainer();

            if (!string.IsNullOrWhiteSpace(parametersJson))
            {
                if (isDasyncJsonRequest)
                {
                    _dasyncJsonSerializer.Populate(parametersJson, parameterContainer);
                }
                else
                {
                    JsonConvert.PopulateObject(parametersJson, parameterContainer, JsonSettings);
                }
            }

            var externalRequestId     = context.Request.Headers.TryGetValue(DasyncHttpHeaders.RequestId, out var requestIdValues) && requestIdValues.Count > 0 ? requestIdValues[0] : null;
            var externalCorrelationId = context.Request.Headers.TryGetValue(DasyncHttpHeaders.CorrelationId, out var correlationIdValues) && correlationIdValues.Count > 0 ? correlationIdValues[0] : null;

            var rfc7240Preferences = GetPreferences(context.Request.Headers);

            var isHttpRequestBlockingExecution = !(rfc7240Preferences.RespondAsync == true);

            _transitionUserContext.Current = GetUserContext(context);

            _intentPreprocessor.PrepareContext(context);
            if (await _intentPreprocessor.PreprocessAsync(context, serviceDefinition, routineMethodId, parameterContainer).ConfigureAwait(false))
            {
                return;
            }

            var serviceId = new ServiceId {
                ServiceName = serviceDefinition.Name
            };
            var intentId = _idGenerator.NewId();

            if (isQueryMethod)
            {
                var serviceInstance = _domainServiceProvider.GetService(serviceDefinition.Implementation);

                foreach (var postAction in _transitionActions)
                {
                    await postAction.OnRoutineStartAsync(serviceDefinition, serviceId, routineMethodId, intentId);
                }

                Task task;
                try
                {
                    task = methodInvoker.Invoke(serviceInstance, parameterContainer);
                    if (routineMethod.ReturnType != typeof(void))
                    {
                        try { await task; } catch { }
                    }
                }
                catch (Exception ex)
                {
                    if (ex is TargetInvocationException)
                    {
                        ex = ex.InnerException;
                    }
                    task = Task.FromException(ex);
                }
                var taskResult = task?.ToTaskResult() ?? new TaskResult();

                foreach (var postAction in _transitionActions)
                {
                    await postAction.OnRoutineCompleteAsync(serviceDefinition, serviceId, routineMethodId, intentId, taskResult);
                }

                await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest);
            }
            else
            {
                var intent = new ExecuteRoutineIntent
                {
                    Id        = intentId,
                    ServiceId = new ServiceId {
                        ServiceName = serviceDefinition.Name
                    },
                    MethodId     = routineMethodId,
                    Parameters   = parameterContainer,
                    Continuation = continuationDescriptor
                };

                var actions = new ScheduledActions
                {
                    ExecuteRoutineIntents = new List <ExecuteRoutineIntent> {
                        intent
                    }
                };

                var options = new TransitionCommitOptions
                {
                    NotifyOnRoutineCompletion = isHttpRequestBlockingExecution,
                    RequestId     = externalRequestId,
                    CorrelationId = externalCorrelationId ?? externalRequestId
                };

                await _transitionCommitter.CommitAsync(actions, null, options, default);

                var waitTime = rfc7240Preferences.Wait;
                if (isHttpRequestBlockingExecution || waitTime > MaxLongPollTime)
                {
                    waitTime = MaxLongPollTime;
                }

                if (waitTime > TimeSpan.Zero)
                {
                    var cts            = new CancellationTokenSource();
                    var completionSink = new TaskCompletionSource <TaskResult>();
                    _routineCompletionNotifier.NotifyCompletion(intent.ServiceId, intent.MethodId, intent.Id, completionSink, cts.Token);
                    try
                    {
                        var taskResult = await completionSink.Task.WithTimeout(waitTime);
                        await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest);

                        return;
                    }
                    catch (TaskCanceledException)
                    {
                        cts.Cancel();
                    }
                }

                var location = context.Request.Path.ToString() + "/" + intent.Id;
                context.Response.Headers.Add("Location", location);
                context.Response.StatusCode = DasyncHttpCodes.Scheduled;
                return;
            }
        }