public async Task <HttpResponseMessage> HandleRequestAsync(HttpRequestMessage request)
        {
            try
            {
                string basePath;
                if (this.localHttpListener.IsListening &&
                    request.RequestUri.IsLoopback &&
                    request.RequestUri.Port == this.localHttpListener.InternalRpcUri.Port)
                {
                    basePath = this.localHttpListener.InternalRpcUri.AbsolutePath;
                }
                else if (this.config.Options.NotificationUrl != null)
                {
                    basePath = this.config.Options.NotificationUrl.AbsolutePath;
                }
                else
                {
                    throw new InvalidOperationException($"Don't know how to handle request to {request.RequestUri}.");
                }

                string path        = "/" + request.RequestUri.AbsolutePath.Substring(basePath.Length).Trim('/');
                var    routeValues = new RouteValueDictionary();
                if (StartOrchestrationRoute.TryMatch(path, routeValues))
                {
                    string functionName = (string)routeValues[FunctionNameRouteParameter];
                    string instanceId   = (string)routeValues[InstanceIdRouteParameter];
                    if (request.Method == HttpMethod.Post)
                    {
                        return(await this.HandleStartOrchestratorRequestAsync(request, functionName, instanceId));
                    }
                    else
                    {
                        return(request.CreateResponse(HttpStatusCode.NotFound));
                    }
                }

                if (EntityRoute.TryMatch(path, routeValues))
                {
                    try
                    {
                        string entityName = (string)routeValues[EntityNameRouteParameter];
                        string entityKey  = (string)routeValues[EntityKeyRouteParameter];

                        if (request.Method == HttpMethod.Get)
                        {
                            if (!string.IsNullOrEmpty(entityKey))
                            {
                                EntityId entityId = new EntityId(entityName, entityKey);
                                return(await this.HandleGetEntityRequestAsync(request, entityId));
                            }
                            else
                            {
                                return(await this.HandleListEntitiesRequestAsync(request, entityName));
                            }
                        }
                        else if (request.Method == HttpMethod.Post || request.Method == HttpMethod.Delete)
                        {
                            EntityId entityId = new EntityId(entityName, entityKey);
                            return(await this.HandlePostEntityOperationRequestAsync(request, entityId));
                        }
                        else
                        {
                            return(request.CreateResponse(HttpStatusCode.NotFound));
                        }
                    }
                    catch (ArgumentException e)
                    {
                        return(request.CreateErrorResponse(HttpStatusCode.BadRequest, e.Message));
                    }
                }

                if (InstancesRoute.TryMatch(path, routeValues))
                {
                    routeValues.TryGetValue(InstanceIdRouteParameter, out object instanceIdValue);
                    routeValues.TryGetValue(OperationRouteParameter, out object operationValue);
                    var instanceId = instanceIdValue as string;
                    var operation  = operationValue as string;

                    if (instanceId == null)
                    {
                        // Retrieve All Status or conditional query in case of the request URL ends e.g. /instances/
                        if (request.Method == HttpMethod.Get)
                        {
                            return(await this.HandleGetStatusRequestAsync(request));
                        }
                        else if (request.Method == HttpMethod.Delete)
                        {
                            return(await this.HandleDeleteHistoryWithFiltersRequestAsync(request));
                        }
                        else
                        {
                            return(request.CreateResponse(HttpStatusCode.NotFound));
                        }
                    }
                    else if (instanceId != null && operation == null)
                    {
                        if (request.Method == HttpMethod.Get)
                        {
                            return(await this.HandleGetStatusRequestAsync(request, instanceId));
                        }
                        else if (request.Method == HttpMethod.Delete)
                        {
                            return(await this.HandleDeleteHistoryByIdRequestAsync(request, instanceId));
                        }
                        else
                        {
                            return(request.CreateResponse(HttpStatusCode.NotFound));
                        }
                    }
                    else
                    {
                        if (string.Equals(operation, TerminateOperation, StringComparison.OrdinalIgnoreCase))
                        {
                            return(await this.HandleTerminateInstanceRequestAsync(request, instanceId));
                        }
                        else if (string.Equals(operation, RewindOperation, StringComparison.OrdinalIgnoreCase))
                        {
                            return(await this.HandleRewindInstanceRequestAsync(request, instanceId));
                        }
                        else
                        {
                            return(request.CreateResponse(HttpStatusCode.NotFound));
                        }
                    }
                }

                if (InstanceRaiseEventRoute.TryMatch(path, routeValues))
                {
                    string instanceId = (string)routeValues[InstanceIdRouteParameter];
                    string eventName  = (string)routeValues[EventNameRouteParameter];
                    if (request.Method == HttpMethod.Post)
                    {
                        return(await this.HandleRaiseEventRequestAsync(request, instanceId, eventName));
                    }
                    else
                    {
                        return(request.CreateResponse(HttpStatusCode.NotFound));
                    }
                }

                return(request.CreateResponse(HttpStatusCode.NotFound));
            }

            /* Some handler methods throw ArgumentExceptions in specialized cases which should be returned to the client, such as when:
             *     - the function name is not found (starting a new function)
             *     - the orchestration instance is not in a Failed state (rewinding an orchestration instance)
             */
            catch (ArgumentException e)
            {
                return(request.CreateErrorResponse(HttpStatusCode.BadRequest, "One or more of the arguments submitted is incorrect", e));
            }
            catch (Exception e)
            {
                return(request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Something went wrong while processing your request", e));
            }
        }
 public Task <TResult> CallAsync <TResult>(EntityId entityId, string operationName, object operationInput)
 {
     this.SignalAndStoreTask(entityId, operationName, operationInput);
     return(Task.FromResult <TResult>(default(TResult)));
 }
 public void Signal(EntityId entityId, string operationName, object operationInput)
 {
     this.SignalAndStoreTask(entityId, operationName, operationInput);
 }
Пример #4
0
 void IDurableEntityContext.SignalEntity <TEntityInterface>(EntityId entityId, DateTime scheduledTimeUtc, Action <TEntityInterface> operation)
 {
     operation(EntityProxyFactory.Create <TEntityInterface>(new EntityContextProxy(this, scheduledTimeUtc), entityId));
 }
 public Task CallAsync(EntityId entityId, string operationName, object operationInput)
 {
     this.SignalAndStoreTask(entityId, operationName, operationInput);
     return(Task.CompletedTask);
 }
Пример #6
0
        Task <EntityStateResponse <T> > IDurableEntityClient.ReadEntityStateAsync <T>(EntityId entityId, string taskHubName, string connectionName)
        {
            if (string.IsNullOrEmpty(taskHubName))
            {
                return(this.ReadEntityStateAsync <T>(this.DurabilityProvider, entityId));
            }
            else
            {
                if (string.IsNullOrEmpty(connectionName))
                {
                    connectionName = this.attribute.ConnectionName;
                }

                var attribute = new DurableClientAttribute
                {
                    TaskHub        = taskHubName,
                    ConnectionName = connectionName,
                };

                DurabilityProvider durabilityProvider = ((DurableClient)this.config.GetClient(attribute)).DurabilityProvider;
                return(this.ReadEntityStateAsync <T>(durabilityProvider, entityId));
            }
        }
Пример #7
0
        private async Task ExecuteOutOfProcBatch()
        {
            object outOfProcResults = null;

            Task invokeTask = this.FunctionInvocationCallback();

            if (invokeTask is Task <object> resultTask)
            {
                outOfProcResults = await resultTask;
            }
            else
            {
                throw new InvalidOperationException("The WebJobs runtime returned a invocation task that does not support return values!");
            }

            var jObj = outOfProcResults as JObject;

            if (jObj == null)
            {
                throw new ArgumentException("Out of proc orchestrators must return a valid JSON schema.");
            }

            var outOfProcResult = jObj.ToObject <OutOfProcResult>();

            // update the state
            this.context.State.EntityExists = outOfProcResult.EntityExists;
            this.context.State.EntityState  = outOfProcResult.EntityState;

            // for each operation, emit trace and send response message (if not a signal)
            for (int i = 0; i < this.OperationBatch.Count; i++)
            {
                var request = this.OperationBatch[i];
                var result  = outOfProcResult.Results[i];

                if (!result.IsError)
                {
                    this.Config.TraceHelper.OperationCompleted(
                        this.context.HubName,
                        this.context.Name,
                        this.context.InstanceId,
                        request.Id.ToString(),
                        request.Operation,
                        this.Config.GetIntputOutputTrace(request.Input),
                        this.Config.GetIntputOutputTrace(result.Result),
                        result.DurationInMilliseconds,
                        isReplay: false);
                }
                else
                {
                    this.context.CaptureApplicationError(new OperationErrorException(
                                                             $"Error in operation '{request.Operation}': {result}"));

                    this.Config.TraceHelper.OperationFailed(
                        this.context.HubName,
                        this.context.Name,
                        this.context.InstanceId,
                        request.Id.ToString(),
                        request.Operation,
                        this.Config.GetIntputOutputTrace(request.Input),
                        this.Config.GetIntputOutputTrace(result.Result),
                        result.DurationInMilliseconds,
                        isReplay: false);
                }

                if (!request.IsSignal)
                {
                    var target = new OrchestrationInstance()
                    {
                        InstanceId  = request.ParentInstanceId,
                        ExecutionId = request.ParentExecutionId,
                    };
                    var responseMessage = new ResponseMessage()
                    {
                        Result        = result.Result,
                        ExceptionType = result.IsError ? "Error" : null,
                    };
                    this.context.SendResponseMessage(target, request.Id, responseMessage, !result.IsError);
                }
            }

            // send signal messages
            foreach (var signal in outOfProcResult.Signals)
            {
                var request = new RequestMessage()
                {
                    ParentInstanceId  = this.context.InstanceId,
                    ParentExecutionId = null, // for entities, message sorter persists across executions
                    Id        = Guid.NewGuid(),
                    IsSignal  = true,
                    Operation = signal.Name,
                    Input     = signal.Input,
                };
                var target = new OrchestrationInstance()
                {
                    InstanceId = EntityId.GetSchedulerIdFromEntityId(signal.Target),
                };
                this.context.SendOperationMessage(target, request);
            }
        }
Пример #8
0
 public DurableEntityContext(DurableTaskExtension config, DurabilityProvider durabilityProvider, EntityId entity, TaskEntityShim shim)
     : base(config, entity.EntityName)
 {
     this.messageDataConverter = config.MessageDataConverter;
     this.errorDataConverter   = config.ErrorDataConverter;
     this.durabilityProvider   = durabilityProvider;
     this.self = entity;
     this.shim = shim;
 }
Пример #9
0
 void IDurableEntityContext.SignalEntity(EntityId entity, string operation, object input)
 {
     this.SignalEntityInternal(entity, null, operation, input);
 }
 /// <summary>
 /// Create an entity proxy.
 /// </summary>
 /// <param name="context">context.</param>
 /// <param name="entityId">Entity id.</param>
 protected EntityProxy(IEntityProxyContext context, EntityId entityId)
 {
     this.context  = context;
     this.entityId = entityId;
 }
Пример #11
0
        internal static TEntityInterface Create <TEntityInterface>(IEntityProxyContext context, EntityId entityId)
        {
            var type = TypeMappings.GetOrAdd(typeof(TEntityInterface), CreateProxyType);

            return((TEntityInterface)Activator.CreateInstance(type, context, entityId));
        }
 /// <summary>
 /// Calls an operation on an entity and waits for it to complete.
 /// </summary>
 /// <param name="context">The context object.</param>
 /// <param name="entityId">The target entity.</param>
 /// <param name="operationName">The name of the operation.</param>
 /// <returns>A task representing the completion of the operation on the entity.</returns>
 public static Task CallEntityAsync(this IDurableOrchestrationContext context, EntityId entityId, string operationName)
 {
     return(context.CallEntityAsync <object>(entityId, operationName, null));
 }
        /// <summary>
        /// Invokes a DF API based on the input action object.
        /// </summary>
        /// <param name="action">An OOProc action object representing a DF task.</param>
        /// <returns>If the API returns a task, the DF task corresponding to the input action. Else, null.</returns>
        private Task InvokeAPIFromAction(AsyncAction action)
        {
            Task fireAndForgetTask = Task.CompletedTask;
            Task task = null;

            switch (action.ActionType)
            {
            case AsyncActionType.CallActivity:
                task = this.context.CallActivityAsync(action.FunctionName, action.Input);
                break;

            case AsyncActionType.CreateTimer:
                DurableOrchestrationContext ctx = this.context as DurableOrchestrationContext;
                using (var cts = new CancellationTokenSource())
                {
                    if (ctx != null)
                    {
                        ctx.ThrowIfInvalidTimerLengthForStorageProvider(action.FireAt);
                    }

                    task = this.context.CreateTimer(action.FireAt, cts.Token);

                    if (action.IsCanceled)
                    {
                        cts.Cancel();
                    }
                }

                break;

            case AsyncActionType.CallActivityWithRetry:
                task = this.context.CallActivityWithRetryAsync(action.FunctionName, action.RetryOptions, action.Input);
                break;

            case AsyncActionType.CallSubOrchestrator:
                task = this.context.CallSubOrchestratorAsync(action.FunctionName, action.InstanceId, action.Input);
                break;

            case AsyncActionType.CallSubOrchestratorWithRetry:
                task = this.context.CallSubOrchestratorWithRetryAsync(action.FunctionName, action.RetryOptions, action.InstanceId, action.Input);
                break;

            case AsyncActionType.CallEntity:
            {
                var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                task = this.context.CallEntityAsync(entityId, action.EntityOperation, action.Input);
                break;
            }

            case AsyncActionType.SignalEntity:
            {
                // We do not add a task because this is 'fire and forget'
                var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                this.context.SignalEntity(entityId, action.EntityOperation, action.Input);
                task = fireAndForgetTask;
                break;
            }

            case AsyncActionType.ScheduledSignalEntity:
            {
                // We do not add a task because this is 'fire and forget'
                var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                this.context.SignalEntity(entityId, action.FireAt, action.EntityOperation, action.Input);
                task = fireAndForgetTask;
                break;
            }

            case AsyncActionType.ContinueAsNew:
                this.context.ContinueAsNew(action.Input);
                task = fireAndForgetTask;
                break;

            case AsyncActionType.WaitForExternalEvent:
                task = this.context.WaitForExternalEvent <object>(action.ExternalEventName);
                break;

            case AsyncActionType.CallHttp:
                task = this.context.CallHttpAsync(action.HttpRequest);
                break;

            case AsyncActionType.WhenAll:
                task = Task.WhenAll(action.CompoundActions.Select(x => this.InvokeAPIFromAction(x)));
                break;

            case AsyncActionType.WhenAny:
                task = Task.WhenAny(action.CompoundActions.Select(x => this.InvokeAPIFromAction(x)));
                break;

            default:
                throw new Exception($"Received an unexpected action type from the out-of-proc function: '${action.ActionType}'.");
            }

            return(task);
        }
        private async Task ProcessAsyncActions(AsyncAction[][] actions)
        {
            if (actions == null)
            {
                throw new ArgumentNullException("Out-of-proc orchestrator schema must have a non-null actions property.");
            }

            // Each actionSet represents a particular execution of the orchestration.
            foreach (AsyncAction[] actionSet in actions)
            {
                var tasks = new List <Task>(actions.Length);

                // An actionSet represents all actions that were scheduled within that execution.
                foreach (AsyncAction action in actionSet)
                {
                    switch (action.ActionType)
                    {
                    case AsyncActionType.CallActivity:
                        tasks.Add(this.context.CallActivityAsync(action.FunctionName, action.Input));
                        break;

                    case AsyncActionType.CreateTimer:
                        using (var cts = new CancellationTokenSource())
                        {
                            DurableOrchestrationContext ctx = this.context as DurableOrchestrationContext;

                            if (ctx != null)
                            {
                                tasks.Add(ctx.OutOfProcCreateTimer(ctx, action.FireAt, cts.Token));
                            }
                            else
                            {
                                tasks.Add(this.context.CreateTimer(action.FireAt, cts.Token));
                            }

                            if (action.IsCanceled)
                            {
                                cts.Cancel();
                            }
                        }

                        break;

                    case AsyncActionType.CallActivityWithRetry:
                        tasks.Add(this.context.CallActivityWithRetryAsync(action.FunctionName, action.RetryOptions, action.Input));
                        break;

                    case AsyncActionType.CallSubOrchestrator:
                        tasks.Add(this.context.CallSubOrchestratorAsync(action.FunctionName, action.InstanceId, action.Input));
                        break;

                    case AsyncActionType.CallSubOrchestratorWithRetry:
                        tasks.Add(this.context.CallSubOrchestratorWithRetryAsync(action.FunctionName, action.RetryOptions, action.InstanceId, action.Input));
                        break;

                    case AsyncActionType.CallEntity:
                    {
                        var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                        tasks.Add(this.context.CallEntityAsync(entityId, action.EntityOperation, action.Input));
                        break;
                    }

                    case AsyncActionType.SignalEntity:
                    {
                        // We do not add a task because this is 'fire and forget'
                        var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                        this.context.SignalEntity(entityId, action.EntityOperation, action.Input);
                        break;
                    }

                    case AsyncActionType.ScheduledSignalEntity:
                    {
                        // We do not add a task because this is 'fire and forget'
                        var entityId = EntityId.GetEntityIdFromSchedulerId(action.InstanceId);
                        this.context.SignalEntity(entityId, action.FireAt, action.EntityOperation, action.Input);
                        break;
                    }

                    case AsyncActionType.ContinueAsNew:
                        this.context.ContinueAsNew(action.Input);
                        break;

                    case AsyncActionType.WaitForExternalEvent:
                        tasks.Add(this.context.WaitForExternalEvent <object>(action.ExternalEventName));
                        break;

                    case AsyncActionType.CallHttp:
                        tasks.Add(this.context.CallHttpAsync(action.HttpRequest));
                        break;

                    default:
                        break;
                    }
                }

                if (tasks.Count > 0)
                {
                    await Task.WhenAny(tasks);
                }
            }
        }
Пример #15
0
 public DurableEntityContext(DurableTaskExtension config, EntityId entity, TaskEntityShim shim)
     : base(config, entity.EntityName)
 {
     this.self = entity;
     this.shim = shim;
 }
Пример #16
0
 void IDurableEntityContext.SignalEntity(EntityId entity, DateTime scheduledTimeUtc, string operation, object input)
 {
     this.SignalEntityInternal(entity, scheduledTimeUtc, operation, input);
 }
Пример #17
0
        private async Task SignalEntityAsyncInternal(DurableClient durableClient, string hubName, EntityId entityId, DateTime?scheduledTimeUtc, string operationName, object operationInput)
        {
            var entityKey = entityId.EntityKey;

            if (entityKey.Any(IsInvalidCharacter))
            {
                throw new ArgumentException(nameof(entityKey), "Entity keys must not contain /, \\, #, ?, or control characters.");
            }

            if (operationName == null)
            {
                throw new ArgumentNullException(nameof(operationName));
            }

            if (scheduledTimeUtc.HasValue)
            {
                scheduledTimeUtc = scheduledTimeUtc.Value.ToUniversalTime();
            }

            if (this.ClientReferencesCurrentApp(durableClient))
            {
                this.config.ThrowIfFunctionDoesNotExist(entityId.EntityName, FunctionType.Entity);
            }

            var guid       = Guid.NewGuid(); // unique id for this request
            var instanceId = EntityId.GetSchedulerIdFromEntityId(entityId);
            var instance   = new OrchestrationInstance()
            {
                InstanceId = instanceId
            };
            var request = new RequestMessage()
            {
                ParentInstanceId  = null, // means this was sent by a client
                ParentExecutionId = null,
                Id            = guid,
                IsSignal      = true,
                Operation     = operationName,
                ScheduledTime = scheduledTimeUtc,
            };

            if (operationInput != null)
            {
                request.SetInput(operationInput, this.messageDataConverter);
            }

            var jrequest  = JToken.FromObject(request, this.messageDataConverter.JsonSerializer);
            var eventName = scheduledTimeUtc.HasValue
                ? EntityMessageEventNames.ScheduledRequestMessageEventName(request.GetAdjustedDeliveryTime(this.durabilityProvider))
                : EntityMessageEventNames.RequestMessageEventName;
            await durableClient.client.RaiseEventAsync(instance, eventName, jrequest);

            this.traceHelper.FunctionScheduled(
                hubName,
                entityId.EntityName,
                EntityId.GetSchedulerIdFromEntityId(entityId),
                reason: $"EntitySignal:{operationName}",
                functionType: FunctionType.Entity,
                isReplay: false);
        }
Пример #18
0
 /// <inheritdoc/>
 void IDurableEntityContext.SignalEntity <TEntityInterface>(EntityId entityId, Action <TEntityInterface> operation)
 {
     operation(EntityProxyFactory.Create <TEntityInterface>(new EntityContextProxy(this), entityId));
 }
Пример #19
0
        private async Task <EntityStateResponse <T> > ReadEntityStateAsync <T>(DurabilityProvider provider, EntityId entityId)
        {
            string entityState = await provider.RetrieveSerializedEntityState(entityId, this.messageDataConverter.JsonSettings);

            return(new EntityStateResponse <T>()
            {
                EntityExists = entityState != null,
                EntityState = this.messageDataConverter.Deserialize <T>(entityState),
            });
        }
 public Task <TResult> CallAsync <TResult>(EntityId entityId, string operationName, object operationInput)
 {
     return(this.context.CallEntityAsync <TResult>(entityId, operationName, operationInput));
 }