private async void RunRoutineInBackground(IServiceDefinition serviceDefinition, ExecuteRoutineIntent intent) { try { var serviceInstance = _domainServiceProvider.GetService(serviceDefinition.Implementation); var routineMethod = _routineMethodResolver.Resolve(serviceDefinition, intent.MethodId); var methodInvoker = _methodInvokerFactory.Create(routineMethod); foreach (var postAction in _transitionActions) { await postAction.OnRoutineStartAsync(serviceDefinition, intent.ServiceId, intent.MethodId, intent.Id); } Task task; try { task = methodInvoker.Invoke(serviceInstance, intent.Parameters); 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, intent.ServiceId, intent.MethodId, intent.Id, taskResult); } _routineCompletionSink.OnRoutineCompleted(intent.Id, taskResult); } catch { } }
public async Task <HttpResponseMessage> ProcessRequestAsync( HttpRequestMessage request, FunctionExecutionContext context, DateTimeOffset requestStartTime, ILogger logger, CancellationToken ct) { _currentFunction.Value = new ExecutingFuncionInfo { FunctionName = context.FunctionName }; try { #warning Create an HTTP connector if (request.Method == HttpMethod.Post) { string serviceName = null; string routineName = null; long? intentId = null; bool @volatile = false; TimeSpan pollTime = TimeSpan.Zero; foreach (var parameter in request.GetQueryNameValuePairs()) { switch (parameter.Key) { case "service": serviceName = parameter.Value; break; case "routine": routineName = parameter.Value; break; case "intent": if (long.TryParse(parameter.Value, out var parsedIntentId)) { intentId = parsedIntentId; } break; } } foreach (var header in request.Headers) { var firstValue = header.Value.FirstOrDefault(); switch (header.Key) { case "Volatile": if (string.IsNullOrEmpty(firstValue) || !bool.TryParse(firstValue, out @volatile)) { @volatile = true; } break; case "Poll-Time": if (!string.IsNullOrEmpty(firstValue)) { if (double.TryParse(firstValue, out var pollTimeInSeconds)) { pollTime = TimeSpan.FromSeconds(pollTimeInSeconds); } else if (TimeSpan.TryParse(firstValue, out var pollTimeTimeSpan)) { pollTime = pollTimeTimeSpan; } } break; } } var serviceId = new ServiceId { ServiceName = serviceName }; var routineMethodId = new RoutineMethodId { MethodName = routineName }; var registration = _serviceRegistry.AllRegistrations .SingleOrDefault(r => r.ServiceName == serviceId.ServiceName); if (registration == null || registration.IsExternal) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent( registration == null ? "Service not found" : "Cannot invoke external service") } } ; IValueContainer parameterContainer = null; if (request.Content != null) { var routineMethod = _routineMethodResolver.Resolve(registration.ServiceType, routineMethodId); var methodInvoker = _methodInvokerFactory.Create(routineMethod); parameterContainer = methodInvoker.CreateParametersContainer(); #warning change to use a serializer based on the format var paramsJson = await request.Content.ReadAsStringAsync(); if (!string.IsNullOrWhiteSpace(paramsJson)) { JsonConvert.PopulateObject(paramsJson, parameterContainer); } } var intent = new ExecuteRoutineIntent { #warning Generate intent ID from function request ID? any benefit (like on re-try)? Id = intentId ?? _idGenerator.NewId(), ServiceId = serviceId, MethodId = routineMethodId, Parameters = parameterContainer, Continuation = null }; var connector = GetConnector(serviceId); var routineInfo = await connector.ScheduleRoutineAsync(intent, ct); if (pollTime > TimeSpan.Zero) { routineInfo = await PollRoutineAsync(routineInfo, connector, requestStartTime, pollTime, ct); } if (routineInfo.Result != null) { return(CreateResponseFromRoutineResult(routineInfo.Result)); } var response = new HttpResponseMessage(HttpStatusCode.Accepted); var location = $"{request.RequestUri.AbsolutePath}?service={serviceId.ServiceName}&routineId={routineInfo.RoutineId}"; response.Headers.Add("Location", location); #warning ETag must be in certain format //if (!string.IsNullOrEmpty(routineInfo.ETag)) // response.Headers.ETag = new EntityTagHeaderValue(routineInfo.ETag); return(response); } else if (request.Method == HttpMethod.Get) { string serviceName = null; string routineId = null; string routineETag = null; TimeSpan pollTime = TimeSpan.Zero; foreach (var parameter in request.GetQueryNameValuePairs()) { switch (parameter.Key) { case "service": serviceName = parameter.Value; break; case "routineId": routineId = parameter.Value; break; case "routineTag": routineETag = parameter.Value; break; } } foreach (var header in request.Headers) { var firstValue = header.Value.FirstOrDefault(); switch (header.Key) { case "Poll-Time": if (!string.IsNullOrEmpty(firstValue)) { if (double.TryParse(firstValue, out var pollTimeInSeconds)) { pollTime = TimeSpan.FromSeconds(pollTimeInSeconds); } else if (TimeSpan.TryParse(firstValue, out var pollTimeTimeSpan)) { pollTime = pollTimeTimeSpan; } } break; } } var serviceId = new ServiceId { ServiceName = serviceName }; var routineInfo = new ActiveRoutineInfo { RoutineId = routineId, ETag = routineETag }; var connector = GetConnector(serviceId); routineInfo = await PollRoutineAsync(routineInfo, connector, requestStartTime, pollTime, ct); if (routineInfo.Result != null) { return(CreateResponseFromRoutineResult(routineInfo.Result)); } var response = new HttpResponseMessage(HttpStatusCode.NoContent); #warning ETag must be in certain format //if (!string.IsNullOrEmpty(routineInfo.ETag)) // response.Headers.ETag = new EntityTagHeaderValue(routineInfo.ETag); return(response); } } catch (Exception ex) { return(CreateInfrastructureErrorResponse(ex)); } finally { _currentFunction.Value = default; } return(new HttpResponseMessage(HttpStatusCode.NotFound)); }
private async Task RunRoutineAsync( ITransitionCarrier transitionCarrier, TransitionDescriptor transitionDescriptor, CancellationToken ct) { using (_transitionScope.Enter(transitionDescriptor)) { var transitionMonitor = _transitionScope.CurrentMonitor; var serviceId = await transitionCarrier.GetServiceIdAsync(ct); var routineDescriptor = await transitionCarrier.GetRoutineDescriptorAsync(ct); object serviceInstance; IServiceDefinition serviceDefinition; #warning IntrinsicRoutines must be registered in the service registry, but it needs the engine IoC to resolve. if (serviceId.ProxyName == nameof(IntrinsicRoutines)) { serviceInstance = _intrinsicRoutines; serviceDefinition = IntrinsicCommunicationModel.IntrinsicRoutinesServiceDefinition; } else { serviceInstance = _serviceProxyBuilder.Build(serviceId); serviceDefinition = ((ServiceProxyContext)((IProxy)serviceInstance).Context).Definition; } var routineMethod = _routineMethodResolver.Resolve(serviceDefinition, routineDescriptor.MethodId); //var serviceStateContainer = _serviceStateValueContainerProvider.CreateContainer(serviceInstance); //var isStatefullService = serviceStateContainer.GetCount() > 0; //if (isStatefullService) // await transitionCarrier.ReadServiceStateAsync(serviceStateContainer, ct); Task completionTask; IValueContainer asmValueContainer = null; if (TryCreateAsyncStateMachine(routineMethod, out var asmInstance, out var asmMetadata)) { var isContinuation = transitionDescriptor.Type == TransitionType.ContinueRoutine; asmValueContainer = await LoadRoutineStateAsync(transitionCarrier, asmInstance, asmMetadata, isContinuation, ct); asmMetadata.Owner.FieldInfo?.SetValue(asmInstance, serviceInstance); transitionMonitor.OnRoutineStart( serviceId, routineDescriptor, serviceInstance, routineMethod, asmInstance); try { #warning possibly need to create a proxy? on a sealed ASM class? How to capture Task.Delay if it's not immediate after first MoveNext? asmInstance.MoveNext(); completionTask = GetCompletionTask(asmInstance, asmMetadata); } catch (Exception ex) { // The MoveNext() must not throw, but instead complete the task with an error. // try-catch is added just in case for a non-compiler-generated state machine. var taskResultType = TaskAccessor.GetTaskResultType(routineMethod.ReturnType); completionTask = TaskAccessor.FromException(taskResultType, ex); } }
private async Task RunRoutineAsync( ITransitionCarrier transitionCarrier, ITransitionData transitionData, TransitionDescriptor transitionDescriptor, CancellationToken ct) { using (_transitionScope.Enter(transitionDescriptor)) { var transitionMonitor = _transitionScope.CurrentMonitor; var serviceId = await transitionData.GetServiceIdAsync(ct); var routineDescriptor = await transitionData.GetRoutineDescriptorAsync(ct); var serviceInstance = #warning IntrinsicRoutines must be registered in the service registry, but it needs the engine IoC to resolve. serviceId.ProxyName == nameof(IntrinsicRoutines) ? _intrinsicRoutines : _serviceProxyBuilder.Build(serviceId); #warning check if the serviceInstance proxy is an actual non-abstract class with implementation // Need exact underlying type of the service implementation type to call // the routine method directly without using the virtual method table. var serviceType = (serviceInstance as IProxy)?.ObjectType ?? serviceInstance.GetType(); var routineMethod = _routineMethodResolver.Resolve(serviceType, routineDescriptor.MethodId); var serviceStateContainer = _serviceStateValueContainerProvider.CreateContainer(serviceInstance); var isStatefullService = serviceStateContainer.GetCount() > 0; if (isStatefullService) { await transitionData.ReadServiceStateAsync(serviceStateContainer, ct); } Task completionTask; IValueContainer asmValueContainer = null; if (TryCreateAsyncStateMachine(routineMethod, out var asmInstance, out var asmMetadata, out completionTask)) { var isContinuation = transitionDescriptor.Type == TransitionType.ContinueRoutine; asmValueContainer = await LoadRoutineStateAsync(transitionData, asmInstance, asmMetadata, isContinuation, ct); asmMetadata.Owner.FieldInfo?.SetValue(asmInstance, serviceInstance); transitionMonitor.OnRoutineStart( serviceId, routineDescriptor, serviceInstance, routineMethod, asmInstance); try { #warning possibly need to create a proxy? on a sealed ASM class? How to capture Task.Delay if it's not immediate after first MoveNext? asmInstance.MoveNext(); } catch (Exception ex) { // The MoveNext() must not throw, but instead complete the task with an error. // try-catch is added just in case for a non-compiler-generated state machine. var taskResultType = TaskAccessor.GetTaskResultType(routineMethod.ReturnType); completionTask = TaskAccessor.FromException(taskResultType, ex); } }
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; } }